@@ -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+ }
0 commit comments