diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8039b572..06db1ff2 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -28,6 +28,7 @@ jobs: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: JAVA_HOME: "/usr/lib/jvm/default-jdk" + SWIFT_JAVA_VERBOSE: true steps: - uses: actions/checkout@v4 - name: Prepare CI Environment @@ -53,6 +54,7 @@ jobs: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: JAVA_HOME: "/usr/lib/jvm/default-jdk" + SWIFT_JAVA_VERBOSE: true steps: - uses: actions/checkout@v4 - name: Prepare CI Environment diff --git a/.licenseignore b/.licenseignore index 04a64766..ce382547 100644 --- a/.licenseignore +++ b/.licenseignore @@ -41,3 +41,5 @@ gradlew.bat **/gradlew.bat **/ci-validate.sh **/DO_NOT_EDIT.txt +Plugins/**/_PluginsShared +Plugins/**/0_PLEASE_SYMLINK* \ No newline at end of file diff --git a/Package.swift b/Package.swift index 0d5a5dab..119aca6e 100644 --- a/Package.swift +++ b/Package.swift @@ -128,9 +128,9 @@ let package = Package( // ==== Plugin for wrapping Java classes in Swift .plugin( - name: "JExtractSwiftPlugin", + name: "SwiftJavaPlugin", targets: [ - "JExtractSwiftPlugin" + "SwiftJavaPlugin" ] ), .plugin( @@ -174,7 +174,7 @@ let package = Package( .target( name: "JavaKit", dependencies: ["JavaRuntime", "JavaKitMacros", "JavaTypes"], - exclude: ["Java2Swift.config"], + exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -201,7 +201,7 @@ let package = Package( .target( name: "JavaKitCollection", dependencies: ["JavaKit"], - exclude: ["Java2Swift.config"], + exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -210,7 +210,7 @@ let package = Package( .target( name: "JavaKitFunction", dependencies: ["JavaKit"], - exclude: ["Java2Swift.config"], + exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -219,7 +219,7 @@ let package = Package( .target( name: "JavaKitJar", dependencies: ["JavaKit", "JavaKitCollection"], - exclude: ["Java2Swift.config"], + exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -228,7 +228,7 @@ let package = Package( .target( name: "JavaKitNetwork", dependencies: ["JavaKit", "JavaKitCollection"], - exclude: ["Java2Swift.config"], + exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -237,7 +237,7 @@ let package = Package( .target( name: "JavaKitReflection", dependencies: ["JavaKit", "JavaKitCollection"], - exclude: ["Java2Swift.config"], + exclude: ["swift-java.config"], swiftSettings: [ .swiftLanguageMode(.v5), .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) @@ -347,7 +347,7 @@ let package = Package( ), .plugin( - name: "JExtractSwiftPlugin", + name: "SwiftJavaPlugin", capability: .buildTool(), dependencies: [ "JExtractSwiftTool" diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift index 008bc7d7..65ce8971 100644 --- a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift +++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift @@ -16,9 +16,10 @@ import Foundation import PackagePlugin @main -final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin { +final class JExtractSwiftCommandPlugin: SwiftJavaPluginProtocol, BuildToolPlugin, CommandPlugin { - var verbose: Bool = false + var pluginName: String = "swift-java-command" + var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE") /// Build the target before attempting to extract from it. /// This avoids trying to extract from broken sources. @@ -48,8 +49,8 @@ final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin { } for target in context.package.targets { - guard let configPath = getSwiftJavaConfig(target: target) else { - log("Skipping target '\(target.name)', has no 'swift-java.config' file") + guard getSwiftJavaConfigPath(target: target) != nil else { + log("[swift-java-command] Skipping jextract step: Missing swift-java.config for target '\(target.name)'") continue } @@ -73,21 +74,15 @@ final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin { let sourceDir = target.directory.string let configuration = try readConfiguration(sourceDir: "\(sourceDir)") - - // We use the the usual maven-style structure of "src/[generated|main|test]/java/..." - // that is common in JVM ecosystem - let outputDirectoryJava = context.pluginWorkDirectoryURL - .appending(path: "src") - .appending(path: "generated") - .appending(path: "java") - let outputDirectorySwift = context.pluginWorkDirectoryURL - .appending(path: "Sources") + guard let javaPackage = configuration.javaPackage else { + throw SwiftJavaPluginError.missingConfiguration(sourceDir: "\(sourceDir)", key: "javaPackage") + } var arguments: [String] = [ "--swift-module", sourceModule.name, - "--package-name", configuration.javaPackage, - "--output-directory-java", outputDirectoryJava.path(percentEncoded: false), - "--output-directory-swift", outputDirectorySwift.path(percentEncoded: false), + "--package-name", javaPackage, + "--output-directory-java", context.outputDirectoryJava.path(percentEncoded: false), + "--output-directory-swift", context.outputDirectorySwift.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. @@ -100,14 +95,17 @@ final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin { /// Perform the command on a specific target. func performCommand(context: PluginContext, target: Target, extraArguments _: [String]) throws { - // Make sure the target can builds properly - try self.packageManager.build(.target(target.name), parameters: .init()) - guard let sourceModule = target.sourceModule else { return } if self.buildInputs { + // Make sure the target can builds properly log("Pre-building target '\(target.name)' before extracting sources...") - try self.packageManager.build(.target(target.name), parameters: .init()) + let targetBuildResult = try self.packageManager.build(.target(target.name), parameters: .init()) + + guard targetBuildResult.succeeded else { + print("[swift-java-command] Build of '\(target.name)' failed: \(targetBuildResult.logText)") + return + } } let arguments = try prepareJExtractArguments(context: context, target: target) @@ -124,7 +122,13 @@ final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin { log("Post-extract building products with target '\(target.name)'...") for product in context.package.products where product.targets.contains(where: { $0.id == target.id }) { log("Post-extract building product '\(product.name)'...") - try self.packageManager.build(.product(product.name), parameters: .init()) + let buildResult = try self.packageManager.build(.product(product.name), parameters: .init()) + + if buildResult.succeeded { + log("Post-extract build: " + "done".green + ".") + } else { + log("Post-extract build: " + "done".red + "!") + } } } } @@ -146,16 +150,15 @@ final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin { } } - func log(_ message: @autoclosure () -> String, terminator: String = "\n") { - if self.verbose { - print("[swift-java-command] \(message())", terminator: terminator) - } - } } // Mini coloring helper, since we cannot have dependencies we keep it minimal here extension String { + var red: String { + "\u{001B}[0;31m" + "\(self)" + "\u{001B}[0;0m" + } var green: String { "\u{001B}[0;32m" + "\(self)" + "\u{001B}[0;0m" } -} \ No newline at end of file +} + diff --git a/Plugins/JExtractSwiftCommandPlugin/PluginsShared/Configuration.swift b/Plugins/JExtractSwiftCommandPlugin/PluginsShared/Configuration.swift deleted file mode 100644 index f917290c..00000000 --- a/Plugins/JExtractSwiftCommandPlugin/PluginsShared/Configuration.swift +++ /dev/null @@ -1,36 +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 Foundation - -/// Configuration for the JExtractSwift translation tool, provided on a per-target -/// basis. -struct Configuration: Codable { - var javaPackage: String -} - -func readConfiguration(sourceDir: String) throws -> Configuration { - let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") - do { - let configData = try Data(contentsOf: configFile) - return try JSONDecoder().decode(Configuration.self, from: configData) - } catch { - throw ConfigurationError(message: "Failed to parse JExtractSwift configuration at '\(configFile)!'", error: error) - } -} - -struct ConfigurationError: Error { - let message: String - let error: any Error -} diff --git a/Plugins/JExtractSwiftCommandPlugin/_PluginsShared b/Plugins/JExtractSwiftCommandPlugin/_PluginsShared new file mode 120000 index 00000000..de623a5e --- /dev/null +++ b/Plugins/JExtractSwiftCommandPlugin/_PluginsShared @@ -0,0 +1 @@ +../PluginsShared \ No newline at end of file diff --git a/Plugins/JExtractSwiftPlugin/PluginsShared/Configuration.swift b/Plugins/JExtractSwiftPlugin/PluginsShared/Configuration.swift deleted file mode 100644 index f917290c..00000000 --- a/Plugins/JExtractSwiftPlugin/PluginsShared/Configuration.swift +++ /dev/null @@ -1,36 +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 Foundation - -/// Configuration for the JExtractSwift translation tool, provided on a per-target -/// basis. -struct Configuration: Codable { - var javaPackage: String -} - -func readConfiguration(sourceDir: String) throws -> Configuration { - let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") - do { - let configData = try Data(contentsOf: configFile) - return try JSONDecoder().decode(Configuration.self, from: configData) - } catch { - throw ConfigurationError(message: "Failed to parse JExtractSwift configuration at '\(configFile)!'", error: error) - } -} - -struct ConfigurationError: Error { - let message: String - let error: any Error -} diff --git a/Plugins/JExtractSwiftPlugin/PluginsShared/PluginUtils.swift b/Plugins/JExtractSwiftPlugin/PluginsShared/PluginUtils.swift deleted file mode 100644 index 6ec58147..00000000 --- a/Plugins/JExtractSwiftPlugin/PluginsShared/PluginUtils.swift +++ /dev/null @@ -1,37 +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 Foundation - -// Note: the JAVA_HOME environment variable must be set to point to where -// Java is installed, e.g., -// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. -func findJavaHome() -> String { - if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { - return home - } - - // This is a workaround for envs (some IDEs) which have trouble with - // picking up env variables during the build process - let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" - if let home = try? String(contentsOfFile: path, encoding: .utf8) { - if let lastChar = home.last, lastChar.isNewline { - return String(home.dropLast()) - } - - return home - } - - fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") -} diff --git a/Plugins/Java2SwiftPlugin/Configuration.swift b/Plugins/Java2SwiftPlugin/Configuration.swift deleted file mode 100644 index c9b17d06..00000000 --- a/Plugins/Java2SwiftPlugin/Configuration.swift +++ /dev/null @@ -1,36 +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 -// -//===----------------------------------------------------------------------===// - -typealias JavaVersion = Int - -/// Configuration for the Java2Swift translation tool, provided on a per-target -/// basis. -/// -/// Note: there is a copy of this struct in the Java2Swift library. They -/// must be kept in sync. -struct Configuration: Codable { - /// The Java class path that should be passed along to the Java2Swift tool. - var classPath: String? = nil - - /// The Java classes that should be translated to Swift. The keys are - /// canonical Java class names (e.g., java.util.Vector) and the values are - /// the corresponding Swift names (e.g., JavaVector). - var classes: [String: String] = [:] - - // Compile for the specified Java SE release. - var sourceCompatibility: JavaVersion? - - // Generate class files suitable for the specified Java SE release. - var targetCompatibility: JavaVersion? -} diff --git a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift b/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift index 452ec8b3..8c7045e0 100644 --- a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift +++ b/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift @@ -15,10 +15,14 @@ import Foundation import PackagePlugin -fileprivate let Java2SwiftConfigFileName = "Java2Swift.config" +fileprivate let SwiftJavaConfigFileName = "swift-java.config" @main -struct Java2SwiftBuildToolPlugin: BuildToolPlugin { +struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { + + var pluginName: String = "swift-java-javac" + var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE") + func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { guard let sourceModule = target.sourceModule else { return [] } @@ -29,7 +33,7 @@ struct Java2SwiftBuildToolPlugin: 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") + .appending(path: SwiftJavaConfigFileName) let configData = try Data(contentsOf: configFile) let config = try JSONDecoder().decode(Configuration.self, from: configData) @@ -41,7 +45,7 @@ struct Java2SwiftBuildToolPlugin: BuildToolPlugin { // Look for a config file within this target. let dependencyConfigURL = dependencyURL - .appending(path: Java2SwiftConfigFileName) + .appending(path: SwiftJavaConfigFileName) let dependencyConfigString = dependencyConfigURL .path(percentEncoded: false) @@ -87,9 +91,14 @@ struct Java2SwiftBuildToolPlugin: BuildToolPlugin { } arguments.append(configFile.path(percentEncoded: false)) + guard let classes = config.classes else { + log("Config at \(configFile) did not have 'classes' configured, skipping java2swift step.") + return [] + } + /// Determine the set of Swift files that will be emitted by the Java2Swift /// tool. - let outputSwiftFiles = config.classes.map { (javaClassName, swiftName) in + let outputSwiftFiles = classes.map { (javaClassName, swiftName) in let swiftNestedName = swiftName.replacingOccurrences(of: ".", with: "+") return outputDirectory.appending(path: "\(swiftNestedName).swift") } @@ -125,10 +134,15 @@ struct Java2SwiftBuildToolPlugin: BuildToolPlugin { arguments += [ "--swift-native-implementation", className] } } + + guard let classes = config.classes else { + log("Skipping java2swift step: Missing 'classes' key in swift-java.config at '\(configFile.path)'") + return [] + } return [ .buildCommand( - displayName: "Wrapping \(config.classes.count) Java classes target \(sourceModule.name) in Swift", + displayName: "Wrapping \(classes.count) Java classes target \(sourceModule.name) in Swift", executable: try context.tool(named: "Java2Swift").url, arguments: arguments, inputFiles: [ configFile ] + compiledClassFiles, @@ -137,25 +151,3 @@ struct Java2SwiftBuildToolPlugin: BuildToolPlugin { ] } } - -// Note: the JAVA_HOME environment variable must be set to point to where -// Java is installed, e.g., -// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. -func findJavaHome() -> String { - if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { - return home - } - - // This is a workaround for envs (some IDEs) which have trouble with - // picking up env variables during the build process - let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" - if let home = try? String(contentsOfFile: path, encoding: .utf8) { - if let lastChar = home.last, lastChar.isNewline { - return String(home.dropLast()) - } - - return home - } - - fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") -} diff --git a/Plugins/Java2SwiftPlugin/_PluginsShared b/Plugins/Java2SwiftPlugin/_PluginsShared new file mode 120000 index 00000000..de623a5e --- /dev/null +++ b/Plugins/Java2SwiftPlugin/_PluginsShared @@ -0,0 +1 @@ +../PluginsShared \ No newline at end of file diff --git a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift index 518073a2..b93fe403 100644 --- a/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift +++ b/Plugins/JavaCompilerPlugin/JavaCompilerPlugin.swift @@ -78,25 +78,3 @@ struct JavaCompilerBuildToolPlugin: BuildToolPlugin { ] } } - -// Note: the JAVA_HOME environment variable must be set to point to where -// Java is installed, e.g., -// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. -func findJavaHome() -> String { - if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { - return home - } - - // This is a workaround for envs (some IDEs) which have trouble with - // picking up env variables during the build process - let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" - if let home = try? String(contentsOfFile: path, encoding: .utf8) { - if let lastChar = home.last, lastChar.isNewline { - return String(home.dropLast()) - } - - return home - } - - fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") -} diff --git a/Plugins/JavaCompilerPlugin/_PluginsShared b/Plugins/JavaCompilerPlugin/_PluginsShared new file mode 120000 index 00000000..de623a5e --- /dev/null +++ b/Plugins/JavaCompilerPlugin/_PluginsShared @@ -0,0 +1 @@ +../PluginsShared \ No newline at end of file diff --git a/Plugins/PluginsShared/0_PLEASE_SYMLINK.txt b/Plugins/PluginsShared/0_PLEASE_SYMLINK.txt new file mode 100644 index 00000000..40f0c528 --- /dev/null +++ b/Plugins/PluginsShared/0_PLEASE_SYMLINK.txt @@ -0,0 +1,3 @@ +This directory should be symlinked from all plugins so that we can avoid copy-pasting the contents. + +Package plugins cannot have dependencies, so this is a way to share sources common between all the plugins. \ No newline at end of file diff --git a/Plugins/JavaCompilerPlugin/Configuration.swift b/Plugins/PluginsShared/Configuration.swift similarity index 56% rename from Plugins/JavaCompilerPlugin/Configuration.swift rename to Plugins/PluginsShared/Configuration.swift index 96180760..769eef70 100644 --- a/Plugins/JavaCompilerPlugin/Configuration.swift +++ b/Plugins/PluginsShared/Configuration.swift @@ -12,21 +12,25 @@ // //===----------------------------------------------------------------------===// +import Foundation + typealias JavaVersion = Int -/// Configuration for the Java2Swift translation tool, provided on a per-target -/// basis. -/// -/// Note: there is a copy of this struct in the Java2Swift library. They -/// must be kept in sync. +/// Configuration for the SwiftJava plugins, provided on a per-target basis. struct Configuration: Codable { + // ==== swift 2 java --------------------------------------------------------- + + var javaPackage: String? + + // ==== java 2 swift --------------------------------------------------------- + /// The Java class path that should be passed along to the Java2Swift tool. var classPath: String? = nil /// The Java classes that should be translated to Swift. The keys are /// canonical Java class names (e.g., java.util.Vector) and the values are /// the corresponding Swift names (e.g., JavaVector). - var classes: [String: String] = [:] + var classes: [String: String]? = [:] // Compile for the specified Java SE release. var sourceCompatibility: JavaVersion? @@ -35,6 +39,17 @@ struct Configuration: Codable { var targetCompatibility: JavaVersion? } +func readConfiguration(sourceDir: String, file: String = #fileID, line: UInt = #line) throws -> Configuration { + let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") + do { + let configData = try Data(contentsOf: configFile) + return try JSONDecoder().decode(Configuration.self, from: configData) + } catch { + throw ConfigurationError(message: "Failed to parse SwiftJava configuration at '\(configFile)!'", error: error, + file: file, line: line) + } +} + extension Configuration { var compilerVersionArgs: [String] { var compilerVersionArgs = [String]() @@ -49,3 +64,18 @@ extension Configuration { return compilerVersionArgs } } + +struct ConfigurationError: Error { + let message: String + let error: any Error + + let file: String + let line: UInt + + init(message: String, error: any Error, file: String = #fileID, line: UInt = #line) { + self.message = message + self.error = error + self.file = file + self.line = line + } +} diff --git a/Plugins/JExtractSwiftCommandPlugin/PluginsShared/PluginUtils.swift b/Plugins/PluginsShared/PluginUtils.swift similarity index 70% rename from Plugins/JExtractSwiftCommandPlugin/PluginsShared/PluginUtils.swift rename to Plugins/PluginsShared/PluginUtils.swift index 320dfc19..bf4d0f0a 100644 --- a/Plugins/JExtractSwiftCommandPlugin/PluginsShared/PluginUtils.swift +++ b/Plugins/PluginsShared/PluginUtils.swift @@ -22,7 +22,7 @@ func findJavaHome() -> String { if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { return home } - + // This is a workaround for envs (some IDEs) which have trouble with // picking up env variables during the build process let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" @@ -30,19 +30,45 @@ func findJavaHome() -> String { if let lastChar = home.last, lastChar.isNewline { return String(home.dropLast()) } - + return home } - + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") } -func getSwiftJavaConfig(target: Target) -> String? { +func getSwiftJavaConfigPath(target: Target) -> String? { let configPath = URL(fileURLWithPath: target.directory.string).appending(component: "swift-java.config").path() - + if FileManager.default.fileExists(atPath: configPath) { - return configPath + return configPath } else { return nil } } + +func getEnvironmentBool(_ name: String) -> Bool { + if let value = ProcessInfo.processInfo.environment[name] { + switch value.lowercased() { + case "true", "yes", "1": true + case "false", "no", "0": false + default: false + } + } else { + false + } +} + +extension PluginContext { + var outputDirectoryJava: URL { + self.pluginWorkDirectoryURL + .appending(path: "src") + .appending(path: "generated") + .appending(path: "java") + } + + var outputDirectorySwift: URL { + self.pluginWorkDirectoryURL + .appending(path: "Sources") + } +} diff --git a/Plugins/PluginsShared/SwiftJavaPlugin+Errors.swift b/Plugins/PluginsShared/SwiftJavaPlugin+Errors.swift new file mode 100644 index 00000000..d9022830 --- /dev/null +++ b/Plugins/PluginsShared/SwiftJavaPlugin+Errors.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +enum SwiftJavaPluginError: Error { + case missingConfiguration(sourceDir: String, key: String?, file: String = #file, line: UInt = #line) +} diff --git a/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift b/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift new file mode 100644 index 00000000..68f8964e --- /dev/null +++ b/Plugins/PluginsShared/SwiftJavaPluginProtocol.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +protocol SwiftJavaPluginProtocol { + var verbose: Bool { get } + var pluginName: String { get } + + func log(_ message: @autoclosure () -> String, terminator: String) +} + +extension SwiftJavaPluginProtocol { + func log(_ message: @autoclosure () -> String, terminator: String = "\n") { +// if self.verbose { + print("[\(pluginName)] \(message())", terminator: terminator) +// } + } +} diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/SwiftJavaPlugin/JExtractSwiftPlugin.swift similarity index 69% rename from Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift rename to Plugins/SwiftJavaPlugin/JExtractSwiftPlugin.swift index 94cd4aa6..f4255ff5 100644 --- a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift +++ b/Plugins/SwiftJavaPlugin/JExtractSwiftPlugin.swift @@ -16,39 +16,46 @@ import Foundation import PackagePlugin @main -struct JExtractSwiftBuildToolPlugin: BuildToolPlugin { +struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { + + var pluginName: String = "swift-java" + var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE") + func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { + let toolURL = try context.tool(named: "JExtractSwiftTool").url + guard let sourceModule = target.sourceModule else { return [] } // Note: Target doesn't have a directoryURL counterpart to directory, // so we cannot eliminate this deprecation warning. - let sourceDir = target.directory.string - - let t = target.dependencies.first! - switch (t) { - case .target(let t): - t.sourceModule - case .product(let p): - p.sourceModules - @unknown default: - fatalError("Unknown target dependency type: \(t)") + for dependency in target.dependencies { + switch (dependency) { + case .target(let t): + t.sourceModule + case .product(let p): + p.sourceModules + @unknown default: + fatalError("Unknown target dependency type: \(dependency)") + } } - let toolURL = try context.tool(named: "JExtractSwiftTool").url + let sourceDir = target.directory.string let configuration = try readConfiguration(sourceDir: "\(sourceDir)") - + + 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 [] + } + // We use the the usual maven-style structure of "src/[generated|main|test]/java/..." // that is common in JVM ecosystem - let outputDirectoryJava = context.pluginWorkDirectoryURL - .appending(path: "src") - .appending(path: "generated") - .appending(path: "java") - let outputDirectorySwift = context.pluginWorkDirectoryURL - .appending(path: "Sources") + let outputDirectoryJava = context.outputDirectoryJava + let outputDirectorySwift = context.outputDirectorySwift var arguments: [String] = [ "--swift-module", sourceModule.name, - "--package-name", configuration.javaPackage, + "--package-name", javaPackage, "--output-directory-java", outputDirectoryJava.path(percentEncoded: false), "--output-directory-swift", outputDirectorySwift.path(percentEncoded: false), // TODO: "--build-cache-directory", ... diff --git a/Plugins/SwiftJavaPlugin/_PluginsShared b/Plugins/SwiftJavaPlugin/_PluginsShared new file mode 120000 index 00000000..de623a5e --- /dev/null +++ b/Plugins/SwiftJavaPlugin/_PluginsShared @@ -0,0 +1 @@ +../PluginsShared \ No newline at end of file diff --git a/Samples/JavaKitSampleApp/Package.swift b/Samples/JavaKitSampleApp/Package.swift index 8976bd41..46dfbd80 100644 --- a/Samples/JavaKitSampleApp/Package.swift +++ b/Samples/JavaKitSampleApp/Package.swift @@ -76,6 +76,7 @@ let package = Package( ], plugins: [ .plugin(name: "JavaCompilerPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), .plugin(name: "Java2SwiftPlugin", package: "swift-java"), ] ), diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/Java2Swift.config b/Samples/JavaKitSampleApp/Sources/JavaKitExample/swift-java.config similarity index 100% rename from Samples/JavaKitSampleApp/Sources/JavaKitExample/Java2Swift.config rename to Samples/JavaKitSampleApp/Sources/JavaKitExample/swift-java.config diff --git a/Samples/JavaKitSampleApp/ci-validate.sh b/Samples/JavaKitSampleApp/ci-validate.sh new file mode 100755 index 00000000..eff61551 --- /dev/null +++ b/Samples/JavaKitSampleApp/ci-validate.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +swift build +"$JAVA_HOME/bin/java" \ + -cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \ + -Djava.library.path=.build/debug \ + "com.example.swift.JavaKitSampleMain" diff --git a/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/Java2Swift.config b/Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/swift-java.config similarity index 100% rename from Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/Java2Swift.config rename to Samples/JavaProbablyPrime/Sources/JavaProbablyPrime/swift-java.config diff --git a/Samples/JavaSieve/Sources/JavaMath/Java2Swift.config b/Samples/JavaSieve/Sources/JavaMath/swift-java.config similarity index 100% rename from Samples/JavaSieve/Sources/JavaMath/Java2Swift.config rename to Samples/JavaSieve/Sources/JavaMath/swift-java.config diff --git a/Samples/JavaSieve/Sources/JavaSieve/Java2Swift.config b/Samples/JavaSieve/Sources/JavaSieve/swift-java.config similarity index 100% rename from Samples/JavaSieve/Sources/JavaSieve/Java2Swift.config rename to Samples/JavaSieve/Sources/JavaSieve/swift-java.config diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index 5f132239..a1212fd7 100644 --- a/Samples/SwiftAndJavaJarSampleLib/Package.swift +++ b/Samples/SwiftAndJavaJarSampleLib/Package.swift @@ -70,7 +70,7 @@ let package = Package( .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ], plugins: [ - .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), ] diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftKitSampleApp/Package.swift index f12ecfd6..bfd3ecbe 100644 --- a/Samples/SwiftKitSampleApp/Package.swift +++ b/Samples/SwiftKitSampleApp/Package.swift @@ -70,7 +70,7 @@ let package = Package( .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ], plugins: [ - .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), + .plugin(name: "SwiftJavaPlugin", package: "swift-java"), ] ), ] diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/Java2Swift/JavaToSwift.swift index 1434629f..125191bd 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/Java2Swift/JavaToSwift.swift @@ -369,8 +369,8 @@ struct JavaToSwift: ParsableCommand { // Write the file. try writeContents( contents, - to: "Java2Swift.config", - description: "Java2Swift configuration file" + to: "swift-java.config", + description: "swift-java configuration file" ) } } diff --git a/Sources/JavaKit/Java2Swift.config b/Sources/JavaKit/swift-java.config similarity index 100% rename from Sources/JavaKit/Java2Swift.config rename to Sources/JavaKit/swift-java.config diff --git a/Sources/JavaKitCollection/Java2Swift.config b/Sources/JavaKitCollection/swift-java.config similarity index 100% rename from Sources/JavaKitCollection/Java2Swift.config rename to Sources/JavaKitCollection/swift-java.config diff --git a/Sources/JavaKitFunction/Java2Swift.config b/Sources/JavaKitFunction/swift-java.config similarity index 100% rename from Sources/JavaKitFunction/Java2Swift.config rename to Sources/JavaKitFunction/swift-java.config diff --git a/Sources/JavaKitJar/Java2Swift.config b/Sources/JavaKitJar/swift-java.config similarity index 100% rename from Sources/JavaKitJar/Java2Swift.config rename to Sources/JavaKitJar/swift-java.config diff --git a/Sources/JavaKitNetwork/Java2Swift.config b/Sources/JavaKitNetwork/swift-java.config similarity index 100% rename from Sources/JavaKitNetwork/Java2Swift.config rename to Sources/JavaKitNetwork/swift-java.config diff --git a/Sources/JavaKitReflection/Java2Swift.config b/Sources/JavaKitReflection/swift-java.config similarity index 100% rename from Sources/JavaKitReflection/Java2Swift.config rename to Sources/JavaKitReflection/swift-java.config