diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5e5b767a..70cff51c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -140,7 +140,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] container: @@ -182,7 +182,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['6.2', 'nightly'] + swift_version: ['6.1.3', '6.2', 'nightly'] os_version: ['jammy'] jdk_vendor: ['corretto'] sample_app: [ # TODO: use a reusable-workflow to generate those names diff --git a/Package.swift b/Package.swift index 09743135..7ac7d1f9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.2 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index 33a35c49..6343e0db 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.2 +// swift-tools-version: 6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport diff --git a/Sources/JExtractSwiftLib/CodePrinter.swift b/Sources/JExtractSwiftLib/CodePrinter.swift index c5f04d51..e6498683 100644 --- a/Sources/JExtractSwiftLib/CodePrinter.swift +++ b/Sources/JExtractSwiftLib/CodePrinter.swift @@ -70,6 +70,20 @@ public struct CodePrinter { } } + public mutating func printHashIfBlock( + _ header: Any, + function: String = #function, + file: String = #fileID, + line: UInt = #line, + body: (inout CodePrinter) throws -> () + ) rethrows { + print("#if \(header)") + indent() + try body(&self) + outdent() + print("#endif // end of \(header)", .sloc, function: function, file: file, line: line) + } + public mutating func printBraceBlock( _ header: Any, function: String = #function, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 727294b1..d4ce9426 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -825,7 +825,7 @@ extension JNISwift2JavaGenerator { func printDo(printer: inout CodePrinter) { printer.print("let swiftResult$ = await \(placeholder)") - printer.print("environment = try JavaVirtualMachine.shared().environment()") + printer.print("environment = try! JavaVirtualMachine.shared().environment()") let inner = inner.render(&printer, "swiftResult$") if swiftFunctionResultType.isVoid { printer.print("environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)])") @@ -839,7 +839,7 @@ extension JNISwift2JavaGenerator { } } - func printTask(printer: inout CodePrinter) { + func printTaskBody(printer: inout CodePrinter) { printer.printBraceBlock("defer") { printer in // Defer might on any thread, so we need to attach environment. printer.print("let deferEnvironment = try! JavaVirtualMachine.shared().environment()") @@ -867,18 +867,22 @@ extension JNISwift2JavaGenerator { } } - printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in - printer.printBraceBlock("Task.immediate") { printer in - // Immediate runs on the caller thread, so we don't need to attach the environment again. - printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. - printTask(printer: &printer) + printer.print("var task: Task? = nil") + printer.printHashIfBlock("swift(>=6.2)") { printer in + printer.printBraceBlock("if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *)") { printer in + printer.printBraceBlock("task = Task.immediate") { printer in + // Immediate runs on the caller thread, so we don't need to attach the environment again. + printer.print("var environment = environment!") // this is to ensure we always use the same environment name, even though we are rebinding it. + printTaskBody(printer: &printer) + } } } - printer.printBraceBlock("else") { printer in - printer.printBraceBlock("Task") { printer in + + printer.printBraceBlock("if task == nil") { printer in + printer.printBraceBlock("task = Task") { printer in // We can be on any thread, so we need to attach the thread. printer.print("var environment = try! JavaVirtualMachine.shared().environment()") - printTask(printer: &printer) + printTaskBody(printer: &printer) } } diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 001d34fa..67671548 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -179,7 +179,9 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(n): \(e)".yellow(if: diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n))) + let isMismatch = diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n) + let marker = isMismatch ? " // <<<<<<<<<<< mismatch" : "" + print("\(n): \(e)\(marker)".yellow(if: isMismatch)) } } @@ -188,15 +190,7 @@ func assertOutput( let printFromLineNo = matchingOutputOffset for (n, g) in gotLines.enumerated() where n >= printFromLineNo { let baseLine = "\(n): \(g)" - var line = baseLine - if diffLineNumbers.contains(n) { - line += "\n" - let leadingCount = "\(n): ".count - let message = "\(String(repeating: " ", count: leadingCount))\(String(repeating: "^", count: 8)) EXPECTED MATCH OR SEARCHING FROM HERE " - line += "\(message)\(String(repeating: "^", count: max(0, line.count - message.count)))" - line = line.red - } - print(line) + print(baseLine) } print("==== ---------------------------------------------------------------\n") } @@ -248,14 +242,18 @@ func assertOutput( print("==== ---------------------------------------------------------------") print("Expected output:") for (n, e) in expectedLines.enumerated() { - print("\(e)".yellow(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? " // <<<<<<<< error: mismatch" : "" + print("\(e)\(marker)".yellow(if: isMismatch)) } } print("==== ---------------------------------------------------------------") print("Got output:") for (n, g) in gotLines.enumerated() { - print("\(g)".red(if: diffLineNumbers.contains(n))) + let isMismatch = diffLineNumbers.contains(n) + let marker = isMismatch ? "// <<<<<<<< error: mismatch" : "" + print("\(g)\(marker)".red(if: isMismatch)) } print("==== ---------------------------------------------------------------\n") } diff --git a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift index 6b26c69b..82922c7d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift @@ -59,27 +59,30 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.asyncVoid() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } - let swiftResult$ = await SwiftModule.asyncVoid() - environment = try JavaVirtualMachine.shared().environment() - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.asyncVoid() - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } } @@ -130,27 +133,30 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) - } - do { - let swiftResult$ = await try SwiftModule.async() - environment = try JavaVirtualMachine.shared().environment() - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) - } - catch { - let catchEnvironment = try! JavaVirtualMachine.shared().environment() - let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) - catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + do { + let swiftResult$ = await try SwiftModule.async() + environment = try! JavaVirtualMachine.shared().environment() + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) + } + catch { + let catchEnvironment = try! JavaVirtualMachine.shared().environment() + let exception = catchEnvironment.interface.NewObjectA(catchEnvironment, _JNIMethodIDCache.Exception.class, _JNIMethodIDCache.Exception.constructWithMessage, [String(describing: error).getJValue(in: catchEnvironment)]) + catchEnvironment.interface.CallBooleanMethodA(catchEnvironment, globalFuture, _JNIMethodIDCache.CompletableFuture.completeExceptionally, [jvalue(l: exception)]) + } } } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() @@ -158,7 +164,7 @@ struct JNIAsyncTests { } do { let swiftResult$ = await try SwiftModule.async() - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: nil)]) } catch { @@ -215,28 +221,31 @@ struct JNIAsyncTests { @_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2") func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer!, thisClass: jclass, i: jlong, result_future: jobject?) { let globalFuture = environment.interface.NewGlobalRef(environment, result_future) + var task: Task? = nil + #if swift(>=6.2) if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { + task = Task.immediate { var environment = environment! defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } } - else { - Task { + #endif // end of swift(>=6.2) + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(i: Int64(fromJNI: i, in: environment)) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(swiftResult$.getJNIValue(in: environment), in: environment) environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } @@ -303,31 +312,34 @@ struct JNIAsyncTests { fatalError("c memory address was null in call to \\(#function)!") } let globalFuture = environment.interface.NewGlobalRef(environment, result_future) - if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { - Task.immediate { - var environment = environment! - defer { - let deferEnvironment = try! JavaVirtualMachine.shared().environment() - environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + var task: Task? = nil + #if swift(>=6.2) + if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) { + task = Task.immediate { + var environment = environment! + defer { + let deferEnvironment = try! JavaVirtualMachine.shared().environment() + environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) + } + let swiftResult$ = await SwiftModule.async(c: c$.pointee) + environment = try! JavaVirtualMachine.shared().environment() + let result$ = UnsafeMutablePointer.allocate(capacity: 1) + result$.initialize(to: swiftResult$) + let resultBits$ = Int64(Int(bitPattern: result$)) + let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) + environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } - let swiftResult$ = await SwiftModule.async(c: c$.pointee) - environment = try JavaVirtualMachine.shared().environment() - let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: swiftResult$) - let resultBits$ = Int64(Int(bitPattern: result$)) - let boxedResult$ = SwiftJavaRuntimeSupport._JNIBoxedConversions.box(resultBits$.getJNIValue(in: environment), in: environment) - environment.interface.CallBooleanMethodA(environment, globalFuture, _JNIMethodIDCache.CompletableFuture.complete, [jvalue(l: boxedResult$)]) } - } - else { - Task { + #endif + if task == nil { + task = Task { var environment = try! JavaVirtualMachine.shared().environment() defer { let deferEnvironment = try! JavaVirtualMachine.shared().environment() environment.interface.DeleteGlobalRef(deferEnvironment, globalFuture) } let swiftResult$ = await SwiftModule.async(c: c$.pointee) - environment = try JavaVirtualMachine.shared().environment() + environment = try! JavaVirtualMachine.shared().environment() let result$ = UnsafeMutablePointer.allocate(capacity: 1) result$.initialize(to: swiftResult$) let resultBits$ = Int64(Int(bitPattern: result$)) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1109dab3..ef428b87 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,10 +21,10 @@ ENV LANGUAGE=en_US.UTF-8 # JDK dependency RUN curl -s "https://get.sdkman.io" | bash RUN bash -c "source /root/.sdkman/bin/sdkman-init.sh && sdk install java 25.0.1-amzn" +ENV JAVA_HOME="$(sdk home java current)" RUN curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \ tar zxf swiftly-$(uname -m).tar.gz && \ ./swiftly init --quiet-shell-followup --assume-yes && \ . "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \ hash -r -