From 5579846fe8b944d310ca0b803b2b783d873873b9 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 09:20:30 +0100 Subject: [PATCH 1/8] propagate build plugin config to command --- .../JExtractSwiftPlugin.swift | 12 ++++++++++ Plugins/PluginsShared/PluginUtils.swift | 23 +++++++++++++++++-- .../Commands/JExtractCommand.swift | 2 +- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 8d0be455..98011a0b 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -89,6 +89,18 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { arguments += ["--java-package", javaPackage] } + if let unsignedNumbersMode = configuration.unsignedNumbersMode { + arguments += ["--unsigned-numbers", \(unsignedNumbersMode.rawValue.kebabCased)] + } + + if let minimumInputAccessLevelMode = configuration.minimumInputAccessLevelMode { + arguments += ["--minimum-input-access-level", \(minimumInputAccessLevelMode.rawValue.kebabCased)] + } + + if let memoryManagementMode = configuration.memoryManagementMode { + arguments += ["--memory-management-mode", \(memoryManagementMode.rawValue.kebabCased)] + } + let swiftFiles = sourceModule.sourceFiles.map { $0.url }.filter { $0.pathExtension == "swift" } diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 863cf99c..4a6d9ca0 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -66,14 +66,33 @@ 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) } } + +extension String { + var kebabCased: String { + var result = "" + + for (index, char) in input.enumerated() { + if char.isUppercase { + if index != 0 { + result.append("-") + } + result.append(char.lowercased()) + } else { + result.append(char) + } + } + + return result + } +} diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index c730cbf6..41bd46bd 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -67,7 +67,7 @@ extension SwiftJava { @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .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 `allow-automatic`, user can omit this parameter and a global GC-based arena will be used. `force-automatic` removes all explicit memory management.") + @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-global-automatic`, user can omit this parameter and a global GC-based arena will be used.") var memoryManagementMode: JExtractMemoryManagementMode = .default @Option( From 2bb9750b6c4ad3a3c343c993e2d557cbadd14cf2 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 09:33:32 +0100 Subject: [PATCH 2/8] fix naming of option values --- .../JExtractSwiftPlugin.swift | 6 +++--- Plugins/PluginsShared/PluginUtils.swift | 19 ------------------- .../Documentation.docc/SupportedFeatures.md | 6 +++--- .../Commands/JExtractCommand.swift | 4 ++-- 4 files changed, 8 insertions(+), 27 deletions(-) diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 98011a0b..37c7e685 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -90,15 +90,15 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { } if let unsignedNumbersMode = configuration.unsignedNumbersMode { - arguments += ["--unsigned-numbers", \(unsignedNumbersMode.rawValue.kebabCased)] + arguments += ["--unsigned-numbers", unsignedNumbersMode.rawValue] } if let minimumInputAccessLevelMode = configuration.minimumInputAccessLevelMode { - arguments += ["--minimum-input-access-level", \(minimumInputAccessLevelMode.rawValue.kebabCased)] + arguments += ["--minimum-input-access-level", minimumInputAccessLevelMode.rawValue] } if let memoryManagementMode = configuration.memoryManagementMode { - arguments += ["--memory-management-mode", \(memoryManagementMode.rawValue.kebabCased)] + arguments += ["--memory-management-mode", memoryManagementMode.rawValue] } let swiftFiles = sourceModule.sourceFiles.map { $0.url }.filter { diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 4a6d9ca0..8278c645 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -77,22 +77,3 @@ extension PluginContext { .appending(path: "\(swiftModule)", directoryHint: .notDirectory) } } - -extension String { - var kebabCased: String { - var result = "" - - for (index, char) in input.enumerated() { - if char.isUppercase { - if index != 0 { - result.append("-") - } - result.append(char.lowercased()) - } else { - result.append(char) - } - } - - return result - } -} diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 997e680e..804c2635 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -136,10 +136,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 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 @@ -163,7 +163,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 41bd46bd..dd3f07cb 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -61,13 +61,13 @@ 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.") + @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 unsignedNumbers: JExtractUnsignedIntegerMode = .default @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .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 `allow-global-automatic`, user can omit this parameter and a global GC-based arena will be used.") + @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 = .default @Option( From 12dd5db69c75e12da43e00025a79fce57ec6b3cd Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 11:34:44 +0100 Subject: [PATCH 3/8] use optionals instead --- .../JExtractSwiftPlugin.swift | 12 ---------- .../Configuration.swift | 3 +++ .../GenerationMode.swift | 4 ++++ .../Commands/JExtractCommand.swift | 24 ++++++++++--------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 37c7e685..8d0be455 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -89,18 +89,6 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { arguments += ["--java-package", javaPackage] } - if let unsignedNumbersMode = configuration.unsignedNumbersMode { - arguments += ["--unsigned-numbers", unsignedNumbersMode.rawValue] - } - - if let minimumInputAccessLevelMode = configuration.minimumInputAccessLevelMode { - arguments += ["--minimum-input-access-level", minimumInputAccessLevelMode.rawValue] - } - - if let memoryManagementMode = configuration.memoryManagementMode { - arguments += ["--memory-management-mode", memoryManagementMode.rawValue] - } - let swiftFiles = sourceModule.sourceFiles.map { $0.url }.filter { $0.pathExtension == "swift" } diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 2d9b4311..f1831964 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/GenerationMode.swift b/Sources/SwiftJavaConfigurationShared/GenerationMode.swift index 22fdd5f5..5a33fcb3 100644 --- a/Sources/SwiftJavaConfigurationShared/GenerationMode.swift +++ b/Sources/SwiftJavaConfigurationShared/GenerationMode.swift @@ -19,6 +19,10 @@ public enum JExtractGenerationMode: String, Codable { /// Java Native Interface case jni + + public static var `default`: JExtractGenerationMode { + .ffm + } } /// Configures how Swift unsigned integers should be extracted by jextract. diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index dd3f07cb..ce818549 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -62,13 +62,13 @@ extension SwiftJava { 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 '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 unsignedNumbers: JExtractUnsignedIntegerMode = .default + var unsignedNumbers: JExtractUnsignedIntegerMode? @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") - var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default + var minimumInputAccessLevel: 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 `allowGlobalAutomatic`, user can omit this parameter and a global GC-based arena will be used.") - var memoryManagementMode: JExtractMemoryManagementMode = .default + var memoryManagementMode: JExtractMemoryManagementMode? @Option( help: """ @@ -86,18 +86,14 @@ 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: mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift config.writeEmptyFiles = writeEmptyFiles - config.unsignedNumbersMode = unsignedNumbers - config.minimumInputAccessLevelMode = minimumInputAccessLevel - config.memoryManagementMode = memoryManagementMode + configure(&config.unsignedNumbersMode, overrideWith: unsignedNumbers) + configure(&config.minimumInputAccessLevelMode, overrideWith: minimumInputAccessLevelMode) + configure(&config.memoryManagementMode, overrideWith: memoryManagementMode) try checkModeCompatibility() @@ -132,6 +128,12 @@ extension SwiftJava.JExtractCommand { } } } + + func configure(_ setting: inout T, overrideWith value: T?) { + if let value { + setting = value + } + } } struct IncompatibleModeError: Error { From 6060a2a9cbbbcb529fcd9c9c590a0112f969aabe Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 12:17:08 +0100 Subject: [PATCH 4/8] use effective modes --- Sources/JExtractSwiftLib/Swift2Java.swift | 4 +-- .../Documentation.docc/SupportedFeatures.md | 2 +- .../Commands/JExtractCommand.swift | 31 +++++++++++-------- .../SwiftJavaBaseAsyncParsableCommand.swift | 2 +- 4 files changed, 22 insertions(+), 17 deletions(-) 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/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 804c2635..ce0705d4 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -139,7 +139,7 @@ on the Java side. #### 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 wrapGuava` +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 diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index ce818549..c5280eee 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -62,10 +62,10 @@ extension SwiftJava { 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 '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 unsignedNumbers: JExtractUnsignedIntegerMode? + var unsignedNumbersMode: JExtractUnsignedIntegerMode? @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") - var minimumInputAccessLevel: JExtractMinimumAccessLevelMode? + 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 `allowGlobalAutomatic`, user can omit this parameter and a global GC-based arena will be used.") var memoryManagementMode: JExtractMemoryManagementMode? @@ -86,16 +86,21 @@ extension SwiftJava.JExtractCommand { if let javaPackage { config.javaPackage = javaPackage } - configure(&config.mode, overrideWith: mode) + configure(&config.mode, overrideWith: self.mode) config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift + + // @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) + config.writeEmptyFiles = writeEmptyFiles - configure(&config.unsignedNumbersMode, overrideWith: unsignedNumbers) - configure(&config.minimumInputAccessLevelMode, overrideWith: minimumInputAccessLevelMode) - configure(&config.memoryManagementMode, overrideWith: memoryManagementMode) + configure(&config.unsignedNumbersMode, overrideWith: self.unsignedNumbersMode) + configure(&config.minimumInputAccessLevelMode, overrideWith: self.minimumInputAccessLevelMode) + configure(&config.memoryManagementMode, overrideWith: self.memoryManagementMode) - try checkModeCompatibility() + try checkModeCompatibility(config: config) if let inputSwift = commonOptions.inputSwift { config.inputSwiftDirectory = inputSwift @@ -104,7 +109,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) @@ -114,16 +119,16 @@ 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)") } } 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 +} From 0109bdcd93aa8d562f6b1b70f531e47067ea4540 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 31 Oct 2025 12:39:58 +0100 Subject: [PATCH 5/8] fix configure --- Samples/SwiftJavaExtractJNISampleApp/build.gradle | 2 +- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index d92c96fb..051ffc71 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -103,7 +103,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/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index c5280eee..284eb8ac 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -95,7 +95,6 @@ extension SwiftJava.JExtractCommand { let writeEmptyFiles = CommandLine.arguments.contains("--write-empty-files") ? true : nil configure(&config.writeEmptyFiles, overrideWith: writeEmptyFiles) - config.writeEmptyFiles = writeEmptyFiles configure(&config.unsignedNumbersMode, overrideWith: self.unsignedNumbersMode) configure(&config.minimumInputAccessLevelMode, overrideWith: self.minimumInputAccessLevelMode) configure(&config.memoryManagementMode, overrideWith: self.memoryManagementMode) @@ -134,7 +133,7 @@ extension SwiftJava.JExtractCommand { } } - func configure(_ setting: inout T, overrideWith value: T?) { + func configure(_ setting: inout T?, overrideWith value: T?) { if let value { setting = value } From 2a71014664c5c88cd09ac17403b109a93a0ea21f Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 2 Nov 2025 10:55:12 +0100 Subject: [PATCH 6/8] also confgure async --- .../JExtract/JExtractGenerationMode.swift | 4 ++++ Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index cd5522a2..9c0b6273 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -93,7 +93,6 @@ extension SwiftJava.JExtractCommand { config.swiftModule = self.effectiveSwiftModule config.outputJavaDirectory = outputJava config.outputSwiftDirectory = outputSwift - config.asyncFuncMode = asyncFuncMode // @Flag does not support optional, so we check ourself if it is passed let writeEmptyFiles = CommandLine.arguments.contains("--write-empty-files") ? true : nil @@ -102,6 +101,7 @@ extension SwiftJava.JExtractCommand { 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) From 4d82eaf48721ea764a0d8e46dc8ebee65f8647e3 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 2 Nov 2025 10:58:40 +0100 Subject: [PATCH 7/8] make async func mode optional --- Sources/SwiftJavaTool/Commands/JExtractCommand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index 9c0b6273..b5942d2a 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -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? } } From 3d143d3e16598fc4669e216b86ede430761a22f2 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 4 Nov 2025 09:35:17 +0900 Subject: [PATCH 8/8] Update Samples/SwiftJavaExtractJNISampleApp/build.gradle --- Samples/SwiftJavaExtractJNISampleApp/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/build.gradle b/Samples/SwiftJavaExtractJNISampleApp/build.gradle index 051ffc71..b1aa8490 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/build.gradle +++ b/Samples/SwiftJavaExtractJNISampleApp/build.gradle @@ -102,7 +102,6 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - // TODO: -v for debugging build issues... 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",/*