From 626e06bf410e6943fba9b3f045f80b0c37fd152c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 11 Dec 2024 18:23:31 +0900 Subject: [PATCH] Reimplement dependency fetching with just swift processes; require gradlew --- .licenseignore | 3 +- JavaKit/build.gradle | 72 ----- JavaKit/gradlew.bat | 1 - .../javakit/annotations/UsedFromSwift.java | 28 -- .../dependencies/DependencyResolver.java | 303 ------------------ .../SwiftJavaBootstrapException.java | 25 -- Package.swift | 47 +-- .../JExtractSwiftPlugin.swift | 0 .../_PluginsShared | 0 .../Java2SwiftPlugin/Java2SwiftPlugin.swift | 4 +- .../SwiftJavaBootstrapJavaPlugin.swift | 190 ----------- Plugins/SwiftJavaPlugin/_PluginsShared | 1 - Samples/JavaDependencySampleApp/Package.swift | 1 + .../Sources/JavaDependencySample/main.swift | 6 +- .../JavaDependencySampleApp/ci-validate.sh | 6 - Samples/JavaDependencySampleApp/gradle | 1 + Samples/JavaDependencySampleApp/gradlew | 1 + Samples/JavaDependencySampleApp/gradlew.bat | 1 + .../SwiftAndJavaJarSampleLib/Package.swift | 2 +- Samples/SwiftKitSampleApp/Package.swift | 2 +- Samples/gradle | 1 + {JavaKit => Samples}/gradlew | 0 .../JavaToSwift+EmitConfiguration.swift | 1 - .../JavaToSwift+FetchDependencies.swift | 176 +++++++--- .../JavaToSwift+GenerateWrappers.swift | 3 +- Sources/Java2Swift/JavaToSwift.swift | 48 +-- Sources/Java2Swift/String+Extensions.swift | 1 - .../Configuration.swift | 11 + .../SwiftJavaBootstrapJavaTool.swift | 137 -------- settings.gradle | 1 - 30 files changed, 179 insertions(+), 894 deletions(-) delete mode 100644 JavaKit/build.gradle delete mode 120000 JavaKit/gradlew.bat delete mode 100644 JavaKit/src/main/java/org/swift/javakit/annotations/UsedFromSwift.java delete mode 100644 JavaKit/src/main/java/org/swift/javakit/dependencies/DependencyResolver.java delete mode 100644 JavaKit/src/main/java/org/swift/javakit/dependencies/SwiftJavaBootstrapException.java rename Plugins/{SwiftJavaPlugin => JExtractSwiftPlugin}/JExtractSwiftPlugin.swift (100%) rename Plugins/{SwiftJavaBootstrapJavaPlugin => JExtractSwiftPlugin}/_PluginsShared (100%) delete mode 100644 Plugins/SwiftJavaBootstrapJavaPlugin/SwiftJavaBootstrapJavaPlugin.swift delete mode 120000 Plugins/SwiftJavaPlugin/_PluginsShared create mode 120000 Samples/JavaDependencySampleApp/gradle create mode 120000 Samples/JavaDependencySampleApp/gradlew create mode 120000 Samples/JavaDependencySampleApp/gradlew.bat create mode 120000 Samples/gradle rename {JavaKit => Samples}/gradlew (100%) delete mode 100644 Sources/SwiftJavaBootstrapJavaTool/SwiftJavaBootstrapJavaTool.swift diff --git a/.licenseignore b/.licenseignore index 8b9d0933..ada8e1de 100644 --- a/.licenseignore +++ b/.licenseignore @@ -45,4 +45,5 @@ gradlew.bat Plugins/**/_PluginsShared Plugins/**/0_PLEASE_SYMLINK* Plugins/PluginsShared/JavaKitConfigurationShared -Sources/_Subprocess/_nio_locks.swift \ No newline at end of file +Samples/JavaDependencySampleApp/gradle +Sources/_Subprocess/_nio_locks.swift diff --git a/JavaKit/build.gradle b/JavaKit/build.gradle deleted file mode 100644 index 70982e90..00000000 --- a/JavaKit/build.gradle +++ /dev/null @@ -1,72 +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 -// -//===----------------------------------------------------------------------===// - -plugins { - id("build-logic.java-library-conventions") -} - -group = "org.swift.javakit" -version = "1.0-SNAPSHOT" - -repositories { - mavenCentral() -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) - } -} - -dependencies { - implementation("dev.gradleplugins:gradle-api:8.10.1") - - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") -} - -tasks.test { - useJUnitPlatform() - testLogging { - events("passed", "skipped", "failed") - } -} - -// Copy the gradle wrapper we're using into the resulting jar's resources. -// We'll use it to bootstrap dependencies (and gradle!) if there is none yet. -tasks.processResources { - from('gradlew') { - into 'gradle/' - } - from('gradlew.bat') { - into 'gradle/' - } - from('../gradle/wrapper/gradle-wrapper.jar') { - into 'gradle/wrapper/' - } - from('../gradle/wrapper/gradle-wrapper.properties') { - into 'gradle/wrapper/' - } -} - -// Task necessary to bootstrap and EXIT, this is to prevent hangs when used via SwiftPM plugin. -tasks.register('printRuntimeClasspath') { - dependsOn 'jar' - - def runtimeClasspath = sourceSets.main.runtimeClasspath - inputs.files(runtimeClasspath) - doLast { - println("SWIFT_JAVA_CLASSPATH:${runtimeClasspath.asPath}") - } -} diff --git a/JavaKit/gradlew.bat b/JavaKit/gradlew.bat deleted file mode 120000 index 28401328..00000000 --- a/JavaKit/gradlew.bat +++ /dev/null @@ -1 +0,0 @@ -../gradlew.bat \ No newline at end of file diff --git a/JavaKit/src/main/java/org/swift/javakit/annotations/UsedFromSwift.java b/JavaKit/src/main/java/org/swift/javakit/annotations/UsedFromSwift.java deleted file mode 100644 index ad9e7918..00000000 --- a/JavaKit/src/main/java/org/swift/javakit/annotations/UsedFromSwift.java +++ /dev/null @@ -1,28 +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 -// -//===----------------------------------------------------------------------===// - -package org.swift.javakit.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Since some public methods may not appear as used in Java source code, but are used by Swift, - * we can use this source annotation to mark such entry points to not accidentally remove them with - * "safe delete" refactorings in Java IDEs which would be unaware of the usages from Swift. - */ -@SuppressWarnings("unused") // used from Swift -@Retention(RetentionPolicy.SOURCE) -public @interface UsedFromSwift { -} diff --git a/JavaKit/src/main/java/org/swift/javakit/dependencies/DependencyResolver.java b/JavaKit/src/main/java/org/swift/javakit/dependencies/DependencyResolver.java deleted file mode 100644 index 0e801525..00000000 --- a/JavaKit/src/main/java/org/swift/javakit/dependencies/DependencyResolver.java +++ /dev/null @@ -1,303 +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 -// -//===----------------------------------------------------------------------===// - -package org.swift.javakit.dependencies; - -import org.gradle.tooling.GradleConnector; -import org.swift.javakit.annotations.UsedFromSwift; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Fetches dependencies using the Gradle resolver and returns the resulting classpath which includes - * the fetched dependency and all of its dependencies. - */ -@UsedFromSwift -@SuppressWarnings("unused") -public class DependencyResolver { - - private static final String COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH = "SWIFT_JAVA_CLASSPATH:"; - private static final String CLASSPATH_CACHE_FILENAME = "JavaKitDependencyResolver.swift-java.classpath"; - - public static String GRADLE_API_DEPENDENCY = "dev.gradleplugins:gradle-api:8.10.1"; - public static String[] BASE_DEPENDENCIES = { - GRADLE_API_DEPENDENCY - }; - - /** - * May throw runtime exceptions including {@link org.gradle.api.internal.artifacts.ivyservice.TypedResolveException} - * if unable to resolve a dependency. - */ - @UsedFromSwift - @SuppressWarnings("unused") - public static String resolveDependenciesToClasspath(String projectBaseDirectoryString, String[] dependencies) throws IOException { - try { - simpleLog("Fetch dependencies: " + Arrays.toString(dependencies)); - simpleLog("Classpath: " + System.getProperty("java.class.path")); - var projectBasePath = new File(projectBaseDirectoryString).toPath(); - - File projectDir = Files.createTempDirectory("java-swift-dependencies").toFile(); - projectDir.mkdirs(); - - if (hasDependencyResolverDependenciesLoaded()) { - // === Resolve dependencies using Gradle API in-process - simpleLog("Gradle API runtime dependency is available, resolve dependencies..."); - return resolveDependenciesUsingAPI(projectDir, dependencies); - } - - // === Bootstrap the resolver dependencies and cache them - simpleLog("Gradle API not available on classpath, bootstrap %s dependencies: %s" - .formatted(DependencyResolver.class.getSimpleName(), Arrays.toString(BASE_DEPENDENCIES))); - String dependencyResolverDependenciesClasspath = bootstrapDependencyResolverClasspath(); - writeDependencyResolverClasspath(projectBasePath, dependencyResolverDependenciesClasspath); - - // --- Resolve dependencies using sub-process process - // TODO: it would be nice to just add the above classpath to the system classloader and here call the API - // immediately, but that's challenging and not a stable API we can rely on (hacks exist to add paths - // to system classloader but are not reliable). - printBuildFiles(projectDir, dependencies); - return resolveDependenciesWithSubprocess(projectDir); - } catch (Exception e) { - e.printStackTrace(); - throw e; - } - } - - - /** - * Use an external {@code gradle} invocation in order to download dependencies such that we can use `gradle-api` - * next time we want to resolve dependencies. This uses an external process and is slightly worse than using the API - * directly. - * - * @return classpath obtained for the dependencies - * @throws IOException if file IO failed during mock project creation - * @throws SwiftJavaBootstrapException if the resolve failed for some other reason - */ - private static String bootstrapDependencyResolverClasspath() throws IOException, SwiftJavaBootstrapException { - var dependencies = BASE_DEPENDENCIES; - simpleLog("Bootstrap gradle-api for DependencyResolver: " + Arrays.toString(dependencies)); - - File bootstrapDir = Files.createTempDirectory("swift-java-dependency-resolver").toFile(); - bootstrapDir.mkdirs(); - simpleLog("Bootstrap dependencies using project at: %s".formatted(bootstrapDir)); - - printBuildFiles(bootstrapDir, dependencies); - - var bootstrapClasspath = resolveDependenciesWithSubprocess(bootstrapDir); - simpleLog("Prepared dependency resolver bootstrap classpath: " + bootstrapClasspath.split(":").length + " entries"); - - return bootstrapClasspath; - - } - - private static String resolveDependenciesWithSubprocess(File gradleProjectDir) throws IOException { - if (!gradleProjectDir.isDirectory()) { - throw new IllegalArgumentException("Gradle project directory is not a directory: " + gradleProjectDir); - } - - File stdoutFile = File.createTempFile("swift-java-bootstrap", ".stdout", gradleProjectDir); - stdoutFile.deleteOnExit(); - File stderrFile = File.createTempFile("swift-java-bootstrap", ".stderr", gradleProjectDir); - stderrFile.deleteOnExit(); - - try { - ProcessBuilder gradleBuilder = new ProcessBuilder("./gradlew", ":printRuntimeClasspath"); - gradleBuilder.directory(gradleProjectDir); - gradleBuilder.redirectOutput(stdoutFile); - gradleBuilder.redirectError(stderrFile); - Process gradleProcess = gradleBuilder.start(); - gradleProcess.waitFor(10, TimeUnit.MINUTES); // TODO: must be configurable - - if (gradleProcess.exitValue() != 0) { - throw new SwiftJavaBootstrapException("Failed to resolve bootstrap dependencies, exit code: " + gradleProcess.exitValue()); - } - - Stream lines = Files.readAllLines(stdoutFile.toPath()).stream(); - var bootstrapClasspath = getClasspathFromGradleCommandOutput(lines); - return bootstrapClasspath; - } catch (Exception ex) { - simpleLog("stdoutFile = " + stdoutFile); - simpleLog("stderrFile = " + stderrFile); - - ex.printStackTrace(); - throw new SwiftJavaBootstrapException("Failed to bootstrap dependencies necessary for " + - DependencyResolver.class.getCanonicalName() + "! " + - "Make sure to invoke SwiftPM with --disable-sandbox because " + - "swift-java needs network access to fetch java dependencies.", ex); - } - } - - private static void writeDependencyResolverClasspath(Path projectBasePath, String dependencyResolverDependenciesClasspath) throws IOException { - File swiftBuildDirectory = new File(String.valueOf(projectBasePath), ".build"); - swiftBuildDirectory.mkdirs(); - - File dependencyResolverClasspathCacheFile = new File(swiftBuildDirectory, CLASSPATH_CACHE_FILENAME); - dependencyResolverClasspathCacheFile.createNewFile(); - simpleLog("Cache %s dependencies classpath at: '%s'. Subsequent dependency resolutions will use gradle-api." - .formatted(DependencyResolver.class.getSimpleName(), dependencyResolverClasspathCacheFile.toPath())); - - Files.writeString( - dependencyResolverClasspathCacheFile.toPath(), - dependencyResolverDependenciesClasspath, - StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); - } - - /** - * Detect if we have the necessary dependencies loaded. - */ - @UsedFromSwift - public static boolean hasDependencyResolverDependenciesLoaded() { - return hasDependencyResolverDependenciesLoaded(DependencyResolver.class.getClassLoader()); - } - - /** - * Resolve dependencies in the passed project directory and return the resulting classpath. - * - * @return classpath which was resolved for the dependencies - */ - private static String resolveDependenciesUsingAPI(File projectDir, String[] dependencies) throws IOException { - printBuildFiles(projectDir, dependencies); - - var connection = GradleConnector.newConnector() - .forProjectDirectory(projectDir) - .connect(); - - try (connection) { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - PrintStream printStream = new PrintStream(outputStream); - - connection.newBuild().forTasks(":printRuntimeClasspath") - .setStandardError(new NoopOutputStream()) - .setStandardOutput(printStream) - .run(); - - var all = outputStream.toString(); - var classpathString = Arrays.stream(all.split("\n")) - .filter(s -> s.startsWith(COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH)) - .map(s -> s.substring(COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH.length())) - .findFirst().orElseThrow(() -> new RuntimeException("Could not find classpath output from ':printRuntimeClasspath' task.")); - - // remove output directories of the project we used for the dependency resolution - var classpath = Arrays.stream(classpathString - .split(":")) - .filter(s -> !s.startsWith(projectDir.getAbsolutePath())) - .collect(Collectors.joining(":")); - - - return classpath; - } - } - - private static String getClasspathFromGradleCommandOutput(Stream lines) { - return lines.filter(s -> s.startsWith(COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH)) - .map(s -> s.substring(COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH.length())) - .findFirst().orElseThrow(() -> new RuntimeException("Could not find classpath output from gradle command output task.")); - } - - - private static boolean hasDependencyResolverDependenciesLoaded(ClassLoader classLoader) { - try { - classLoader.loadClass("org.gradle.tooling.GradleConnector"); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - private static void printBuildFiles(File projectDir, String[] dependencies) throws IOException { - // === build.gradle - File buildFile = new File(projectDir, "build.gradle"); - try (PrintWriter writer = new PrintWriter(buildFile)) { - writer.println("plugins { id 'java-library' }"); - writer.println("repositories { mavenCentral() }"); - - writer.println("dependencies {"); - for (String dependency : dependencies) { - writer.println("implementation(\"" + dependency + "\")"); - } - writer.println("}"); - - writer.println(""" - tasks.register("printRuntimeClasspath") { - def runtimeClasspath = sourceSets.main.runtimeClasspath - inputs.files(runtimeClasspath) - doLast { - println("SWIFT_JAVA_CLASSPATH:${runtimeClasspath.asPath}") - } - } - """); - } - - // === settings.gradle - File settingsFile = new File(projectDir, "settings.gradle.kts"); - try (PrintWriter writer = new PrintWriter(settingsFile)) { - writer.println(""" - rootProject.name = "swift-java-resolve-temp-project" - """); - } - - // === gradle wrapper files, so we can even download gradle when necessary to bootstrap - File gradlew = new File(projectDir, "gradlew"); - writeResourceToFile("/gradle/gradlew", gradlew); - gradlew.setExecutable(true); - - File gradlewBat = new File(projectDir, "gradlew.bat"); - writeResourceToFile("/gradle/gradlew.bat", gradlewBat); - gradlew.setExecutable(true); - - File gradleDir = new File(projectDir, "gradle"); - File gradleWrapperDir = new File(gradleDir, "wrapper"); - gradleWrapperDir.mkdirs(); - - File gradleWrapperJar = new File(gradleWrapperDir, "gradle-wrapper.jar"); - writeResourceToFile("/gradle/wrapper/gradle-wrapper.jar", gradleWrapperJar); - File gradleWrapperProps = new File(gradleWrapperDir, "gradle-wrapper.properties"); - writeResourceToFile("/gradle/wrapper/gradle-wrapper.properties", gradleWrapperProps); - } - - private static void writeResourceToFile(String resource, File target) throws IOException { - try (PrintWriter writer = new PrintWriter(target)) { - try (InputStream inputStream = DependencyResolver.class.getResourceAsStream(resource)) { - if (inputStream == null) { - throw new FileNotFoundException("Not found: gradlew wrapper in resources!"); - } - try (var os = new BufferedOutputStream(new FileOutputStream(target))) { - byte[] buffer = new byte[8192]; // Buffer size of 8 KB - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - os.write(buffer, 0, bytesRead); - } - } - } - } - } - - private static void simpleLog(String message) { - System.err.println("[info][swift-java/" + DependencyResolver.class.getSimpleName() + "] " + message); - } - - private static class NoopOutputStream extends OutputStream { - @Override - public void write(int b) throws IOException { - // ignore - } - } -} diff --git a/JavaKit/src/main/java/org/swift/javakit/dependencies/SwiftJavaBootstrapException.java b/JavaKit/src/main/java/org/swift/javakit/dependencies/SwiftJavaBootstrapException.java deleted file mode 100644 index 3c8d475a..00000000 --- a/JavaKit/src/main/java/org/swift/javakit/dependencies/SwiftJavaBootstrapException.java +++ /dev/null @@ -1,25 +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 -// -//===----------------------------------------------------------------------===// - -package org.swift.javakit.dependencies; - -public class SwiftJavaBootstrapException extends RuntimeException { - public SwiftJavaBootstrapException(String message) { - super(message); - } - - public SwiftJavaBootstrapException(String message, Exception ex) { - super(message, ex); - } -} diff --git a/Package.swift b/Package.swift index 8cda1aed..72a95155 100644 --- a/Package.swift +++ b/Package.swift @@ -128,9 +128,9 @@ let package = Package( // ==== Plugin for wrapping Java classes in Swift .plugin( - name: "SwiftJavaPlugin", + name: "JExtractSwiftPlugin", targets: [ - "SwiftJavaPlugin" + "JExtractSwiftPlugin" ] ), .plugin( @@ -175,25 +175,6 @@ let package = Package( ] ), - .target( - name: "JavaKitDependencyResolver", - dependencies: [ - "JavaKit", - ], - exclude: [ - "swift-java.config", - ], - swiftSettings: [ - .swiftLanguageMode(.v5), - .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]), - ] -// // FIXME: when the tool is run from plugin it hangs even if sandbox is disabled -// , -// plugins: [ -// "SwiftJavaBootstrapJavaPlugin", -// ] - ), - .target( name: "JavaKit", dependencies: [ @@ -329,7 +310,7 @@ let package = Package( "JavaTypes", "JavaKitShared", "JavaKitConfigurationShared", - "JavaKitDependencyResolver", + "_Subprocess", // using process spawning ], swiftSettings: [ .swiftLanguageMode(.v5), @@ -349,7 +330,6 @@ let package = Package( "JavaKitJar", "JavaKitNetwork", "Java2SwiftLib", - "JavaKitDependencyResolver", "JavaKitShared", ], @@ -386,27 +366,8 @@ let package = Package( ] ), - .executableTarget( - name: "SwiftJavaBootstrapJavaTool", - dependencies: [ - "JavaKitConfigurationShared", // for Configuration reading at runtime - "_Subprocess", - ], - swiftSettings: [ - .swiftLanguageMode(.v5) - ] - ), - - .plugin( - name: "SwiftJavaBootstrapJavaPlugin", - capability: .buildTool(), - dependencies: [ - "SwiftJavaBootstrapJavaTool" - ] - ), - .plugin( - name: "SwiftJavaPlugin", + name: "JExtractSwiftPlugin", capability: .buildTool(), dependencies: [ "JExtractSwiftTool" diff --git a/Plugins/SwiftJavaPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift similarity index 100% rename from Plugins/SwiftJavaPlugin/JExtractSwiftPlugin.swift rename to Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift diff --git a/Plugins/SwiftJavaBootstrapJavaPlugin/_PluginsShared b/Plugins/JExtractSwiftPlugin/_PluginsShared similarity index 100% rename from Plugins/SwiftJavaBootstrapJavaPlugin/_PluginsShared rename to Plugins/JExtractSwiftPlugin/_PluginsShared diff --git a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift b/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift index d932ee97..88507f2e 100644 --- a/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift +++ b/Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift @@ -156,11 +156,11 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { if let dependencies = config.dependencies, !dependencies.isEmpty { let displayName = "Fetch (Java) dependencies for Swift target \(sourceModule.name)" log("Prepared: \(displayName)") - + fetchDependenciesOutputFiles += [ outputFilePath(context: context, generated: false, filename: "\(sourceModule.name).swift-java.classpath") ] - + commands += [ .buildCommand( displayName: displayName, diff --git a/Plugins/SwiftJavaBootstrapJavaPlugin/SwiftJavaBootstrapJavaPlugin.swift b/Plugins/SwiftJavaBootstrapJavaPlugin/SwiftJavaBootstrapJavaPlugin.swift deleted file mode 100644 index 7d614371..00000000 --- a/Plugins/SwiftJavaBootstrapJavaPlugin/SwiftJavaBootstrapJavaPlugin.swift +++ /dev/null @@ -1,190 +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 -import PackagePlugin - -fileprivate let SwiftJavaConfigFileName = "swift-java.config" - -@main -struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin { - - var pluginName: String = "swift-java-bootstrap" - var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE") - - func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { - log("Create build commands for target '\(target.name)'") - guard let sourceModule = target.sourceModule else { return [] } - - let executable = try context.tool(named: "SwiftJavaBootstrapJavaTool").url - var commands: [Command] = [] - - // Note: Target doesn't have a directoryURL counterpart to directory, - // so we cannot eliminate this deprecation warning. - let sourceDir = target.directory.string - - // 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: SwiftJavaConfigFileName) - let config = try readConfiguration(sourceDir: sourceDir) - - log("Config on path: \(configFile.path(percentEncoded: false))") - log("Config was: \(config)") - var javaDependencies = config.dependencies ?? [] - - /// Find the manifest files from other Java2Swift executions in any targets - /// this target depends on. - var dependentConfigFiles: [(String, URL)] = [] - func searchForConfigFiles(in target: any Target) { - // log("Search for config files in target: \(target.name)") - let dependencyURL = URL(filePath: target.directory.string) - - // Look for a config file within this target. - let dependencyConfigURL = dependencyURL - .appending(path: SwiftJavaConfigFileName) - let dependencyConfigString = dependencyConfigURL - .path(percentEncoded: false) - - if FileManager.default.fileExists(atPath: dependencyConfigString) { - dependentConfigFiles.append((target.name, dependencyConfigURL)) - } - } - - // Process direct dependencies of this target. - for dependency in target.dependencies { - switch dependency { - case .target(let target): - searchForConfigFiles(in: target) - - case .product(let product): - for target in product.targets { - searchForConfigFiles(in: target) - } - - @unknown default: - break - } - } - - // Process indirect target dependencies. - for dependency in target.recursiveTargetDependencies { - searchForConfigFiles(in: dependency) - } - - var arguments: [String] = [] - arguments += argumentsModuleName(sourceModule: sourceModule) - arguments += argumentsOutputDirectory(context: context) - - arguments += dependentConfigFiles.flatMap { moduleAndConfigFile in - let (moduleName, configFile) = moduleAndConfigFile - return [ - "--depends-on", - "\(moduleName)=\(configFile.path(percentEncoded: false))" - ] - } - arguments.append(configFile.path(percentEncoded: false)) - - let classes = config.classes ?? [:] - print("Classes to wrap: \(classes.map(\.key))") - - /// Determine the set of Swift files that will be emitted by the Java2Swift tool. - // TODO: this is not precise and won't work with more advanced Java files, e.g. lambdas etc. - let outputDirectoryGenerated = self.outputDirectory(context: context, generated: true) - let outputSwiftFiles = classes.map { (javaClassName, swiftName) in - let swiftNestedName = swiftName.replacingOccurrences(of: ".", with: "+") - return outputDirectoryGenerated.appending(path: "\(swiftNestedName).swift") - } - - arguments += [ - "--cache-directory", - context.pluginWorkDirectoryURL.path(percentEncoded: false) - ] - - // Find the Java .class files generated from prior plugins. - let compiledClassFiles = sourceModule.pluginGeneratedResources.filter { url in - url.pathExtension == "class" - } - - if let firstClassFile = compiledClassFiles.first { - // Keep stripping off parts of the path until we hit the "Java" part. - // That's where the class path starts. - var classpath = firstClassFile - while classpath.lastPathComponent != "Java" { - classpath.deleteLastPathComponent() - } - arguments += ["--classpath", classpath.path()] - } - - var fetchDependenciesOutputFiles: [URL] = [] - if let dependencies = config.dependencies, !dependencies.isEmpty { - let displayName = "Fetch (Java) dependencies for Swift target \(sourceModule.name)" - log("Prepared: \(displayName)") - - let arguments = [ - "--fetch", configFile.path(percentEncoded: false), - "--module-name", sourceModule.name, - "--output-directory", outputDirectory(context: context, generated: false).path(percentEncoded: false) - ] - - log("Command: \(executable) \(arguments.joined(separator: " "))") - - fetchDependenciesOutputFiles += [ - outputFilePath(context: context, generated: false, filename: "\(sourceModule.name).swift-java.classpath") - ] - - commands += [ - .buildCommand( - displayName: displayName, - executable: executable, - arguments: arguments, - inputFiles: [configFile], - outputFiles: fetchDependenciesOutputFiles - ) - ] - } else { - log("No dependencies to fetch for target \(sourceModule.name)") - } - - return commands - } -} - -extension Java2SwiftBuildToolPlugin { - func argumentsModuleName(sourceModule: Target) -> [String] { - return [ - "--module-name", sourceModule.name - ] - } - - func argumentsOutputDirectory(context: PluginContext, generated: Bool = true) -> [String] { - return [ - "--output-directory", - outputDirectory(context: context, generated: generated).path(percentEncoded: false) - ] - } - - func outputDirectory(context: PluginContext, generated: Bool = true) -> URL { - let dir = context.pluginWorkDirectoryURL - if generated { - return dir.appending(path: "generated") - } else { - return dir - } - } - - func outputFilePath(context: PluginContext, generated: Bool, filename: String) -> URL { - outputDirectory(context: context, generated: generated).appending(path: filename) - } -} diff --git a/Plugins/SwiftJavaPlugin/_PluginsShared b/Plugins/SwiftJavaPlugin/_PluginsShared deleted file mode 120000 index de623a5e..00000000 --- a/Plugins/SwiftJavaPlugin/_PluginsShared +++ /dev/null @@ -1 +0,0 @@ -../PluginsShared \ No newline at end of file diff --git a/Samples/JavaDependencySampleApp/Package.swift b/Samples/JavaDependencySampleApp/Package.swift index 3d210e36..a90cd255 100644 --- a/Samples/JavaDependencySampleApp/Package.swift +++ b/Samples/JavaDependencySampleApp/Package.swift @@ -93,6 +93,7 @@ let package = Package( .swiftLanguageMode(.v5), ], plugins: [ +// .plugin(name: "SwiftJavaBootstrapJavaPlugin", package: "swift-java"), .plugin(name: "Java2SwiftPlugin", package: "swift-java"), ] ), diff --git a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift index 86770ee8..16c2f4a9 100644 --- a/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift +++ b/Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift @@ -25,10 +25,8 @@ print("") print("-----------------------------------------------------------------------") print("Start Sample app...") -// Make sure we have the classpath loaded -// TODO: this is more complex than that, need to account for dependencies of our module -let currentDir = FileManager.default.currentDirectoryPath -let swiftJavaClasspath = findSwiftJavaClasspaths() +// TODO: locating the classpath is more complex, need to account for dependencies of our module +let swiftJavaClasspath = findSwiftJavaClasspaths() // scans for .classpath files // 1) Start a JVM with appropriate classpath let jvm = try JavaVirtualMachine.shared(classpath: swiftJavaClasspath) diff --git a/Samples/JavaDependencySampleApp/ci-validate.sh b/Samples/JavaDependencySampleApp/ci-validate.sh index b21d19d4..7012b841 100755 --- a/Samples/JavaDependencySampleApp/ci-validate.sh +++ b/Samples/JavaDependencySampleApp/ci-validate.sh @@ -3,10 +3,4 @@ set -e set -x -# TODO: this is a workaround for build plugins getting stuck running the bootstrap plugin -cd ../../ -swift build --product SwiftJavaBootstrapJavaTool -.build/debug/SwiftJavaBootstrapJavaTool --fetch Sources/JavaKitDependencyResolver/swift-java.config --module-name JavaKitDependencyResolver --output-directory .build/plugins/outputs/swift-java/JavaKitDependencyResolver/destination/SwiftJavaBootstrapJavaPlugin - -cd - swift run --disable-sandbox diff --git a/Samples/JavaDependencySampleApp/gradle b/Samples/JavaDependencySampleApp/gradle new file mode 120000 index 00000000..fc7fc3b4 --- /dev/null +++ b/Samples/JavaDependencySampleApp/gradle @@ -0,0 +1 @@ +../../gradle/ \ No newline at end of file diff --git a/Samples/JavaDependencySampleApp/gradlew b/Samples/JavaDependencySampleApp/gradlew new file mode 120000 index 00000000..343e0d2c --- /dev/null +++ b/Samples/JavaDependencySampleApp/gradlew @@ -0,0 +1 @@ +../../gradlew \ No newline at end of file diff --git a/Samples/JavaDependencySampleApp/gradlew.bat b/Samples/JavaDependencySampleApp/gradlew.bat new file mode 120000 index 00000000..cb5a9464 --- /dev/null +++ b/Samples/JavaDependencySampleApp/gradlew.bat @@ -0,0 +1 @@ +../../gradlew.bat \ No newline at end of file diff --git a/Samples/SwiftAndJavaJarSampleLib/Package.swift b/Samples/SwiftAndJavaJarSampleLib/Package.swift index a1212fd7..5f132239 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: "SwiftJavaPlugin", package: "swift-java"), + .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), ] ), ] diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftKitSampleApp/Package.swift index bfd3ecbe..f12ecfd6 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: "SwiftJavaPlugin", package: "swift-java"), + .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), ] ), ] diff --git a/Samples/gradle b/Samples/gradle new file mode 120000 index 00000000..3337596a --- /dev/null +++ b/Samples/gradle @@ -0,0 +1 @@ +../gradle \ No newline at end of file diff --git a/JavaKit/gradlew b/Samples/gradlew similarity index 100% rename from JavaKit/gradlew rename to Samples/gradlew diff --git a/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift b/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift index fd567234..6754d381 100644 --- a/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift +++ b/Sources/Java2Swift/JavaToSwift+EmitConfiguration.swift @@ -18,7 +18,6 @@ import Java2SwiftLib import JavaKit import JavaKitJar import Java2SwiftLib -import JavaKitDependencyResolver import JavaKitConfigurationShared extension JavaToSwift { diff --git a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift b/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift index 391abb27..60100fca 100644 --- a/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift +++ b/Sources/Java2Swift/JavaToSwift+FetchDependencies.swift @@ -18,62 +18,105 @@ import JavaKit import Foundation import JavaKitJar import Java2SwiftLib -import JavaKitDependencyResolver import JavaKitConfigurationShared import JavaKitShared +import _Subprocess extension JavaToSwift { - /// Must be the same as `DependencyResolver#CLASSPATH_CACHE_FILENAME` on the java side. - var JavaKitDependencyResolverClasspathCacheFilename: String { - "JavaKitDependencyResolver.swift-java.classpath" - } - var JavaKitDependencyResolverClasspathCacheFilePath: String { - ".build/\(JavaKitDependencyResolverClasspathCacheFilename)" - } + var SwiftJavaClasspathPrefix: String { "SWIFT_JAVA_CLASSPATH:" } - func fetchDependenciesCachedClasspath() -> [String]? { - let cachedClasspathURL = URL( - fileURLWithPath: FileManager.default.currentDirectoryPath + "/" + JavaKitDependencyResolverClasspathCacheFilePath) - - guard FileManager.default.fileExists(atPath: cachedClasspathURL.path) else { - return [] - } - - guard let javaKitDependencyResolverCachedClasspath = try? String(contentsOf: cachedClasspathURL) else { - return [] - } - - print("[debug][swift-java] Cached dependency resolver classpath: \(javaKitDependencyResolverCachedClasspath)") - return javaKitDependencyResolverCachedClasspath.split(separator: ":").map(String.init) - } + var printRuntimeClasspathTaskName: String { "printRuntimeClasspath" } func fetchDependencies(moduleName: String, - dependencies: [JavaDependencyDescriptor], - baseClasspath: [String], - environment: JNIEnvironment) throws -> ResolvedDependencyClasspath { + dependencies: [JavaDependencyDescriptor]) async throws -> ResolvedDependencyClasspath { let deps = dependencies.map { $0.descriptionGradleStyle } print("[debug][swift-java] Resolve and fetch dependencies for: \(deps)") - let resolverClass = try JavaClass(environment: environment) - let fullClasspath = try resolverClass.resolveDependenciesToClasspath( - projectBaseDirectory: URL(fileURLWithPath: ".").path, - dependencies: deps) - .split(separator: ":") + let dependenciesClasspath = await resolveDependencies(dependencies: dependencies) + let classpathEntries = dependenciesClasspath.split(separator: ":") - let classpathEntries = fullClasspath.filter { - $0.hasSuffix(".jar") - } - let classpath = classpathEntries.joined(separator: ":") print("[info][swift-java] Resolved classpath for \(deps.count) dependencies of '\(moduleName)', classpath entries: \(classpathEntries.count), ", terminator: "") print("done.".green) - return ResolvedDependencyClasspath(for: dependencies, classpath: classpath) + return ResolvedDependencyClasspath(for: dependencies, classpath: dependenciesClasspath) + } + + func resolveDependencies(dependencies: [JavaDependencyDescriptor]) async -> String { + let workDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + .appendingPathComponent(".build") + let resolverDir = try! createTemporaryDirectory(in: workDir) + + try! copyGradlew(to: resolverDir) + + try! printGradleProject(directory: resolverDir, dependencies: dependencies) + + let process = try! await Subprocess.run( + .at(.init(resolverDir.appendingPathComponent("gradlew").path)), + arguments: [ + "--no-daemon", + "--rerun-tasks", + "\(printRuntimeClasspathTaskName)", + ], + workingDirectory: .init(platformString: resolverDir.path) + ) + + let outString = String( + data: process.standardOutput, + encoding: .utf8 + ) + let errString = String( + data: process.standardError, + encoding: .utf8 + ) + + let classpathOutput: String + if let found = outString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { + classpathOutput = String(found) + } else if let found = errString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { + classpathOutput = String(found) + } else { + let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." + fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + + "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>") + } + + return String(classpathOutput.dropFirst(SwiftJavaClasspathPrefix.count)) + } + + func printGradleProject(directory: URL, dependencies: [JavaDependencyDescriptor]) throws { + let buildGradle = directory + .appendingPathComponent("build.gradle", isDirectory: false) + + let buildGradleText = + """ + plugins { id 'java-library' } + repositories { mavenCentral() } + + dependencies { + \(dependencies.map({ dep in "implementation(\"\(dep.descriptionGradleStyle)\")" }).joined(separator: ",\n")) + } + + tasks.register("printRuntimeClasspath") { + def runtimeClasspath = sourceSets.main.runtimeClasspath + inputs.files(runtimeClasspath) + doLast { + println("\(SwiftJavaClasspathPrefix)${runtimeClasspath.asPath}") + } + } + """ + try buildGradleText.write(to: buildGradle, atomically: true, encoding: .utf8) + + let settingsGradle = directory + .appendingPathComponent("settings.gradle.kts", isDirectory: false) + let settingsGradleText = + """ + rootProject.name = "swift-java-resolve-temp-project" + """ + try settingsGradleText.write(to: settingsGradle, atomically: true, encoding: .utf8) } -} -extension JavaToSwift { mutating func writeFetchedDependenciesClasspath( moduleName: String, cacheDir: String, @@ -98,6 +141,62 @@ extension JavaToSwift { let camelCased = components.map { $0.capitalized }.joined() return camelCased } + + func copyGradlew(to resolverWorkDirectory: URL) throws { + var searchDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + + while searchDir.pathComponents.count > 1 { + print("[COPY] Search dir: \(searchDir)") + + let gradlewFile = searchDir.appendingPathComponent("gradlew") + let gradlewExists = FileManager.default.fileExists(atPath: gradlewFile.path) + guard gradlewExists else { + searchDir = searchDir.deletingLastPathComponent() + continue + } + + let gradleDir = searchDir.appendingPathComponent("gradle") + let gradleDirExists = FileManager.default.fileExists(atPath: gradleDir.path) + guard gradleDirExists else { + searchDir = searchDir.deletingLastPathComponent() + continue + } +// +// let gradleWrapperDir = gradleDir.appendingPathComponent("wrapper") +// let gradleWrapperDirExists = FileManager.default.fileExists(atPath: gradleWrapperDir.path) +// guard gradleWrapperDirExists else { +// searchDir = searchDir.deletingLastPathComponent() +// continue +// } +// +// let gradleWrapperJarFile = gradleWrapperDir.appendingPathComponent("gradle-wrapper.jar") +// let gradleWrapperJarFileExists = FileManager.default.fileExists(atPath: gradleWrapperJarFile.path) +// guard gradleWrapperJarFileExists else { +// searchDir = searchDir.deletingLastPathComponent() +// continue +// } + + print("COPY: \(gradlewFile) -> \(resolverWorkDirectory)") + print("COPY: \(gradleDir) -> \(resolverWorkDirectory)") + try? FileManager.default.copyItem( + at: gradlewFile, + to: resolverWorkDirectory.appendingPathComponent("gradlew")) + try? FileManager.default.copyItem( + at: gradleDir, + to: resolverWorkDirectory.appendingPathComponent("gradle")) + return + } + } + + func createTemporaryDirectory(in directory: URL) throws -> URL { + let uuid = UUID().uuidString + let resolverDirectoryURL = directory.appendingPathComponent("swift-java-dependencies-\(uuid)") + + try FileManager.default.createDirectory(at: resolverDirectoryURL, withIntermediateDirectories: true, attributes: nil) + + return resolverDirectoryURL + } + } struct ResolvedDependencyClasspath: CustomStringConvertible { @@ -120,3 +219,4 @@ struct ResolvedDependencyClasspath: CustomStringConvertible { "JavaClasspath(for: \(rootDependencies), classpath: \(classpath))" } } + diff --git a/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift b/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift index 99f83b4e..67aa3f9c 100644 --- a/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift +++ b/Sources/Java2Swift/JavaToSwift+GenerateWrappers.swift @@ -18,7 +18,6 @@ import Java2SwiftLib import JavaKit import JavaKitJar import Java2SwiftLib -import JavaKitDependencyResolver import JavaKitConfigurationShared extension JavaToSwift { @@ -142,4 +141,4 @@ extension JavaToSwift { ) } } -} \ No newline at end of file +} diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/Java2Swift/JavaToSwift.swift index 0d196933..536b3fd1 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/Java2Swift/JavaToSwift.swift @@ -26,7 +26,7 @@ import JavaKitShared /// Command-line utility to drive the export of Java classes into Swift types. @main -struct JavaToSwift: ParsableCommand { +struct JavaToSwift: AsyncParsableCommand { static var _commandName: String { "Java2Swift" } @Option(help: "The name of the Swift module into which the resulting Swift types will be generated.") @@ -159,14 +159,13 @@ struct JavaToSwift: ParsableCommand { /// Generate Swift wrappers for Java classes based on the given /// configuration. - case classWrappers // (Configuration) + case classWrappers /// Fetch dependencies for a module - case fetchDependencies // (Configuration) - // FIXME each mode should have its own config? + case fetchDependencies } - mutating func run() { + mutating func run() async { print("[info][swift-java] Run: \(CommandLine.arguments.joined(separator: " "))") do { let config: Configuration @@ -246,25 +245,16 @@ struct JavaToSwift: ParsableCommand { // if we have already fetched dependencies for the dependency loader, // let's use them so we can in-process resolve rather than forking a new // gradle process. - if let dependencyResolverClasspath = fetchDependenciesCachedClasspath() { - print("[debug][swift-java] Found cached dependency resolver classpath: \(dependencyResolverClasspath)") - classpathEntries += dependencyResolverClasspath - } + print("[debug][swift-java] Add classpath from .classpath files") + classpathEntries += findSwiftJavaClasspaths(in: FileManager.default.currentDirectoryPath) +// if let dependencyResolverClasspath = fetchDependenciesCachedClasspath() { +// print("[debug][swift-java] Found cached dependency resolver classpath: \(dependencyResolverClasspath)") +// classpathEntries += dependencyResolverClasspath +// } case .classWrappers: break; } - // Add extra classpath entries which are specific to building the JavaKit project and samples - let classpathBuildJavaKitEntries = [ // FIXME: THIS IS A TRICK UNTIL WE FIGURE OUT HOW TO BOOTSTRAP THIS PART - FileManager.default.currentDirectoryPath, - FileManager.default.currentDirectoryPath + "/.build", - FileManager.default.currentDirectoryPath + "/JavaKit/build/libs", - URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - .deletingLastPathComponent() - .deletingLastPathComponent().absoluteURL.path + "/JavaKit/build/libs/JavaKit-1.0-SNAPSHOT.jar" - ] - classpathEntries += classpathBuildJavaKitEntries - // Bring up the Java VM. // TODO: print only in verbose mode let classpath = classpathEntries.joined(separator: ":") @@ -272,18 +262,6 @@ struct JavaToSwift: ParsableCommand { let jvm = try JavaVirtualMachine.shared(classpath: classpathEntries) - // FIXME: we should resolve dependencies here perhaps - // if let dependencies = config.dependencies { - // print("[info][swift-java] Resolve dependencies...") - // let dependencyClasspath = try fetchDependencies( - // moduleName: moduleName, - // dependencies: dependencies, - // baseClasspath: classpathOptionEntries, - // environment: jvm.environment() - // ) - // classpathEntries += dependencyClasspath.classpathEntries - // } - // * Classespaths from all dependent configuration files for (_, config) in dependentConfigs { // TODO: may need to resolve the dependent configs rather than just get their configs @@ -323,11 +301,9 @@ struct JavaToSwift: ParsableCommand { print("[debug][swift-java] Base classpath to fetch dependencies: \(classpathOptionEntries)") - let dependencyClasspath = try fetchDependencies( + let dependencyClasspath = try await fetchDependencies( moduleName: moduleName, - dependencies: dependencies, - baseClasspath: classpathOptionEntries, - environment: jvm.environment() + dependencies: dependencies ) try writeFetchedDependenciesClasspath( diff --git a/Sources/Java2Swift/String+Extensions.swift b/Sources/Java2Swift/String+Extensions.swift index ba26f892..26a20241 100644 --- a/Sources/Java2Swift/String+Extensions.swift +++ b/Sources/Java2Swift/String+Extensions.swift @@ -18,7 +18,6 @@ import Java2SwiftLib import JavaKit import JavaKitJar import Java2SwiftLib -import JavaKitDependencyResolver import JavaKitConfigurationShared extension String { diff --git a/Sources/JavaKitConfigurationShared/Configuration.swift b/Sources/JavaKitConfigurationShared/Configuration.swift index d401c657..13c7fd9b 100644 --- a/Sources/JavaKitConfigurationShared/Configuration.swift +++ b/Sources/JavaKitConfigurationShared/Configuration.swift @@ -121,6 +121,17 @@ public func readConfiguration(configPath: URL, file: String = #fileID, line: UIn } } +public func findSwiftJavaClasspaths(moduleName: String) -> [String] { + let basePath: String = FileManager.default.currentDirectoryPath + let pluginOutputsDir = URL(fileURLWithPath: basePath) + .appendingPathComponent(".build", isDirectory: true) + .appendingPathComponent("plugins", isDirectory: true) + .appendingPathComponent("outputs", isDirectory: true) + .appendingPathComponent(moduleName, isDirectory: true) + + return findSwiftJavaClasspaths(in: pluginOutputsDir.path) +} + public func findSwiftJavaClasspaths(in basePath: String = FileManager.default.currentDirectoryPath) -> [String] { let fileManager = FileManager.default diff --git a/Sources/SwiftJavaBootstrapJavaTool/SwiftJavaBootstrapJavaTool.swift b/Sources/SwiftJavaBootstrapJavaTool/SwiftJavaBootstrapJavaTool.swift deleted file mode 100644 index 11f26a67..00000000 --- a/Sources/SwiftJavaBootstrapJavaTool/SwiftJavaBootstrapJavaTool.swift +++ /dev/null @@ -1,137 +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 -import Synchronization -import JavaKitConfigurationShared -import Dispatch -import _Subprocess - -@available(macOS 15.0, *) -@main -final class SwiftJavaBootstrapJavaTool { - - let SwiftJavaClasspathPrefix = "SWIFT_JAVA_CLASSPATH:" - - // We seem to have weird "hang" issues with Gradle launched from Process(), workaround it by existing once we get the classpath - let printRuntimeClasspathTaskName = "printRuntimeClasspath" - - let out = Synchronization.Mutex(Data()) - let err = Synchronization.Mutex(Data()) - - static func main() async { - await SwiftJavaBootstrapJavaTool().run() - } - - func run() async { - print("[debug][swift-java-bootstrap] RUN SwiftJavaBootstrapJavaTool: \(CommandLine.arguments.joined(separator: " "))") - - var args = CommandLine.arguments - _ = args.removeFirst() // executable - - assert(args.removeFirst() == "--fetch") - let configPath = args.removeFirst() - - assert(args.removeFirst() == "--module-name") - let moduleName = args.removeFirst() - - assert(args.removeFirst() == "--output-directory") - let outputDirectoryPath = args.removeFirst() - - let configPathURL = URL(fileURLWithPath: configPath) - print("[debug][swift-java-bootstrap] Load config: \(configPathURL.absoluteString)") - let config = try! readConfiguration(configPath: configPathURL) - - // We only support a single dependency right now. - let localGradleProjectDependencyName = (config.dependencies ?? []).filter { - $0.artifactID.hasPrefix(":") - }.map { - $0.artifactID - }.first! - - let process = try! await Subprocess.run( - .at("./gradlew"), - arguments: [ - "--no-daemon", - "--rerun-tasks", - // "--debug", - // "\(localGradleProjectDependencyName):jar", - "\(localGradleProjectDependencyName):\(printRuntimeClasspathTaskName)" - ] - ) - - let outString = String( - data: process.standardOutput, - encoding: .utf8 - ) - let errString = String( - data: process.standardError, - encoding: .utf8 - ) - - print("OUT ==== \(outString?.count) ::: \(outString ?? "")") - print("ERR ==== \(errString?.count) ::: \(errString ?? "")") - - let classpathOutput: String - if let found = outString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { - classpathOutput = String(found) - } else if let found = errString?.split(separator: "\n").first(where: { $0.hasPrefix(self.SwiftJavaClasspathPrefix) }) { - classpathOutput = String(found) - } else { - let suggestDisablingSandbox = "It may be that the Sandbox has prevented dependency fetching, please re-run with '--disable-sandbox'." - fatalError("Gradle output had no SWIFT_JAVA_CLASSPATH! \(suggestDisablingSandbox). \n" + - "Output was:<<<\(outString ?? "")>>>; Err was:<<<\(errString ?? "")>>>") - } - - let classpathString = String(classpathOutput.dropFirst(self.SwiftJavaClasspathPrefix.count)) - - _ = try? FileManager.default.createDirectory( - at: URL(fileURLWithPath: outputDirectoryPath), - withIntermediateDirectories: true, - attributes: nil - ) - - let classpathOutputURL = - URL(fileURLWithPath: outputDirectoryPath) - .appendingPathComponent("\(moduleName).swift-java.classpath", isDirectory: false) - - try! classpathString.write(to: classpathOutputURL, atomically: true, encoding: .utf8) - - print("[swift-java-bootstrap] Done, written classpath to: \(classpathOutputURL)") - } - - func writeBuildGradle(directory: URL) { - // """ - // plugins { id 'java-library' } - // repositories { mavenCentral() } - // - // dependencies { - // implementation("dev.gradleplugins:gradle-api:8.10.1") - // } - // - // task \(printRuntimeClasspathTaskName) { - // def runtimeClasspath = sourceSets.main.runtimeClasspath - // inputs.files(runtimeClasspath) - // doLast { - // println("CLASSPATH:${runtimeClasspath.asPath}") - // } - // } - // """.write(to: URL(fileURLWithPath: tempDir.appendingPathComponent("build.gradle")).path(percentEncoded: false), atomically: true, encoding: .utf8) - // - // """ - // rootProject.name = "swift-java-resolve-temp-project" - // """.write(to: URL(fileURLWithPath: tempDir.appendingPathComponent("settings.gradle.kts")).path(percentEncoded: false), atomically: true, encoding: .utf8) - } - -} diff --git a/settings.gradle b/settings.gradle index c1168a68..fa0fa5bd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,7 +18,6 @@ pluginManagement { rootProject.name = "swift-java" -include "JavaKit" include "SwiftKit" // Include sample apps -- you can run them via `gradle Name:run`