Skip to content

Commit f4a5aa2

Browse files
authored
Merge branch 'main' into wip-dependency-traits
2 parents a4547a1 + 0685f55 commit f4a5aa2

File tree

105 files changed

+3889
-1018
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+3889
-1018
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
.sdkmanrc
33

44
.DS_Store
5+
.metals
56
.build
67
.idea
78
.vscode

Package.swift

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ let package = Package(
100100
// ==== SwiftJava (i.e. calling Java directly Swift utilities)
101101
.library(
102102
name: "SwiftJava",
103+
type: .dynamic,
103104
targets: ["SwiftJava"]
104105
),
105106

@@ -505,26 +506,18 @@ let package = Package(
505506
.target(
506507
name: "JExtractSwiftLib",
507508
dependencies: [
508-
.product(
509-
name: "SwiftBasicFormat", package: "swift-syntax",
510-
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"])
511-
),
512-
.product(
513-
name: "SwiftLexicalLookup", package: "swift-syntax",
514-
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"])
515-
),
516-
.product(
517-
name: "SwiftSyntax", package: "swift-syntax",
518-
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"])
519-
),
520-
.product(
521-
name: "SwiftSyntaxBuilder", package: "swift-syntax",
522-
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"])
523-
),
524-
.product(
525-
name: "ArgumentParser", package: "swift-argument-parser",
509+
.product(name: "SwiftBasicFormat", package: "swift-syntax",
510+
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"]),
511+
.product(name: "SwiftLexicalLookup", package: "swift-syntax",
512+
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"]),
513+
.product(name: "SwiftSyntax", package: "swift-syntax",
514+
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"]),
515+
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax",
516+
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"]),
517+
.product(name: "ArgumentParser", package: "swift-argument-parser",
518+
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"]),
519+
.product(name: "OrderedCollections", package: "swift-collections",
526520
condition: .when(traits: ["_SwiftJavaToolDependencyGuardTrait"]),
527-
),
528521
"JavaTypes",
529522
"SwiftJavaShared",
530523
"SwiftJavaConfigurationShared",

Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift

Lines changed: 196 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
2525

2626
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
2727
let toolURL = try context.tool(named: "SwiftJavaTool").url
28-
28+
29+
var commands: [Command] = []
30+
2931
guard let sourceModule = target.sourceModule else { return [] }
3032

33+
3134
// Note: Target doesn't have a directoryURL counterpart to directory,
3235
// so we cannot eliminate this deprecation warning.
3336
for dependency in target.dependencies {
@@ -80,7 +83,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
8083
let (moduleName, configFile) = moduleAndConfigFile
8184
return [
8285
"--depends-on",
83-
"\(configFile.path(percentEncoded: false))"
86+
"\(moduleName)=\(configFile.path(percentEncoded: false))"
8487
]
8588
}
8689
arguments += dependentConfigFilesArguments
@@ -93,7 +96,7 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
9396
$0.pathExtension == "swift"
9497
}
9598

96-
// Output Swift files are just Java filename based converted to Swift files one-to-one
99+
// Output files are flattened filenames of the inputs, with the appended +SwiftJava suffix.
97100
var outputSwiftFiles: [URL] = swiftFiles.compactMap { sourceFileURL in
98101
guard sourceFileURL.isFileURL else {
99102
return nil as URL?
@@ -104,7 +107,6 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
104107
fatalError("Could not get relative path for source file \(sourceFilePath)")
105108
}
106109
let outputURL = outputSwiftDirectory
107-
.appending(path: String(sourceFilePath.dropFirst(sourceDir.count).dropLast(sourceFileURL.lastPathComponent.count + 1)))
108110

109111
let inputFileName = sourceFileURL.deletingPathExtension().lastPathComponent
110112
return outputURL.appending(path: "\(inputFileName)+SwiftJava.swift")
@@ -123,15 +125,165 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
123125

124126
print("[swift-java-plugin] Output swift files:\n - \(outputSwiftFiles.map({$0.absoluteString}).joined(separator: "\n - "))")
125127

126-
return [
128+
var jextractOutputFiles = outputSwiftFiles
129+
130+
// If the developer has enabled java callbacks in the configuration (default is false)
131+
// and we are running in JNI mode, we will run additional phases in this build plugin
132+
// to generate Swift wrappers using wrap-java that can be used to callback to Java.
133+
let shouldRunJavaCallbacksPhases =
134+
if let configuration,
135+
configuration.enableJavaCallbacks == true,
136+
configuration.effectiveMode == .jni {
137+
true
138+
} else {
139+
false
140+
}
141+
142+
// Extract list of all sources
143+
let javaSourcesListFileName = "jextract-generated-sources.txt"
144+
let javaSourcesFile = outputJavaDirectory.appending(path: javaSourcesListFileName)
145+
if shouldRunJavaCallbacksPhases {
146+
arguments += [
147+
"--generated-java-sources-list-file-output", javaSourcesListFileName
148+
]
149+
jextractOutputFiles += [javaSourcesFile]
150+
}
151+
152+
commands += [
127153
.buildCommand(
128154
displayName: "Generate Java wrappers for Swift types",
129155
executable: toolURL,
130156
arguments: arguments,
131157
inputFiles: [ configFile ] + swiftFiles,
132-
outputFiles: outputSwiftFiles
158+
outputFiles: jextractOutputFiles
159+
)
160+
]
161+
162+
// If we do not need Java callbacks, we can skip the remaining steps.
163+
guard shouldRunJavaCallbacksPhases else {
164+
return commands
165+
}
166+
167+
// The URL of the compiled Java sources
168+
let javaCompiledClassesURL = context.pluginWorkDirectoryURL
169+
.appending(path: "compiled-java-output")
170+
171+
// Build SwiftKitCore and get the classpath
172+
// as the jextracted sources will depend on that
173+
174+
guard let swiftJavaDirectory = findSwiftJavaDirectory(for: target) else {
175+
fatalError("Unable to find the path to the swift-java sources, please file an issue.")
176+
}
177+
log("Found swift-java at \(swiftJavaDirectory)")
178+
179+
let swiftKitCoreClassPath = swiftJavaDirectory.appending(path: "SwiftKitCore/build/classes/java/main")
180+
181+
// We need to use a different gradle home, because
182+
// this plugin might be run from inside another gradle task
183+
// and that would cause conflicts.
184+
let gradleUserHome = context.pluginWorkDirectoryURL.appending(path: "gradle-user-home")
185+
186+
let GradleUserHome = "GRADLE_USER_HOME"
187+
let gradleUserHomePath = gradleUserHome.path(percentEncoded: false)
188+
log("Prepare command: :SwiftKitCore:build in \(GradleUserHome)=\(gradleUserHomePath)")
189+
var gradlewEnvironment = ProcessInfo.processInfo.environment
190+
gradlewEnvironment[GradleUserHome] = gradleUserHomePath
191+
log("Forward environment: \(gradlewEnvironment)")
192+
193+
let gradleExecutable = findExecutable(name: "gradle") ?? // try using installed 'gradle' if available in PATH
194+
swiftJavaDirectory.appending(path: "gradlew") // fallback to calling ./gradlew if gradle is not installed
195+
log("Detected 'gradle' executable (or gradlew fallback): \(gradleExecutable)")
196+
197+
commands += [
198+
.buildCommand(
199+
displayName: "Build SwiftKitCore using Gradle (Java)",
200+
executable: gradleExecutable,
201+
arguments: [
202+
":SwiftKitCore:build",
203+
"--project-dir", swiftJavaDirectory.path(percentEncoded: false),
204+
"--gradle-user-home", gradleUserHomePath,
205+
"--configure-on-demand",
206+
"--no-daemon"
207+
],
208+
environment: gradlewEnvironment,
209+
inputFiles: [swiftJavaDirectory],
210+
outputFiles: [swiftKitCoreClassPath]
211+
)
212+
]
213+
214+
// Compile the jextracted sources
215+
let javaHome = URL(filePath: findJavaHome())
216+
217+
commands += [
218+
.buildCommand(
219+
displayName: "Build extracted Java sources",
220+
executable: javaHome
221+
.appending(path: "bin")
222+
.appending(path: self.javacName),
223+
arguments: [
224+
"@\(javaSourcesFile.path(percentEncoded: false))",
225+
"-d", javaCompiledClassesURL.path(percentEncoded: false),
226+
"-parameters",
227+
"-classpath", swiftKitCoreClassPath.path(percentEncoded: false)
228+
],
229+
inputFiles: [javaSourcesFile, swiftKitCoreClassPath],
230+
outputFiles: [javaCompiledClassesURL]
231+
)
232+
]
233+
234+
// Run `configure` to extract a swift-java config to use for wrap-java
235+
let swiftJavaConfigURL = context.pluginWorkDirectoryURL.appending(path: "swift-java.config")
236+
237+
commands += [
238+
.buildCommand(
239+
displayName: "Output swift-java.config that contains all extracted Java sources",
240+
executable: toolURL,
241+
arguments: [
242+
"configure",
243+
"--output-directory", context.pluginWorkDirectoryURL.path(percentEncoded: false),
244+
"--cp", javaCompiledClassesURL.path(percentEncoded: false),
245+
"--swift-module", sourceModule.name,
246+
"--swift-type-prefix", "Java"
247+
],
248+
inputFiles: [javaCompiledClassesURL],
249+
outputFiles: [swiftJavaConfigURL]
133250
)
134251
]
252+
253+
let singleSwiftFileOutputName = "WrapJavaGenerated.swift"
254+
255+
// In the end we can run wrap-java on the previous inputs
256+
var wrapJavaArguments = [
257+
"wrap-java",
258+
"--swift-module", sourceModule.name,
259+
"--output-directory", outputSwiftDirectory.path(percentEncoded: false),
260+
"--config", swiftJavaConfigURL.path(percentEncoded: false),
261+
"--cp", swiftKitCoreClassPath.path(percentEncoded: false),
262+
"--single-swift-file-output", singleSwiftFileOutputName
263+
]
264+
265+
// Add any dependent config files as arguments
266+
wrapJavaArguments += dependentConfigFilesArguments
267+
268+
commands += [
269+
.buildCommand(
270+
displayName: "Wrap compiled Java sources using wrap-java",
271+
executable: toolURL,
272+
arguments: wrapJavaArguments,
273+
inputFiles: [swiftJavaConfigURL, swiftKitCoreClassPath],
274+
outputFiles: [outputSwiftDirectory.appending(path: singleSwiftFileOutputName)]
275+
)
276+
]
277+
278+
return commands
279+
}
280+
281+
var javacName: String {
282+
#if os(Windows)
283+
"javac.exe"
284+
#else
285+
"javac"
286+
#endif
135287
}
136288

137289
/// Find the manifest files from other swift-java executions in any targets
@@ -181,5 +333,43 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
181333

182334
return dependentConfigFiles
183335
}
336+
337+
private func findSwiftJavaDirectory(for target: any Target) -> URL? {
338+
for dependency in target.dependencies {
339+
switch dependency {
340+
case .target(let target):
341+
continue
342+
343+
case .product(let product):
344+
guard let swiftJava = product.sourceModules.first(where: { $0.name == "SwiftJava" }) else {
345+
continue
346+
}
347+
348+
// We are inside Sources/SwiftJava
349+
return swiftJava.directoryURL.deletingLastPathComponent().deletingLastPathComponent()
350+
351+
@unknown default:
352+
continue
353+
}
354+
}
355+
356+
return nil
357+
}
184358
}
185359

360+
func findExecutable(name: String) -> URL? {
361+
let fileManager = FileManager.default
362+
363+
guard let path = ProcessInfo.processInfo.environment["PATH"] else {
364+
return nil
365+
}
366+
367+
for path in path.split(separator: ":") {
368+
let fullURL = URL(fileURLWithPath: String(path)).appendingPathComponent(name)
369+
if fileManager.isExecutableFile(atPath: fullURL.path) {
370+
return fullURL
371+
}
372+
}
373+
374+
return nil
375+
}

Plugins/SwiftJavaPlugin/SwiftJavaPlugin.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,12 @@ struct SwiftJavaBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
171171
arguments += javaStdlibModules.flatMap { ["--depends-on", $0] }
172172

173173
if !outputSwiftFiles.isEmpty {
174-
arguments += [ configFile.path(percentEncoded: false) ]
175-
176174
let displayName = "Wrapping \(classes.count) Java classes in Swift target '\(sourceModule.name)'"
177175
log("Prepared: \(displayName)")
176+
177+
for f in outputSwiftFiles {
178+
log("Swift output file: \(f)")
179+
}
178180
commands += [
179181
.buildCommand(
180182
displayName: displayName,
@@ -266,4 +268,4 @@ func getExtractedJavaStdlibModules() -> [String] {
266268
}
267269
return url.lastPathComponent
268270
}.sorted()
269-
}
271+
}

Samples/JavaDependencySampleApp/ci-validate.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@
33
set -e
44
set -x
55

6+
# WORKAROUND: prebuilts broken on Swift 6.2.1 and Linux and tests using macros https://github.com/swiftlang/swift-java/issues/418
7+
if [ "$(uname)" = "Darwin" ]; then
8+
DISABLE_EXPERIMENTAL_PREBUILTS=''
9+
else
10+
DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts'
11+
fi
12+
613
# invoke resolve as part of a build run
714
swift build \
8-
--disable-experimental-prebuilts \
15+
$DISABLE_EXPERIMENTAL_PREBUILTS \
916
--disable-sandbox
1017

1118
# explicitly invoke resolve without explicit path or dependency
1219
# the dependencies should be uses from the --swift-module
1320

1421
# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418
1522
swift run \
16-
--disable-experimental-prebuilts \
23+
$DISABLE_EXPERIMENTAL_PREBUILTS \
1724
swift-java resolve \
1825
Sources/JavaCommonsCSV/swift-java.config \
1926
--swift-module JavaCommonsCSV \

Samples/JavaKitSampleApp/ci-validate.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
set -e
44
set -x
55

6-
swift build \
7-
--disable-experimental-prebuilts # FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418
6+
# WORKAROUND: prebuilts broken on Swift 6.2.1 and Linux and tests using macros https://github.com/swiftlang/swift-java/issues/418
7+
if [ "$(uname)" = "Darwin" ]; then
8+
DISABLE_EXPERIMENTAL_PREBUILTS=''
9+
else
10+
DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts'
11+
fi
12+
13+
swift build $DISABLE_EXPERIMENTAL_PREBUILTS
814

915
"$JAVA_HOME/bin/java" \
1016
-cp .build/plugins/outputs/javakitsampleapp/JavaKitExample/destination/JavaCompilerPlugin/Java \

Samples/JavaProbablyPrime/ci-validate.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
set -e
44
set -x
55

6-
# FIXME: until prebuilt swift-syntax isn't broken on 6.2 anymore: https://github.com/swiftlang/swift-java/issues/418
6+
# WORKAROUND: prebuilts broken on Swift 6.2.1 and Linux and tests using macros https://github.com/swiftlang/swift-java/issues/418
7+
if [ "$(uname)" = "Darwin" ]; then
8+
DISABLE_EXPERIMENTAL_PREBUILTS=''
9+
else
10+
DISABLE_EXPERIMENTAL_PREBUILTS='--disable-experimental-prebuilts'
11+
fi
12+
713
swift run \
8-
--disable-experimental-prebuilts \
14+
$DISABLE_EXPERIMENTAL_PREBUILTS \
915
JavaProbablyPrime 1337

Samples/SwiftAndJavaJarSampleLib/Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ let package = Package(
6363
.target(
6464
name: "MySwiftLibrary",
6565
dependencies: [
66-
.product(name: "SwiftRuntimeFunctions", package: "swift-java"),
66+
.product(name: "SwiftJava", package: "swift-java"),
67+
.product(name: "CSwiftJavaJNI", package: "swift-java"),
68+
.product(name: "SwiftRuntimeFunctions", package: "swift-java")
6769
],
6870
exclude: [
6971
"swift-java.config",

0 commit comments

Comments
 (0)