diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 863cf99c..8278c645 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -66,12 +66,12 @@ extension PluginContext { .appending(path: "generated") .appending(path: "java") } - + var outputSwiftDirectory: URL { self.pluginWorkDirectoryURL .appending(path: "Sources") } - + func cachedClasspathFile(swiftModule: String) -> URL { self.pluginWorkDirectoryURL .appending(path: "\(swiftModule)", directoryHint: .notDirectory) diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index d92c96fb..b1aa8490 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,8 +102,7 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - // TODO: -v for debugging build issues... - args("build", "-v") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build + args("build") // since Swift targets which need to be jextract-ed have the jextract build plugin, we just need to build // If we wanted to execute a specific subcommand, we can like this: // args("run",/* // "swift-java", "jextract", diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 19241592..ed76483e 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -95,8 +95,8 @@ public struct SwiftToJava { try translator.analyze() - switch config.mode { - case .some(.ffm), .none: + switch config.effectiveMode { + case .ffm: let generator = FFMSwift2JavaGenerator( config: self.config, translator: translator, diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 6d8d20e5..c9d0cedf 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -39,6 +39,9 @@ public struct Configuration: Codable { public var outputJavaDirectory: String? public var mode: JExtractGenerationMode? + public var effectiveMode: JExtractGenerationMode { + mode ?? .default + } public var writeEmptyFiles: Bool? // FIXME: default it to false, but that plays not nice with Codable diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift index 8df00cf0..1ad331da 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractGenerationMode.swift @@ -19,4 +19,8 @@ public enum JExtractGenerationMode: String, Codable { /// Java Native Interface case jni + + public static var `default`: JExtractGenerationMode { + .ffm + } } diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift index d7a94070..b53a2c6b 100644 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift +++ b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractUnsignedIntegerMode.swift @@ -11,7 +11,6 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// - /// Configures how Swift unsigned integers should be extracted by jextract. public enum JExtractUnsignedIntegerMode: String, Codable { /// Treat unsigned Swift integers as their signed equivalents in Java signatures, diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index e97ed03e..98dac732 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -135,10 +135,10 @@ on the Java side. | `Float` | `float` | | `Double` | `double` | -#### Unsigned numbers mode: wrap-guava +#### Unsigned numbers mode: wrapGuava You can configure `jextract` (in FFM mode) to instead import unsigned values as their unsigned type-safe representations -as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers wrap-guava` +as offered by the Guava library: `UnsignedLong` or `UnsignedInt`. To enable this mode pass the `--unsigned-numbers-mode wrapGuava` command line option, or set the corresponding configuration value in `swift-java.config` (TODO). This approach is type-safe, however it incurs a performance penalty for allocating a wrapper class for every @@ -162,7 +162,7 @@ you are expected to add a Guava dependency to your Java project. | `Float` | `float` | | `Double` | `double` | -> Note: The `wrap-guava` mode is currently only available in FFM mode of jextract. +> Note: The `wrapGuava` mode is currently only available in FFM mode of jextract. ### Enums diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index aba79983..b5942d2a 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,14 +61,14 @@ extension SwiftJava { @Flag(help: "Some build systems require an output to be present when it was 'expected', even if empty. This is used by the JExtractSwiftPlugin build plugin, but otherwise should not be necessary.") var writeEmptyFiles: Bool = false - @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") - var unsignedNumbers: JExtractUnsignedIntegerMode = .default + @Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrapGuava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.") + var unsignedNumbersMode: JExtractUnsignedIntegerMode? @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") - var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? - @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allow-automatic`, user can omit this parameter and a global GC-based arena will be used. `force-automatic` removes all explicit memory management.") - var memoryManagementMode: JExtractMemoryManagementMode = .default + @Option(help: "The memory management mode to use for the generated code. By default, the user must explicitly provide `SwiftArena` to all calls that require it. By choosing `allowGlobalAutomatic`, user can omit this parameter and a global GC-based arena will be used.") + var memoryManagementMode: JExtractMemoryManagementMode? @Option( help: """ @@ -80,7 +80,7 @@ extension SwiftJava { var dependsOn: [String] = [] @Option(help: "The mode to use for extracting asynchronous Swift functions. By default async methods are extracted as Java functions returning CompletableFuture.") - var asyncFuncMode: JExtractAsyncFuncMode = .default + var asyncFuncMode: JExtractAsyncFuncMode? } } @@ -89,21 +89,21 @@ extension SwiftJava.JExtractCommand { if let javaPackage { config.javaPackage = javaPackage } - if let mode { - config.mode = mode - } else if config.mode == nil { - config.mode = .ffm - } + configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift - config.writeEmptyFiles = writeEmptyFiles - config.unsignedNumbersMode = unsignedNumbers - config.minimumInputAccessLevelMode = minimumInputAccessLevel - config.memoryManagementMode = memoryManagementMode - config.asyncFuncMode = asyncFuncMode - try checkModeCompatibility() + // @Flag does not support optional, so we check ourself if it is passed + let writeEmptyFiles = CommandLine.arguments.contains("--write-empty-files") ? true : nil + configure(&config.writeEmptyFiles, overrideWith: writeEmptyFiles) + + configure(&config.unsignedNumbersMode, overrideWith: self.unsignedNumbersMode) + configure(&config.minimumInputAccessLevelMode, overrideWith: self.minimumInputAccessLevelMode) + configure(&config.memoryManagementMode, overrideWith: self.memoryManagementMode) + configure(&config.asyncFuncMode, overrideWith: self.asyncFuncMode) + + try checkModeCompatibility(config: config) if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -112,7 +112,7 @@ extension SwiftJava.JExtractCommand { config.inputSwiftDirectory = "\(FileManager.default.currentDirectoryPath)/Sources/\(swiftModule)" } - print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.mode ?? .ffm)".bold) + print("[debug][swift-java] Running 'swift-java jextract' in mode: " + "\(config.effectiveMode)".bold) // Load all of the dependent configurations and associate them with Swift modules. let dependentConfigs = try loadDependentConfigs(dependsOn: self.dependsOn) @@ -122,20 +122,26 @@ extension SwiftJava.JExtractCommand { } /// Check if the configured modes are compatible, and fail if not - func checkModeCompatibility() throws { - if self.mode == .jni { - switch self.unsignedNumbers { + func checkModeCompatibility(config: Configuration) throws { + if config.effectiveMode == .jni { + switch config.effectiveUnsignedNumbersMode { case .annotate: () // OK case .wrapGuava: throw IllegalModeCombinationError("JNI mode does not support '\(JExtractUnsignedIntegerMode.wrapGuava)' Unsigned integer mode! \(Self.helpMessage)") } - } else if self.mode == .ffm { - guard self.memoryManagementMode == .explicit else { + } else if config.effectiveMode == .ffm { + guard config.effectiveMemoryManagementMode == .explicit else { throw IllegalModeCombinationError("FFM mode does not support '\(self.memoryManagementMode)' memory management mode! \(Self.helpMessage)") } } } + + func configure(_ setting: inout T?, overrideWith value: T?) { + if let value { + setting = value + } + } } struct IncompatibleModeError: Error { diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 25b162e1..6b8c0ec6 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -167,4 +167,4 @@ extension SwiftJavaBaseAsyncParsableCommand { config.logLevel = command.logLevel return config } -} \ No newline at end of file +}