diff --git a/Package.swift b/Package.swift index 42b74041..16c8a1a3 100644 --- a/Package.swift +++ b/Package.swift @@ -4,8 +4,7 @@ import CompilerPluginSupport import PackageDescription -import class Foundation.FileManager -import class Foundation.ProcessInfo +import Foundation // Note: the JAVA_HOME environment variable must be set to point to where // Java is installed, e.g., @@ -25,9 +24,53 @@ func findJavaHome() -> String { return home } + + if let home = getJavaHomeFromLibexecJavaHome(), + !home.isEmpty { + return home + } fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") } + +/// On MacOS we can use the java_home tool as a fallback if we can't find JAVA_HOME environment variable. +func getJavaHomeFromLibexecJavaHome() -> String? { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/libexec/java_home") + + // Check if the executable exists before trying to run it + guard FileManager.default.fileExists(atPath: task.executableURL!.path) else { + print("/usr/libexec/java_home does not exist") + return nil + } + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe // Redirect standard error to the same pipe for simplicity + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) + + if task.terminationStatus == 0 { + return output + } else { + print("java_home terminated with status: \(task.terminationStatus)") + // Optionally, log the error output for debugging + if let errorOutput = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) { + print("Error output: \(errorOutput)") + } + return nil + } + } catch { + print("Error running java_home: \(error)") + return nil + } +} + let javaHome = findJavaHome() let javaIncludePath = "\(javaHome)/include" @@ -92,8 +135,8 @@ let package = Package( ), .executable( - name: "Java2Swift", - targets: ["Java2Swift"] + name: "swift-java", + targets: ["SwiftJavaTool"] ), // ==== Plugin for building Java code @@ -106,19 +149,12 @@ let package = Package( // ==== Plugin for wrapping Java classes in Swift .plugin( - name: "Java2SwiftPlugin", + name: "SwiftJavaPlugin", targets: [ - "Java2SwiftPlugin" + "SwiftJavaPlugin" ] ), - // ==== jextract-swift (extract Java accessors from Swift interface files) - - .executable( - name: "jextract-swift", - targets: ["JExtractSwiftTool"] - ), - // Support library written in Swift for SwiftKit "Java" .library( name: "SwiftKitSwift", @@ -127,8 +163,8 @@ let package = Package( ), .library( - name: "JExtractSwift", - targets: ["JExtractSwift"] + name: "JExtractSwiftLib", + targets: ["JExtractSwiftLib"] ), // ==== Plugin for wrapping Java classes in Swift @@ -271,10 +307,10 @@ let package = Package( ), .plugin( - name: "Java2SwiftPlugin", + name: "SwiftJavaPlugin", capability: .buildTool(), dependencies: [ - "Java2Swift" + "SwiftJavaTool" ] ), @@ -312,7 +348,7 @@ let package = Package( ), .target( - name: "Java2SwiftLib", + name: "SwiftJavaLib", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), @@ -334,7 +370,7 @@ let package = Package( ), .executableTarget( - name: "Java2Swift", + name: "SwiftJavaTool", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), @@ -343,7 +379,8 @@ let package = Package( "JavaKit", "JavaKitJar", "JavaKitNetwork", - "Java2SwiftLib", + "SwiftJavaLib", + "JExtractSwiftLib", "JavaKitShared", ], @@ -355,7 +392,7 @@ let package = Package( ), .target( - name: "JExtractSwift", + name: "JExtractSwiftLib", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), @@ -370,21 +407,11 @@ let package = Package( ] ), - .executableTarget( - name: "JExtractSwiftTool", - dependencies: [ - "JExtractSwift", - ], - swiftSettings: [ - .swiftLanguageMode(.v5) - ] - ), - .plugin( name: "JExtractSwiftPlugin", capability: .buildTool(), dependencies: [ - "JExtractSwiftTool" + "SwiftJavaTool" ] ), .plugin( @@ -394,7 +421,7 @@ let package = Package( permissions: [ ]), dependencies: [ - "JExtractSwiftTool" + "SwiftJavaTool" ] ), @@ -427,8 +454,8 @@ let package = Package( ), .testTarget( - name: "Java2SwiftTests", - dependencies: ["Java2SwiftLib"], + name: "SwiftJavaTests", + dependencies: ["SwiftJavaLib"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -438,7 +465,7 @@ let package = Package( .testTarget( name: "JExtractSwiftTests", dependencies: [ - "JExtractSwift" + "JExtractSwiftLib" ], swiftSettings: [ .swiftLanguageMode(.v5), diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift index d6cfb7cb..3ea89886 100644 --- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift +++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift @@ -69,21 +69,21 @@ final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin let sourceDir = target.directory.string let configuration = try readConfiguration(sourceDir: "\(sourceDir)") - guard let javaPackage = configuration.javaPackage else { - throw SwiftJavaPluginError.missingConfiguration(sourceDir: "\(sourceDir)", key: "javaPackage") - } var arguments: [String] = [ - "--swift-module", sourceModule.name, - "--package-name", javaPackage, - "--output-directory-java", context.outputDirectoryJava.path(percentEncoded: false), - "--output-directory-swift", context.outputDirectorySwift.path(percentEncoded: false), + "--input-swift", sourceDir, + "--module-name", sourceModule.name, + "--output-java", context.outputJavaDirectory.path(percentEncoded: false), + "--output-swift", context.outputSwiftDirectory.path(percentEncoded: false), // TODO: "--build-cache-directory", ... // Since plugins cannot depend on libraries we cannot detect what the output files will be, // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin. // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc. ] - arguments.append(sourceDir) + // arguments.append(sourceDir) // TODO: we could do this shape maybe? to have the dirs last? + if let package = configuration?.javaPackage, !package.isEmpty { + ["--java-package", package] + } return arguments } @@ -130,7 +130,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..a2cda352 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 [] } @@ -42,7 +42,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { let sourceDir = target.directory.string let configuration = try readConfiguration(sourceDir: "\(sourceDir)") - guard let javaPackage = configuration.javaPackage else { + guard let javaPackage = configuration?.javaPackage else { // throw SwiftJavaPluginError.missingConfiguration(sourceDir: "\(sourceDir)", key: "javaPackage") log("Skipping jextract step, no 'javaPackage' configuration in \(getSwiftJavaConfigPath(target: target) ?? "")") return [] @@ -50,20 +50,23 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // We use the the usual maven-style structure of "src/[generated|main|test]/java/..." // that is common in JVM ecosystem - let outputDirectoryJava = context.outputDirectoryJava - let outputDirectorySwift = context.outputDirectorySwift + let outputJavaDirectory = context.outputJavaDirectory + let outputSwiftDirectory = context.outputSwiftDirectory var arguments: [String] = [ - "--swift-module", sourceModule.name, - "--package-name", javaPackage, - "--output-directory-java", outputDirectoryJava.path(percentEncoded: false), - "--output-directory-swift", outputDirectorySwift.path(percentEncoded: false), + "--input-swift", sourceDir, + "--module-name", sourceModule.name, + "--output-java", outputJavaDirectory.path(percentEncoded: false), + "--output-swift", outputSwiftDirectory.path(percentEncoded: false), // TODO: "--build-cache-directory", ... // Since plugins cannot depend on libraries we cannot detect what the output files will be, // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin. // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc. ] - arguments.append(sourceDir) + // arguments.append(sourceDir) + if !javaPackage.isEmpty { + arguments.append(contentsOf: ["--java-package", javaPackage]) + } return [ .prebuildCommand( @@ -72,7 +75,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { arguments: arguments, // inputFiles: [ configFile ] + swiftFiles, // outputFiles: outputJavaFiles - outputFilesDirectory: outputDirectorySwift + outputFilesDirectory: outputSwiftDirectory ) ] } diff --git a/Plugins/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift index 6932253a..691d3375 100644 --- a/Plugins/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -60,14 +60,14 @@ func getEnvironmentBool(_ name: String) -> Bool { } extension PluginContext { - var outputDirectoryJava: URL { + var outputJavaDirectory: URL { self.pluginWorkDirectoryURL .appending(path: "src") .appending(path: "generated") .appending(path: "java") } - var outputDirectorySwift: URL { + var outputSwiftDirectory: URL { self.pluginWorkDirectoryURL .appending(path: "Sources") } diff --git a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift similarity index 96% rename from Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift rename to Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift index 88507f2e..77d7058d 100644 --- a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift +++ b/Plugins/SwiftJavaPlugin/SwiftJavaPlugin.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, @@ -38,7 +38,7 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { // which we are generating Swift wrappers for Java classes. let configFile = URL(filePath: sourceDir) .appending(path: SwiftJavaConfigFileName) - let config = try readConfiguration(sourceDir: sourceDir) + let config = try readConfiguration(sourceDir: sourceDir) ?? Configuration() log("Config on path: \(configFile.path(percentEncoded: false))") log("Config was: \(config)") @@ -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 2b5ae361..b39e7b81 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"), ] ), @@ -96,7 +96,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/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/Sources/JExtractSwift/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/CDeclLowering/CRepresentation.swift similarity index 100% rename from Sources/JExtractSwift/CDeclLowering/CRepresentation.swift rename to Sources/JExtractSwiftLib/CDeclLowering/CRepresentation.swift diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwiftLib/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift similarity index 100% rename from Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift rename to Sources/JExtractSwiftLib/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift diff --git a/Sources/JExtractSwift/CTypes/CEnum.swift b/Sources/JExtractSwiftLib/CTypes/CEnum.swift similarity index 100% rename from Sources/JExtractSwift/CTypes/CEnum.swift rename to Sources/JExtractSwiftLib/CTypes/CEnum.swift diff --git a/Sources/JExtractSwift/CTypes/CFunction.swift b/Sources/JExtractSwiftLib/CTypes/CFunction.swift similarity index 100% rename from Sources/JExtractSwift/CTypes/CFunction.swift rename to Sources/JExtractSwiftLib/CTypes/CFunction.swift diff --git a/Sources/JExtractSwift/CTypes/CParameter.swift b/Sources/JExtractSwiftLib/CTypes/CParameter.swift similarity index 100% rename from Sources/JExtractSwift/CTypes/CParameter.swift rename to Sources/JExtractSwiftLib/CTypes/CParameter.swift diff --git a/Sources/JExtractSwift/CTypes/CStruct.swift b/Sources/JExtractSwiftLib/CTypes/CStruct.swift similarity index 100% rename from Sources/JExtractSwift/CTypes/CStruct.swift rename to Sources/JExtractSwiftLib/CTypes/CStruct.swift diff --git a/Sources/JExtractSwift/CTypes/CTag.swift b/Sources/JExtractSwiftLib/CTypes/CTag.swift similarity index 100% rename from Sources/JExtractSwift/CTypes/CTag.swift rename to Sources/JExtractSwiftLib/CTypes/CTag.swift diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwiftLib/CTypes/CType.swift similarity index 100% rename from Sources/JExtractSwift/CTypes/CType.swift rename to Sources/JExtractSwiftLib/CTypes/CType.swift diff --git a/Sources/JExtractSwift/CTypes/CUnion.swift b/Sources/JExtractSwiftLib/CTypes/CUnion.swift similarity index 100% rename from Sources/JExtractSwift/CTypes/CUnion.swift rename to Sources/JExtractSwiftLib/CTypes/CUnion.swift diff --git a/Sources/JExtractSwift/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift similarity index 100% rename from Sources/JExtractSwift/CodePrinter.swift rename to Sources/JExtractSwiftLib/CodePrinter.swift diff --git a/Sources/JExtractSwift/Convenience/Collection+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift similarity index 100% rename from Sources/JExtractSwift/Convenience/Collection+Extensions.swift rename to Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift diff --git a/Sources/JExtractSwift/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift similarity index 100% rename from Sources/JExtractSwift/Convenience/String+Extensions.swift rename to Sources/JExtractSwiftLib/Convenience/String+Extensions.swift diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift similarity index 100% rename from Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift rename to Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift diff --git a/Sources/JExtractSwift/ConversionStep.swift b/Sources/JExtractSwiftLib/ConversionStep.swift similarity index 100% rename from Sources/JExtractSwift/ConversionStep.swift rename to Sources/JExtractSwiftLib/ConversionStep.swift diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift similarity index 100% rename from Sources/JExtractSwift/ImportedDecls.swift rename to Sources/JExtractSwiftLib/ImportedDecls.swift diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwiftLib/JavaConstants/ForeignValueLayouts.swift similarity index 100% rename from Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift rename to Sources/JExtractSwiftLib/JavaConstants/ForeignValueLayouts.swift diff --git a/Sources/JExtractSwift/JavaConstants/JavaTypes.swift b/Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift similarity index 100% rename from Sources/JExtractSwift/JavaConstants/JavaTypes.swift rename to Sources/JExtractSwiftLib/JavaConstants/JavaTypes.swift diff --git a/Sources/JExtractSwift/Logger.swift b/Sources/JExtractSwiftLib/Logger.swift similarity index 72% rename from Sources/JExtractSwift/Logger.swift rename to Sources/JExtractSwiftLib/Logger.swift index 9aceb201..180ffb54 100644 --- a/Sources/JExtractSwift/Logger.swift +++ b/Sources/JExtractSwiftLib/Logger.swift @@ -14,6 +14,8 @@ import Foundation import SwiftSyntax +import ArgumentParser +import JavaKitConfigurationShared // Placeholder for some better logger, we could depend on swift-log public struct Logger { @@ -95,47 +97,17 @@ public struct Logger { } extension Logger { - public enum Level: String, Hashable { - case trace = "trace" - case debug = "debug" - case info = "info" - case notice = "notice" - case warning = "warning" - case error = "error" - case critical = "critical" - } + public typealias Level = JavaKitConfigurationShared.LogLevel } -extension Logger.Level { - public init(from decoder: any Decoder) throws { - var container = try decoder.unkeyedContainer() - let string = try container.decode(String.self) - switch string { - case "trace": self = .trace - case "debug": self = .debug - case "info": self = .info - case "notice": self = .notice - case "warning": self = .warning - case "error": self = .error - case "critical": self = .critical - default: fatalError("Unknown value for \(Logger.Level.self): \(string)") - } +extension Logger.Level: ExpressibleByArgument { + public var defaultValueDescription: String { + "log level" } + public private(set) static var allValueStrings: [String] = + ["trace", "debug", "info", "notice", "warning", "error", "critical"] - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - let text = - switch self { - case .trace: "trace" - case .debug: "debug" - case .info: "info" - case .notice: "notice" - case .warning: "warning" - case .error: "error" - case .critical: "critical" - } - try container.encode(text) - } + public private(set) static var defaultCompletionKind: CompletionKind = .default } extension Logger.Level { diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift similarity index 56% rename from Sources/JExtractSwift/Swift2Java.swift rename to Sources/JExtractSwiftLib/Swift2Java.swift index 6525fc0a..79cb0fec 100644 --- a/Sources/JExtractSwift/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -12,57 +12,49 @@ // //===----------------------------------------------------------------------===// -import ArgumentParser import Foundation import SwiftSyntax import SwiftSyntaxBuilder import JavaKitShared +import JavaKitConfigurationShared // TODO: this should become SwiftJavaConfigurationShared -/// Command-line utility, similar to `jextract` to export Swift types to Java. -public struct SwiftToJava: ParsableCommand { - public init() {} +public struct SwiftToJava { + let config: Configuration - public static var _commandName: String { - "jextract-swift" + public init(config: Configuration) { + self.config = config } - @Option(help: "The package the generated Java code should be emitted into.") - var packageName: String - - @Option( - name: .shortAndLong, - help: "The directory in which to output the generated Swift files and manifest.") - var outputDirectoryJava: String = ".build/jextract-swift/generated" - - @Option(help: "Swift output directory") - var outputDirectorySwift: String - - @Option( - name: .long, - help: "Name of the Swift module to import (and the swift interface files belong to)") - var swiftModule: String - - @Option(name: .shortAndLong, help: "Configure the level of lots that should be printed") - var logLevel: Logger.Level = .info - - @Argument(help: "The Swift files or directories to recursively export to Java.") - var input: [String] - public func run() throws { - let inputPaths = self.input.dropFirst().map { URL(string: $0)! } + guard let swiftModule = config.swiftModule else { + fatalError("Missing '--swift-module' name.") + } let translator = Swift2JavaTranslator( - javaPackage: packageName, + javaPackage: config.javaPackage ?? "", // no package is ok, we'd generate all into top level swiftModuleName: swiftModule ) - translator.log.logLevel = logLevel + translator.log.logLevel = config.logLevel ?? .info + + if config.javaPackage == nil || config.javaPackage!.isEmpty { + translator.log.warning("Configured java package is '', consider specifying concrete package for generated sources.") + } + + print("===== CONFIG ==== \(config)") + + guard let inputSwift = config.inputSwiftDirectory else { + fatalError("Missing '--swift-input' directory!") + } + + let inputPaths = inputSwift.split(separator: ",").map { URL(string: String($0))! } + translator.log.info("Input paths = \(inputPaths)") var allFiles: [URL] = [] let fileManager = FileManager.default let log = translator.log - + for path in inputPaths { - log.debug("Input path: \(path)") + log.info("Input path: \(path)") if isDirectory(url: path) { if let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: nil) { for case let fileURL as URL in enumerator { @@ -88,10 +80,21 @@ public struct SwiftToJava: ParsableCommand { translator.add(filePath: file.path, text: text) } + guard let outputSwiftDirectory = config.outputSwiftDirectory else { + fatalError("Missing --output-swift directory!") + } + guard let outputJavaDirectory = config.outputJavaDirectory else { + fatalError("Missing --output-java directory!") + } + try translator.analyze() - try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift) - try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava) - print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/") + + try translator.writeSwiftThunkSources(outputDirectory: outputSwiftDirectory) + print("[swift-java] Generated Swift sources (module: '\(config.swiftModule ?? "")') in: \(outputSwiftDirectory)/") + + try translator.writeExportedJavaSources(outputDirectory: outputJavaDirectory) + print("[swift-java] Generated Java sources (package: '\(config.javaPackage ?? "")') in: \(outputJavaDirectory)/") + print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green) } @@ -102,16 +105,6 @@ public struct SwiftToJava: ParsableCommand { } -extension Logger.Level: ExpressibleByArgument { - public var defaultValueDescription: String { - "log level" - } - public private(set) static var allValueStrings: [String] = - ["trace", "debug", "info", "notice", "warning", "error", "critical"] - - public private(set) static var defaultCompletionKind: CompletionKind = .default -} - func isDirectory(url: URL) -> Bool { var isDirectory: ObjCBool = false _ = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaBindingsPrinting.swift similarity index 100% rename from Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaBindingsPrinting.swift diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaTranslation.swift similarity index 100% rename from Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+JavaTranslation.swift diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+Printing.swift similarity index 100% rename from Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+Printing.swift diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator+SwiftThunkPrinting.swift similarity index 100% rename from Sources/JExtractSwift/Swift2JavaTranslator+SwiftThunkPrinting.swift rename to Sources/JExtractSwiftLib/Swift2JavaTranslator+SwiftThunkPrinting.swift diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift similarity index 100% rename from Sources/JExtractSwift/Swift2JavaTranslator.swift rename to Sources/JExtractSwiftLib/Swift2JavaTranslator.swift diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift similarity index 100% rename from Sources/JExtractSwift/Swift2JavaVisitor.swift rename to Sources/JExtractSwiftLib/Swift2JavaVisitor.swift diff --git a/Sources/JExtractSwift/SwiftKit+Printing.swift b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift similarity index 100% rename from Sources/JExtractSwift/SwiftKit+Printing.swift rename to Sources/JExtractSwiftLib/SwiftKit+Printing.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTable.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftResult.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftResult.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftResult.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftStandardLibraryTypes.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift similarity index 100% rename from Sources/JExtractSwift/SwiftTypes/SwiftType.swift rename to Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift diff --git a/Sources/JExtractSwift/ThunkNameRegistry.swift b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift similarity index 100% rename from Sources/JExtractSwift/ThunkNameRegistry.swift rename to Sources/JExtractSwiftLib/ThunkNameRegistry.swift 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..1c4fad56 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -21,12 +21,23 @@ import Foundation public typealias JavaVersion = Int -/// Configuration for the SwiftJava plugins, provided on a per-target basis. +/// Configuration for the SwiftJava tools and plugins, provided on a per-target basis. public struct Configuration: Codable { - // ==== swift 2 java --------------------------------------------------------- + + public var logLevel: LogLevel? + + // ==== swift 2 java / jextract swift --------------------------------------- public var javaPackage: String? + public var swiftModule: String? + + public var inputSwiftDirectory: String? + + public var outputSwiftDirectory: String? + + public var outputJavaDirectory: String? + // ==== java 2 swift --------------------------------------------------------- /// The Java class path that should be passed along to the Java2Swift tool. @@ -102,7 +113,7 @@ public struct JavaDependencyDescriptor: Hashable, Codable { } } -public func readConfiguration(sourceDir: String, file: String = #fileID, line: UInt = #line) throws -> Configuration { +public func readConfiguration(sourceDir: String, file: String = #fileID, line: UInt = #line) throws -> Configuration? { // Workaround since filePath is macOS 13 let sourcePath = if sourceDir.hasPrefix("file://") { sourceDir } else { "file://" + sourceDir } @@ -111,12 +122,15 @@ public func readConfiguration(sourceDir: String, file: String = #fileID, line: U return try readConfiguration(configPath: configPath, file: file, line: line) } -public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration { +public func readConfiguration(configPath: URL, file: String = #fileID, line: UInt = #line) throws -> Configuration? { + guard let configData = try? Data(contentsOf: configPath) else { + return nil + } + do { - let configData = try Data(contentsOf: configPath) return try JSONDecoder().decode(Configuration.self, from: configData) } catch { - throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.absoluteURL)'!", error: error, + throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configPath.absoluteURL)'! \(#fileID):\(#line)", error: error, file: file, line: line) } } @@ -200,3 +214,45 @@ public struct ConfigurationError: Error { self.line = line } } + +public enum LogLevel: String, Codable, Hashable { + case trace = "trace" + case debug = "debug" + case info = "info" + case notice = "notice" + case warning = "warning" + case error = "error" + case critical = "critical" +} + +extension LogLevel { + public init(from decoder: any Decoder) throws { + var container = try decoder.unkeyedContainer() + let string = try container.decode(String.self) + switch string { + case "trace": self = .trace + case "debug": self = .debug + case "info": self = .info + case "notice": self = .notice + case "warning": self = .warning + case "error": self = .error + case "critical": self = .critical + default: fatalError("Unknown value for \(LogLevel.self): \(string)") + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + let text = + switch self { + case .trace: "trace" + case .debug: "debug" + case .info: "info" + case .notice: "notice" + case .warning: "warning" + case .error: "error" + case .critical: "critical" + } + try container.encode(text) + } +} \ No newline at end of file diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/SwiftJavaLib/JavaClassTranslator.swift similarity index 100% rename from Sources/Java2SwiftLib/JavaClassTranslator.swift rename to Sources/SwiftJavaLib/JavaClassTranslator.swift diff --git a/Sources/Java2SwiftLib/JavaTranslator+Configuration.swift b/Sources/SwiftJavaLib/JavaTranslator+Configuration.swift similarity index 100% rename from Sources/Java2SwiftLib/JavaTranslator+Configuration.swift rename to Sources/SwiftJavaLib/JavaTranslator+Configuration.swift diff --git a/Sources/Java2SwiftLib/JavaTranslator+Validation.swift b/Sources/SwiftJavaLib/JavaTranslator+Validation.swift similarity index 100% rename from Sources/Java2SwiftLib/JavaTranslator+Validation.swift rename to Sources/SwiftJavaLib/JavaTranslator+Validation.swift diff --git a/Sources/Java2SwiftLib/JavaTranslator.swift b/Sources/SwiftJavaLib/JavaTranslator.swift similarity index 100% rename from Sources/Java2SwiftLib/JavaTranslator.swift rename to Sources/SwiftJavaLib/JavaTranslator.swift diff --git a/Sources/Java2SwiftLib/MethodVariance.swift b/Sources/SwiftJavaLib/MethodVariance.swift similarity index 100% rename from Sources/Java2SwiftLib/MethodVariance.swift rename to Sources/SwiftJavaLib/MethodVariance.swift diff --git a/Sources/Java2SwiftLib/OptionalKind.swift b/Sources/SwiftJavaLib/OptionalKind.swift similarity index 100% rename from Sources/Java2SwiftLib/OptionalKind.swift rename to Sources/SwiftJavaLib/OptionalKind.swift diff --git a/Sources/Java2SwiftLib/StringExtras.swift b/Sources/SwiftJavaLib/StringExtras.swift similarity index 100% rename from Sources/Java2SwiftLib/StringExtras.swift rename to Sources/SwiftJavaLib/StringExtras.swift diff --git a/Sources/Java2SwiftLib/TranslationError.swift b/Sources/SwiftJavaLib/TranslationError.swift similarity index 100% rename from Sources/Java2SwiftLib/TranslationError.swift rename to Sources/SwiftJavaLib/TranslationError.swift diff --git a/Sources/Java2Swift/String+Extensions.swift b/Sources/SwiftJavaTool/String+Extensions.swift similarity index 97% rename from Sources/Java2Swift/String+Extensions.swift rename to Sources/SwiftJavaTool/String+Extensions.swift index 26a20241..f2bb9e72 100644 --- a/Sources/Java2Swift/String+Extensions.swift +++ b/Sources/SwiftJavaTool/String+Extensions.swift @@ -14,10 +14,10 @@ import Foundation import ArgumentParser -import Java2SwiftLib +import SwiftJavaLib import JavaKit import JavaKitJar -import Java2SwiftLib +import SwiftJavaLib import JavaKitConfigurationShared extension String { diff --git a/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift b/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift similarity index 98% rename from Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift rename to Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift index 6754d381..e029d2db 100644 --- a/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift +++ b/Sources/SwiftJavaTool/SwiftJava+EmitConfiguration.swift @@ -14,13 +14,12 @@ import Foundation import ArgumentParser -import Java2SwiftLib +import SwiftJavaLib import JavaKit import JavaKitJar -import Java2SwiftLib import JavaKitConfigurationShared -extension JavaToSwift { +extension SwiftJava { // TODO: make this perhaps "emit type mappings" mutating func emitConfiguration( diff --git a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift b/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift similarity index 99% rename from Sources/Java2Swift/JavaToSwift+FetchDependencies.swift rename to Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift index 2a9694c0..47570b19 100644 --- a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift +++ b/Sources/SwiftJavaTool/SwiftJava+FetchDependencies.swift @@ -13,16 +13,16 @@ //===----------------------------------------------------------------------===// import Foundation -import Java2SwiftLib +import SwiftJavaLib import JavaKit import Foundation import JavaKitJar -import Java2SwiftLib +import SwiftJavaLib import JavaKitConfigurationShared import JavaKitShared import _Subprocess -extension JavaToSwift { +extension SwiftJava { var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" } diff --git a/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift b/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift similarity index 98% rename from Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift rename to Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift index 67aa3f9c..a57644de 100644 --- a/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift +++ b/Sources/SwiftJavaTool/SwiftJava+GenerateWrappers.swift @@ -14,13 +14,13 @@ import Foundation import ArgumentParser -import Java2SwiftLib +import SwiftJavaLib import JavaKit import JavaKitJar -import Java2SwiftLib +import SwiftJavaLib import JavaKitConfigurationShared -extension JavaToSwift { +extension SwiftJava { mutating func generateWrappers( config: Configuration, classpath: String, diff --git a/Sources/SwiftJavaTool/SwiftJava+JExtract.swift b/Sources/SwiftJavaTool/SwiftJava+JExtract.swift new file mode 100644 index 00000000..79c96d3e --- /dev/null +++ b/Sources/SwiftJavaTool/SwiftJava+JExtract.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// 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 Foundation +import ArgumentParser +import SwiftJavaLib +import JavaKit +import JavaKitJar +import SwiftJavaLib +import JExtractSwiftLib +import JavaKitConfigurationShared + +/// Extract Java bindings from Swift sources or interface files. +/// +/// Example usage: +/// ``` +/// > swift-java --input-swift Sources/SwiftyBusiness \ +/// --output-swift .build/.../outputs/SwiftyBusiness \ +/// --output-Java .build/.../outputs/Java +/// ``` +extension SwiftJava { + + mutating func jextractSwift( + config: Configuration + ) throws { + try SwiftToJava(config: config).run() + } + +} diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/SwiftJavaTool/SwiftJava.swift similarity index 75% rename from Sources/Java2Swift/JavaToSwift.swift rename to Sources/SwiftJavaTool/SwiftJava.swift index 536b3fd1..badd5455 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/SwiftJavaTool/SwiftJava.swift @@ -14,7 +14,8 @@ import ArgumentParser import Foundation -import Java2SwiftLib +import SwiftJavaLib +import JExtractSwiftLib import JavaKit import JavaKitJar import JavaKitNetwork @@ -26,11 +27,11 @@ import JavaKitShared /// Command-line utility to drive the export of Java classes into Swift types. @main -struct JavaToSwift: AsyncParsableCommand { - static var _commandName: String { "Java2Swift" } +struct SwiftJava: AsyncParsableCommand { + 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? + var moduleName: String? // TODO: rename to --swift-module? @Option( help: @@ -59,13 +60,28 @@ 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(help: "Directory containing Swift files which should be extracted into Java bindings. Also known as 'jextract' mode. Must be paired with --output-java and --output-swift.") + var inputSwift: String? = nil + + @Option(help: "The directory where generated Swift files should be written. Generally used with jextract mode.") + var outputSwift: String? = nil + + @Option(help: "The directory where generated Java files should be written. Generally used with jextract mode.") + var outputJava: String? = nil + + @Option(help: "The Java package the generated Java code should be emitted into.") + var javaPackage: String? = nil + + // TODO: clarify this vs outputSwift (history: outputSwift is jextract, and this was java2swift) + @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files or the SwiftJava 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 - + + @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") + var logLevel: Logger.Level = .info + var effectiveCacheDirectory: String? { if let cacheDirectory { return cacheDirectory @@ -87,10 +103,9 @@ struct JavaToSwift: AsyncParsableCommand { var javaPackageFilter: String? = nil @Argument( - help: - "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file." + help: "The input file, which is either a Java2Swift configuration file or (if '-jar' was specified) a Jar file." ) - var input: String + var input: String? /// Whether we have ensured that the output directory exists. var createdOutputDirectory: Bool = false @@ -101,6 +116,7 @@ struct JavaToSwift: AsyncParsableCommand { return nil } + print("[debug][swift-java] Module base directory based on outputDirectory!") return URL(fileURLWithPath: outputDirectory) } @@ -143,6 +159,7 @@ struct JavaToSwift: AsyncParsableCommand { // The configuration file goes at the top level. let outputDir: Foundation.URL if jar { + precondition(self.input != nil, "-jar mode requires path to jar to be specified as input path") outputDir = baseDir } else { outputDir = baseDir @@ -163,23 +180,58 @@ struct JavaToSwift: AsyncParsableCommand { /// Fetch dependencies for a module case fetchDependencies + + /// Extract Java bindings from provided Swift sources. + case jextract // TODO: carry jextract specific config here? } mutating func run() async { print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))") + print("[info][swift-java] Current work directory: \(URL(fileURLWithPath: "."))") + print("[info][swift-java] Module base directory: \(moduleBaseDir)") do { - let config: Configuration - + var earlyConfig: Configuration? + if let moduleBaseDir { + print("[debug][swift-java] Load config from module base directory: \(moduleBaseDir.path)") + earlyConfig = try readConfiguration(sourceDir: moduleBaseDir.path) + } else if let inputSwift { + print("[debug][swift-java] Load config from module swift input directory: \(inputSwift)") + earlyConfig = try readConfiguration(sourceDir: inputSwift) + } + var config = earlyConfig ?? Configuration() + + config.logLevel = self.logLevel + if let javaPackage { + config.javaPackage = javaPackage + } + // Determine the mode in which we'll execute. let toolMode: ToolMode - if jar { - if let moduleBaseDir { - config = try readConfiguration(sourceDir: moduleBaseDir.path) - } else { - config = Configuration() + // TODO: some options are exclusive to each other so we should detect that + if let inputSwift { + guard let outputSwift else { + print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-swift directory was provided!\n\(Self.helpMessage())") + return + } + guard let outputJava else { + print("[swift-java] --input-swift enabled 'jextract' mode, however no --output-java directory was provided!\n\(Self.helpMessage())") + return + } + config.swiftModule = self.moduleName // FIXME: rename the moduleName + config.inputSwiftDirectory = self.inputSwift + config.outputSwiftDirectory = self.outputSwift + config.outputJavaDirectory = self.outputJava + + toolMode = .jextract + } else if jar { + guard let input else { + fatalError("Mode -jar requires path\n\(Self.helpMessage())") } toolMode = .configuration(extraClasspath: input) } else if fetch { + guard let input else { + fatalError("Mode -jar requires path\n\(Self.helpMessage())") + } config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) guard let dependencies = config.dependencies else { print("[swift-java] Running in 'fetch dependencies' mode but dependencies list was empty!") @@ -188,13 +240,23 @@ struct JavaToSwift: AsyncParsableCommand { } toolMode = .fetchDependencies } else { + guard let input else { + fatalError("Mode -jar requires path\n\(Self.helpMessage())") + } config = try JavaTranslator.readConfiguration(from: URL(fileURLWithPath: input)) toolMode = .classWrappers } - let moduleName = self.moduleName ?? - input.split(separator: "/").dropLast().last.map(String.init) ?? - "__UnknownModule" + print("[debug][swift-java] Running swift-java in mode: " + "\(toolMode.prettyName)".bold) + + let moduleName: String = + if let name = self.moduleName { + name + } else if let input { + input.split(separator: "/").dropLast().last.map(String.init) ?? "__UnknownModule" + } else { + "__UnknownModule" + } // Load all of the dependent configurations and associate them with Swift // modules. @@ -251,18 +313,24 @@ struct JavaToSwift: AsyncParsableCommand { // print("[debug][swift-java] Found cached dependency resolver classpath: \(dependencyResolverClasspath)") // classpathEntries += dependencyResolverClasspath // } - case .classWrappers: + case .classWrappers, .jextract: break; } - // Bring up the Java VM. + // Bring up the Java VM when necessary // TODO: print only in verbose mode let classpath = classpathEntries.joined(separator: ":") - print("[debug][swift-java] Initialize JVM with classpath: \(classpath)") - let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries) + let jvm: JavaVirtualMachine! + switch toolMode { + case .configuration, .classWrappers: + print("[debug][swift-java] Initialize JVM with classpath: \(classpath)") + jvm = try JavaVirtualMachine.shared(classpath: classpathEntries) + default: + jvm = nil + } - // * Classespaths from all dependent configuration files + // * Classpaths from all dependent configuration files for (_, config) in dependentConfigs { // TODO: may need to resolve the dependent configs rather than just get their configs // TODO: We should cache the resolved classpaths as well so we don't do it many times @@ -292,9 +360,6 @@ struct JavaToSwift: AsyncParsableCommand { guard let dependencies = config.dependencies else { fatalError("Configuration for fetching dependencies must have 'dependencies' defined!") } - guard let moduleName = self.moduleName else { - fatalError("Fetching dependencies must specify module name (--module-name)!") - } guard let effectiveCacheDirectory else { fatalError("Fetching dependencies must effective cache directory! Specify --output-directory or --cache-directory") } @@ -310,6 +375,9 @@ struct JavaToSwift: AsyncParsableCommand { moduleName: moduleName, cacheDir: effectiveCacheDirectory, resolvedClasspath: dependencyClasspath) + + case .jextract: + try jextractSwift(config: config) } } catch { // We fail like this since throwing out of the run often ends up hiding the failure reason when it is executed as SwiftPM plugin (!) @@ -382,7 +450,7 @@ struct JavaToSwift: AsyncParsableCommand { } } -extension JavaToSwift { +extension SwiftJava { /// Get base configuration, depending on if we are to 'amend' or 'overwrite' the existing configuration. package func getBaseConfigurationForWrite() throws -> (Bool, Configuration) { guard let actualOutputDirectory = self.actualOutputDirectory else { @@ -396,7 +464,10 @@ extension JavaToSwift { return (false, .init()) case .amend: let configPath = actualOutputDirectory - return (true, try readConfiguration(sourceDir: configPath.path)) + guard let config = try readConfiguration(sourceDir: configPath.path) else { + return (false, .init()) + } + return (true, config) } } } @@ -425,3 +496,13 @@ extension JavaClass { public func getSystemClassLoader() -> ClassLoader? } +extension SwiftJava.ToolMode { + var prettyName: String { + switch self { + case .configuration: "Configuration" + case .fetchDependencies: "Fetch dependencies" + case .classWrappers: "Wrap Java classes" + case .jextract: "JExtract Swift for Java" + } + } +} diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index cbdcc6dd..b306689d 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@_spi(Testing) import JExtractSwift +@_spi(Testing) import JExtractSwiftLib import SwiftSyntax import Testing diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 1686f0ab..3923cf3e 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing import struct Foundation.CharacterSet diff --git a/Tests/JExtractSwiftTests/CTypeTests.swift b/Tests/JExtractSwiftTests/CTypeTests.swift index 569296d6..a2339e34 100644 --- a/Tests/JExtractSwiftTests/CTypeTests.swift +++ b/Tests/JExtractSwiftTests/CTypeTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing @Suite("C type system tests") diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift index 94cd8a40..1edcdaf0 100644 --- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift +++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing struct ClassPrintingTests { diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 50022c55..8b401ce3 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing final class FuncCallbackImportTests { diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 9cdcdf58..2963ce9e 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing @Suite diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index dce594ae..b7855e43 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import SwiftSyntax import Testing diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 25c028ce..48515ff2 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing final class MethodImportTests { diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift index d57e449e..f6f5ce71 100644 --- a/Tests/JExtractSwiftTests/MethodThunkTests.swift +++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing final class MethodThunkTests { diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index 08190399..ae131b94 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing final class StringPassingTests { diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index 5d1e5e2b..2bbaa913 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@_spi(Testing) import JExtractSwift +@_spi(Testing) import JExtractSwiftLib import SwiftSyntax import SwiftParser import Testing diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 55724293..9d8650b4 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import JExtractSwift +import JExtractSwiftLib import Testing final class VariableImportTests { diff --git a/Tests/Java2SwiftTests/Java2SwiftTests.swift b/Tests/SwiftJavaTests/Java2SwiftTests.swift similarity index 99% rename from Tests/Java2SwiftTests/Java2SwiftTests.swift rename to Tests/SwiftJavaTests/Java2SwiftTests.swift index 48440522..e2b68a34 100644 --- a/Tests/Java2SwiftTests/Java2SwiftTests.swift +++ b/Tests/SwiftJavaTests/Java2SwiftTests.swift @@ -14,7 +14,7 @@ @_spi(Testing) import JavaKit -import Java2SwiftLib +import SwiftJavaLib import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43 /// Handy reference to the JVM abstraction. diff --git a/Tests/Java2SwiftTests/JavaTranslatorValidationTests.swift b/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift similarity index 98% rename from Tests/Java2SwiftTests/JavaTranslatorValidationTests.swift rename to Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift index e5c3a951..a203486a 100644 --- a/Tests/Java2SwiftTests/JavaTranslatorValidationTests.swift +++ b/Tests/SwiftJavaTests/JavaTranslatorValidationTests.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import Java2SwiftLib +import SwiftJavaLib import XCTest final class JavaTranslatorValidationTests: XCTestCase {