Skip to content

Commit 561efe2

Browse files
committed
Fetch and wrap dependencies as build plugin now works!
1 parent 8c23a23 commit 561efe2

File tree

9 files changed

+198
-74
lines changed

9 files changed

+198
-74
lines changed

JavaKit/build.gradle

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,48 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
plugins {
16-
id("build-logic.java-application-conventions")
17-
}
18-
19-
group = "org.swift.javakit"
20-
version = "1.0-SNAPSHOT"
21-
22-
repositories {
23-
mavenCentral()
24-
}
25-
26-
java {
27-
toolchain {
28-
languageVersion.set(JavaLanguageVersion.of(22))
29-
}
30-
}
31-
32-
dependencies {
33-
implementation("dev.gradleplugins:gradle-api:8.10.1")
34-
35-
testImplementation(platform("org.junit:junit-bom:5.10.0"))
36-
testImplementation("org.junit.jupiter:junit-jupiter")
37-
}
38-
39-
tasks.test {
40-
useJUnitPlatform()
41-
testLogging {
42-
events("passed", "skipped", "failed")
43-
}
44-
}
15+
import groovy.json.JsonSlurper
16+
17+
plugins {
18+
id("build-logic.java-library-conventions")
19+
}
20+
21+
group = "org.swift.javakit"
22+
version = "1.0-SNAPSHOT"
23+
24+
repositories {
25+
mavenCentral()
26+
}
27+
28+
java {
29+
toolchain {
30+
languageVersion.set(JavaLanguageVersion.of(22))
31+
}
32+
}
33+
34+
List<String> loadDependenciesFromSwiftJavaJSON() {
35+
def jsonFile = file("../Sources/JavaKitDependencyResolver/swift-java.config")
36+
if (!jsonFile.exists()) {
37+
throw new FileNotFoundException("Dependencies must be declared in Swift config file! Not found: ${jsonFile.absolutePath}")
38+
}
39+
def jsonContent = new JsonSlurper().parseText(jsonFile.text)
40+
return jsonContent.dependencies
41+
}
42+
43+
dependencies {
44+
// We load dependencies from one source of truth: the swift-java.config of module
45+
// that is used to access this Java code.
46+
loadDependenciesFromSwiftJavaJSON().each {
47+
implementation(it)
48+
}
49+
50+
testImplementation(platform("org.junit:junit-bom:5.10.0"))
51+
testImplementation("org.junit.jupiter:junit-jupiter")
52+
}
53+
54+
tasks.test {
55+
useJUnitPlatform()
56+
testLogging {
57+
events("passed", "skipped", "failed")
58+
}
59+
}

JavaKit/src/main/java/org/swift/javakit/dependencies/DependencyResolver.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.nio.file.StandardOpenOption;
2424
import java.util.Arrays;
2525
import java.util.concurrent.TimeUnit;
26+
import java.util.stream.Collectors;
2627
import java.util.stream.Stream;
2728

