From d977e985a6db56b5df96b898569adf8deac6ca70 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 10 Apr 2025 17:55:04 +0900 Subject: [PATCH 1/2] Refactor to converge many command line tools into a single "swift-java" And we'll do subcommands instead. --- .gitignore | 2 ++ Makefile | 20 +++++------ Package.swift | 35 ++++++++----------- .../JExtractSwiftCommandPlugin.swift | 2 +- .../JExtractSwiftPlugin.swift | 2 +- .../JavaCompilerPlugin.swift | 2 +- .../Java2SwiftPlugin.swift | 10 +++--- .../_PluginsShared | 0 Samples/JavaDependencySampleApp/Package.swift | 4 +-- Samples/JavaKitSampleApp/Package.swift | 2 +- Samples/JavaProbablyPrime/Package.swift | 2 +- Samples/JavaProbablyPrime/README.md | 2 +- Samples/JavaSieve/Package.swift | 4 +-- Samples/JavaSieve/README.md | 4 +-- .../JExtractSwiftTool/JExtractSwiftTool.swift | 23 ------------ .../Configuration.swift | 2 +- .../JavaToSwift+EmitConfiguration.swift | 0 .../JavaToSwift+FetchDependencies.swift | 0 .../JavaToSwift+GenerateWrappers.swift | 0 .../JavaToSwift.swift | 11 +++--- .../String+Extensions.swift | 0 Tests/Java2SwiftTests/Java2SwiftTests.swift | 4 +-- USER_GUIDE.md | 18 +++++----- WIP.md | 4 +-- 24 files changed, 62 insertions(+), 91 deletions(-) rename Plugins/{Java2SwiftPlugin => SwiftJavaPlugin}/Java2SwiftPlugin.swift (96%) rename Plugins/{Java2SwiftPlugin => SwiftJavaPlugin}/_PluginsShared (100%) delete mode 100644 Sources/JExtractSwiftTool/JExtractSwiftTool.swift rename Sources/{Java2Swift => SwiftJavaTool}/JavaToSwift+EmitConfiguration.swift (100%) rename Sources/{Java2Swift => SwiftJavaTool}/JavaToSwift+FetchDependencies.swift (100%) rename Sources/{Java2Swift => SwiftJavaTool}/JavaToSwift+GenerateWrappers.swift (100%) rename Sources/{Java2Swift => SwiftJavaTool}/JavaToSwift.swift (96%) rename Sources/{Java2Swift => SwiftJavaTool}/String+Extensions.swift (100%) diff --git a/.gitignore b/.gitignore index 11bbeceb..82e8e094 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ DerivedData/ *.class bin/ BuildLogic/out/ +**/.index-build/ +.build-vscode/ # Ignore gradle build artifacts .gradle diff --git a/Makefile b/Makefile index 4b24b5a3..cf1a5257 100644 --- a/Makefile +++ b/Makefile @@ -56,29 +56,29 @@ SAMPLES_DIR := "Samples" all: @echo "Welcome to swift-java! There are several makefile targets to choose from:" @echo " javakit-run: Run the JavaKit example program that uses Java libraries from Swift." - @echo " javakit-generate: Regenerate the Swift wrapper code for the various JavaKit libraries from Java. This only has to be done when changing the Java2Swift tool." + @echo " javakit-generate: Regenerate the Swift wrapper code for the various JavaKit libraries from Java. This only has to be done when changing the SwiftJava tool." -$(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/Java2Swift: +$(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/SwiftJava: swift build javakit-run: cd Samples/JavaKitSampleApp && swift build && java -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java -Djava.library.path=.build/debug com.example.swift.JavaKitSampleMain -Java2Swift: $(BUILD_DIR)/debug/Java2Swift +SwiftJava: $(BUILD_DIR)/debug/SwiftJava -generate-JavaKit: Java2Swift +generate-JavaKit: SwiftJava mkdir -p Sources/JavaKit/generated - $(BUILD_DIR)/debug/Java2Swift --module-name JavaKit -o Sources/JavaKit/generated Sources/JavaKit/swift-java.config + $(BUILD_DIR)/debug/SwiftJava --module-name JavaKit -o Sources/JavaKit/generated Sources/JavaKit/swift-java.config -generate-JavaKitCollection: Java2Swift +generate-JavaKitCollection: SwiftJava mkdir -p Sources/JavaKitCollection/generated - $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitCollection --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitCollection/generated Sources/JavaKitCollection/swift-java.config + $(BUILD_DIR)/debug/SwiftJava --module-name JavaKitCollection --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitCollection/generated Sources/JavaKitCollection/swift-java.config -generate-JavaKitFunction: Java2Swift +generate-JavaKitFunction: SwiftJava mkdir -p Sources/JavaKitFunction/generated - $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitFunction --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitFunction/generated Sources/JavaKitFunction/swift-java.config + $(BUILD_DIR)/debug/SwiftJava --module-name JavaKitFunction --depends-on JavaKit=Sources/JavaKit/swift-java.config -o Sources/JavaKitFunction/generated Sources/JavaKitFunction/swift-java.config -generate-JavaKitReflection: Java2Swift generate-JavaKit generate-JavaKitCollection +generate-JavaKitReflection: SwiftJava generate-JavaKit generate-JavaKitCollection mkdir -p Sources/JavaKitReflection/generated $(BUILD_DIR)/debug/Java2Swift --module-name JavaKitReflection --depends-on JavaKit=Sources/JavaKit/swift-java.config --depends-on JavaKitCollection=Sources/JavaKitCollection/swift-java.config -o Sources/JavaKitReflection/generated Sources/JavaKitReflection/swift-java.config diff --git a/Package.swift b/Package.swift index 72a95155..9b75c42d 100644 --- a/Package.swift +++ b/Package.swift @@ -87,8 +87,8 @@ let package = Package( ), .executable( - name: "Java2Swift", - targets: ["Java2Swift"] + name: "SwiftJavaTool", + targets: ["SwiftJavaTool"] ), // ==== Plugin for building Java code @@ -101,9 +101,9 @@ let package = Package( // ==== Plugin for wrapping Java classes in Swift .plugin( - name: "Java2SwiftPlugin", + name: "SwiftJavaPlugin", targets: [ - "Java2SwiftPlugin" + "SwiftJavaPlugin" ] ), @@ -111,7 +111,7 @@ let package = Package( .executable( name: "jextract-swift", - targets: ["JExtractSwiftTool"] + targets: ["SwiftJavaTool"] ), // Support library written in Swift for SwiftKit "Java" @@ -257,10 +257,10 @@ let package = Package( ), .plugin( - name: "Java2SwiftPlugin", + name: "SwiftJavaPlugin", capability: .buildTool(), dependencies: [ - "Java2Swift" + "SwiftJavaTool" ] ), @@ -320,7 +320,7 @@ let package = Package( ), .executableTarget( - name: "Java2Swift", + name: "SwiftJavaTool", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), @@ -330,6 +330,7 @@ let package = Package( "JavaKitJar", "JavaKitNetwork", "Java2SwiftLib", + "JExtractSwift", // TODO: Swift2JavaLib "JavaKitShared", ], @@ -356,21 +357,11 @@ let package = Package( ] ), - .executableTarget( - name: "JExtractSwiftTool", - dependencies: [ - "JExtractSwift", - ], - swiftSettings: [ - .swiftLanguageMode(.v5) - ] - ), - .plugin( name: "JExtractSwiftPlugin", capability: .buildTool(), dependencies: [ - "JExtractSwiftTool" + "SwiftJavaTool" ] ), .plugin( @@ -380,7 +371,7 @@ let package = Package( permissions: [ ]), dependencies: [ - "JExtractSwiftTool" + "SwiftJavaTool" ] ), @@ -414,7 +405,9 @@ let package = Package( .testTarget( name: "Java2SwiftTests", - dependencies: ["Java2SwiftLib"], + dependencies: [ + "Java2SwiftLib" + ], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift index 65ce8971..980b78d0 100644 --- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift +++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift @@ -135,7 +135,7 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin func runExtract(context: PluginContext, target: Target, arguments: [String]) throws { let process = Process() - process.executableURL = try context.tool(named: "JExtractSwiftTool").url + process.executableURL = try context.tool(named: "SwiftJavaTool").url process.arguments = arguments do { diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index f4255ff5..30f8019b 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -22,7 +22,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE") func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { - let toolURL = try context.tool(named: "JExtractSwiftTool").url + let toolURL = try context.tool(named: "SwiftJavaTool").url guard let sourceModule = target.sourceModule else { return [] } diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index b93fe403..cceca5f1 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -34,7 +34,7 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { // The name of the configuration file JavaKit.config from the target for // which we are generating Swift wrappers for Java classes. - let configFile = URL(filePath: sourceDir).appending(path: "Java2Swift.config") + let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") let config: Configuration? if let configData = try? Data(contentsOf: configFile) { diff --git a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift b/Plugins/SwiftJavaPlugin/Java2SwiftPlugin.swift similarity index 96% rename from Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift rename to Plugins/SwiftJavaPlugin/Java2SwiftPlugin.swift index 88507f2e..b65ee1ac 100644 --- a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift +++ b/Plugins/SwiftJavaPlugin/Java2SwiftPlugin.swift @@ -18,7 +18,7 @@ import PackagePlugin fileprivate let SwiftJavaConfigFileName = "swift-java.config" @main -struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { +struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { var pluginName: String = "swift-java" var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE") @@ -27,7 +27,7 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("Create build commands for target '\(target.name)'") guard let sourceModule = target.sourceModule else { return [] } - let executable = try context.tool(named: "Java2Swift").url + let executable = try context.tool(named: "SwiftJavaTool").url var commands: [Command] = [] // Note: Target doesn't have a directoryURL counterpart to directory, @@ -44,7 +44,7 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { log("Config was: \(config)") var javaDependencies = config.dependencies ?? [] - /// Find the manifest files from other Java2Swift executions in any targets + /// Find the manifest files from other SwiftJava executions in any targets /// this target depends on. var dependentConfigFiles: [(String, URL)] = [] func searchForConfigFiles(in target: any Target) { @@ -107,7 +107,7 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let classes = config.classes ?? [:] print("Classes to wrap: \(classes.map(\.key))") - /// Determine the set of Swift files that will be emitted by the Java2Swift tool. + /// Determine the set of Swift files that will be emitted by the SwiftJava tool. // TODO: this is not precise and won't work with more advanced Java files, e.g. lambdas etc. let outputDirectoryGenerated = self.outputDirectory(context: context, generated: true) let outputSwiftFiles = classes.map { (javaClassName, swiftName) in @@ -199,7 +199,7 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { } } -extension Java2SwiftBuildToolPlugin { +extension SwiftJavaBuildToolPlugin { func argumentsModuleName(sourceModule: Target) -> [String] { return [ "--module-name", sourceModule.name diff --git a/Plugins/Java2SwiftPlugin/_PluginsShared b/Plugins/SwiftJavaPlugin/_PluginsShared similarity index 100% rename from Plugins/Java2SwiftPlugin/_PluginsShared rename to Plugins/SwiftJavaPlugin/_PluginsShared diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index a90cd255..4b51988c 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -76,7 +76,7 @@ let package = Package( .swiftLanguageMode(.v5), ], plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), @@ -94,7 +94,7 @@ let package = Package( ], plugins: [ // .plugin(name: "SwiftJavaBootstrapJavaPlugin", package: "swift-java"), - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index e51867cc..0956290c 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -77,7 +77,7 @@ let package = Package( plugins: [ .plugin(name: "JavaCompilerPlugin", package: "swift-java"), .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), ] diff --git a/Samples/JavaProbablyPrime/Package.swift b/Samples/JavaProbablyPrime/Package.swift index e837bfdb..4cc887f8 100644 --- a/Samples/JavaProbablyPrime/Package.swift +++ b/Samples/JavaProbablyPrime/Package.swift @@ -34,7 +34,7 @@ let package = Package( .swiftLanguageMode(.v5) ], plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), ] diff --git a/Samples/JavaProbablyPrime/README.md b/Samples/JavaProbablyPrime/README.md index 819ecf04..f72c0669 100644 --- a/Samples/JavaProbablyPrime/README.md +++ b/Samples/JavaProbablyPrime/README.md @@ -8,5 +8,5 @@ swift run JavaProbablyPrime The package itself demonstrates how to: -* Use the Java2Swift build tool plugin to wrap the `java.math.BigInteger` type in Swift. +* Use the SwiftJava build tool plugin to wrap the `java.math.BigInteger` type in Swift. * Create an instance of `BigInteger` in Swift and use its `isProbablyPrime`. diff --git a/Samples/JavaSieve/Package.swift b/Samples/JavaSieve/Package.swift index 35dcd19c..cd65f82e 100644 --- a/Samples/JavaSieve/Package.swift +++ b/Samples/JavaSieve/Package.swift @@ -58,7 +58,7 @@ let package = Package( .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ], plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), @@ -75,7 +75,7 @@ let package = Package( .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ], plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), ] diff --git a/Samples/JavaSieve/README.md b/Samples/JavaSieve/README.md index 09fbde83..bc7ef1f5 100644 --- a/Samples/JavaSieve/README.md +++ b/Samples/JavaSieve/README.md @@ -2,8 +2,8 @@ This package contains an example program that demonstrates importing a Java library distributed as a Jar file into Swift and using some APIs from that library. It demonstrates how to: -* Use the Java2Swift tool to discover the classes in a Jar file and make them available in Swift -* Layer Swift wrappers for Java classes as separate Swift modules using Java2Swift +* Use the SwiftJava tool to discover the classes in a Jar file and make them available in Swift +* Layer Swift wrappers for Java classes as separate Swift modules using SwiftJava * Access static methods of Java classes from Swift This example wraps an [open-source Java library](https://github.com/gazman-sdk/quadratic-sieve-Java) implementing the [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) algorithm for finding prime numbers, among other algorithms. To get started, clone that repository and build a Jar file containing the library: diff --git a/Sources/JExtractSwiftTool/JExtractSwiftTool.swift b/Sources/JExtractSwiftTool/JExtractSwiftTool.swift deleted file mode 100644 index f219cc8c..00000000 --- a/Sources/JExtractSwiftTool/JExtractSwiftTool.swift +++ /dev/null @@ -1,23 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import JExtractSwift - -@main -struct JExtractSwift { - static func main() throws { - let command = SwiftToJava.parseOrExit(CommandLine.arguments) - try command.run() - } -} diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index 13c7fd9b..4a1aac55 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -29,7 +29,7 @@ public struct Configuration: Codable { // ==== java 2 swift --------------------------------------------------------- - /// The Java class path that should be passed along to the Java2Swift tool. + /// The Java class path that should be passed along to the SwiftJava tool. public var classpath: String? = nil public var classpathEntries: [String] { diff --git a/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift b/Sources/SwiftJavaTool/JavaToSwift+EmitConfiguration.swift similarity index 100% rename from Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift rename to Sources/SwiftJavaTool/JavaToSwift+EmitConfiguration.swift diff --git a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift b/Sources/SwiftJavaTool/JavaToSwift+FetchDependencies.swift similarity index 100% rename from Sources/Java2Swift/JavaToSwift+FetchDependencies.swift rename to Sources/SwiftJavaTool/JavaToSwift+FetchDependencies.swift diff --git a/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift b/Sources/SwiftJavaTool/JavaToSwift+GenerateWrappers.swift similarity index 100% rename from Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift rename to Sources/SwiftJavaTool/JavaToSwift+GenerateWrappers.swift diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/SwiftJavaTool/JavaToSwift.swift similarity index 96% rename from Sources/Java2Swift/JavaToSwift.swift rename to Sources/SwiftJavaTool/JavaToSwift.swift index 536b3fd1..c9d0d2f5 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/SwiftJavaTool/JavaToSwift.swift @@ -24,24 +24,23 @@ import SwiftSyntaxBuilder import JavaKitConfigurationShared import JavaKitShared -/// Command-line utility to drive the export of Java classes into Swift types. @main struct JavaToSwift: AsyncParsableCommand { - static var _commandName: String { "Java2Swift" } + static var _commandName: String { "swift-java" } @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") var moduleName: String? @Option( help: - "A Java2Swift configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/Java2Swift.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources." + "A swift-java configuration file for a given Swift module name on which this module depends, e.g., JavaKitJar=Sources/JavaKitJar/swift-java.config. There should be one of these options for each Swift module that this module depends on (transitively) that contains wrapped Java sources." ) var dependsOn: [String] = [] // TODO: This should be a "make wrappers" option that just detects when we give it a jar @Flag( help: - "Specifies that the input is a Jar file whose public classes will be loaded. The output of Java2Swift will be a configuration file (Java2Swift.config) that can be used as input to a subsequent Java2Swift invocation to generate wrappers for those public classes." + "Specifies that the input is a Jar file whose public classes will be loaded. The output of swift-java will be a configuration file (swift-java.config) that can be used as input to a subsequent swift-java invocation to generate wrappers for those public classes." ) var jar: Bool = false @@ -59,7 +58,7 @@ struct JavaToSwift: AsyncParsableCommand { ) var swiftNativeImplementation: [String] = [] - @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the Java2Swift configuration file.") + @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the swift-java configuration file.") var outputDirectory: String? = nil @@ -88,7 +87,7 @@ struct JavaToSwift: AsyncParsableCommand { @Argument( help: - "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file." + "The input file, which is either a swift-java configuration file or (if '-jar' was specified) a Jar file." ) var input: String diff --git a/Sources/Java2Swift/String+Extensions.swift b/Sources/SwiftJavaTool/String+Extensions.swift similarity index 100% rename from Sources/Java2Swift/String+Extensions.swift rename to Sources/SwiftJavaTool/String+Extensions.swift diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/Java2SwiftTests/Java2SwiftTests.swift index 48440522..b295277a 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/Java2SwiftTests/Java2SwiftTests.swift @@ -100,7 +100,7 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self = APRIL } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run SwiftJava on the most updated Java class") } """, """ @@ -361,7 +361,7 @@ class Java2SwiftTests: XCTestCase { if let APRIL = classObj.APRIL { self.init(javaHolder: APRIL.javaHolder) } else { - fatalError("Enum value APRIL was unexpectedly nil, please re-run Java2Swift on the most updated Java class") + fatalError("Enum value APRIL was unexpectedly nil, please re-run SwiftJava on the most updated Java class") } """, """ diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 2abe8ea0..e847d194 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -8,8 +8,8 @@ Before using this package, set the `JAVA_HOME` environment variable to point at ### Using Java libraries from Swift -Existing Java libraries can be wrapped for use in Swift with the `Java2Swift` -tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `Java2Swift.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`: +Existing Java libraries can be wrapped for use in Swift with the `SwiftJava` +tool. In a Swift program, the most direct way to access a Java API is to use the SwiftPM plugin to provide Swift wrappers for the Java classes. To do so, add a configuration file `SwiftJava.config` into the source directory for the Swift target. This is a JSON file that specifies Java classes and the Swift type name that should be generated to wrap them. For example, the following file maps `java.math.BigInteger` to a Swift type named `BigInteger`: ```json { @@ -31,11 +31,11 @@ or, equivalently, adding the following to the package dependencies: .package(url: "https://github.com/swiftlang/swift-java", branch: "main"), ``` -Finally, update `Package.swift` so that the `Java2SwiftPlugin` plugin runs on the target in which you want to generate Swift wrappers. The plugin looks like this: +Finally, update `Package.swift` so that the `SwiftJavaPlugin` plugin runs on the target in which you want to generate Swift wrappers. The plugin looks like this: ```swift plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ``` @@ -101,10 +101,10 @@ let bigInt = BigInteger(veryBigNumber, environment: jniEnvironment) ### Importing a Jar file into Swift -Java libraries are often distributed as Jar files. The `Java2Swift` tool can inspect a Jar file to create a `Java2Swift.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command: +Java libraries are often distributed as Jar files. The `SwiftJava` tool can inspect a Jar file to create a `SwiftJava.config` file that will wrap all of the public classes for use in Swift. Following the example in `swift-java/Samples/JavaSieve`, we will wrap a small [Java library for computing prime numbers](https://github.com/gazman-sdk/quadratic-sieve-Java) for use in Swift. Assuming we have a Jar file `QuadraticSieve-1.0.jar` in the package directory, run the following command: ```swift -swift run Java2Swift --module-name JavaSieve --jar QuadraticSieve-1.0.jar +swift run SwiftJava --module-name JavaSieve --jar QuadraticSieve-1.0.jar ``` The resulting configuration file will look something like this: @@ -142,7 +142,7 @@ The resulting configuration file will look something like this: } ``` -As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`) and apply the `Java2Swift` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target. +As with the previous `JavaProbablyPrime` sample, the `JavaSieve` target in `Package.swift` should depend on the `swift-java` package modules (`JavaKit`) and apply the `SwiftJava` plugin. This makes all of the Java classes found in the Jar file available to Swift within the `JavaSieve` target. If you inspect the build output, there are a number of warnings that look like this: @@ -159,12 +159,12 @@ These warnings mean that some of the APIs in the Java library aren't available i .product(name: "JavaKit", package: "swift-java"), ], plugins: [ - .plugin(name: "Java2SwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), ``` -Then define a a Java2Swift configuration file in `Sources/JavaMath/Java2Swift.config` to bring in the types we need: +Then define a a SwiftJava configuration file in `Sources/JavaMath/SwiftJava.config` to bring in the types we need: ```json { diff --git a/WIP.md b/WIP.md index e1f58612..519a90b4 100644 --- a/WIP.md +++ b/WIP.md @@ -23,9 +23,9 @@ improve: - [ ] [SwiftPM build plugin](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) to generate the Swift projections from Java as part of the build - [x] Figure out how to launch the Java runtime from Swift code so we don't need to always start via `java` - [x] Figure out how to unit-test this framework using Swift Testing - - [x] Add a "Jar mode" to `Java2Swift` that translates all classes in the given Jar file. + - [x] Add a "Jar mode" to `SwiftJava` that translates all classes in the given Jar file. - [ ] Generate Swift projections for more common Java types into JavaKit libraries to make it easier to get started - - [ ] Teach `Java2Swift` when to create extensions of already-translated types that pick up any members that couldn't be translated because of missing types. See, for example, how `JavaKitReflection` adds extensions to `JavaClass` based on types like `Method` and `Parameter` + - [ ] Teach `SwiftJava` when to create extensions of already-translated types that pick up any members that couldn't be translated because of missing types. See, for example, how `JavaKitReflection` adds extensions to `JavaClass` based on types like `Method` and `Parameter` - Performance: - [ ] Cache method/field IDs when we can - [ ] Investigate noncopyable types to remove excess copies From 3b8bcb59ec6043f2524ca97ef056a1b5fd426160 Mon Sep 17 00:00:00 2001 From: Konrad 'ktoso' Malawski Date: Thu, 10 Apr 2025 19:19:44 +0900 Subject: [PATCH 2/2] Further cleanup of commands, splitting into sub commands --- .../JExtractSwiftCommandPlugin.swift | 1 + .../JExtractSwiftPlugin.swift | 1 + Sources/JExtractSwift/Swift2Java.swift | 24 +++++++---- Sources/JExtractSwift/Swift2JavaVisitor.swift | 4 +- Sources/SwiftJavaTool/JavaToSwift.swift | 21 ++++++---- Sources/SwiftJavaTool/SwiftJavaMain.swift | 41 +++++++++++++++++++ 6 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 Sources/SwiftJavaTool/SwiftJavaMain.swift diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift index 980b78d0..447e5340 100644 --- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift +++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift @@ -79,6 +79,7 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin } var arguments: [String] = [ + "jextract", "--swift-module", sourceModule.name, "--package-name", javaPackage, "--output-directory-java", context.outputDirectoryJava.path(percentEncoded: false), diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift index 30f8019b..0473af0f 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -54,6 +54,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let outputDirectorySwift = context.outputDirectorySwift var arguments: [String] = [ + "jextract", "--swift-module", sourceModule.name, "--package-name", javaPackage, "--output-directory-java", outputDirectoryJava.path(percentEncoded: false), diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwift/Swift2Java.swift index a46d1ab4..a05f221d 100644 --- a/Sources/JExtractSwift/Swift2Java.swift +++ b/Sources/JExtractSwift/Swift2Java.swift @@ -20,11 +20,14 @@ import JavaKitShared /// Command-line utility, similar to `jextract` to export Swift types to Java. public struct SwiftToJava: ParsableCommand { - public init() {} + public static var _commandName: String { "jextract" } + + public static var configuration = CommandConfiguration( + commandName: Self._commandName, + abstract: "Generate Java wrappers for Swift code.") - public static var _commandName: String { - "jextract-swift" - } + + public init() {} @Option(help: "The package the generated Java code should be emitted into.") var packageName: String @@ -49,18 +52,23 @@ public struct SwiftToJava: ParsableCommand { var input: [String] public func run() throws { - let inputPaths = self.input.dropFirst().map { URL(string: $0)! } + let inputPaths = self.input.map { URL(string: $0)! } let translator = Swift2JavaTranslator( javaPackage: packageName, swiftModuleName: swiftModule ) translator.log.logLevel = logLevel - + var allFiles: [URL] = [] let fileManager = FileManager.default let log = translator.log - + + guard !inputPaths.isEmpty else { + log.warning("Input paths are empty!") + return + } + for path in inputPaths { log.debug("Input path: \(path)") if isDirectory(url: path) { @@ -74,6 +82,8 @@ public struct SwiftToJava: ParsableCommand { } } + log.trace("Input file: \(allFiles)") + for file in allFiles where canExtract(from: file) { translator.log.debug("Importing module '\(swiftModule)', file: \(file)") diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 5b577589..4470b6b4 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -263,12 +263,12 @@ extension InitializerDeclSyntax { let isFailable = self.optionalMark != nil if isFailable { - log.warning("Skip importing failable initializer: \(self)") + log.debug("Skip importing failable initializer: \(self)") return false } // Ok, import it - log.warning("Import initializer: \(self)") + log.debug("Import initializer: \(self)") return true } } diff --git a/Sources/SwiftJavaTool/JavaToSwift.swift b/Sources/SwiftJavaTool/JavaToSwift.swift index c9d0d2f5..13589cd2 100644 --- a/Sources/SwiftJavaTool/JavaToSwift.swift +++ b/Sources/SwiftJavaTool/JavaToSwift.swift @@ -24,9 +24,12 @@ import SwiftSyntaxBuilder import JavaKitConfigurationShared import JavaKitShared -@main struct JavaToSwift: AsyncParsableCommand { - static var _commandName: String { "swift-java" } + static var _commandName: String { "java2swift" } + + static var configuration = CommandConfiguration( + commandName: Self._commandName, + abstract: "Generate Swift wrappers for Java code.") @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") var moduleName: String? @@ -61,10 +64,10 @@ struct JavaToSwift: AsyncParsableCommand { @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the swift-java configuration file.") var outputDirectory: String? = nil - + @Option(name: .shortAndLong, help: "Directory where to write cached values (e.g. swift-java.classpath files)") var cacheDirectory: String? = nil - + var effectiveCacheDirectory: String? { if let cacheDirectory { return cacheDirectory @@ -74,7 +77,7 @@ struct JavaToSwift: AsyncParsableCommand { return nil } } - + @Option(name: .shortAndLong, help: "How to handle an existing swift-java.config; by default 'overwrite' by can be changed to amending a configuration") var existingConfig: ExistingConfigFileMode = .overwrite public enum ExistingConfigFileMode: String, ExpressibleByArgument, Codable { @@ -168,7 +171,7 @@ struct JavaToSwift: AsyncParsableCommand { print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))") do { let config: Configuration - + // Determine the mode in which we'll execute. let toolMode: ToolMode if jar { @@ -217,14 +220,14 @@ struct JavaToSwift: AsyncParsableCommand { let classpathFromEnv = ProcessInfo.processInfo.environment["CLASSPATH"]?.split(separator: ":").map(String.init) ?? [] let classpathFromConfig: [String] = config.classpath?.split(separator: ":").map(String.init) ?? [] print("[debug][swift-java] Base classpath from config: \(classpathFromConfig)") - + var classpathEntries: [String] = classpathFromConfig - + let swiftJavaCachedModuleClasspath = findSwiftJavaClasspaths( in: self.effectiveCacheDirectory ?? FileManager.default.currentDirectoryPath) print("[debug][swift-java] Classpath from *.swift-java.classpath files: \(swiftJavaCachedModuleClasspath)") classpathEntries += swiftJavaCachedModuleClasspath - + if !classpathOptionEntries.isEmpty { print("[debug][swift-java] Classpath from options: \(classpathOptionEntries)") classpathEntries += classpathOptionEntries diff --git a/Sources/SwiftJavaTool/SwiftJavaMain.swift b/Sources/SwiftJavaTool/SwiftJavaMain.swift new file mode 100644 index 00000000..117c14e3 --- /dev/null +++ b/Sources/SwiftJavaTool/SwiftJavaMain.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Foundation +import Java2SwiftLib +import JExtractSwift +import JavaKit +import JavaKitJar +import JavaKitNetwork +import JavaKitReflection +import SwiftSyntax +import SwiftSyntaxBuilder +import JavaKitConfigurationShared +import JavaKitShared + +@main +struct SwiftJavaMain: AsyncParsableCommand { + static var _commandName: String { "swift-java" } + + static var configuration = CommandConfiguration( + abstract: "A utility for Swift and Java interoperability.", + subcommands: [ + JavaToSwift.self, + SwiftToJava.self, + ], + defaultSubcommand: JavaToSwift.self + ) +} +