@@ -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
@@ -123,15 +126,165 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
123126
124127 print ( " [swift-java-plugin] Output swift files: \n - \( outputSwiftFiles. map ( { $0. absoluteString} ) . joined ( separator: " \n - " ) ) " )
125128
126- return [
129+ var jextractOutputFiles = outputSwiftFiles
130+
131+ // If the developer has enabled java callbacks in the configuration (default is false)
132+ // and we are running in JNI mode, we will run additional phases in this build plugin
133+ // to generate Swift wrappers using wrap-java that can be used to callback to Java.
134+ let shouldRunJavaCallbacksPhases =
135+ if let configuration,
136+ configuration. enableJavaCallbacks == true ,
137+ configuration. effectiveMode == . jni {
138+ true
139+ } else {
140+ false
141+ }
142+
143+ // Extract list of all sources
144+ let javaSourcesListFileName = " jextract-generated-sources.txt "
145+ let javaSourcesFile = outputJavaDirectory. appending ( path: javaSourcesListFileName)
146+ if shouldRunJavaCallbacksPhases {
147+ arguments += [
148+ " --generated-java-sources-list-file-output " , javaSourcesListFileName
149+ ]
150+ jextractOutputFiles += [ javaSourcesFile]
151+ }
152+
153+ commands += [
127154 . buildCommand(
128155 displayName: " Generate Java wrappers for Swift types " ,
129156 executable: toolURL,
130157 arguments: arguments,
131158 inputFiles: [ configFile ] + swiftFiles,
132- outputFiles: outputSwiftFiles
159+ outputFiles: jextractOutputFiles
160+ )
161+ ]
162+
163+ // If we do not need Java callbacks, we can skip the remaining steps.
164+ guard shouldRunJavaCallbacksPhases else {
165+ return commands
166+ }
167+
168+ // The URL of the compiled Java sources
169+ let javaCompiledClassesURL = context. pluginWorkDirectoryURL
170+ . appending ( path: " compiled-java-output " )
171+
172+ // Build SwiftKitCore and get the classpath
173+ // as the jextracted sources will depend on that
174+
175+ guard let swiftJavaDirectory = findSwiftJavaDirectory ( for: target) else {
176+ fatalError ( " Unable to find the path to the swift-java sources, please file an issue. " )
177+ }
178+ log ( " Found swift-java at \( swiftJavaDirectory) " )
179+
180+ let swiftKitCoreClassPath = swiftJavaDirectory. appending ( path: " SwiftKitCore/build/classes/java/main " )
181+
182+ // We need to use a different gradle home, because
183+ // this plugin might be run from inside another gradle task
184+ // and that would cause conflicts.
185+ let gradleUserHome = context. pluginWorkDirectoryURL. appending ( path: " gradle-user-home " )
186+
187+ let GradleUserHome = " GRADLE_USER_HOME "
188+ let gradleUserHomePath = gradleUserHome. path ( percentEncoded: false )
189+ log ( " Prepare command: :SwiftKitCore:build in \( GradleUserHome) = \( gradleUserHomePath) " )
190+ var gradlewEnvironment = ProcessInfo . processInfo. environment
191+ gradlewEnvironment [ GradleUserHome] = gradleUserHomePath
192+ log ( " Forward environment: \( gradlewEnvironment) " )
193+
194+ let gradleExecutable = findExecutable ( name: " gradle " ) ?? // try using installed 'gradle' if available in PATH
195+ swiftJavaDirectory. appending ( path: " gradlew " ) // fallback to calling ./gradlew if gradle is not installed
196+ log ( " Detected 'gradle' executable (or gradlew fallback): \( gradleExecutable) " )
197+
198+ commands += [
199+ . buildCommand(
200+ displayName: " Build SwiftKitCore using Gradle (Java) " ,
201+ executable: gradleExecutable,
202+ arguments: [
203+ " :SwiftKitCore:build " ,
204+ " --project-dir " , swiftJavaDirectory. path ( percentEncoded: false ) ,
205+ " --gradle-user-home " , gradleUserHomePath,
206+ " --configure-on-demand " ,
207+ " --no-daemon "
208+ ] ,
209+ environment: gradlewEnvironment,
210+ inputFiles: [ swiftJavaDirectory] ,
211+ outputFiles: [ swiftKitCoreClassPath]
212+ )
213+ ]
214+
215+ // Compile the jextracted sources
216+ let javaHome = URL ( filePath: findJavaHome ( ) )
217+
218+ commands += [
219+ . buildCommand(
220+ displayName: " Build extracted Java sources " ,
221+ executable: javaHome
222+ . appending ( path: " bin " )
223+ . appending ( path: self . javacName) ,
224+ arguments: [
225+ " @ \( javaSourcesFile. path ( percentEncoded: false ) ) " ,
226+ " -d " , javaCompiledClassesURL. path ( percentEncoded: false ) ,
227+ " -parameters " ,
228+ " -classpath " , swiftKitCoreClassPath. path ( percentEncoded: false )
229+ ] ,
230+ inputFiles: [ javaSourcesFile, swiftKitCoreClassPath] ,
231+ outputFiles: [ javaCompiledClassesURL]
232+ )
233+ ]
234+
235+ // Run `configure` to extract a swift-java config to use for wrap-java
236+ let swiftJavaConfigURL = context. pluginWorkDirectoryURL. appending ( path: " swift-java.config " )
237+
238+ commands += [
239+ . buildCommand(
240+ displayName: " Output swift-java.config that contains all extracted Java sources " ,
241+ executable: toolURL,
242+ arguments: [
243+ " configure " ,
244+ " --output-directory " , context. pluginWorkDirectoryURL. path ( percentEncoded: false ) ,
245+ " --cp " , javaCompiledClassesURL. path ( percentEncoded: false ) ,
246+ " --swift-module " , sourceModule. name,
247+ " --swift-type-prefix " , " Java "
248+ ] ,
249+ inputFiles: [ javaCompiledClassesURL] ,
250+ outputFiles: [ swiftJavaConfigURL]
133251 )
134252 ]
253+
254+ let singleSwiftFileOutputName = " WrapJavaGenerated.swift "
255+
256+ // In the end we can run wrap-java on the previous inputs
257+ var wrapJavaArguments = [
258+ " wrap-java " ,
259+ " --swift-module " , sourceModule. name,
260+ " --output-directory " , outputSwiftDirectory. path ( percentEncoded: false ) ,
261+ " --config " , swiftJavaConfigURL. path ( percentEncoded: false ) ,
262+ " --cp " , swiftKitCoreClassPath. path ( percentEncoded: false ) ,
263+ " --single-swift-file-output " , singleSwiftFileOutputName
264+ ]
265+
266+ // Add any dependent config files as arguments
267+ wrapJavaArguments += dependentConfigFilesArguments
268+
269+ commands += [
270+ . buildCommand(
271+ displayName: " Wrap compiled Java sources using wrap-java " ,
272+ executable: toolURL,
273+ arguments: wrapJavaArguments,
274+ inputFiles: [ swiftJavaConfigURL, swiftKitCoreClassPath] ,
275+ outputFiles: [ outputSwiftDirectory. appending ( path: singleSwiftFileOutputName) ]
276+ )
277+ ]
278+
279+ return commands
280+ }
281+
282+ var javacName : String {
283+ #if os(Windows)
284+ " javac.exe "
285+ #else
286+ " javac "
287+ #endif
135288 }
136289
137290 /// Find the manifest files from other swift-java executions in any targets
@@ -181,5 +334,43 @@ struct JExtractSwiftBuildToolPlugin: SwiftJavaPluginProtocol, BuildToolPlugin {
181334
182335 return dependentConfigFiles
183336 }
337+
338+ private func findSwiftJavaDirectory( for target: any Target ) -> URL ? {
339+ for dependency in target. dependencies {
340+ switch dependency {
341+ case . target( let target) :
342+ continue
343+
344+ case . product( let product) :
345+ guard let swiftJava = product. sourceModules. first ( where: { $0. name == " SwiftJava " } ) else {
346+ return nil
347+ }
348+
349+ // We are inside Sources/SwiftJava
350+ return swiftJava. directoryURL. deletingLastPathComponent ( ) . deletingLastPathComponent ( )
351+
352+ @unknown default :
353+ continue
354+ }
355+ }
356+
357+ return nil
358+ }
184359}
185360
361+ func findExecutable( name: String ) -> URL ? {
362+ let fileManager = FileManager . default
363+
364+ guard let path = ProcessInfo . processInfo. environment [ " PATH " ] else {
365+ return nil
366+ }
367+
368+ for path in path. split ( separator: " : " ) {
369+ let fullURL = URL ( fileURLWithPath: String ( path) ) . appendingPathComponent ( name)
370+ if fileManager. isExecutableFile ( atPath: fullURL. path) {
371+ return fullURL
372+ }
373+ }
374+
375+ return nil
376+ }
0 commit comments