2829
/**
@@ -137,7 +138,9 @@ private static String resolveDependenciesWithSubprocess(File gradleProjectDir) t
137138

138139
ex.printStackTrace();
139140
throw new SwiftJavaBootstrapException("Failed to bootstrap dependencies necessary for " +
140-
DependencyResolver.class.getCanonicalName() + "!", ex);
141+
DependencyResolver.class.getCanonicalName() + "! " +
142+
"Make sure to invoke SwiftPM with --disable-sandbox because " +
143+
"swift-java needs network access to fetch java dependencies.", ex);
141144
}
142145
}
143146

@@ -186,10 +189,19 @@ private static String resolveDependenciesUsingAPI(File projectDir, String[] depe
186189
.run();
187190

188191
var all = outputStream.toString();
189-
var classpath = Arrays.stream(all.split("\n"))
192+
var classpathString = Arrays.stream(all.split("\n"))
190193
.filter(s -> s.startsWith(COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH))
191194
.map(s -> s.substring(COMMAND_OUTPUT_LINE_PREFIX_CLASSPATH.length()))
192195
.findFirst().orElseThrow(() -> new RuntimeException("Could not find classpath output from ':printRuntimeClasspath' task."));
196+
simpleLog("DEPENDENCY BUILD PATH = " + projectDir.getAbsolutePath());
197+
198+
// remove output directories of the project we used for the dependency resolution
199+
var classpath = Arrays.stream(classpathString
200+
.split(":"))
201+
.filter(s -> !s.startsWith(projectDir.getAbsolutePath()))
202+
.collect(Collectors.joining(":"));
203+
204+
193205
return classpath;
194206
}
195207
}

Plugins/Java2SwiftPlugin/Java2SwiftPlugin.swift

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ fileprivate let SwiftJavaConfigFileName = "swift-java.config"
2020
@main
2121
struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
2222

23-
var pluginName: String = "swift-java-javac"
23+
var pluginName: String = "swift-java"
2424
var verbose: Bool = getEnvironmentBool("SWIFT_JAVA_VERBOSE")
2525

2626
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
27-
log("Create build commands for: \(target.name)")
27+
log("Create build commands for target '\(target.name)'")
2828
guard let sourceModule = target.sourceModule else { return [] }
2929

30+
let executable = try context.tool(named: "Java2Swift").url
31+
var commands: [Command] = []
32+
3033
// Note: Target doesn't have a directoryURL counterpart to directory,
3134
// so we cannot eliminate this deprecation warning.
3235
let sourceDir = target.directory.string
@@ -35,14 +38,17 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
3538
// which we are generating Swift wrappers for Java classes.
3639
let configFile = URL(filePath: sourceDir)
3740
.appending(path: SwiftJavaConfigFileName)
38-
let configData = try Data(contentsOf: configFile)
39-
let config = try JSONDecoder().decode(Configuration.self, from: configData)
41+
let config = try readConfiguration(sourceDir: sourceDir)
42+
43+
log("Config on path: \(configFile.path(percentEncoded: false))")
44+
log("Config was: \(config)")
45+
var javaDependencies = config.dependencies ?? []
4046

4147
/// Find the manifest files from other Java2Swift executions in any targets
4248
/// this target depends on.
4349
var dependentConfigFiles: [(String, URL)] = []
4450
func searchForConfigFiles(in target: any Target) {
45-
log("Search for config files in target: \(target.name)")
51+
// log("Search for config files in target: \(target.name)")
4652
let dependencyURL = URL(filePath: target.directory.string)
4753

4854
// Look for a config file within this target.
@@ -60,13 +66,13 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
6066
for dependency in target.dependencies {
6167
switch dependency {
6268
case .target(let target):
63-
log("Dependency target: \(target.name)")
69+
// log("Dependency target: \(target.name)")
6470
searchForConfigFiles(in: target)
6571

6672
case .product(let product):
67-
log("Dependency product: \(product.name)")
73+
// log("Dependency product: \(product.name)")
6874
for target in product.targets {
69-
log("Dependency product: \(product.name), target: \(target.name)")
75+
// log("Dependency product: \(product.name), target: \(target.name)")
7076
searchForConfigFiles(in: target)
7177
}
7278

@@ -77,17 +83,14 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
7783

7884
// Process indirect target dependencies.
7985
for dependency in target.recursiveTargetDependencies {
80-
log("Recursive dependency target: \(dependency.name)")
86+
// log("Recursive dependency target: \(dependency.name)")
8187
searchForConfigFiles(in: dependency)
8288
}
8389

84-
let outputDirectory = context.pluginWorkDirectoryURL
85-
.appending(path: "generated")
86-
87-
var arguments: [String] = [
88-
"--module-name", sourceModule.name,
89-
"--output-directory", outputDirectory.path(percentEncoded: false),
90-
]
90+
var arguments: [String] = []
91+
arguments += argumentsModuleName(sourceModule: sourceModule)
92+
arguments += argumentsOutputDirectory(context: context)
93+
9194
arguments += dependentConfigFiles.flatMap { moduleAndConfigFile in
9295
let (moduleName, configFile) = moduleAndConfigFile
9396
return [
@@ -102,12 +105,14 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
102105
// return []
103106
// }
104107
let classes = config.classes ?? [:]
108+
print("Classes to wrap: \(classes.map(\.key))")
105109

106-
/// Determine the set of Swift files that will be emitted by the Java2Swift
107-
/// tool.
110+
/// Determine the set of Swift files that will be emitted by the Java2Swift tool.
111+
// TODO: this is not precise and won't work with more advanced Java files, e.g. lambdas etc.
112+
let outputDirectoryGenerated = self.outputDirectory(context: context, generated: true)
108113
let outputSwiftFiles = classes.map { (javaClassName, swiftName) in
109114
let swiftNestedName = swiftName.replacingOccurrences(of: ".", with: "+")
110-
return outputDirectory.appending(path: "\(swiftNestedName).swift")
115+
return outputDirectoryGenerated.appending(path: "\(swiftNestedName).swift")
111116
}
112117

113118
arguments += [
@@ -147,19 +152,77 @@ struct Java2SwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
147152
}
148153
}
149154

150-
let executable = try context.tool(named: "Java2Swift").url
155+
var fetchDependenciesOutputFiles: [URL] = []
156+
if let dependencies = config.dependencies, !dependencies.isEmpty {
157+
let displayName = "Fetch (Java) dependencies for Swift target \(sourceModule.name)"
158+
log("Prepared: \(displayName)")
159+
160+
fetchDependenciesOutputFiles += [
161+
outputFilePath(context: context, generated: false, filename: "\(sourceModule.name).swift-java.classpath")
162+
]
163+
164+
commands += [
165+
.buildCommand(
166+
displayName: displayName,
167+
executable: executable,
168+
arguments: [
169+
"--fetch", configFile.path(percentEncoded: false),
170+
"--module-name", sourceModule.name,
171+
"--output-directory", outputDirectory(context: context, generated: false).path(percentEncoded: false)
172+
],
173+
environment: [:],
174+
inputFiles: [configFile],
175+
outputFiles: fetchDependenciesOutputFiles
176+
)
177+
]
178+
} else {
179+
log("No dependencies to fetch for target \(sourceModule.name)")
180+
}
181+
182+
if !outputSwiftFiles.isEmpty {
183+
commands += [
184+
.buildCommand(
185+
displayName: "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'",
186+
executable: executable,
187+
arguments: arguments,
188+
inputFiles: compiledClassFiles + fetchDependenciesOutputFiles + [
189+
configFile
190+
],
191+
outputFiles: outputSwiftFiles
192+
)
193+
]
194+
} else {
195+
log("No Swift output files, skip wrapping")
196+
}
197+
198+
return commands
199+
}
200+
}
151201

202+
extension Java2SwiftBuildToolPlugin {
203+
func argumentsModuleName(sourceModule: Target) -> [String] {
204+
return [
205+
"--module-name", sourceModule.name
206+
]
207+
}
208+
209+
func argumentsOutputDirectory(context: PluginContext, generated: Bool = true) -> [String] {
152210
return [
153-
.buildCommand(
154-
displayName: "Wrapping \(classes.count) Java classes target in Swift target '\(sourceModule.name)'",
155-
executable: executable,
156-
arguments: arguments,
157-
inputFiles: compiledClassFiles + [
158-
configFile,
159-
context.cachedClasspathFile(moduleName: sourceModule.name)
160-
],
161-
outputFiles: outputSwiftFiles
162-
)
211+
"--output-directory",
212+
outputDirectory(context: context, generated: generated).path(percentEncoded: false)
163213
]
164214
}
215+
216+
func outputDirectory(context: PluginContext, generated: Bool = true) -> URL {
217+
let dir = context.pluginWorkDirectoryURL
218+
if generated {
219+
return dir.appending(path: "generated")
220+
} else {
221+
return dir
222+
}
223+
}
224+
225+
func outputFilePath(context: PluginContext, generated: Bool, filename: String) -> URL {
226+
outputDirectory(context: context, generated: generated).appending(path: filename)
227+
}
165228
}

Samples/JavaDependencySampleApp/Package.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ let package = Package(
7070
.product(name: "JavaKitFunction", package: "swift-java"),
7171
"JavaCommonsCSV"
7272
],
73+
exclude: ["swift-java.config"],
7374
swiftSettings: [
7475
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
7576
.swiftLanguageMode(.v5),
7677
],
7778
plugins: [
7879
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
79-
// .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
8080
]
8181
),
8282

@@ -85,14 +85,14 @@ let package = Package(
8585
dependencies: [
8686
.product(name: "JavaKit", package: "swift-java"),
8787
.product(name: "JavaKitFunction", package: "swift-java"),
88-
.product(name: "JavaRuntime", package: "swift-java"),
88+
.product(name: "JavaKitCollection", package: "swift-java"),
8989
],
90+
exclude: ["swift-java.config"],
9091
swiftSettings: [
9192
.unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]),
9293
.swiftLanguageMode(.v5),
9394
],
9495
plugins: [
95-
// .plugin(name: "SwiftJavaPlugin", package: "swift-java"),
9696
.plugin(name: "Java2SwiftPlugin", package: "swift-java"),
9797
]
9898
),

Samples/JavaDependencySampleApp/Sources/JavaCommonsCSV/swift-java.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"classes" : {
3-
"org.apache.commons.io.FilenameUtils" : "FilenameUtils"
3+
"org.apache.commons.io.FilenameUtils" : "FilenameUtils",
4+
"org.apache.commons.io.IOCase" : "IOCase"
45
},
5-
"classpath" : "\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:..\/..\/JavaKit\/build\/classes\/java\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:..\/..\/JavaKit\/build\/classes\/java\/main:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/org.apache.commons\/commons-csv\/1.12.0\/c77e053d7189bc0857f8d323ab61cb949965fbd1\/commons-csv-1.12.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-io\/commons-io\/2.17.0\/ddcc8433eb019fb48fe25207c0278143f3e1d7e2\/commons-io-2.17.0.jar:\/Users\/ktoso\/.gradle\/caches\/modules-2\/files-2.1\/commons-codec\/commons-codec\/1.17.1\/973638b7149d333563584137ebf13a691bb60579\/commons-codec-1.17.1.jar:..\/..\/JavaKit\/build\/classes\/java\/main",
66
"dependencies" : [
77
"org.apache.commons:commons-csv:1.12.0"
88
]

Samples/JavaDependencySampleApp/Sources/JavaDependencySample/main.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ import JavaCommonsCSV
2323
// Make sure we have the classpath loaded
2424
// TODO: this is more complex than that, need to account for dependencies of our module
2525
let currentDir = FileManager.default.currentDirectoryPath
26-
let configuration = try readConfiguration(sourceDir: "\(currentDir)/Sources/JavaCommonsCSV/")
26+
let swiftJavaClasspath = findSwiftJavaClasspaths()
2727

28-
// 1) Start a JVM with apropriate classpath
29-
let jvm = try JavaVirtualMachine.shared(classpath: configuration.classpathEntries)
28+
// 1) Start a JVM with appropriate classpath
29+
let jvm = try JavaVirtualMachine.shared(classpath: swiftJavaClasspath)
3030

3131
// 2) Get the FilenameUtils Java class so we can call the static methods on it
3232
let FilenameUtilsClass = try JavaClass<FilenameUtils>()

Samples/JavaDependencySampleApp/ci-validate.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ set -e
44
set -x
55

66
JAVASWIFT="../../.build/debug/Java2Swift"
7+
cd ../../
8+
echo "Build Java2Swift binary..."
9+
swift build
710

11+
cd -
812
MODULE_NAME="JavaCommonsCSV"
913
MODULE_CONFIG_DIR=$(pwd)/Sources/$MODULE_NAME/
1014

0 commit comments

Comments
 (0)