diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 0114da27..9144db55 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -44,8 +44,8 @@ jobs: - name: Generate sources (make) (Temporary) # TODO: this should be triggered by the respective builds run: make jextract-run - - name: Gradle build - run: ./gradlew build --no-daemon + - name: Gradle test + run: ./gradlew test --no-daemon test-swift: name: Swift tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) @@ -71,3 +71,25 @@ jobs: run: "make jextract-run" - name: Test Swift run: "swift test" + + make: + name: Make build (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift_version: [ 'nightly-main' ] + os_version: [ 'jammy' ] + jdk_vendor: [ 'Corretto' ] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + JAVA_HOME: "/usr/lib/jvm/default-jdk" + steps: + - uses: actions/checkout@v4 + - name: Install Make + run: apt-get -qq update && apt-get -qq install -y make + - name: Install JDK + run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" + - name: Make (default target) + run: "make" diff --git a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts index 85142bd9..004e72be 100644 --- a/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts +++ b/BuildLogic/src/main/kotlin/build-logic.java-common-conventions.gradle.kts @@ -12,15 +12,18 @@ // //===----------------------------------------------------------------------===// +import org.gradle.api.tasks.testing.logging.* +import java.util.* + plugins { java } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(22) - } -} +//java { +// toolchain { +// languageVersion = JavaLanguageVersion.of(22) +// } +//} repositories { mavenCentral() @@ -40,22 +43,61 @@ tasks.withType(JavaCompile::class).forEach { it.options.compilerArgs.add("-Xlint:preview") } +fun javaLibraryPaths(): List { + val osName = System.getProperty("os.name") + val osArch = System.getProperty("os.arch") + val isLinux = osName.lowercase(Locale.getDefault()).contains("linux") + + return listOf( + if (osName.lowercase(Locale.getDefault()).contains("linux")) { + """$rootDir/.build/$osArch-unknown-linux-gnu/debug/""" + } else { + if (osArch.equals("aarch64")) { + """$rootDir/.build/arm64-apple-macosx/debug/""" + } else { + """$rootDir/.build/$osArch-apple-macosx/debug/""" + } + }, + if (isLinux) { + "/usr/lib/swift/linux" + } else { + // assume macOS + "/usr/lib/swift/" + } + ) +} + + // Configure paths for native (Swift) libraries tasks.test { jvmArgs( "--enable-native-access=ALL-UNNAMED", // Include the library paths where our dylibs are that we want to load and call - "-Djava.library.path=" + listOf( - """$rootDir/.build/arm64-apple-macosx/debug/""", - "/usr/lib/swift/" - ).joinToString(File.pathSeparator) + "-Djava.library.path=" + javaLibraryPaths().joinToString(File.pathSeparator) ) } tasks.withType { this.testLogging { - this.showStandardStreams = true + showStandardStreams = true + exceptionFormat = TestExceptionFormat.FULL + + // set options for log level LIFECYCLE + events = setOf(TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_OUT) + showExceptions = true + showCauses = true + showStackTraces = true + + beforeTest(closureOf { + logger.lifecycle("Test: ${this.className} > ${this.displayName}: ...") + }) + afterTest(KotlinClosure2({ descriptor: TestDescriptor, result: TestResult -> + logger.lifecycle("Test: ${descriptor.className} > ${descriptor.displayName}: ${result.resultType}") + })) } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05b02de7..1b7dd427 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,6 +53,23 @@ A good patch is: 3. Documented, adding API documentation as needed to cover new functions and properties. 4. Accompanied by a great commit message, using our commit message template. +### Developing in Docker + +You can develop in Docker in order to test Linux behaviors while developing on a Mac. + + +First build the image: + +``` +docker build --tag swift-java docker +``` + +And run a development container: + +``` +docker run --rm -it -v$(pwd):/code swift-java /bin/bash +``` + ### Run CI checks locally You can run the Github Actions workflows locally using diff --git a/JavaSwiftKitDemo/build.gradle.kts b/JavaSwiftKitDemo/build.gradle.kts index 84c0b924..9976a947 100644 --- a/JavaSwiftKitDemo/build.gradle.kts +++ b/JavaSwiftKitDemo/build.gradle.kts @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import java.util.* + plugins { id("build-logic.java-application-conventions") } @@ -38,6 +40,30 @@ tasks.test { useJUnitPlatform() } +fun javaLibraryPaths(): List { + val osName = System.getProperty("os.name") + val osArch = System.getProperty("os.arch") + val isLinux = osName.lowercase(Locale.getDefault()).contains("linux") + + return listOf( + if (osName.lowercase(Locale.getDefault()).contains("linux")) { + """$rootDir/.build/$osArch-unknown-linux-gnu/debug/""" + } else { + if (osArch.equals("aarch64")) { + """$rootDir/.build/arm64-apple-macosx/debug/""" + } else { + """$rootDir/.build/$osArch-apple-macosx/debug/""" + } + }, + if (isLinux) { + "/usr/lib/swift/linux" + } else { + // assume macOS + "/usr/lib/swift/" + } + ) +} + application { mainClass = "org.example.HelloJava2Swift" @@ -51,10 +77,7 @@ application { "--enable-native-access=ALL-UNNAMED", // Include the library paths where our dylibs are that we want to load and call - "-Djava.library.path=" + listOf( - """$rootDir/.build/arm64-apple-macosx/debug/""", - "/usr/lib/swift/" - ).joinToString(":"), + "-Djava.library.path=" + javaLibraryPaths().joinToString(File.pathSeparator), // Enable tracing downcalls (to Swift) "-Djextract.trace.downcalls=true" diff --git a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java index 00264946..20d2bbe3 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java @@ -18,18 +18,10 @@ import com.example.swift.generated.JavaKitExample; import com.example.swift.generated.MySwiftClass; -import org.example.swift.ManualJavaKitExample; -import org.example.swift.ManualMySwiftClass; -import org.swift.javakit.*; +import org.swift.swiftkit.SwiftKit; // Import swift-extract generated sources -import static com.example.swift.generated.JavaKitExample.*; -import static com.example.swift.generated.MySwiftClass.*; -import java.lang.foreign.*; -import java.nio.file.FileSystems; -import java.nio.file.Paths; -import java.util.Arrays; import java.util.List; diff --git a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java index 53b21e8a..759dc527 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java @@ -17,7 +17,7 @@ // ==== Extra convenience APIs ------------------------------------------------------------------------------------- // TODO: Think about offering these or not, perhaps only as an option? -import org.swift.javakit.ManagedSwiftType; +import org.swift.swiftkit.ManagedSwiftType; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/ManagedSwiftType.java b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/ManagedSwiftType.java similarity index 96% rename from JavaSwiftKitDemo/src/main/java/org/swift/javakit/ManagedSwiftType.java rename to JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/ManagedSwiftType.java index 117a0a09..2abaf85a 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/ManagedSwiftType.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/ManagedSwiftType.java @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.javakit; +package org.swift.swiftkit; import java.lang.foreign.MemorySegment; diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java index 2c60dc3f..54c801b4 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java @@ -14,8 +14,6 @@ package org.swift.swiftkit; -import org.swift.javakit.SwiftKit; - import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.util.concurrent.ConcurrentSkipListSet; diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java index 5d1945dd..4d32222f 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java @@ -16,6 +16,6 @@ import java.lang.foreign.MemorySegment; -public interface SwiftHeapObject { +public interface SwiftHeapObject extends ManagedSwiftType { MemorySegment $self(); } diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftKit.java similarity index 89% rename from JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java rename to JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftKit.java index 07765a08..e0bc7693 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftKit.java @@ -12,9 +12,7 @@ // //===----------------------------------------------------------------------===// -package org.swift.javakit; - -import org.swift.swiftkit.SwiftHeapObject; +package org.swift.swiftkit; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; @@ -25,8 +23,7 @@ public class SwiftKit { - // FIXME: why do we need to hardcore the path, seems it can't find by name - private static final String STDLIB_DYLIB_NAME = "swiftCore"; + private static final String STDLIB_DYLIB_NAME = "swiftCore"; private static final String STDLIB_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; private static final Arena LIBRARY_ARENA = Arena.ofAuto(); @@ -36,19 +33,37 @@ public class SwiftKit { System.loadLibrary(STDLIB_DYLIB_NAME); } - public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, JAVA_BYTE)); - static final SymbolLookup SYMBOL_LOOKUP = - // FIXME: why does this not find just by name? + getSymbolLookup(); + + private static SymbolLookup getSymbolLookup() { + if (isMacOS()) { + // FIXME: why does this not find just by name on macOS? // SymbolLookup.libraryLookup(System.mapLibraryName(STDLIB_DYLIB_NAME), LIBRARY_ARENA) - SymbolLookup.libraryLookup(STDLIB_DYLIB_PATH, LIBRARY_ARENA) + return SymbolLookup.libraryLookup(STDLIB_DYLIB_PATH, LIBRARY_ARENA) .or(SymbolLookup.loaderLookup()) .or(Linker.nativeLinker().defaultLookup()); + } else { + return SymbolLookup.loaderLookup() + .or(Linker.nativeLinker().defaultLookup()); + } + } public SwiftKit() { } + public static boolean isLinux() { + return System.getProperty("os.name").toLowerCase().contains("linux"); + } + + public static boolean isMacOS() { + return System.getProperty("os.name").toLowerCase().contains("mac"); + } + + public static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("windows"); + } + static void traceDowncall(String name, Object... args) { String traceArgs = Arrays.stream(args) .map(Object::toString) @@ -153,6 +168,7 @@ public static long release(SwiftHeapObject object) { } // ==== ------------------------------------------------------------------------------------------------------------ + // swift_getTypeByName /** * {@snippet lang=swift : diff --git a/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java b/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java index 7faff6b0..d244c2e1 100644 --- a/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java +++ b/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java @@ -18,12 +18,18 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static com.example.swift.generated.JavaKitExample.*; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.opentest4j.TestSkippedException; +import org.swift.swiftkit.SwiftKit; + import static org.junit.jupiter.api.Assertions.*; public class GlobalFunctionsTest { @BeforeAll static void beforeAll() { + System.out.printf("java.library.path = %s\n", System.getProperty("java.library.path")); + System.loadLibrary("swiftCore"); System.loadLibrary("JavaKitExample"); @@ -31,30 +37,19 @@ static void beforeAll() { } @Test + @DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces void call_helloWorld() { - helloWorld(); + JavaKitExample.helloWorld(); - assertNotNull(helloWorld$address()); + assertNotNull(JavaKitExample.helloWorld$address()); } @Test + @DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces void call_globalTakeInt() { JavaKitExample.globalTakeInt(12); - assertNotNull(globalTakeInt$address()); + assertNotNull(JavaKitExample.globalTakeInt$address()); } -// @Test -// void call_globalCallJavaCallback() { -// var num = 0; -// -// JavaKitExample.globalCallJavaCallback(new Runnable() { -// @Override -// public void run() { -// num += 1; -// } -// }); -// -// assertEquals(1, num); -// } } diff --git a/JavaSwiftKitDemo/src/test/java/org/example/swift/JavaKitTest.java b/JavaSwiftKitDemo/src/test/java/org/example/swift/JavaKitTest.java deleted file mode 100644 index 404dca42..00000000 --- a/JavaSwiftKitDemo/src/test/java/org/example/swift/JavaKitTest.java +++ /dev/null @@ -1,44 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -package org.example.swift; - -import com.example.swift.generated.MySwiftClass; -import org.junit.jupiter.api.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.example.swift.ManualJavaKitExample.*; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; - -public class JavaKitTest { - @BeforeAll - static void beforeAll() { - System.out.printf("java.library.path = %s\n", System.getProperty("java.library.path")); - - System.loadLibrary("swiftCore"); - System.loadLibrary("JavaKitExample"); - - System.setProperty("jextract.trace.downcalls", "true"); - } - - @Test - void call_helloWorld() { - helloWorld(); - - assertNotNull(helloWorld$address()); - } - -} diff --git a/JavaSwiftKitDemo/src/test/java/org/example/swift/SwiftKitTest.java b/JavaSwiftKitDemo/src/test/java/org/swift/swiftkit/SwiftKitTest.java similarity index 68% rename from JavaSwiftKitDemo/src/test/java/org/example/swift/SwiftKitTest.java rename to JavaSwiftKitDemo/src/test/java/org/swift/swiftkit/SwiftKitTest.java index 390b1ceb..29075d00 100644 --- a/JavaSwiftKitDemo/src/test/java/org/example/swift/SwiftKitTest.java +++ b/JavaSwiftKitDemo/src/test/java/org/swift/swiftkit/SwiftKitTest.java @@ -12,20 +12,15 @@ // //===----------------------------------------------------------------------===// -package org.example.swift; +package org.swift.swiftkit; import com.example.swift.generated.MySwiftClass; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.javakit.SwiftKit; -import org.swift.swiftkit.SwiftArena; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; - -import static org.example.swift.ManualJavaKitExample.helloWorld; -import static org.example.swift.ManualJavaKitExample.helloWorld$address; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -41,8 +36,9 @@ static void beforeAll() { } @Test + @DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces void call_retain_retainCount_release() { - var obj = MySwiftClass.init(1, 2); + var obj = new MySwiftClass(1, 2); assertEquals(1, SwiftKit.retainCount(obj.$memorySegment())); // TODO: test directly on SwiftHeapObject inheriting obj @@ -53,18 +49,4 @@ void call_retain_retainCount_release() { SwiftKit.release(obj.$memorySegment()); assertEquals(1, SwiftKit.retainCount(obj.$memorySegment())); } - - @Test - void use_MySwiftClass_repr_init_arena() { - int lenValue = 1111; - int capValue = 2222; - - try (Arena arena = Arena.ofConfined()) { - ManualMySwiftClass obj = ManualMySwiftClass.init(arena, lenValue, capValue); - - -// assertEquals(lenValue, obj.len()); -// assertEquals(capValue, obj.cap()); - } - } } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 7f238c75..2cfc71de 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -239,12 +239,23 @@ extension Swift2JavaTranslator { """ ) + // SymbolLookup.libraryLookup is platform dependent and does not take into account java.library.path + // https://bugs.openjdk.org/browse/JDK-8311090 printer.print( """ static final SymbolLookup SYMBOL_LOOKUP = - SymbolLookup.libraryLookup(System.mapLibraryName(DYLIB_NAME), LIBRARY_ARENA) + getSymbolLookup(); + + private static SymbolLookup getSymbolLookup() { + if (SwiftKit.isMacOS()) { + return SymbolLookup.libraryLookup(System.mapLibraryName(DYLIB_NAME), LIBRARY_ARENA) .or(SymbolLookup.loaderLookup()) .or(Linker.nativeLinker().defaultLookup()); + } else { + return SymbolLookup.loaderLookup() + .or(Linker.nativeLinker().defaultLookup()); + } + } """ ) diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index e942ff9b..797f7e0b 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -119,11 +119,15 @@ extension Swift2JavaTranslator { ) // FIXME: the use of dylibs to get symbols is a hack we need to remove and replace with interfaces containing mangled names - let dylibPath = ".build/arm64-apple-macosx/debug/lib\(swiftModuleName).dylib" - guard var dylib = SwiftDylib(path: dylibPath) else { + #if os(Linux) + let libPath = ".build/aarch64-unknown-linux-gnu/debug/lib\(swiftModuleName).so" + #else + let libPath = ".build/arm64-apple-macosx/debug/lib\(swiftModuleName).dylib" + #endif + guard var dylib = SwiftDylib(path: libPath) else { log.warning( """ - Unable to find mangled names for imported symbols. Dylib not found: \(dylibPath) This method of obtaining symbols is a workaround; it will be removed. + Unable to find mangled names for imported symbols. Dylib not found: \(libPath) This method of obtaining symbols is a workaround; it will be removed. """ ) return @@ -171,7 +175,7 @@ extension Swift2JavaTranslator { /// Default set Java imports for every generated file static let defaultJavaImports: Array = [ // Support library in Java - "org.swift.javakit.SwiftKit", + "org.swift.swiftkit.SwiftKit", // Necessary for native calls and type mapping "java.lang.foreign.*", diff --git a/Sources/JavaKit/JavaObject+MethodCalls.swift b/Sources/JavaKit/JavaObject+MethodCalls.swift index ad091943..c4eada98 100644 --- a/Sources/JavaKit/JavaObject+MethodCalls.swift +++ b/Sources/JavaKit/JavaObject+MethodCalls.swift @@ -186,6 +186,7 @@ extension AnyJavaObject { arguments: repeat each Param, resultType: Result.Type ) throws -> Result { + print("CALL: \(methodName)") let methodID = try javaMethodLookup( methodName: methodName, parameterTypes: repeat (each Param).self, diff --git a/SwiftKit/build.gradle.kts b/build.gradle.kts similarity index 100% rename from SwiftKit/build.gradle.kts rename to build.gradle.kts