Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Plugins/PluginsShared/PluginUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions Samples/SwiftJavaExtractJNISampleApp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions Sources/JExtractSwiftLib/Swift2Java.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftJavaConfigurationShared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ public enum JExtractGenerationMode: String, Codable {

/// Java Native Interface
case jni

public static var `default`: JExtractGenerationMode {
.ffm
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
52 changes: 29 additions & 23 deletions Sources/SwiftJavaTool/Commands/JExtractCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: """
Expand All @@ -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?
}
}

Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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<T>(_ setting: inout T?, overrideWith value: T?) {
if let value {
setting = value
}
}
}

struct IncompatibleModeError: Error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,4 @@ extension SwiftJavaBaseAsyncParsableCommand {
config.logLevel = command.logLevel
return config
}
}
}