diff --git a/.github/actions/prepare_env/action.yml b/.github/actions/prepare_env/action.yml new file mode 100644 index 00000000..ebcc8aed --- /dev/null +++ b/.github/actions/prepare_env/action.yml @@ -0,0 +1,44 @@ +name: 'Swift Java CI Env' +description: 'Prepare the CI environment by installing Swift and selected JDK etc.' + +runs: + using: composite + steps: + - name: Install System Dependencies + run: apt-get -qq update && apt-get -qq install -y make curl wget libjemalloc2 libjemalloc-dev + shell: bash + - name: Cache JDK + id: cache-jdk + uses: actions/cache@v4 + continue-on-error: true + with: + path: /usr/lib/jvm/default-jdk/ + key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/default-jdk/*') }} + restore-keys: | + ${{ runner.os }}-jdk- + - name: Install JDK + if: steps.cache-jdk.outputs.cache-hit != 'true' + run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" + shell: bash + # TODO: not using setup-java since incompatible with the swiftlang/swift base image + # - name: Install Untested Nightly Swift + # run: "bash -xc './docker/install_untested_nightly_swift.sh'" + - name: Cache local Gradle repository + uses: actions/cache@v4 + continue-on-error: true + with: + path: | + /root/.gradle/caches + /root/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('*/*.gradle*', 'settings.gradle') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Cache local SwiftPM repository + uses: actions/cache@v4 + continue-on-error: true + with: + path: /__w/swift-java/swift-java/.build/checkouts + key: ${{ runner.os }}-swiftpm-cache-${{ hashFiles('Package.swift') }} + restore-keys: | + ${{ runner.os }}-swiftpm-cache + ${{ runner.os }}-swiftpm- diff --git a/.github/scripts/validate_samples.sh b/.github/scripts/validate_samples.sh new file mode 100755 index 00000000..26273575 --- /dev/null +++ b/.github/scripts/validate_samples.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# shellcheck disable=SC2034 +declare -r GREEN='\033[0;32m' +declare -r BOLD='\033[1m' +declare -r RESET='\033[0m' + +# shellcheck disable=SC2155 +declare -r SAMPLE_PACKAGES=$(find Samples -name Package.swift -maxdepth 2) +declare -r CI_VALIDATE_SCRIPT='ci-validate.sh' + +for samplePackage in ${SAMPLE_PACKAGES} ; do + sampleDir=$(dirname "$samplePackage") + + echo "" + echo "" + echo "========================================================================" + printf "Validate sample '${BOLD}%s${RESET}' using: " "$sampleDir" + cd "$sampleDir" || exit + if [[ $(find . -name ${CI_VALIDATE_SCRIPT} -maxdepth 1) ]]; then + echo -e "Custom ${BOLD}${CI_VALIDATE_SCRIPT}${RESET} script..." + ./${CI_VALIDATE_SCRIPT} || exit + elif [[ $(find . -name 'build.gradle*' -maxdepth 1) ]]; then + echo -e "${BOLD}Gradle${RESET} build..." + ./gradlew build || ./gradlew build --info # re-run to get better failure output + else + echo -e "${BOLD}SwiftPM${RESET} build..." + swift build || exit + fi + + echo -e "Validated sample '${BOLD}${sampleDir}${RESET}': ${BOLD}passed${RESET}." + cd - || exit +done + +echo +printf "Done validating samples: " +echo -e "${GREEN}done${RESET}." diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index eea13444..8039b572 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -8,6 +8,7 @@ jobs: soundness: uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: + # Not API stable package (yet) api_breakage_check_enabled: false # FIXME: Something is off with the format task and it gets "stuck", need to investigate format_check_enabled: false @@ -19,7 +20,8 @@ jobs: strategy: fail-fast: true matrix: - swift_version: ['nightly-main'] + # swift_version: ['nightly-main'] + swift_version: ['6.0.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -28,50 +30,14 @@ jobs: JAVA_HOME: "/usr/lib/jvm/default-jdk" steps: - uses: actions/checkout@v4 - - name: Install System Dependencies - run: apt-get -qq update && apt-get -qq install -y make curl wget libjemalloc2 libjemalloc-dev - - name: Cache JDK - id: cache-jdk - uses: actions/cache@v4 - continue-on-error: true - with: - path: /usr/lib/jvm/default-jdk/ - key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/default-jdk/*') }} - restore-keys: | - ${{ runner.os }}-jdk- - - name: Install JDK - if: steps.cache-jdk.outputs.cache-hit != 'true' - run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" - # TODO: not using setup-java since incompatible with the swiftlang/swift base image - - name: Install Untested Nightly Swift - run: "bash -xc './docker/install_untested_nightly_swift.sh'" - - name: Cache local Gradle repository - uses: actions/cache@v4 - continue-on-error: true - with: - path: | - /root/.gradle/caches - /root/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('*/*.gradle*', 'settings.gradle') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Cache local SwiftPM repository - uses: actions/cache@v4 - continue-on-error: true - with: - path: /__w/swift-java/swift-java/.build/checkouts - key: ${{ runner.os }}-swiftpm-cache-${{ hashFiles('Package.swift') }} - restore-keys: | - ${{ runner.os }}-swiftpm-cache - ${{ runner.os }}-swiftpm- - # run the actual build - - name: Gradle build - run: | - ./gradlew build -x test --no-daemon # just build - ./gradlew build --info --no-daemon - - name: Gradle build (benchmarks) - run: | - ./gradlew compileJmh --info --no-daemon + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Gradle :SwiftKit:build + run: ./gradlew build -x test + - name: Gradle :SwiftKit:check + run: ./gradlew :SwiftKit:check --info + - name: Gradle compile JMH benchmarks + run: ./gradlew compileJmh --info test-swift: name: Swift tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) @@ -79,7 +45,8 @@ jobs: strategy: fail-fast: false matrix: - swift_version: ['nightly-main'] + # swift_version: ['nightly-main'] + swift_version: ['6.0.2'] os_version: ['jammy'] jdk_vendor: ['Corretto'] container: @@ -88,51 +55,33 @@ jobs: JAVA_HOME: "/usr/lib/jvm/default-jdk" steps: - uses: actions/checkout@v4 - - name: Install System Dependencies - run: apt-get -qq update && apt-get -qq install -y make curl wget libjemalloc2 libjemalloc-dev - - name: Cache JDK - id: cache-jdk - uses: actions/cache@v4 - continue-on-error: true - with: - path: /usr/lib/jvm/default-jdk/ - key: ${{ runner.os }}-jdk-${{ matrix.jdk_vendor }}-${{ hashFiles('/usr/lib/jvm/default-jdk/*') }} - restore-keys: | - ${{ runner.os }}-jdk- - - name: Install JDK - if: steps.cache-jdk.outputs.cache-hit != 'true' - run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" - # TODO: not using setup-java since incompatible with the swiftlang/swift base image - - name: Install Untested Nightly Swift - run: "bash -xc './docker/install_untested_nightly_swift.sh'" - - name: Cache local Gradle repository - uses: actions/cache@v4 - continue-on-error: true - with: - path: | - /root/.gradle/caches - /root/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('*/*.gradle*', 'settings.gradle') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Cache local SwiftPM repository - uses: actions/cache@v4 - continue-on-error: true - with: - path: /__w/swift-java/swift-java/.build/checkouts - key: ${{ runner.os }}-swiftpm-cache-${{ hashFiles('Package.swift') }} - restore-keys: | - ${{ runner.os }}-swiftpm-cache - ${{ runner.os }}-swiftpm- - # run the actual build - - name: Generate sources (make) (Temporary) - # TODO: this should be triggered by the respective builds - run: "make jextract-generate" - - name: Test Swift + - name: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Swift Build + run: "swift build --build-tests" + - name: Swift Test run: "swift test" - - name: Build (Swift) Sample Apps - run: | - find Samples/ -name Package.swift -maxdepth 2 -exec swift build --package-path $(dirname {}) \;; - # TODO: Benchmark compile crashes in CI, enable when nightly toolchains in better shape. - # - name: Build (Swift) Benchmarks - # run: "swift package --package-path Benchmarks/ benchmark list" + + verify-samples: + name: Verify Samples (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'] + swift_version: ['6.0.2'] + 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: Prepare CI Environment + uses: ./.github/actions/prepare_env + - name: Verify Samples (All) + run: .github/scripts/validate_samples.sh + # TODO: Benchmark compile crashes in CI, enable when nightly toolchains in better shape. + # - name: Build (Swift) Benchmarks + # run: "swift package --package-path Benchmarks/ benchmark list" diff --git a/.gitignore b/.gitignore index 5055eec2..11bbeceb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .build +.idea Packages xcuserdata/ DerivedData/ @@ -33,3 +34,8 @@ Package.resolved # Ignore files generated by jextract, we always can re-generate them */**/src/generated/java/**/* + +*/**/*.d +*/**/*.o +*/**/*.swiftdeps +*/**/*.swiftdeps~ diff --git a/.licenseignore b/.licenseignore index 02866b01..c5dfe3d2 100644 --- a/.licenseignore +++ b/.licenseignore @@ -37,3 +37,6 @@ Makefile gradle/wrapper/gradle-wrapper.properties gradlew gradlew.bat +**/gradlew +**/gradlew.bat +**/ci-validate.sh 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 e3fc499f..136da7d3 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 @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import java.util.* +import java.io.* plugins { java @@ -44,7 +45,7 @@ tasks.withType(JavaCompile::class).forEach { // FIXME: cannot share definition with 'buildSrc' so we duplicated the impl here -fun javaLibraryPaths(): List { +fun javaLibraryPaths(dir: File): List { val osName = System.getProperty("os.name") val osArch = System.getProperty("os.arch") val isLinux = osName.lowercase(Locale.getDefault()).contains("linux") @@ -52,15 +53,15 @@ fun javaLibraryPaths(): List { return listOf( if (isLinux) { if (osArch.equals("x86_64") || osArch.equals("amd64")) { - "$rootDir/.build/x86_64-unknown-linux-gnu/debug/" + "$dir/.build/x86_64-unknown-linux-gnu/debug/" } else { - "$rootDir/.build/$osArch-unknown-linux-gnu/debug/" + "$dir/.build/$osArch-unknown-linux-gnu/debug/" } } else { if (osArch.equals("aarch64")) { - "$rootDir/.build/arm64-apple-macosx/debug/" + "$dir/.build/arm64-apple-macosx/debug/" } else { - "$rootDir/.build/$osArch-apple-macosx/debug/" + "$dir/.build/$osArch-apple-macosx/debug/" } }, if (isLinux) { @@ -68,6 +69,12 @@ fun javaLibraryPaths(): List { } else { // assume macOS "/usr/lib/swift/" + }, + if (isLinux) { + System.getProperty("user.home") + "/.local/share/swiftly/toolchains/6.0.2/usr/lib/swift/linux" + } else { + // assume macOS + "/usr/lib/swift/" } ) } @@ -79,7 +86,9 @@ tasks.test { "--enable-native-access=ALL-UNNAMED", // Include the library paths where our dylibs are that we want to load and call - "-Djava.library.path=" + javaLibraryPaths().joinToString(File.pathSeparator) + "-Djava.library.path=" + + (javaLibraryPaths(rootDir) + javaLibraryPaths(project.projectDir)) + .joinToString(File.pathSeparator) ) } @@ -88,17 +97,3 @@ tasks.withType { this.showStandardStreams = true } } - - -// TODO: This is a crude workaround, we'll remove 'make' soon and properly track build dependencies -// val buildSwiftJExtract = tasks.register("buildMake") { -// description = "Triggers 'make' build" -// -// workingDir(rootDir) -// commandLine("make") -// } -// -// tasks.build { -// dependsOn(buildSwiftJExtract) -// } - diff --git a/Makefile b/Makefile index aeb3eedd..d8f8717a 100644 --- a/Makefile +++ b/Makefile @@ -57,8 +57,6 @@ all: @echo "Welcome to swift-java! There are several makefile targets to choose from:" @echo " javakit-run: Run the JavaKit example program that uses Java libraries from Swift." @echo " javakit-generate: Regenerate the Swift wrapper code for the various JavaKit libraries from Java. This only has to be done when changing the Java2Swift tool." - @echo " jextract-run: Run the Java example code that uses the wrapped Swift library. NOTE: this requires development toolchain described in the README." - @echo " jextract-generate: Generate Java wrapper code for the example Swift library allowing Swift to be called from Java. NOTE: this requires development toolchain described in the README." $(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/Java2Swift: swift build @@ -96,6 +94,9 @@ javakit-generate: generate-JavaKit generate-JavaKitReflection generate-JavaKitJa clean: rm -rf .build; \ + rm -rf build; \ + rm -rf Samples/JExtractPluginSampleApp/.build; \ + rm -rf Samples/JExtractPluginSampleApp/build; \ rm -rf Samples/SwiftKitExampleApp/src/generated/java/* format: @@ -105,42 +106,5 @@ format: ### "SwiftKit" is the "call swift from java" ### ################################################# -JEXTRACT_BUILD_DIR="$(BUILD_DIR)/jextract" - -define make_swiftinterface - $(eval $@_MODULE = $(1)) - $(eval $@_FILENAME = $(2)) - eval ${SWIFTC} \ - -emit-module-interface-path ${JEXTRACT_BUILD_DIR}/${$@_MODULE}/${$@_FILENAME}.swiftinterface \ - -emit-module-path ${JEXTRACT_BUILD_DIR}/${$@_MODULE}/${$@_FILENAME}.swiftmodule \ - -enable-library-evolution \ - -Xfrontend -abi-comments-in-module-interface \ - -module-name ${$@_MODULE} \ - -Xfrontend -abi-comments-in-module-interface \ - Sources/${$@_MODULE}/${$@_FILENAME}.swift - echo "Generated: ${JEXTRACT_BUILD_DIR}/${$@_MODULE}/${$@_FILENAME}.swiftinterface" -endef - -jextract-swift: generate-JExtract-interface-files - swift build - -generate-JExtract-interface-files: $(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) - @echo "Generate .swiftinterface files..." - @$(call make_swiftinterface, "ExampleSwiftLibrary", "MySwiftLibrary") - @$(call make_swiftinterface, "SwiftKitSwift", "SwiftKit") - -jextract-generate: jextract-swift generate-JExtract-interface-files - swift run jextract-swift \ - --package-name com.example.swift.generated \ - --swift-module ExampleSwiftLibrary \ - --output-directory ${SAMPLES_DIR}/SwiftKitSampleApp/build/generated/sources/jextract/main \ - $(BUILD_DIR)/jextract/ExampleSwiftLibrary/MySwiftLibrary.swiftinterface; \ - swift run jextract-swift \ - --package-name org.swift.swiftkit.generated \ - --swift-module SwiftKitSwift \ - --output-directory ${SAMPLES_DIR}/SwiftKitSampleApp/build/generated/sources/jextract/main \ - $(BUILD_DIR)/jextract/SwiftKitSwift/SwiftKit.swiftinterface - - jextract-run: jextract-generate ./gradlew Samples:SwiftKitSampleApp:run diff --git a/Package.swift b/Package.swift index cd456d76..0d5a5dab 100644 --- a/Package.swift +++ b/Package.swift @@ -126,6 +126,20 @@ let package = Package( targets: ["JExtractSwift"] ), + // ==== Plugin for wrapping Java classes in Swift + .plugin( + name: "JExtractSwiftPlugin", + targets: [ + "JExtractSwiftPlugin" + ] + ), + .plugin( + name: "JExtractSwiftCommandPlugin", + targets: [ + "JExtractSwiftCommandPlugin" + ] + ), + // ==== Examples .library( @@ -136,7 +150,7 @@ let package = Package( ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "600.0.1"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), .package(url: "https://github.com/ordo-one/package-benchmark", .upToNextMajor(from: "1.4.0")), ], @@ -229,6 +243,7 @@ let package = Package( .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), + .plugin( name: "JavaCompilerPlugin", capability: .buildTool() @@ -331,6 +346,24 @@ let package = Package( ] ), + .plugin( + name: "JExtractSwiftPlugin", + capability: .buildTool(), + dependencies: [ + "JExtractSwiftTool" + ] + ), + .plugin( + name: "JExtractSwiftCommandPlugin", + capability: .command( + intent: .custom(verb: "jextract", description: "Extract Java accessors from Swift module"), + permissions: [ + ]), + dependencies: [ + "JExtractSwiftTool" + ] + ), + .testTarget( name: "JavaKitTests", dependencies: ["JavaKit", "JavaKitNetwork"], diff --git a/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift new file mode 100644 index 00000000..008bc7d7 --- /dev/null +++ b/Plugins/JExtractSwiftCommandPlugin/JExtractSwiftCommandPlugin.swift @@ -0,0 +1,161 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +@main +final class JExtractSwiftCommandPlugin: BuildToolPlugin, CommandPlugin { + + var verbose: Bool = false + + /// Build the target before attempting to extract from it. + /// This avoids trying to extract from broken sources. + /// + /// You may disable this if confident that input targets sources are correct and there's no need to kick off a pre-build for some reason. + var buildInputs: Bool = true + + /// Build the target once swift-java sources have been generated. + /// This helps verify that the generated output is correct, and won't miscompile on the next build. + var buildOutputs: Bool = true + + func createBuildCommands(context: PluginContext, target: any Target) async throws -> [Command] { + // FIXME: This is not a build plugin but SwiftPM forces us to impleme the protocol anyway? rdar://139556637 + return [] + } + + func performCommand(context: PluginContext, arguments: [String]) throws { + // Plugin can't have dependencies, so we have some naive argument parsing instead: + self.verbose = arguments.contains("-v") || arguments.contains("--verbose") + + let selectedTargets: [String] = + if let last = arguments.lastIndex(where: { $0.starts(with: "-")}), + last < arguments.endIndex { + Array(arguments[.. [String] { + guard let sourceModule = target.sourceModule else { return [] } + + // Note: Target doesn't have a directoryURL counterpart to directory, + // so we cannot eliminate this deprecation warning. + let sourceDir = target.directory.string + + let configuration = try readConfiguration(sourceDir: "\(sourceDir)") + + // We use the the usual maven-style structure of "src/[generated|main|test]/java/..." + // that is common in JVM ecosystem + let outputDirectoryJava = context.pluginWorkDirectoryURL + .appending(path: "src") + .appending(path: "generated") + .appending(path: "java") + let outputDirectorySwift = context.pluginWorkDirectoryURL + .appending(path: "Sources") + + var arguments: [String] = [ + "--swift-module", sourceModule.name, + "--package-name", configuration.javaPackage, + "--output-directory-java", outputDirectoryJava.path(percentEncoded: false), + "--output-directory-swift", outputDirectorySwift.path(percentEncoded: false), + // TODO: "--build-cache-directory", ... + // Since plugins cannot depend on libraries we cannot detect what the output files will be, + // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin. + // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc. + ] + arguments.append(sourceDir) + + return arguments + } + + /// Perform the command on a specific target. + func performCommand(context: PluginContext, target: Target, extraArguments _: [String]) throws { + // Make sure the target can builds properly + try self.packageManager.build(.target(target.name), parameters: .init()) + + guard let sourceModule = target.sourceModule else { return } + + if self.buildInputs { + log("Pre-building target '\(target.name)' before extracting sources...") + try self.packageManager.build(.target(target.name), parameters: .init()) + } + + let arguments = try prepareJExtractArguments(context: context, target: target) + + try runExtract(context: context, target: target, arguments: arguments) + + if self.buildOutputs { + // Building the *products* since we need to build the dylib that contains our newly generated sources, + // so just building the target again would not be enough. We build all products which we affected using + // our source generation, which usually would be just a product dylib with our library. + // + // In practice, we'll always want to build after generating; either here, + // or via some other task before we run any Java code, calling into Swift. + log("Post-extract building products with target '\(target.name)'...") + for product in context.package.products where product.targets.contains(where: { $0.id == target.id }) { + log("Post-extract building product '\(product.name)'...") + try self.packageManager.build(.product(product.name), parameters: .init()) + } + } + } + + func runExtract(context: PluginContext, target: Target, arguments: [String]) throws { + let process = Process() + process.executableURL = try context.tool(named: "JExtractSwiftTool").url + process.arguments = arguments + + do { + log("Execute: \(process.executableURL!.absoluteURL.relativePath) \(arguments.joined(separator: " "))") + + try process.run() + process.waitUntilExit() + + assert(process.terminationStatus == 0, "Process failed with exit code: \(process.terminationStatus)") + } catch { + print("[swift-java-command] Failed to extract Java sources for target: '\(target.name); Error: \(error)") + } + } + + func log(_ message: @autoclosure () -> String, terminator: String = "\n") { + if self.verbose { + print("[swift-java-command] \(message())", terminator: terminator) + } + } +} + +// Mini coloring helper, since we cannot have dependencies we keep it minimal here +extension String { + var green: String { + "\u{001B}[0;32m" + "\(self)" + "\u{001B}[0;0m" + } +} \ No newline at end of file diff --git a/Plugins/JExtractSwiftCommandPlugin/PluginsShared/Configuration.swift b/Plugins/JExtractSwiftCommandPlugin/PluginsShared/Configuration.swift new file mode 100644 index 00000000..f917290c --- /dev/null +++ b/Plugins/JExtractSwiftCommandPlugin/PluginsShared/Configuration.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Configuration for the JExtractSwift translation tool, provided on a per-target +/// basis. +struct Configuration: Codable { + var javaPackage: String +} + +func readConfiguration(sourceDir: String) throws -> Configuration { + let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") + do { + let configData = try Data(contentsOf: configFile) + return try JSONDecoder().decode(Configuration.self, from: configData) + } catch { + throw ConfigurationError(message: "Failed to parse JExtractSwift configuration at '\(configFile)!'", error: error) + } +} + +struct ConfigurationError: Error { + let message: String + let error: any Error +} diff --git a/Plugins/JExtractSwiftCommandPlugin/PluginsShared/PluginUtils.swift b/Plugins/JExtractSwiftCommandPlugin/PluginsShared/PluginUtils.swift new file mode 100644 index 00000000..320dfc19 --- /dev/null +++ b/Plugins/JExtractSwiftCommandPlugin/PluginsShared/PluginUtils.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} + +func getSwiftJavaConfig(target: Target) -> String? { + let configPath = URL(fileURLWithPath: target.directory.string).appending(component: "swift-java.config").path() + + if FileManager.default.fileExists(atPath: configPath) { + return configPath + } else { + return nil + } +} diff --git a/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift new file mode 100644 index 00000000..f23a1dbf --- /dev/null +++ b/Plugins/JExtractSwiftPlugin/JExtractSwiftPlugin.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation +import PackagePlugin + +@main +struct JExtractSwiftBuildToolPlugin: BuildToolPlugin { + func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { + guard let sourceModule = target.sourceModule else { return [] } + + // Note: Target doesn't have a directoryURL counterpart to directory, + // so we cannot eliminate this deprecation warning. + let sourceDir = target.directory.string + + let toolURL = try context.tool(named: "JExtractSwiftTool").url + let configuration = try readConfiguration(sourceDir: "\(sourceDir)") + + // We use the the usual maven-style structure of "src/[generated|main|test]/java/..." + // that is common in JVM ecosystem + let outputDirectoryJava = context.pluginWorkDirectoryURL + .appending(path: "src") + .appending(path: "generated") + .appending(path: "java") + let outputDirectorySwift = context.pluginWorkDirectoryURL + .appending(path: "Sources") + + var arguments: [String] = [ + "--swift-module", sourceModule.name, + "--package-name", configuration.javaPackage, + "--output-directory-java", outputDirectoryJava.path(percentEncoded: false), + "--output-directory-swift", outputDirectorySwift.path(percentEncoded: false), + // TODO: "--build-cache-directory", ... + // Since plugins cannot depend on libraries we cannot detect what the output files will be, + // as it depends on the contents of the input files. Therefore we have to implement this as a prebuild plugin. + // We'll have to make up some caching inside the tool so we don't re-parse files which have not changed etc. + ] + arguments.append(sourceDir) + + return [ + .prebuildCommand( + displayName: "Generate Java wrappers for Swift types", + executable: toolURL, + arguments: arguments, + // inputFiles: [ configFile ] + swiftFiles, + // outputFiles: outputJavaFiles + outputFilesDirectory: outputDirectorySwift + ) + ] + } +} + diff --git a/Plugins/JExtractSwiftPlugin/PluginsShared/Configuration.swift b/Plugins/JExtractSwiftPlugin/PluginsShared/Configuration.swift new file mode 100644 index 00000000..f917290c --- /dev/null +++ b/Plugins/JExtractSwiftPlugin/PluginsShared/Configuration.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation + +/// Configuration for the JExtractSwift translation tool, provided on a per-target +/// basis. +struct Configuration: Codable { + var javaPackage: String +} + +func readConfiguration(sourceDir: String) throws -> Configuration { + let configFile = URL(filePath: sourceDir).appending(path: "swift-java.config") + do { + let configData = try Data(contentsOf: configFile) + return try JSONDecoder().decode(Configuration.self, from: configData) + } catch { + throw ConfigurationError(message: "Failed to parse JExtractSwift configuration at '\(configFile)!'", error: error) + } +} + +struct ConfigurationError: Error { + let message: String + let error: any Error +} diff --git a/Plugins/JExtractSwiftPlugin/PluginsShared/PluginUtils.swift b/Plugins/JExtractSwiftPlugin/PluginsShared/PluginUtils.swift new file mode 100644 index 00000000..6ec58147 --- /dev/null +++ b/Plugins/JExtractSwiftPlugin/PluginsShared/PluginUtils.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} diff --git a/Samples/JavaProbablyPrime/ci-validate.sh b/Samples/JavaProbablyPrime/ci-validate.sh new file mode 100755 index 00000000..948a390c --- /dev/null +++ b/Samples/JavaProbablyPrime/ci-validate.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +swift run JavaProbablyPrime 1337 \ No newline at end of file diff --git a/Samples/JavaSieve/Sources/JavaSieve/main.swift b/Samples/JavaSieve/Sources/JavaSieve/main.swift index c075a8a1..b06cc6bb 100644 --- a/Samples/JavaSieve/Sources/JavaSieve/main.swift +++ b/Samples/JavaSieve/Sources/JavaSieve/main.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JavaKit +import JavaMath let jvm = try JavaVirtualMachine.shared(classPath: ["QuadraticSieve-1.0.jar"]) do { @@ -20,6 +21,8 @@ do { for prime in sieveClass.findPrimes(100)! { print("Found prime: \(prime.intValue())") } + + try JavaClass().HALF_UP } catch { print("Failure: \(error)") } diff --git a/Samples/JavaSieve/ci-validate.sh b/Samples/JavaSieve/ci-validate.sh new file mode 100755 index 00000000..3eebd3ca --- /dev/null +++ b/Samples/JavaSieve/ci-validate.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "Not validated in CI..." diff --git a/Samples/SwiftKitSampleApp/Package.swift b/Samples/SwiftKitSampleApp/Package.swift new file mode 100644 index 00000000..f12ecfd6 --- /dev/null +++ b/Samples/SwiftKitSampleApp/Package.swift @@ -0,0 +1,77 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import CompilerPluginSupport +import PackageDescription + +import class Foundation.FileManager +import class Foundation.ProcessInfo + +// Note: the JAVA_HOME environment variable must be set to point to where +// Java is installed, e.g., +// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home. +func findJavaHome() -> String { + if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return home + } + + // This is a workaround for envs (some IDEs) which have trouble with + // picking up env variables during the build process + let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home" + if let home = try? String(contentsOfFile: path, encoding: .utf8) { + if let lastChar = home.last, lastChar.isNewline { + return String(home.dropLast()) + } + + return home + } + + fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.") +} +let javaHome = findJavaHome() + +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) + let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#else + // TODO: Handle windows as well + #error("Currently only macOS and Linux platforms are supported, this may change in the future.") +#endif + +let package = Package( + name: "SwiftKitSampleApp", + platforms: [ + .macOS(.v10_15) + ], + products: [ + .library( + name: "MySwiftLibrary", + type: .dynamic, + targets: ["MySwiftLibrary"] + ), + + ], + dependencies: [ + .package(name: "swift-java", path: "../../"), + ], + targets: [ + .target( + name: "MySwiftLibrary", + dependencies: [ + .product(name: "SwiftKitSwift", package: "swift-java"), + ], + exclude: [ + "swift-java.config", + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) + ], + plugins: [ + .plugin(name: "JExtractSwiftPlugin", package: "swift-java"), + ] + ), + ] +) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift new file mode 100644 index 00000000..84e4618f --- /dev/null +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// This is a "plain Swift" file containing various types of declarations, +// that is exported to Java by using the `jextract-swift` tool. +// +// No annotations are necessary on the Swift side to perform the export. + +#if os(Linux) +import Glibc +#else +import Darwin.C +#endif + +public func helloWorld() { + p("\(#function)") +} + +public func globalTakeInt(i: Int) { + p("i:\(i)") +} + +public func globalTakeIntInt(i: Int, j: Int) { + p("i:\(i), j:\(j)") +} + +public func globalCallMeRunnable(run: () -> ()) { + run() +} + +public class MySwiftClass { + + public var len: Int + public var cap: Int + + public init(len: Int, cap: Int) { + self.len = len + self.cap = cap + + p("\(MySwiftClass.self).len = \(self.len)") + p("\(MySwiftClass.self).cap = \(self.cap)") + let addr = unsafeBitCast(self, to: UInt64.self) + p("initializer done, self = 0x\(String(addr, radix: 16, uppercase: true))") + } + + deinit { + let addr = unsafeBitCast(self, to: UInt64.self) + p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))") + } + + public var counter: Int32 = 0 + + public func voidMethod() { + p("") + } + + public func takeIntMethod(i: Int) { + p("i:\(i)") + } + + public func echoIntMethod(i: Int) -> Int { + p("i:\(i)") + return i + } + + public func makeIntMethod() -> Int { + p("make int -> 12") + return 12 + } + + public func makeRandomIntMethod() -> Int { + return Int.random(in: 1..<256) + } +} + +// ==== Internal helpers + +private func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { + print("[swift][\(file):\(line)](\(function)) \(msg)") + fflush(stdout) +} + +#if os(Linux) +// FIXME: why do we need this workaround? +@_silgen_name("_objc_autoreleaseReturnValue") +public func _objc_autoreleaseReturnValue(a: Any) {} + +@_silgen_name("objc_autoreleaseReturnValue") +public func objc_autoreleaseReturnValue(a: Any) {} +#endif diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/swift-java.config new file mode 100644 index 00000000..6e5bc2af --- /dev/null +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/swift-java.config @@ -0,0 +1,3 @@ +{ + "javaPackage": "com.example.swift" +} diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle index 3b79bd72..ff9e1837 100644 --- a/Samples/SwiftKitSampleApp/build.gradle +++ b/Samples/SwiftKitSampleApp/build.gradle @@ -14,6 +14,8 @@ import org.swift.swiftkit.gradle.BuildUtils +import java.nio.file.* + plugins { id("build-logic.java-application-conventions") id("me.champeau.jmh") version "0.7.2" @@ -33,27 +35,86 @@ java { } +// This is for development, when we edit the Swift swift-java project, the outputs of the generated sources may change. +// Thus, we also need to watch and re-build the top level project. +def compileSwiftJExtractPlugin = tasks.register("compileSwiftJExtractPlugin", Exec) { + description = "Rebuild the swift-java root project" + + inputs.file(new File(rootDir, "Package.swift")) + inputs.dir(new File(rootDir, "Sources")) + outputs.dir(new File(rootDir, ".build")) + + workingDir = rootDir + commandLine "swift" + args("build", + "--product", "SwiftKitSwift", + "--product", "JExtractSwiftPlugin", + "--product", "JExtractSwiftCommandPlugin") +} + def jextract = tasks.register("jextract", Exec) { - description = "Extracts Java accessor sources using jextract" + description = "Builds swift sources, including swift-java source generation" + dependsOn compileSwiftJExtractPlugin - outputs.dir(layout.buildDirectory.dir("generated/sources/jextract/main")) - inputs.dir("$rootDir/Sources/ExampleSwiftLibrary") // monitored library + // only because we depend on "live developing" the plugin while using this project to test it + inputs.file(new File(rootDir, "Package.swift")) + inputs.dir(new File(rootDir, "Sources")) - // any changes in the source generator sources also mean the resulting output might change - inputs.dir("$rootDir/Sources/JExtractSwift") - inputs.dir("$rootDir/Sources/JExtractSwiftTool") + inputs.file(new File(projectDir, "Package.swift")) + inputs.dir(new File(projectDir, "Sources")) - workingDir = rootDir - commandLine "make" - args "jextract-generate" + // TODO: we can use package describe --type json to figure out which targets depend on JExtractSwiftPlugin and will produce outputs + // Avoid adding this directory, but create the expected one specifically for all targets + // which WILL produce sources because they have the plugin + outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}")) + + File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile + if (!baseSwiftPluginOutputsDir.exists()) { + baseSwiftPluginOutputsDir.mkdirs() + } + Files.walk(layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile.toPath()).each { + // Add any Java sources generated by the plugin to our sourceSet + if (it.endsWith("JExtractSwiftPlugin/src/generated/java")) { + outputs.dir(it) + } + } + + workingDir = layout.projectDirectory + commandLine "swift" + args("package", "jextract", "-v", "--log-level", "info") // TODO: pass log level from Gradle build } +// Add the java-swift generated Java sources sourceSets { main { java { srcDir(jextract) } } + test { + java { + srcDir(jextract) + } + } + jmh { + java { + srcDir(jextract) + } + } +} + +tasks.build { + dependsOn("jextract") +} + + +def cleanSwift = tasks.register("cleanSwift", Exec) { + workingDir = layout.projectDirectory + commandLine "swift" + args("package", "clean") +} +tasks.clean { + dependsOn("cleanSwift") } dependencies { @@ -70,17 +131,14 @@ tasks.named('test', Test) { application { mainClass = "com.example.swift.HelloJava2Swift" - // In order to silence: - // WARNING: A restricted method in java.lang.foreign.SymbolLookup has been called - // WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.swift.JavaKitExample in an unnamed module - // WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module - // WARNING: Restricted methods will be blocked in a future release unless native access is enabled - // FIXME: Find out the proper solution to this applicationDefaultJvmArgs = [ "--enable-native-access=ALL-UNNAMED", // Include the library paths where our dylibs are that we want to load and call - "-Djava.library.path=" + BuildUtils.javaLibraryPaths(rootDir).join(":"), + "-Djava.library.path=" + + (BuildUtils.javaLibraryPaths(rootDir) + + BuildUtils.javaLibraryPaths(project.projectDir)).join(":"), + // Enable tracing downcalls (to Swift) "-Djextract.trace.downcalls=true" @@ -89,6 +147,8 @@ application { jmh { jvmArgsAppend = [ - "-Djava.library.path=" + BuildUtils.javaLibraryPaths(rootDir).join(":"), + "-Djava.library.path=" + + (BuildUtils.javaLibraryPaths(rootDir) + + BuildUtils.javaLibraryPaths(project.projectDir)).join(":"), ] } diff --git a/Samples/SwiftKitSampleApp/gradlew b/Samples/SwiftKitSampleApp/gradlew new file mode 120000 index 00000000..343e0d2c --- /dev/null +++ b/Samples/SwiftKitSampleApp/gradlew @@ -0,0 +1 @@ +../../gradlew \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/gradlew.bat b/Samples/SwiftKitSampleApp/gradlew.bat new file mode 120000 index 00000000..cb5a9464 --- /dev/null +++ b/Samples/SwiftKitSampleApp/gradlew.bat @@ -0,0 +1 @@ +../../gradlew.bat \ No newline at end of file diff --git a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java index 5ce9ee6b..614697a3 100644 --- a/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java +++ b/Samples/SwiftKitSampleApp/src/jmh/java/org/swift/swiftkit/JavaToSwiftBenchmark.java @@ -26,8 +26,9 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.infra.Blackhole; -import com.example.swift.generated.MySwiftClass; +import com.example.swift.MySwiftClass; +@SuppressWarnings("unused") public class JavaToSwiftBenchmark { @State(Scope.Benchmark) diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index 51dd48b1..2a86e403 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -15,15 +15,16 @@ package com.example.swift; // Import swift-extract generated sources -import com.example.swift.generated.ExampleSwiftLibrary; -import com.example.swift.generated.MySwiftClass; + +import com.example.swift.MySwiftLibrary; +import com.example.swift.MySwiftClass; // Import javakit/swiftkit support libraries import org.swift.swiftkit.SwiftArena; import org.swift.swiftkit.SwiftKit; import org.swift.swiftkit.SwiftValueWitnessTable; -import java.lang.foreign.*; +import java.util.Arrays; public class HelloJava2Swift { @@ -31,23 +32,27 @@ public static void main(String[] args) { boolean traceDowncalls = Boolean.getBoolean("jextract.trace.downcalls"); System.out.println("Property: jextract.trace.downcalls = " + traceDowncalls); - System.out.printf("java.library.path = %s\n", System.getProperty("java.library.path")); + System.out.print("Property: java.library.path = " +SwiftKit.getJavaLibraryPath()); examples(); } static void examples() { - ExampleSwiftLibrary.helloWorld(); + MySwiftLibrary.helloWorld(); - ExampleSwiftLibrary.globalTakeInt(1337); + MySwiftLibrary.globalTakeInt(1337); - MySwiftClass obj = new MySwiftClass(2222, 7777); + // Example of using an arena; MyClass.deinit is run at end of scope + try (var arena = SwiftArena.ofConfined()) { + MySwiftClass obj = new MySwiftClass(arena, 2222, 7777); - SwiftKit.retain(obj.$memorySegment()); - System.out.println("[java] obj ref count = " + SwiftKit.retainCount(obj.$memorySegment())); + // just checking retains/releases work + SwiftKit.retain(obj.$memorySegment()); + SwiftKit.release(obj.$memorySegment()); - obj.voidMethod(); - obj.takeIntMethod(42); + obj.voidMethod(); + obj.takeIntMethod(42); + } System.out.println("DONE."); } diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java similarity index 56% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java rename to Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 7495ff19..fa17ef1a 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -12,33 +12,41 @@ // //===----------------------------------------------------------------------===// -package com.example.swift.generated; +package com.example.swift; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; +import org.swift.swiftkit.SwiftKit; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.io.File; +import java.util.stream.Stream; -public class MySwiftClassTest { +import static org.junit.jupiter.api.Assertions.*; - @BeforeAll - static void beforeAll() { - System.out.printf("java.library.path = %s\n", System.getProperty("java.library.path")); +public class MySwiftClassTest { - System.loadLibrary("swiftCore"); - System.loadLibrary("ExampleSwiftLibrary"); + void checkPaths(Throwable throwable) { + var paths = SwiftKit.getJavaLibraryPath().split(":"); + for (var path : paths) { + System.out.println("CHECKING PATH: " + path); + Stream.of(new File(path).listFiles()) + .filter(file -> !file.isDirectory()) + .forEach((file) -> { + System.out.println(" - " + file.getPath()); + }); + } - System.setProperty("jextract.trace.downcalls", "true"); + throw new RuntimeException(throwable); } @Test void test_MySwiftClass_voidMethod() { - MySwiftClass o = new MySwiftClass(12, 42); - o.voidMethod(); + try { + MySwiftClass o = new MySwiftClass(12, 42); + o.voidMethod(); + } catch (Throwable throwable) { + checkPaths(throwable); + } } @Test diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/GeneratedJavaKitExampleModuleTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java similarity index 59% rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/GeneratedJavaKitExampleModuleTest.java rename to Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index 9cceacf5..ffa90359 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/generated/GeneratedJavaKitExampleModuleTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -12,46 +12,42 @@ // //===----------------------------------------------------------------------===// -package com.example.swift.generated; +package com.example.swift; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; import org.swift.swiftkit.SwiftKit; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; -public class GeneratedJavaKitExampleModuleTest { - - @BeforeAll - static void beforeAll() { - System.out.println("java.library.path = " + SwiftKit.getJavaLibraryPath()); - System.out.println("java.library.path = " + SwiftKit.getJextractTraceDowncalls()); - } +public class MySwiftLibraryTest { @Test void call_helloWorld() { - ExampleSwiftLibrary.helloWorld(); + MySwiftLibrary.helloWorld(); - assertNotNull(ExampleSwiftLibrary.helloWorld$address()); + assertNotNull(MySwiftLibrary.helloWorld$address()); } @Test void call_globalTakeInt() { - ExampleSwiftLibrary.globalTakeInt(12); + MySwiftLibrary.globalTakeInt(12); - assertNotNull(ExampleSwiftLibrary.globalTakeInt$address()); + assertNotNull(MySwiftLibrary.globalTakeInt$address()); } @Test + @Disabled("Upcalls not yet implemented in new scheme") @SuppressWarnings({"Convert2Lambda", "Convert2MethodRef"}) void call_globalCallMeRunnable() { CountDownLatch countDownLatch = new CountDownLatch(3); - ExampleSwiftLibrary.globalCallMeRunnable(new Runnable() { + MySwiftLibrary.globalCallMeRunnable(new Runnable() { @Override public void run() { countDownLatch.countDown(); @@ -59,10 +55,10 @@ public void run() { }); assertEquals(2, countDownLatch.getCount()); - ExampleSwiftLibrary.globalCallMeRunnable(() -> countDownLatch.countDown()); + MySwiftLibrary.globalCallMeRunnable(() -> countDownLatch.countDown()); assertEquals(1, countDownLatch.getCount()); - ExampleSwiftLibrary.globalCallMeRunnable(countDownLatch::countDown); + MySwiftLibrary.globalCallMeRunnable(countDownLatch::countDown); assertEquals(0, countDownLatch.getCount()); } diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java index 069a4e41..633d5f1c 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftClassTest.java @@ -15,19 +15,13 @@ package org.swift.swiftkit; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.BeforeAll; + import org.junit.jupiter.api.Test; -import com.example.swift.generated.MySwiftClass; +import com.example.swift.MySwiftClass; public class MySwiftClassTest { - @BeforeAll - static void beforeAll() { - System.out.printf("java.library.path = %s\n", SwiftKit.getJavaLibraryPath()); - System.out.printf("jextract.trace.downcalls = %s\n", SwiftKit.getJextractTraceDowncalls()); - } - @Test void call_retain_retainCount_release() { var arena = SwiftArena.ofConfined(); diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java index 4f4ab9df..ad514e1b 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java @@ -14,24 +14,21 @@ package org.swift.swiftkit; -import com.example.swift.generated.MySwiftClass; +import com.example.swift.MySwiftClass; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; import org.swift.swiftkit.util.PlatformUtils; +import java.util.Arrays; +import java.util.stream.Collectors; + import static org.junit.jupiter.api.Assertions.*; import static org.swift.swiftkit.SwiftKit.*; import static org.swift.swiftkit.SwiftKit.retainCount; public class SwiftArenaTest { - @BeforeAll - static void beforeAll() { - System.out.printf("java.library.path = %s\n", SwiftKit.getJavaLibraryPath()); - System.out.printf("jextract.trace.downcalls = %s\n", SwiftKit.getJextractTraceDowncalls()); - } - static boolean isAmd64() { return PlatformUtils.isAmd64(); } diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 36e09b14..8376d9b6 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -101,3 +101,12 @@ private func p(_ msg: String, file: String = #fileID, line: UInt = #line, functi print("[swift][\(file):\(line)](\(function)) \(msg)") fflush(stdout) } + +#if os(Linux) +// FIXME: why do we need this workaround? +@_silgen_name("_objc_autoreleaseReturnValue") +public func _objc_autoreleaseReturnValue(a: Any) {} + +@_silgen_name("objc_autoreleaseReturnValue") +public func objc_autoreleaseReturnValue(a: Any) {} +#endif diff --git a/Sources/JExtractSwift/CodePrinter.swift b/Sources/JExtractSwift/CodePrinter.swift index 308db69e..6e26b960 100644 --- a/Sources/JExtractSwift/CodePrinter.swift +++ b/Sources/JExtractSwift/CodePrinter.swift @@ -34,14 +34,19 @@ public struct CodePrinter { } public var indentationText: String = "" - public static func toString(_ block: (inout CodePrinter) -> ()) -> String { + public static func toString(_ block: (inout CodePrinter) throws -> ()) rethrows -> String { var printer = CodePrinter() - block(&printer) + try block(&printer) return printer.finalize() } - public init() { - + var mode: PrintMode + public enum PrintMode { + case accumulateAll + case flushToFileOnWrite + } + public init(mode: PrintMode = .flushToFileOnWrite) { + self.mode = mode } internal mutating func append(_ text: String) { @@ -91,6 +96,16 @@ public struct CodePrinter { } } + /// Print a plain newline, e.g. to separate declarations. + public mutating func println( + _ terminator: PrinterTerminator = .newLine, + function: String = #function, + file: String = #fileID, + line: UInt = #line + ) { + print("") + } + public mutating func print( _ text: Any, _ terminator: PrinterTerminator = .newLine, @@ -159,6 +174,12 @@ public struct CodePrinter { public var isEmpty: Bool { self.contents.isEmpty } + + public mutating func dump(file: String = #fileID, line: UInt = #line) { + Swift.print("// CodePrinter.dump @ \(file):\(line)") + Swift.print(contents) + } + } public enum PrinterTerminator: String { @@ -185,3 +206,49 @@ public enum PrinterTerminator: String { } } } + +extension CodePrinter { + + /// - Returns: the output path of the generated file, if any (i.e. not in accumulate in memory mode) + package mutating func writeContents( + outputDirectory: String, + javaPackagePath: String?, + filename: String + ) throws -> URL? { + guard self.mode != .accumulateAll else { + // if we're accumulating everything, we don't want to finalize/flush any contents + // let's mark that this is where a write would have happened though: + print("// ^^^^ Contents of: \(outputDirectory)/\(filename)") + return nil + } + + let contents = finalize() + if outputDirectory == "-" { + print( + "// ==== ---------------------------------------------------------------------------------------------------" + ) + if let javaPackagePath { + print("// \(javaPackagePath)/\(filename)") + } else { + print("// \(filename)") + } + print(contents) + return nil + } + + let targetDirectory = [outputDirectory, javaPackagePath].compactMap { $0 }.joined(separator: PATH_SEPARATOR) + log.trace("Prepare target directory: \(targetDirectory)") + try FileManager.default.createDirectory( + atPath: targetDirectory, withIntermediateDirectories: true) + + let outputPath = Foundation.URL(fileURLWithPath: targetDirectory).appendingPathComponent(filename) + try contents.write( + to: outputPath, + atomically: true, + encoding: .utf8 + ) + + return outputPath + } + +} diff --git a/Sources/JExtractSwift/Convenience/Collection+Extensions.swift b/Sources/JExtractSwift/Convenience/Collection+Extensions.swift index 5df901d2..fcb817d7 100644 --- a/Sources/JExtractSwift/Convenience/Collection+Extensions.swift +++ b/Sources/JExtractSwift/Convenience/Collection+Extensions.swift @@ -39,3 +39,13 @@ extension Collection { } } } + +extension Collection where Element == Int { + var sum: Int { + var s = 0 + for i in self { + s += i + } + return s + } +} \ No newline at end of file diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index 56c3bd6b..cbff9969 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -28,17 +28,15 @@ public typealias JavaPackage = String public struct ImportedNominalType: ImportedDecl { public let swiftTypeName: String public let javaType: JavaType - public var swiftMangledName: String? public var kind: NominalTypeKind public var initializers: [ImportedFunc] = [] public var methods: [ImportedFunc] = [] public var variables: [ImportedVariable] = [] - public init(swiftTypeName: String, javaType: JavaType, swiftMangledName: String? = nil, kind: NominalTypeKind) { + public init(swiftTypeName: String, javaType: JavaType, kind: NominalTypeKind) { self.swiftTypeName = swiftTypeName self.javaType = javaType - self.swiftMangledName = swiftMangledName self.kind = kind } @@ -55,7 +53,7 @@ public struct ImportedNominalType: ImportedDecl { /// The Java class name without the package. public var javaClassName: String { switch javaType { - case .class(package: _, name: let name): name + case .class(package: _, let name): name default: javaType.description } } @@ -69,10 +67,10 @@ public enum NominalTypeKind { } public struct ImportedParam { - let param: FunctionParameterSyntax + let syntax: FunctionParameterSyntax var firstName: String? { - let text = param.firstName.trimmed.text + let text = syntax.firstName.trimmed.text guard text != "_" else { return nil } @@ -81,7 +79,7 @@ public struct ImportedParam { } var secondName: String? { - let text = param.secondName?.trimmed.text + let text = syntax.secondName?.trimmed.text guard text != "_" else { return nil } @@ -95,7 +93,7 @@ public struct ImportedParam { // The Swift type as-is from the swift interface var swiftType: String { - param.type.trimmed.description + syntax.type.trimmed.description } // The mapped-to Java type of the above Java type, collections and optionals may be replaced with Java ones etc. @@ -116,22 +114,33 @@ extension ImportedParam { } // TODO: this is used in different contexts and needs a cleanup +// Perhaps this is "which parameter passing style"? public enum SelfParameterVariant { + // ==== Java forwarding patterns + /// Make a method that accepts the raw memory pointer as a MemorySegment case memorySegment /// Make a method that accepts the the Java wrapper class of the type case wrapper /// Raw SWIFT_POINTER case pointer + + // ==== Swift forwarding patterns + + case swiftThunkSelf } public struct ImportedFunc: ImportedDecl, CustomStringConvertible { + + /// Swift module name (e.g. the target name where a type or function was declared) + public var module: String + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// /// This is necessary when rendering accessor Java code we need the type that "self" is expecting to have. - public var parentName: TranslatedType? - public var hasParent: Bool { parentName != nil } + public var parent: TranslatedType? + public var hasParent: Bool { parent != nil } /// This is a full name such as init(cap:name:). public var identifier: String @@ -148,8 +157,8 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { /// A display name to use to refer to the Swift declaration with its /// enclosing type, if there is one. public var displayName: String { - if let parentName { - return "\(parentName.swiftTypeName).\(identifier)" + if let parent { + return "\(parent.swiftTypeName).\(identifier)" } return identifier @@ -158,36 +167,33 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { public var returnType: TranslatedType public var parameters: [ImportedParam] - public func effectiveParameters(selfVariant: SelfParameterVariant?) -> [ImportedParam] { - if let parentName { + public func effectiveParameters(paramPassingStyle: SelfParameterVariant?) -> [ImportedParam] { + if let parent { var params = parameters // Add `self: Self` for method calls on a member // // allocating initializer takes a Self.Type instead, but it's also a pointer - switch selfVariant { + switch paramPassingStyle { case nil, .wrapper: break case .pointer: let selfParam: FunctionParameterSyntax = "self$: $swift_pointer" params.append( - ImportedParam( - param: selfParam, - type: parentName - ) + ImportedParam(syntax: selfParam, type: parent) ) case .memorySegment: let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment" - var parentForSelf = parentName + var parentForSelf = parent parentForSelf.javaType = .javaForeignMemorySegment params.append( - ImportedParam( - param: selfParam, - type: parentForSelf - ) + ImportedParam(syntax: selfParam, type: parentForSelf) ) + + case .swiftThunkSelf: + break } // TODO: add any metadata for generics and other things we may need to add here @@ -198,19 +204,25 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { } } - public var swiftMangledName: String = "" + public var swiftDecl: any DeclSyntaxProtocol - public var syntax: String? = nil + public var syntax: String? { + "\(self.swiftDecl)" + } public var isInit: Bool = false public init( - parentName: TranslatedType?, + module: String, + decl: any DeclSyntaxProtocol, + parent: TranslatedType?, identifier: String, returnType: TranslatedType, parameters: [ImportedParam] ) { - self.parentName = parentName + self.swiftDecl = decl + self.module = module + self.parent = parent self.identifier = identifier self.returnType = returnType self.parameters = parameters @@ -219,7 +231,6 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { public var description: String { """ ImportedFunc { - mangledName: \(swiftMangledName) identifier: \(identifier) returnType: \(returnType) parameters: \(parameters) @@ -232,12 +243,26 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible { } } +extension ImportedFunc: Hashable { + public func hash(into hasher: inout Swift.Hasher) { + self.swiftDecl.id.hash(into: &hasher) + } + + public static func ==(lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool { + lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id && + lhs.swiftDecl.id == rhs.swiftDecl.id + } +} + public enum VariableAccessorKind { case get case set } public struct ImportedVariable: ImportedDecl, CustomStringConvertible { + + public var module: String + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// @@ -249,7 +274,7 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible { public var identifier: String /// Which accessors are we able to expose. - /// + /// /// Usually this will be all the accessors the variable declares, /// however if the getter is async or throwing we may not be able to import it /// (yet), and therefore would skip it from the supported set. @@ -284,51 +309,54 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible { switch kind { case .set: - let newValueParam: FunctionParameterSyntax = "_ newValue: \(self.returnType.cCompatibleSwiftType)" - var funcDecl = ImportedFunc( - parentName: self.parentName, + let newValueParam: FunctionParameterSyntax = + "_ newValue: \(self.returnType.cCompatibleSwiftType)" + let funcDecl = ImportedFunc( + module: self.module, + decl: self.syntax!, + parent: self.parentName, identifier: self.identifier, returnType: TranslatedType.void, - parameters: [.init(param: newValueParam, type: self.returnType)]) - funcDecl.swiftMangledName = self.swiftMangledName + "s" // form mangled name of the getter by adding the suffix + parameters: [.init(syntax: newValueParam, type: self.returnType)]) return funcDecl case .get: - var funcDecl = ImportedFunc( - parentName: self.parentName, + let funcDecl = ImportedFunc( + module: self.module, + decl: self.syntax!, + parent: self.parentName, identifier: self.identifier, returnType: self.returnType, parameters: []) - funcDecl.swiftMangledName = self.swiftMangledName + "g" // form mangled name of the getter by adding the suffix return funcDecl } } - public func effectiveAccessorParameters(_ kind: VariableAccessorKind, selfVariant: SelfParameterVariant?) -> [ImportedParam] { + public func effectiveAccessorParameters( + _ kind: VariableAccessorKind, paramPassingStyle: SelfParameterVariant? + ) -> [ImportedParam] { var params: [ImportedParam] = [] if kind == .set { - let newValueParam: FunctionParameterSyntax = "_ newValue: \(raw: self.returnType.swiftTypeName)" + let newValueParam: FunctionParameterSyntax = + "_ newValue: \(raw: self.returnType.swiftTypeName)" params.append( ImportedParam( - param: newValueParam, + syntax: newValueParam, type: self.returnType) - ) + ) } if let parentName { // Add `self: Self` for method calls on a member // // allocating initializer takes a Self.Type instead, but it's also a pointer - switch selfVariant { - case nil, .wrapper: - break - + switch paramPassingStyle { case .pointer: let selfParam: FunctionParameterSyntax = "self$: $swift_pointer" params.append( ImportedParam( - param: selfParam, + syntax: selfParam, type: parentName ) ) @@ -339,10 +367,15 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible { parentForSelf.javaType = .javaForeignMemorySegment params.append( ImportedParam( - param: selfParam, + syntax: selfParam, type: parentForSelf ) ) + + case nil, + .wrapper, + .swiftThunkSelf: + break } } @@ -354,10 +387,12 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible { public var syntax: VariableDeclSyntax? = nil public init( + module: String, parentName: TranslatedType?, identifier: String, returnType: TranslatedType ) { + self.module = module self.parentName = parentName self.identifier = identifier self.returnType = returnType diff --git a/Sources/JExtractSwift/Logger.swift b/Sources/JExtractSwift/Logger.swift index 7309c3d7..9aceb201 100644 --- a/Sources/JExtractSwift/Logger.swift +++ b/Sources/JExtractSwift/Logger.swift @@ -88,21 +88,21 @@ public struct Logger { } let metadataString: String = - metadata.isEmpty ? "\(metadata)" : "" + metadata.isEmpty ? "" : "\(metadata)" print("[trace][\(file):\(line)](\(function)) \(message()) \(metadataString)") } } extension Logger { - public enum Level: Int, Hashable { - case trace = 0 - case debug = 1 - case info = 2 - case notice = 3 - case warning = 4 - case error = 5 - case critical = 6 + public enum Level: String, Hashable { + case trace = "trace" + case debug = "debug" + case info = "info" + case notice = "notice" + case warning = "warning" + case error = "error" + case critical = "critical" } } diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwift/Swift2Java.swift index e01143cc..2167c391 100644 --- a/Sources/JExtractSwift/Swift2Java.swift +++ b/Sources/JExtractSwift/Swift2Java.swift @@ -28,22 +28,27 @@ public struct SwiftToJava: ParsableCommand { @Option(help: "The package the generated Java code should be emitted into.") var packageName: String - @Option(name: .shortAndLong, help: "The directory in which to output the generated Swift files and manifest.") - var outputDirectory: String = ".build/jextract-swift/generated" + @Option( + name: .shortAndLong, + help: "The directory in which to output the generated Swift files and manifest.") + var outputDirectoryJava: String = ".build/jextract-swift/generated" - @Option(name: .long, help: "Name of the Swift module to import (and the swift interface files belong to)") + @Option(help: "Swift output directory") + var outputDirectorySwift: String + + @Option( + name: .long, + help: "Name of the Swift module to import (and the swift interface files belong to)") var swiftModule: String - // TODO: Once we ship this, make this `.warning` by default @Option(name: .shortAndLong, help: "Configure the level of lots that should be printed") - var logLevel: Logger.Level = .notice + var logLevel: Logger.Level = .info - @Argument(help: "The Swift interface files to export to Java.") - var swiftInterfaceFiles: [String] + @Argument(help: "The Swift files or directories to recursively export to Java.") + var input: [String] public func run() throws { - let interfaceFiles = self.swiftInterfaceFiles.dropFirst() - print("Interface files: \(interfaceFiles)") + let inputPaths = self.input.dropFirst().map { URL(string: $0)! } let translator = Swift2JavaTranslator( javaPackage: packageName, @@ -51,21 +56,41 @@ public struct SwiftToJava: ParsableCommand { ) translator.log.logLevel = logLevel - var fileNo = 1 - for interfaceFile in interfaceFiles { - print("[\(fileNo)/\(interfaceFiles.count)] Importing module '\(swiftModule)', interface file: \(interfaceFile)") - defer { fileNo += 1 } + var allFiles: [URL] = [] + let fileManager = FileManager.default + let log = translator.log + + for path in inputPaths { + log.debug("Input path: \(path)") + if isDirectory(url: path) { + if let enumerator = fileManager.enumerator(at: path, includingPropertiesForKeys: nil) { + for case let fileURL as URL in enumerator { + allFiles.append(fileURL) + } + } + } else if path.isFileURL { + allFiles.append(path) + } + } + + for file in allFiles where canExtract(from: file) { + translator.log.debug("Importing module '\(swiftModule)', file: \(file)") - try translator.analyze(swiftInterfacePath: interfaceFile) - try translator.writeImportedTypesTo(outputDirectory: outputDirectory) + try translator.analyze(file: file.path) + try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava) + try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift) - print("[\(fileNo)/\(interfaceFiles.count)] Imported interface file: \(interfaceFile) " + "done.".green) + log.debug("[swift-java] Imported interface file: \(file.path)") } - try translator.writeModuleTo(outputDirectory: outputDirectory) - print("") - print("Generated Java sources in package '\(packageName)' in: \(outputDirectory)/") - print("Swift module '\(swiftModule)' import: " + "done.".green) + try translator.writeExportedJavaModule(outputDirectory: outputDirectoryJava) + print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/") + print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green) + } + + func canExtract(from file: URL) -> Bool { + file.lastPathComponent.hasSuffix(".swift") || + file.lastPathComponent.hasSuffix(".swiftinterface") } } @@ -79,3 +104,9 @@ extension Logger.Level: ExpressibleByArgument { public private(set) static var defaultCompletionKind: CompletionKind = .default } + +func isDirectory(url: URL) -> Bool { + var isDirectory: ObjCBool = false + _ = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) + return isDirectory.boolValue +} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift index 4e8f6aa7..21d6946b 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift @@ -20,16 +20,20 @@ import SwiftSyntax extension Swift2JavaTranslator { public func javaMemoryLayoutDescriptors( forParametersOf decl: ImportedFunc, - selfVariant: SelfParameterVariant? + paramPassingStyle: SelfParameterVariant? ) -> [ForeignValueLayout] { var layouts: [ForeignValueLayout] = [] layouts.reserveCapacity(decl.parameters.count + 1) // // When the method is `init()` it does not accept a self (well, unless allocating init but we don't import those) - // let selfVariant: SelfParameterVariant? = + // let paramPassingStyle: SelfParameterVariant? = // decl.isInit ? nil : .wrapper - for param in decl.effectiveParameters(selfVariant: selfVariant) { + for param in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { + if param.type.cCompatibleJavaMemoryLayout == CCompatibleJavaMemoryLayout.primitive(.void) { + continue + } + layouts.append(param.type.foreignValueLayout) } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index c8b9bf09..cb9fd7ed 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -25,64 +25,125 @@ let PATH_SEPARATOR = "/" // TODO: Windows extension Swift2JavaTranslator { /// Every imported public type becomes a public class in its own file in Java. - public func writeImportedTypesTo(outputDirectory: String) throws { + public func writeExportedJavaSources(outputDirectory: String) throws { var printer = CodePrinter() + try writeExportedJavaSources(outputDirectory: outputDirectory, printer: &printer) + } + public func writeExportedJavaSources(outputDirectory: String, printer: inout CodePrinter) throws { for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { let filename = "\(ty.javaClassName).java" log.info("Printing contents: \(filename)") printImportedClass(&printer, ty) - try writeContents( - printer.finalize(), + if let outputFile = try printer.writeContents( outputDirectory: outputDirectory, javaPackagePath: javaPackagePath, filename: filename - ) + ) { + print("[swift-java] Generated: \(ty.javaClassName.bold).java (at \(outputFile))") + } + } + } + + public func writeSwiftThunkSources(outputDirectory: String) throws { + var printer = CodePrinter() + + try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer) + } + + public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws { + let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" + let moduleFilename = "\(moduleFilenameBase).swift" + do { + log.info("Printing contents: \(moduleFilename)") + + try printGlobalSwiftThunkSources(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: outputDirectory, + javaPackagePath: nil, + filename: moduleFilename) { + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)") + } + } catch { + log.warning("Failed to write to Swift thunks: \(moduleFilename)") + } + + // === All types + for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let fileNameBase = "\(ty.swiftTypeName)+SwiftJava" + let filename = "\(fileNameBase).swift" + log.info("Printing contents: \(filename)") + + do { + try printSwiftThunkSources(&printer, ty: ty) + + if let outputFile = try printer.writeContents( + outputDirectory: outputDirectory, + javaPackagePath: nil, + filename: filename) { + print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)") + } + } catch { + log.warning("Failed to write to Swift thunks: \(filename)") + } + } + } + + public func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { + let stt = SwiftThunkTranslator(self) + + for thunk in stt.renderGlobalThunks() { + printer.print(thunk) + printer.println() + } + } + + public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) { + let stt = SwiftThunkTranslator(self) + + for thunk in stt.render(forFunc: decl) { + printer.print(thunk) + printer.println() + } + } + + public func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws { + let stt = SwiftThunkTranslator(self) + + printer.print( + """ + // Generated by swift-java + + import SwiftKitSwift + + """ + ) + + for thunk in stt.renderThunks(forType: ty) { + printer.print("\(thunk)") + printer.print("") } } /// A module contains all static and global functions from the Swift module, /// potentially from across multiple swift interfaces. - public func writeModuleTo(outputDirectory: String) throws { + public func writeExportedJavaModule(outputDirectory: String) throws { var printer = CodePrinter() + try writeExportedJavaModule(outputDirectory: outputDirectory, printer: &printer) + } + + public func writeExportedJavaModule(outputDirectory: String, printer: inout CodePrinter) throws { printModule(&printer) - try writeContents( - printer.finalize(), + if let file = try printer.writeContents( outputDirectory: outputDirectory, javaPackagePath: javaPackagePath, filename: "\(swiftModuleName).java" - ) - } - - private func writeContents( - _ contents: String, - outputDirectory: String, - javaPackagePath: String, - filename: String - ) throws { - if outputDirectory == "-" { - print( - "// ==== ---------------------------------------------------------------------------------------------------" - ) - print("// \(javaPackagePath)/\(filename)") - print(contents) - return + ) { + self.log.info("Generated: \(file): \("done".green).") } - - let targetDirectory = [outputDirectory, javaPackagePath].joined(separator: PATH_SEPARATOR) - log.trace("Prepare target directory: \(targetDirectory)") - try FileManager.default.createDirectory(atPath: targetDirectory, withIntermediateDirectories: true) - - let targetFilePath = [javaPackagePath, filename].joined(separator: PATH_SEPARATOR) - print("Writing '\(targetFilePath)'...", terminator: "") - try contents.write( - to: Foundation.URL(fileURLWithPath: targetDirectory).appendingPathComponent(filename), - atomically: true, - encoding: .utf8 - ) - print(" done.".green) } } @@ -100,9 +161,6 @@ extension Swift2JavaTranslator { printImports(&printer) printModuleClass(&printer) { printer in - - printStaticLibraryLoad(&printer) - // TODO: print all "static" methods for decl in importedGlobalFuncs { printFunctionDowncallMethods(&printer, decl) @@ -116,14 +174,30 @@ extension Swift2JavaTranslator { printImports(&printer) printClass(&printer, decl) { printer in - // Ensure we have loaded the library where the Swift type was declared before we attempt to resolve types in Swift - printStaticLibraryLoad(&printer) - - // Prepare type metadata, we're going to need these when invoking e.g. initializers so cache them in a static - printer.print( + // Prepare type metadata, we're going to need these when invoking e.g. initializers so cache them in a static. + // We call into source swift-java source generated accessors which give us the type of the Swift object: + // TODO: seems we no longer need the mangled name per se, so avoiding such constant and downcall + // printer.printParts( + // "public static final String TYPE_MANGLED_NAME = ", + // SwiftKitPrinting.renderCallGetSwiftTypeMangledName(module: self.swiftModuleName, nominal: decl), + // ";" + // ) + + // We use a static field to abuse the initialization order such that by the time we get type metadata, + // we already have loaded the library where it will be obtained from. + printer.printParts( """ - public static final String TYPE_MANGLED_NAME = "\(decl.swiftMangledName ?? "")"; - public static final SwiftAnyType TYPE_METADATA = SwiftKit.getTypeByMangledNameInEnvironment(TYPE_MANGLED_NAME).get(); + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = initializeLibs(); + static boolean initializeLibs() { + System.loadLibrary(SwiftKit.STDLIB_DYLIB_NAME); + System.loadLibrary("SwiftKitSwift"); + System.loadLibrary(LIB_NAME); + return true; + } + + public static final SwiftAnyType TYPE_METADATA = + new SwiftAnyType(\(SwiftKitPrinting.renderCallGetSwiftType(module: self.swiftModuleName, nominal: decl))); public final SwiftAnyType $swiftType() { return TYPE_METADATA; } @@ -178,8 +252,11 @@ extension Swift2JavaTranslator { printer.print("") } - public func printClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void) { - printer.printTypeDecl("public final class \(decl.javaClassName) implements SwiftHeapObject") { printer in + public func printClass( + _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void + ) { + printer.printTypeDecl("public final class \(decl.javaClassName) implements SwiftHeapObject") { + printer in // ==== Storage of the class printClassSelfProperty(&printer, decl) @@ -190,9 +267,6 @@ extension Swift2JavaTranslator { // Layout of the class printClassMemoryLayout(&printer, decl) - // Render the 'trace' functions etc - printTraceFunctionDecls(&printer) - body(&printer) } } @@ -205,9 +279,6 @@ extension Swift2JavaTranslator { printClassConstants(printer: &printer) printTypeMappingDecls(&printer) - // Render the 'trace' functions etc - printTraceFunctionDecls(&printer) - printer.print( """ static MemorySegment findOrThrow(String symbol) { @@ -253,6 +324,11 @@ extension Swift2JavaTranslator { """ static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup(); private static SymbolLookup getSymbolLookup() { + // Ensure Swift and our Lib are loaded during static initialization of the class. + System.loadLibrary("swiftCore"); + System.loadLibrary("SwiftKitSwift"); + System.loadLibrary(LIB_NAME); + if (PlatformUtils.isMacOS()) { return SymbolLookup.libraryLookup(System.mapLibraryName(LIB_NAME), LIBRARY_ARENA) .or(SymbolLookup.loaderLookup()) @@ -284,6 +360,11 @@ extension Swift2JavaTranslator { private \(typeName)() { // Should not be called directly } + + // Static enum to force initialization + private static enum Initializer { + FORCE; // Refer to this to force outer Class initialization (and static{} blocks to trigger) + } """ ) } @@ -308,7 +389,7 @@ extension Swift2JavaTranslator { """ private static final GroupLayout $LAYOUT = MemoryLayout.structLayout( SWIFT_POINTER - ).withName("\(decl.swiftMangledName ?? decl.swiftTypeName)"); + ).withName("\(decl.swiftTypeName)"); public final GroupLayout $layout() { return $LAYOUT; @@ -356,42 +437,8 @@ extension Swift2JavaTranslator { ) } - public func printTraceFunctionDecls(_ printer: inout CodePrinter) { - printer.print( - """ - static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); - - static void traceDowncall(Object... args) { - var ex = new RuntimeException(); - - String traceArgs = Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")); - System.out.printf("[java][%s:%d] Downcall: %s(%s)\\n", - ex.getStackTrace()[1].getFileName(), - ex.getStackTrace()[1].getLineNumber(), - ex.getStackTrace()[1].getMethodName(), - traceArgs); - } - - static void trace(Object... args) { - var ex = new RuntimeException(); - - String traceArgs = Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")); - System.out.printf("[java][%s:%d] %s: %s\\n", - ex.getStackTrace()[1].getFileName(), - ex.getStackTrace()[1].getLineNumber(), - ex.getStackTrace()[1].getMethodName(), - traceArgs); - } - """ - ) - } - public func printClassConstructors(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let parentName = decl.parentName else { + guard let parentName = decl.parent else { fatalError("init must be inside a parent type! Was: \(decl)") } printer.printSeparator(decl.identifier) @@ -399,7 +446,7 @@ extension Swift2JavaTranslator { let descClassIdentifier = renderDescClassName(decl) printer.printTypeDecl("private static class \(descClassIdentifier)") { printer in printFunctionDescriptorValue(&printer, decl) - printFindMemorySegmentAddrByMangledName(&printer, decl) + printAccessorFunctionAddr(&printer, decl) printMethodDowncallHandleForAddrDesc(&printer) } @@ -420,8 +467,8 @@ extension Swift2JavaTranslator { * \(decl.renderCommentSnippet ?? " *") */ - public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) { - this(/*arena=*/null, \(renderForwardParams(decl, selfVariant: .wrapper))); + public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) { + this(/*arena=*/null, \(renderForwardJavaParams(decl, paramPassingStyle: .wrapper))); } """ ) @@ -434,14 +481,14 @@ extension Swift2JavaTranslator { * \(decl.renderCommentSnippet ?? " *") */ - public \(parentName.unqualifiedJavaTypeName)(SwiftArena arena, \(renderJavaParamDecls(decl, selfVariant: .wrapper))) { + public \(parentName.unqualifiedJavaTypeName)(SwiftArena arena, \(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) { var mh$ = \(descClassIdentifier).HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(\(renderForwardParams(decl, selfVariant: nil))); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: nil))); } - this.selfMemorySegment = (MemorySegment) mh$.invokeExact(\(renderForwardParams(decl, selfVariant: nil)), TYPE_METADATA.$memorySegment()); + this.selfMemorySegment = (MemorySegment) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: nil)), TYPE_METADATA.$memorySegment()); if (arena != null) { arena.register(this); } @@ -453,23 +500,12 @@ extension Swift2JavaTranslator { ) } - public func printStaticLibraryLoad(_ printer: inout CodePrinter) { - printer.print( - """ - static { - System.loadLibrary("swiftCore"); - System.loadLibrary(LIB_NAME); - } - """ - ) - } - public func printFunctionDowncallMethods(_ printer: inout CodePrinter, _ decl: ImportedFunc) { printer.printSeparator(decl.identifier) printer.printTypeDecl("private static class \(decl.baseIdentifier)") { printer in - printFunctionDescriptorValue(&printer, decl); - printFindMemorySegmentAddrByMangledName(&printer, decl) + printFunctionDescriptorValue(&printer, decl) + printAccessorFunctionAddr(&printer, decl) printMethodDowncallHandleForAddrDesc(&printer) } @@ -479,16 +515,18 @@ extension Swift2JavaTranslator { // Render the basic "make the downcall" function if decl.hasParent { - printFuncDowncallMethod(&printer, decl: decl, selfVariant: .memorySegment) - printFuncDowncallMethod(&printer, decl: decl, selfVariant: .wrapper) + printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: .memorySegment) + printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: .wrapper) } else { - printFuncDowncallMethod(&printer, decl: decl, selfVariant: nil) + printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: nil) } } - private func printFunctionAddressMethod(_ printer: inout CodePrinter, - decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil) { + private func printFunctionAddressMethod( + _ printer: inout CodePrinter, + decl: ImportedFunc, + accessorKind: VariableAccessorKind? = nil + ) { let addrName = accessorKind.renderAddrFieldName let methodNameSegment = accessorKind.renderMethodNameSegment @@ -507,9 +545,11 @@ extension Swift2JavaTranslator { ) } - private func printFunctionMethodHandleMethod(_ printer: inout CodePrinter, - decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil) { + private func printFunctionMethodHandleMethod( + _ printer: inout CodePrinter, + decl: ImportedFunc, + accessorKind: VariableAccessorKind? = nil + ) { let handleName = accessorKind.renderHandleFieldName let methodNameSegment = accessorKind.renderMethodNameSegment let snippet = decl.renderCommentSnippet ?? "* " @@ -527,9 +567,11 @@ extension Swift2JavaTranslator { ) } - private func printFunctionDescriptorMethod(_ printer: inout CodePrinter, - decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil) { + private func printFunctionDescriptorMethod( + _ printer: inout CodePrinter, + decl: ImportedFunc, + accessorKind: VariableAccessorKind? = nil + ) { let descName = accessorKind.renderDescFieldName let methodNameSegment = accessorKind.renderMethodNameSegment let snippet = decl.renderCommentSnippet ?? "* " @@ -557,8 +599,8 @@ extension Swift2JavaTranslator { continue } - printFunctionDescriptorValue(&printer, accessor, accessorKind: accessorKind); - printFindMemorySegmentAddrByMangledName(&printer, accessor, accessorKind: accessorKind) + printFunctionDescriptorValue(&printer, accessor, accessorKind: accessorKind) + printAccessorFunctionAddr(&printer, accessor, accessorKind: accessorKind) printMethodDowncallHandleForAddrDesc(&printer, accessorKind: accessorKind) } } @@ -583,24 +625,36 @@ extension Swift2JavaTranslator { // Render the basic "make the downcall" function if decl.hasParent { - printFuncDowncallMethod(&printer, decl: accessor, selfVariant: .memorySegment, accessorKind: accessorKind) - printFuncDowncallMethod(&printer, decl: accessor, selfVariant: .wrapper, accessorKind: accessorKind) + printFuncDowncallMethod( + &printer, decl: accessor, paramPassingStyle: .memorySegment, accessorKind: accessorKind) + printFuncDowncallMethod( + &printer, decl: accessor, paramPassingStyle: .wrapper, accessorKind: accessorKind) } else { - printFuncDowncallMethod(&printer, decl: accessor, selfVariant: nil, accessorKind: accessorKind) + printFuncDowncallMethod( + &printer, decl: accessor, paramPassingStyle: nil, accessorKind: accessorKind) } } } - func printFindMemorySegmentAddrByMangledName(_ printer: inout CodePrinter, _ decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil) { + func printAccessorFunctionAddr( + _ printer: inout CodePrinter, _ decl: ImportedFunc, + accessorKind: VariableAccessorKind? = nil + ) { +// var thunkName = SwiftKitPrinting.Names.functionThunk( +// thunkNameRegistry: &self.thunkNameRegistry, +// module: self.swiftModuleName, function: decl) + let thunkName = thunkNameRegistry.functionThunkName(module: self.swiftModuleName, decl: decl) printer.print( """ - public static final MemorySegment \(accessorKind.renderAddrFieldName) = \(swiftModuleName).findOrThrow("\(decl.swiftMangledName)"); + public static final MemorySegment \(accessorKind.renderAddrFieldName) = + \(self.swiftModuleName).findOrThrow("\(thunkName)"); """ - ); + ) } - func printMethodDowncallHandleForAddrDesc(_ printer: inout CodePrinter, accessorKind: VariableAccessorKind? = nil) { + func printMethodDowncallHandleForAddrDesc( + _ printer: inout CodePrinter, accessorKind: VariableAccessorKind? = nil + ) { printer.print( """ public static final MethodHandle \(accessorKind.renderHandleFieldName) = Linker.nativeLinker().downcallHandle(\(accessorKind.renderAddrFieldName), \(accessorKind.renderDescFieldName)); @@ -611,7 +665,7 @@ extension Swift2JavaTranslator { public func printFuncDowncallMethod( _ printer: inout CodePrinter, decl: ImportedFunc, - selfVariant: SelfParameterVariant?, + paramPassingStyle: SelfParameterVariant?, accessorKind: VariableAccessorKind? = nil ) { let returnTy = decl.returnType.javaType @@ -635,13 +689,13 @@ extension Swift2JavaTranslator { // An identifier may be "getX", "setX" or just the plain method name let identifier = accessorKind.renderMethodName(decl) - if selfVariant == SelfParameterVariant.wrapper { + if paramPassingStyle == SelfParameterVariant.wrapper { // delegate to the MemorySegment "self" accepting overload printer.print( """ \(javaDocComment) - public \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) { - \(maybeReturnCast) \(identifier)(\(renderForwardParams(decl, selfVariant: .wrapper))); + public \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) { + \(maybeReturnCast) \(identifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper))); } """ ) @@ -654,7 +708,7 @@ extension Swift2JavaTranslator { printer.printParts( """ \(javaDocComment) - public static \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, selfVariant: selfVariant))) { + public static \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: paramPassingStyle))) { var mh$ = \(decl.baseIdentifier).\(handleName); \(renderTry(withArena: needsArena)) """, @@ -662,10 +716,10 @@ extension Swift2JavaTranslator { \(renderUpcallHandles(decl)) """, """ - if (TRACE_DOWNCALLS) { - traceDowncall(\(renderForwardParams(decl, selfVariant: .memorySegment))); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: .memorySegment))); } - \(maybeReturnCast) mh$.invokeExact(\(renderForwardParams(decl, selfVariant: selfVariant))); + \(maybeReturnCast) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: paramPassingStyle))); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -677,7 +731,7 @@ extension Swift2JavaTranslator { public func printPropertyAccessorDowncallMethod( _ printer: inout CodePrinter, decl: ImportedFunc, - selfVariant: SelfParameterVariant? + paramPassingStyle: SelfParameterVariant? ) { let returnTy = decl.returnType.javaType @@ -688,7 +742,7 @@ extension Swift2JavaTranslator { maybeReturnCast = "return (\(returnTy))" } - if selfVariant == SelfParameterVariant.wrapper { + if paramPassingStyle == SelfParameterVariant.wrapper { // delegate to the MemorySegment "self" accepting overload printer.print( """ @@ -697,8 +751,8 @@ extension Swift2JavaTranslator { * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") * } */ - public \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: .wrapper))) { - \(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardParams(decl, selfVariant: .wrapper))); + public \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) { + \(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper))); } """ ) @@ -712,13 +766,13 @@ extension Swift2JavaTranslator { * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") * } */ - public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, selfVariant: selfVariant))) { + public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, paramPassingStyle: paramPassingStyle))) { var mh$ = \(decl.baseIdentifier).HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(\(renderForwardParams(decl, selfVariant: .memorySegment))); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: .memorySegment))); } - \(maybeReturnCast) mh$.invokeExact(\(renderForwardParams(decl, selfVariant: selfVariant))); + \(maybeReturnCast) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: paramPassingStyle))); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -737,7 +791,7 @@ extension Swift2JavaTranslator { return "p\(pCounter)" } - for p in decl.effectiveParameters(selfVariant: nil) { + for p in decl.effectiveParameters(paramPassingStyle: nil) { let param = "\(p.effectiveName ?? nextUniqueParamName())" ps.append(param) } @@ -758,7 +812,7 @@ extension Swift2JavaTranslator { return false } - + public func renderTry(withArena: Bool) -> String { if withArena { "try (Arena arena = Arena.ofConfined()) {" @@ -767,7 +821,7 @@ extension Swift2JavaTranslator { } } - public func renderJavaParamDecls(_ decl: ImportedFunc, selfVariant: SelfParameterVariant?) -> String { + public func renderJavaParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { var ps: [String] = [] var pCounter = 0 @@ -776,7 +830,7 @@ extension Swift2JavaTranslator { return "p\(pCounter)" } - for p in decl.effectiveParameters(selfVariant: selfVariant) { + for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { let param = "\(p.type.javaType.description) \(p.effectiveName ?? nextUniqueParamName())" ps.append(param) } @@ -785,12 +839,53 @@ extension Swift2JavaTranslator { return res } + // TODO: these are stateless, find new place for them? + public func renderSwiftParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { + var ps: [String] = [] + var pCounter = 0 + + func nextUniqueParamName() -> String { + pCounter += 1 + return "p\(pCounter)" + } + + for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { + let firstName = p.firstName ?? "_" + let secondName = p.secondName ?? p.firstName ?? nextUniqueParamName() + + let paramTy: String = + if paramPassingStyle == .swiftThunkSelf { + "\(p.type.cCompatibleSwiftType)" + } else { + p.type.swiftTypeName.description + } + + let param = + if firstName == secondName { + // We have to do this to avoid a 'extraneous duplicate parameter name; 'number' already has an argument label' warning + "\(firstName): \(paramTy)" + } else { + "\(firstName) \(secondName): \(paramTy)" + } + ps.append(param) + } + + if paramPassingStyle == .swiftThunkSelf { + ps.append("_self: UnsafeMutableRawPointer") +// ps.append("_self: Any") + } + + let res = ps.joined(separator: ", ") + return res + } + public func renderUpcallHandles(_ decl: ImportedFunc) -> String { var printer = CodePrinter() for p in decl.parameters where p.type.javaType.isSwiftClosure { if p.type.javaType == .javaLangRunnable { let paramName = p.secondName ?? p.firstName ?? "_" - let handleDesc = p.type.javaType.prepareClosureDowncallHandle(decl: decl, parameter: paramName) + let handleDesc = p.type.javaType.prepareClosureDowncallHandle( + decl: decl, parameter: paramName) printer.print(handleDesc) } } @@ -798,7 +893,7 @@ extension Swift2JavaTranslator { return printer.contents } - public func renderForwardParams(_ decl: ImportedFunc, selfVariant: SelfParameterVariant?) -> String { + public func renderForwardJavaParams(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { var ps: [String] = [] var pCounter = 0 @@ -807,12 +902,12 @@ extension Swift2JavaTranslator { return "p\(pCounter)" } - for p in decl.effectiveParameters(selfVariant: selfVariant) { + for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { // FIXME: fix the handling here we're already a memory segment let param: String if p.effectiveName == "self$" { - precondition(selfVariant == .memorySegment) - param = "self$"; + precondition(paramPassingStyle == .memorySegment) + param = "self$" } else { param = "\(p.renderParameterForwarding() ?? nextUniqueParamName())" } @@ -820,30 +915,52 @@ extension Swift2JavaTranslator { } // Add the forwarding "self" - if selfVariant == .wrapper && !decl.isInit { + if paramPassingStyle == .wrapper && !decl.isInit { ps.append("$memorySegment()") } return ps.joined(separator: ", ") } + // TODO: these are stateless, find new place for them? + public func renderForwardSwiftParams(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { + var ps: [String] = [] + var pCounter = 0 + + func nextUniqueParamName() -> String { + pCounter += 1 + return "p\(pCounter)" + } + + for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { + if let firstName = p.firstName { + ps.append("\(firstName): \(p.effectiveName ?? nextUniqueParamName())") + } else { + ps.append("\(p.effectiveName ?? nextUniqueParamName())") + } + } + + return ps.joined(separator: ", ") + } + public func printFunctionDescriptorValue( _ printer: inout CodePrinter, _ decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil) { + accessorKind: VariableAccessorKind? = nil + ) { let fieldName = accessorKind.renderDescFieldName printer.start("public static final FunctionDescriptor \(fieldName) = ") let parameterLayoutDescriptors = javaMemoryLayoutDescriptors( forParametersOf: decl, - selfVariant: .pointer + paramPassingStyle: .pointer ) if decl.returnType.javaType == .void { - printer.print("FunctionDescriptor.ofVoid("); + printer.print("FunctionDescriptor.ofVoid(") printer.indent() } else { - printer.print("FunctionDescriptor.of("); + printer.print("FunctionDescriptor.of(") printer.indent() printer.print("", .continue) @@ -851,7 +968,9 @@ extension Swift2JavaTranslator { let returnTyIsLastTy = decl.parameters.isEmpty && !decl.hasParent if decl.isInit { // when initializing, we return a pointer to the newly created object - printer.print("/* -> */\(ForeignValueLayout.SwiftPointer)", .parameterNewlineSeparator(returnTyIsLastTy)) + printer.print( + "/* -> */\(ForeignValueLayout.SwiftPointer)", .parameterNewlineSeparator(returnTyIsLastTy) + ) } else { var returnDesc = decl.returnType.foreignValueLayout returnDesc.inlineComment = " -> " @@ -864,11 +983,13 @@ extension Swift2JavaTranslator { printer.print(desc, .parameterNewlineSeparator(isLast)) } - printer.outdent(); - printer.print(");"); + printer.outdent() + printer.print(");") } - public func printHeapObjectToStringMethod(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + public func printHeapObjectToStringMethod( + _ printer: inout CodePrinter, _ decl: ImportedNominalType + ) { printer.print( """ @Override diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index a954620b..9894244b 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -36,15 +36,16 @@ public final class Swift2JavaTranslator { // ==== Output state - // TODO: consider how/if we need to store those etc - public var importedGlobalFuncs: [ImportedFunc] = [] + package var importedGlobalFuncs: [ImportedFunc] = [] /// A mapping from Swift type names (e.g., A.B) over to the imported nominal /// type representation. - public var importedTypes: [String: ImportedNominalType] = [:] + package var importedTypes: [String: ImportedNominalType] = [:] let nominalResolution: NominalTypeResolution = NominalTypeResolution() + var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry() + public init( javaPackage: String, swiftModuleName: String @@ -68,31 +69,22 @@ extension Swift2JavaTranslator { var javaPrimitiveForSwiftInt: JavaType { .long } public func analyze( - swiftInterfacePath: String, + file: String, text: String? = nil ) throws { - if text == nil { - precondition( - swiftInterfacePath.hasSuffix(Self.SWIFT_INTERFACE_SUFFIX), - "Swift interface path must end with \(Self.SWIFT_INTERFACE_SUFFIX), was: \(swiftInterfacePath)" - ) - - if !FileManager.default.fileExists(atPath: swiftInterfacePath) { - throw Swift2JavaTranslatorError(message: "Missing input file: \(swiftInterfacePath)") - } + guard text != nil || FileManager.default.fileExists(atPath: file) else { + throw Swift2JavaTranslatorError(message: "Missing input file: \(file)") } - log.trace("Analyze: \(swiftInterfacePath)") - let text = try text ?? String(contentsOfFile: swiftInterfacePath) + log.trace("Analyze: \(file)") + let text = try text ?? String(contentsOfFile: file) - try analyzeSwiftInterface(interfaceFilePath: swiftInterfacePath, text: text) + try analyzeSwiftInterface(interfaceFilePath: file, text: text) - log.info("Done processing: \(swiftInterfacePath)") + log.debug("Done processing: \(file)") } package func analyzeSwiftInterface(interfaceFilePath: String, text: String) throws { - assert(interfaceFilePath.hasSuffix(Self.SWIFT_INTERFACE_SUFFIX)) - let sourceFileSyntax = Parser.parse(source: text) // Find all of the types and extensions, then bind the extensions. @@ -168,7 +160,6 @@ extension Swift2JavaTranslator { package: javaPackage, name: fullName ), - swiftMangledName: nominal.mangledNameFromComment, kind: kind ) diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 8cf39eab..3feceec3 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -50,7 +50,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { override func visitPost(_ node: ClassDeclSyntax) { if currentTypeName != nil { - log.info("Completed import: \(node.kind) \(node.name)") + log.debug("Completed import: \(node.kind) \(node.name)") currentTypeName = nil } } @@ -59,7 +59,8 @@ final class Swift2JavaVisitor: SyntaxVisitor { // Resolve the extended type of the extension as an imported nominal, and // recurse if we found it. guard let nominal = translator.nominalResolution.extendedType(of: node), - let importedNominalType = translator.importedNominalType(nominal) else { + let importedNominalType = translator.importedNominalType(nominal) + else { return .skipChildren } @@ -94,7 +95,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { // TODO: more robust parameter handling // TODO: More robust type handling return ImportedParam( - param: param, + syntax: param, type: try cCompatibleType(for: param.type) ) } @@ -107,18 +108,14 @@ final class Swift2JavaVisitor: SyntaxVisitor { let fullName = "\(node.name.text)" - var funcDecl = ImportedFunc( - parentName: currentTypeName.map { translator.importedTypes[$0] }??.translatedType, + let funcDecl = ImportedFunc( + module: self.translator.swiftModuleName, + decl: node.trimmed, + parent: currentTypeName.map { translator.importedTypes[$0] }??.translatedType, identifier: fullName, returnType: javaResultType, parameters: params ) - funcDecl.syntax = "\(node.trimmed)" // TODO: rethink this, it's useful for comments in Java - - // Retrieve the mangled name, if available. - if let mangledName = node.mangledNameFromComment { - funcDecl.swiftMangledName = mangledName - } if let currentTypeName { log.debug("Record method in \(currentTypeName)") @@ -139,10 +136,10 @@ final class Swift2JavaVisitor: SyntaxVisitor { // TODO: filter out kinds of variables we cannot import - self.log.info("Import variable: \(node.kind) \(fullName)") + self.log.debug("Import variable: \(node.kind) \(fullName)") let returnTy: TypeSyntax - if let typeAnnotation = binding.typeAnnotation{ + if let typeAnnotation = binding.typeAnnotation { returnTy = typeAnnotation.type } else { returnTy = "Swift.Void" @@ -157,6 +154,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { } var varDecl = ImportedVariable( + module: self.translator.swiftModuleName, parentName: currentTypeName.map { translator.importedTypes[$0] }??.translatedType, identifier: fullName, returnType: javaResultType @@ -169,7 +167,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { } if let currentTypeName { - log.info("Record variable in \(currentTypeName)") + log.debug("Record variable in \(currentTypeName)") translator.importedTypes[currentTypeName]!.variables.append(varDecl) } else { fatalError("Global variables are not supported yet: \(node.debugDescription)") @@ -180,21 +178,22 @@ final class Swift2JavaVisitor: SyntaxVisitor { override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard let currentTypeName, - let currentType = translator.importedTypes[currentTypeName] else { + let currentType = translator.importedTypes[currentTypeName] + else { fatalError("Initializer must be within a current type, was: \(node)") } guard node.shouldImport(log: log) else { return .skipChildren } - self.log.info("Import initializer: \(node.kind) \(currentType.javaType.description)") + self.log.debug("Import initializer: \(node.kind) \(currentType.javaType.description)") let params: [ImportedParam] do { params = try node.signature.parameterClause.parameters.map { param in // TODO: more robust parameter handling // TODO: More robust type handling return ImportedParam( - param: param, + syntax: param, type: try cCompatibleType(for: param.type) ) } @@ -207,24 +206,25 @@ final class Swift2JavaVisitor: SyntaxVisitor { "init(\(String(params.flatMap { "\($0.effectiveName ?? "_"):" })))" var funcDecl = ImportedFunc( - parentName: currentType.translatedType, + module: self.translator.swiftModuleName, + decl: node.trimmed, + parent: currentType.translatedType, identifier: initIdentifier, returnType: currentType.translatedType, parameters: params ) funcDecl.isInit = true - funcDecl.syntax = "\(node.trimmed)" // TODO: rethink this, it's useful for comments in Java - // Retrieve the mangled name, if available. - if let mangledName = node.mangledNameFromComment { - funcDecl.swiftMangledName = mangledName - } - - log.info("Record initializer method in \(currentType.javaType.description): \(funcDecl.identifier)") + log.debug( + "Record initializer method in \(currentType.javaType.description): \(funcDecl.identifier)") translator.importedTypes[currentTypeName]!.initializers.append(funcDecl) return .skipChildren } + + override func visit(_ node: DeinitializerDeclSyntax) -> SyntaxVisitorContinueKind { + return .skipChildren + } } extension DeclGroupSyntax where Self: NamedDeclSyntax { diff --git a/Sources/JExtractSwift/SwiftKit+Printing.swift b/Sources/JExtractSwift/SwiftKit+Printing.swift new file mode 100644 index 00000000..ed7d05d6 --- /dev/null +++ b/Sources/JExtractSwift/SwiftKit+Printing.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation +import SwiftBasicFormat +import SwiftParser +import SwiftSyntax + +/// Helper for printing calls into SwiftKit generated code from generated sources. +package struct SwiftKitPrinting { + + /// Forms syntax for a Java call to a swiftkit exposed function. + static func renderCallGetSwiftType(module: String, nominal: ImportedNominalType) -> String { + """ + SwiftKit.swiftjava.getType("\(module)", "\(nominal.swiftTypeName)") + """ + } +} + +// ==== ------------------------------------------------------------------------ +// Helpers to form names of "well known" SwiftKit generated functions + +extension SwiftKitPrinting { + enum Names { + } +} + +extension SwiftKitPrinting.Names { + static func getType(module: String, nominal: ImportedNominalType) -> String { + "swiftjava_getType_\(module)_\(nominal.swiftTypeName)" + } + +} diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift new file mode 100644 index 00000000..5fc01279 --- /dev/null +++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift @@ -0,0 +1,136 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import Foundation +import SwiftBasicFormat +import SwiftParser +import SwiftSyntax + +struct SwiftThunkTranslator { + + let st: Swift2JavaTranslator + + init(_ st: Swift2JavaTranslator) { + self.st = st + } + + func renderGlobalThunks() -> [DeclSyntax] { + var decls: [DeclSyntax] = [] + + for decl in st.importedGlobalFuncs { + decls.append(contentsOf: render(forFunc: decl)) + } + + return decls + } + + /// Render all the thunks that make Swift methods accessible to Java. + func renderThunks(forType nominal: ImportedNominalType) -> [DeclSyntax] { + var decls: [DeclSyntax] = [] + decls.reserveCapacity(nominal.initializers.count + nominal.methods.count) + + decls.append(renderSwiftTypeAccessor(nominal)) + + for decl in nominal.initializers { + decls.append(contentsOf: renderSwiftInitAccessor(decl)) + } + + for decl in nominal.methods { + decls.append(contentsOf: render(forFunc: decl)) + } + +// for v in nominal.variables { +// if let acc = v.accessorFunc(kind: .get) { +// decls.append(contentsOf: render(forFunc: acc)) +// } +// if let acc = v.accessorFunc(kind: .set) { +// decls.append(contentsOf: render(forFunc: acc)) +// } +// } + + return decls + } + + /// Accessor to get the `T.self` of the Swift type, without having to rely on mangled name lookups. + func renderSwiftTypeAccessor(_ nominal: ImportedNominalType) -> DeclSyntax { + let funcName = SwiftKitPrinting.Names.getType( + module: st.swiftModuleName, + nominal: nominal) + + return + """ + @_cdecl("\(raw: funcName)") + public func \(raw: funcName)() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(\(raw: nominal.swiftTypeName).self, to: UnsafeMutableRawPointer.self) + } + """ + } + + func renderSwiftInitAccessor(_ function: ImportedFunc) -> [DeclSyntax] { + guard let parent = function.parent else { + fatalError("Cannot render initializer accessor if init function has no parent! Was: \(function)") + } + + let thunkName = self.st.thunkNameRegistry.functionThunkName( + module: st.swiftModuleName, decl: function) + + return + [ + """ + @_cdecl("\(raw: thunkName)") + public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil))) -> UnsafeMutableRawPointer /* \(raw: parent.swiftTypeName) */ { + let _self = \(raw: parent.swiftTypeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil))) + let self$ = unsafeBitCast(_self, to: UnsafeMutableRawPointer.self) + return _swiftjava_swift_retain(object: self$) + } + """ + ] + } + + func render(forFunc decl: ImportedFunc) -> [DeclSyntax] { + st.log.trace("Rendering thunks for: \(decl.baseIdentifier)") + let thunkName = st.thunkNameRegistry.functionThunkName(module: st.swiftModuleName, decl: decl) + + // Do we need to pass a self parameter? + let paramPassingStyle: SelfParameterVariant? + let callBaseDot: String + if let parent = decl.parent { + paramPassingStyle = .swiftThunkSelf + callBaseDot = "unsafeBitCast(_self, to: \(parent.originalSwiftType).self)." + // callBaseDot = "(_self as! \(parent.originalSwiftType))." + } else { + paramPassingStyle = nil + callBaseDot = "" + } + + let returnArrowTy = + if decl.returnType.cCompatibleJavaMemoryLayout == .primitive(.void) { + "/* \(decl.returnType.swiftTypeName) */" + } else { + "-> \(decl.returnType.cCompatibleSwiftType) /* \(decl.returnType.swiftTypeName) */" + } + + // FIXME: handle in thunk: errors + + return + [ + """ + @_cdecl("\(raw: thunkName)") + public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(decl, paramPassingStyle: paramPassingStyle))) \(raw: returnArrowTy) { + return \(raw: callBaseDot)\(raw: decl.baseIdentifier)(\(raw: st.renderForwardSwiftParams(decl, paramPassingStyle: paramPassingStyle))) + } + """ + ] + } +} diff --git a/Sources/JExtractSwift/TerminalColors.swift b/Sources/JExtractSwift/TerminalColors.swift index fcef5a3c..6170e2bc 100644 --- a/Sources/JExtractSwift/TerminalColors.swift +++ b/Sources/JExtractSwift/TerminalColors.swift @@ -21,6 +21,7 @@ package enum Rainbow: String { case magenta = "\u{001B}[0;35m" case cyan = "\u{001B}[0;36m" case white = "\u{001B}[0;37m" + case bold = "\u{001B}[1m" case `default` = "\u{001B}[0;0m" func name() -> String { @@ -33,6 +34,7 @@ package enum Rainbow: String { case .magenta: return "Magenta" case .cyan: return "Cyan" case .white: return "White" + case .bold: return "Bold" case .default: return "Default" } } @@ -127,6 +129,17 @@ package extension String { } } + var bold: String { + self.colored(as: .bold) + } + func bold(if condition: Bool) -> String { + if condition { + self.colored(as: .bold) + } else { + self + } + } + var `default`: String { self.colored(as: .default) } @@ -169,6 +182,10 @@ package extension Substring { self.colored(as: .white) } + var bold: String { + self.colored(as: .bold) + } + var `default`: String { self.colored(as: .default) } diff --git a/Sources/JExtractSwift/ThunkNameRegistry.swift b/Sources/JExtractSwift/ThunkNameRegistry.swift new file mode 100644 index 00000000..92f1397d --- /dev/null +++ b/Sources/JExtractSwift/ThunkNameRegistry.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +/// Registry of names we've already emitted as @_cdecl and must be kept unique. +/// In order to avoid duplicate symbols, the registry can append some unique identifier to duplicated names +package struct ThunkNameRegistry { + /// Maps base names such as "swiftjava_Module_Type_method_a_b_c" to the number of times we've seen them. + /// This is used to de-duplicate symbols as we emit them. + private var registry: [ImportedFunc: String] = [:] + private var duplicateNames: [String: Int] = [:] + + package init() {} + + package mutating func functionThunkName( + module: String, decl: ImportedFunc, + file: String = #fileID, line: UInt = #line) -> String { + if let existingName = self.registry[decl] { + return existingName + } + + let params = decl.effectiveParameters(paramPassingStyle: .swiftThunkSelf) + var paramsPart = "" + if !params.isEmpty { + paramsPart = "_" + params.map { param in + param.firstName ?? "_" + }.joined(separator: "_") + } + + + + let name = + if let parent = decl.parent { + "swiftjava_\(module)_\(parent.swiftTypeName)_\(decl.baseIdentifier)\(paramsPart)" + } else { + "swiftjava_\(module)_\(decl.baseIdentifier)\(paramsPart)" + } + + let emittedCount = self.duplicateNames[name, default: 0] + defer { self.duplicateNames[name] = emittedCount + 1 } + + let deduplicatedName = + if emittedCount == 0 { + name // first occurrence of a name we keep as-is + } else { + "\(name)$\(emittedCount)" + } + + // Store the name we assigned to this specific decl. + self.registry[decl] = deduplicatedName + return deduplicatedName + } +} diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index ca1ecf73..9dc12e07 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -55,12 +55,7 @@ extension Swift2JavaVisitor { // Translate the generic arguments to the C-compatible types. let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in try genericArgumentClause.arguments.map { argument in - switch argument.argument { - case .type(let argumentType): - try cCompatibleType(for: argumentType) - @unknown default: - throw TypeTranslationError.unimplementedType(TypeSyntax(memberType)) - } + try cCompatibleType(for: argument.argument) } } @@ -76,12 +71,7 @@ extension Swift2JavaVisitor { // Translate the generic arguments to the C-compatible types. let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in try genericArgumentClause.arguments.map { argument in - switch argument.argument { - case .type(let argumentType): - try cCompatibleType(for: argumentType) - @unknown default: - throw TypeTranslationError.unimplementedType(TypeSyntax(identifierType)) - } + try cCompatibleType(for: argument.argument) } } @@ -256,7 +246,7 @@ extension TranslatedType { } /// Describes the C-compatible layout as it should be referenced from Java. -enum CCompatibleJavaMemoryLayout { +enum CCompatibleJavaMemoryLayout: Hashable { /// A primitive Java type that has a direct counterpart in C. case primitive(JavaType) @@ -288,7 +278,7 @@ extension TranslatedType { case .long: return .SwiftInt64 case .float: return .SwiftFloat case .double: return .SwiftDouble - case .array, .class, .void: fatalError("Not a primitive type") + case .array, .class, .void: fatalError("Not a primitive type: \(cCompatibleJavaMemoryLayout) in \(self)") } case .int: diff --git a/Sources/Java2SwiftLib/JavaClassTranslator.swift b/Sources/Java2SwiftLib/JavaClassTranslator.swift index b90a538a..b0b0628f 100644 --- a/Sources/Java2SwiftLib/JavaClassTranslator.swift +++ b/Sources/Java2SwiftLib/JavaClassTranslator.swift @@ -518,7 +518,9 @@ extension JavaClassTranslator { preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) - if resultType != "Void" { + + // FIXME: cleanup the checking here + if resultType != "Void" && resultType != "Swift.Void" { resultTypeStr = " -> \(resultType)" } else { resultTypeStr = "" diff --git a/Sources/SwiftKitSwift/SwiftKit.swift b/Sources/SwiftKitSwift/SwiftKit.swift index 9fdcdd80..38b3c1c8 100644 --- a/Sources/SwiftKitSwift/SwiftKit.swift +++ b/Sources/SwiftKitSwift/SwiftKit.swift @@ -12,12 +12,6 @@ // //===----------------------------------------------------------------------===// -// This is a "plain Swift" file containing various types of declarations, -// that is exported to Java by using the `jextract-swift` tool. -// -// No annotations are necessary on the Swift side to perform the export. - -// FIXME: this is a workaround until we can pass String to Swift directly @_silgen_name("getTypeByStringByteArray") public func getTypeByStringByteArray(_ name: UnsafePointer) -> Any.Type? { let string = String(cString: name) @@ -26,9 +20,25 @@ public func getTypeByStringByteArray(_ name: UnsafePointer) -> Any.Type? return type } -//// FIXME: this is internal in stdlib, it would make things easier here -//@_silgen_name("swift_stdlib_getTypeByMangledNameUntrusted") -//public func _getTypeByMangledNameUntrusted( -// _ name: UnsafePointer, -// _ nameLength: UInt) -// -> Any.Type? +@_silgen_name("swift_retain") +public func _swiftjava_swift_retain(object: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer + +@_silgen_name("swift_release") +public func _swiftjava_swift_release(object: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer + +@_silgen_name("swift_retainCount") +public func _swiftjava_swift_retainCount(object: UnsafeMutableRawPointer) -> Int + +@_silgen_name("swift_isUniquelyReferenced") +public func _swiftjava_swift_isUniquelyReferenced(object: UnsafeMutableRawPointer) -> Bool + + + @_alwaysEmitIntoClient @_transparent + internal func _swiftjava_withHeapObject( + of object: AnyObject, + _ body: (UnsafeMutableRawPointer) -> R + ) -> R { + defer { _fixLifetime(object) } + let unmanaged = Unmanaged.passUnretained(object) + return body(unmanaged.toOpaque()) + } diff --git a/SwiftKit/build.gradle b/SwiftKit/build.gradle index 3479e6c8..e0896e66 100644 --- a/SwiftKit/build.gradle +++ b/SwiftKit/build.gradle @@ -40,3 +40,32 @@ tasks.test { events("passed", "skipped", "failed") } } + +// SwiftKit depends on SwiftKitSwift (Swift library that this Java library calls into) + +def compileSwift = tasks.register("compileSwift", Exec) { + description "Compile the swift-java SwiftKitSwift dynamic library that SwiftKit (Java) calls into" + + inputs.file(new File(rootDir, "Package.swift")) + inputs.dir(new File(rootDir, "Sources/")) // a bit generous, but better safe than sorry, and include all Swift source changes + outputs.dir(new File(rootDir, ".build")) + + workingDir = rootDir + commandLine "swift" + args("build", "--target", "SwiftKitSwift") +} +tasks.build { + dependsOn("compileSwift") +} + + + +def cleanSwift = tasks.register("cleanSwift", Exec) { + workingDir = rootDir + commandLine "swift" + args("package", "clean") +} +tasks.clean { + dependsOn("cleanSwift") +} + diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java index 6dcd1f6b..cf7cc238 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java @@ -27,11 +27,11 @@ public final class SwiftAnyType { private final MemorySegment memorySegment; public SwiftAnyType(MemorySegment memorySegment) { - if (memorySegment.byteSize() == 0) { - throw new IllegalArgumentException("A Swift Any.Type cannot be null!"); - } +// if (SwiftKit.getSwiftInt(memorySegment, 0) > 0) { +// throw new IllegalArgumentException("A Swift Any.Type cannot be null!"); +// } - this.memorySegment = memorySegment; + this.memorySegment = memorySegment.asReadOnly(); } public SwiftAnyType(SwiftHeapObject object) { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java index 064be4ec..1951c23e 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java @@ -27,15 +27,23 @@ public class SwiftKit { - private static final String STDLIB_DYLIB_NAME = "swiftCore"; + public static final String STDLIB_DYLIB_NAME = "swiftCore"; + public static final String SWIFTKITSWIFT_DYLIB_NAME = "SwiftKitSwift"; + public static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); + private static final String STDLIB_MACOS_DYLIB_PATH = "/usr/lib/swift/libswiftCore.dylib"; private static final Arena LIBRARY_ARENA = Arena.ofAuto(); - static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); - static { + @SuppressWarnings("unused") + private static final boolean INITIALIZED_LIBS = loadLibraries(false); + + public static boolean loadLibraries(boolean loadSwiftKit) { System.loadLibrary(STDLIB_DYLIB_NAME); - System.loadLibrary("SwiftKitSwift"); + if (loadSwiftKit) { + System.loadLibrary(SWIFTKITSWIFT_DYLIB_NAME); + } + return true; } static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup(); @@ -55,12 +63,31 @@ private static SymbolLookup getSymbolLookup() { public SwiftKit() { } - static void traceDowncall(String name, Object... args) { - String traceArgs = Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")); - System.out.printf("[java] Downcall: %s(%s)\n", name, traceArgs); - } + public static void traceDowncall(Object... args) { + var ex = new RuntimeException(); + + String traceArgs = Arrays.stream(args) + .map(Object::toString) + .collect(Collectors.joining(", ")); + System.out.printf("[java][%s:%d] Downcall: %s(%s)\n", + ex.getStackTrace()[1].getFileName(), + ex.getStackTrace()[1].getLineNumber(), + ex.getStackTrace()[1].getMethodName(), + traceArgs); + } + + public static void trace(Object... args) { + var ex = new RuntimeException(); + + String traceArgs = Arrays.stream(args) + .map(Object::toString) + .collect(Collectors.joining(", ")); + System.out.printf("[java][%s:%d] %s: %s\n", + ex.getStackTrace()[1].getFileName(), + ex.getStackTrace()[1].getLineNumber(), + ex.getStackTrace()[1].getMethodName(), + traceArgs); + } static MemorySegment findOrThrow(String symbol) { return SYMBOL_LOOKUP.find(symbol) @@ -260,17 +287,6 @@ private static class swift_getTypeByMangledNameInEnvironment { public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); } - private static class getTypeByStringByteArray { - public static final FunctionDescriptor DESC = FunctionDescriptor.of( - /*returns=*/SwiftValueLayout.SWIFT_POINTER, - ValueLayout.ADDRESS - ); - - public static final MemorySegment ADDR = findOrThrow("getTypeByStringByteArray"); - - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); - } - /** * Get a Swift {@code Any.Type} wrapped by {@link SwiftAnyType} which represents the type metadata available at runtime. * @@ -308,6 +324,64 @@ public static Optional getTypeByMangledNameInEnvironment(String ma } } + /** + * Produce the name of the Swift type given its Swift type metadata. + *

+ * If 'qualified' is true, leave all the qualification in place to + * disambiguate the type, producing a more complete (but longer) type name. + * + * @param typeMetadata the memory segment must point to a Swift metadata, + * e.g. the result of a {@link swift_getTypeByMangledNameInEnvironment} call + */ + public static String nameOfSwiftType(MemorySegment typeMetadata, boolean qualified) { + MethodHandle mh = swift_getTypeName.HANDLE; + + try (Arena arena = Arena.ofConfined()) { + MemorySegment charsAndLength = (MemorySegment) mh.invokeExact((SegmentAllocator) arena, typeMetadata, qualified); + MemorySegment utf8Chars = charsAndLength.get(SwiftValueLayout.SWIFT_POINTER, 0); + String typeName = utf8Chars.getString(0); + + // FIXME: this free is not always correct: + // java(80175,0x17008f000) malloc: *** error for object 0x600000362610: pointer being freed was not allocated + // cFree(utf8Chars); + + return typeName; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + /*** + * Namespace for calls down into swift-java generated thunks and accessors, such as {@code swiftjava_getType_...} etc. + *

Not intended to be used by end-user code directly, but used by swift-java generated Java code. + */ + @SuppressWarnings("unused") // used by source generated Java code + public static final class swiftjava { + private swiftjava() { /* just a namespace */ } + + private static class getType { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */ValueLayout.ADDRESS); + } + + public static MemorySegment getType(String moduleName, String nominalName) { + // We cannot cache this statically since it depends on the type names we're looking up + // TODO: we could cache the handles per type once we have them, to speed up subsequent calls + String symbol = "swiftjava_getType_" + moduleName + "_" + nominalName; + + try { + var addr = findOrThrow(symbol); + var mh$ = Linker.nativeLinker().downcallHandle(addr, getType.DESC); + return (MemorySegment) mh$.invokeExact(); + } catch (Throwable e) { + throw new AssertionError("Failed to call: " + symbol, e); + } + } + } + + // ==== ------------------------------------------------------------------------------------------------------------ + // Get Swift values out of native memory segments + /** * Read a Swift.Int value from memory at the given offset and translate it into a Java long. *

@@ -329,7 +403,7 @@ private static class swift_getTypeName { * Descriptor for the swift_getTypeName runtime function. */ public static final FunctionDescriptor DESC = FunctionDescriptor.of( - /*returns=*/MemoryLayout.structLayout( + /* -> */MemoryLayout.structLayout( SwiftValueLayout.SWIFT_POINTER.withName("utf8Chars"), SwiftValueLayout.SWIFT_INT.withName("length") ), @@ -348,31 +422,4 @@ private static class swift_getTypeName { public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); } - /** - * Produce the name of the Swift type given its Swift type metadata. - *

- * If 'qualified' is true, leave all the qualification in place to - * disambiguate the type, producing a more complete (but longer) type name. - * - * @param typeMetadata the memory segment must point to a Swift metadata, - * e.g. the result of a {@link swift_getTypeByMangledNameInEnvironment} call - */ - public static String nameOfSwiftType(MemorySegment typeMetadata, boolean qualified) { - MethodHandle mh = swift_getTypeName.HANDLE; - - try (Arena arena = Arena.ofConfined()) { - MemorySegment charsAndLength = (MemorySegment) mh.invokeExact((SegmentAllocator) arena, typeMetadata, qualified); - MemorySegment utf8Chars = charsAndLength.get(SwiftValueLayout.SWIFT_POINTER, 0); - String typeName = utf8Chars.getString(0); - - // FIXME: this free is not always correct: - // java(80175,0x17008f000) malloc: *** error for object 0x600000362610: pointer being freed was not allocated - // cFree(utf8Chars); - - return typeName; - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - } - } diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java b/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java index 5354cc4f..ffefe72e 100644 --- a/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java +++ b/SwiftKit/src/test/java/org/swift/swiftkit/SwiftRuntimeMetadataTest.java @@ -15,6 +15,7 @@ package org.swift.swiftkit; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class SwiftRuntimeMetadataTest { diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 84c4b2ec..0efff0c7 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -16,6 +16,112 @@ import JExtractSwift import Testing import struct Foundation.CharacterSet +enum RenderKind { + case swift + case java +} + +func assertOutput( + dump: Bool = false, + _ translator: Swift2JavaTranslator, + input: String, + _ renderKind: RenderKind, + detectChunkByInitialLines: Int = 4, + expectedChunks: [String], + fileID: String = #fileID, + filePath: String = #filePath, + line: Int = #line, + column: Int = #column +) throws { + try! translator.analyze(file: "/fake/Fake.swiftinterface", text: input) + + let output: String + var printer: CodePrinter = CodePrinter(mode: .accumulateAll) + switch renderKind { + case .swift: + try translator.writeSwiftThunkSources(outputDirectory: "/fake", printer: &printer) + case .java: + try translator.writeExportedJavaSources(outputDirectory: "/fake", printer: &printer) + } + output = printer.finalize() + + let gotLines = output.split(separator: "\n") + for expected in expectedChunks { + let expectedLines = expected.split(separator: "\n") + + var matchingOutputOffset: Int? = nil + let expectedInitialMatchingLines = expectedLines[0.. (offset+detectChunkByInitialLines) { + let textLinesAtOffset = gotLines[offset.. 0 + if hasDiff || dump { + print("") + if hasDiff { + print("error: Number of not matching lines: \(diffLineNumbers.count)!".red) + + print("==== ---------------------------------------------------------------") + print("Expected output:") + for (n, e) in expectedLines.enumerated() { + print("\(n): \(e)".yellow(if: diffLineNumbers.map({$0 - matchingOutputOffset}).contains(n))) + } + } + + print("==== ---------------------------------------------------------------") + print("Got output:") + let printFromLineNo = matchingOutputOffset + let printToLineNo = matchingOutputOffset + expectedLines.count + for (n, g) in gotLines.enumerated() where n >= printFromLineNo && n <= printToLineNo { + print("\(n): \(g)".red(if: diffLineNumbers.contains(n))) + } + print("==== ---------------------------------------------------------------\n") + } + } +} + func assertOutput( dump: Bool = false, _ got: String, @@ -32,21 +138,13 @@ func assertOutput( for (no, (g, e)) in zip(gotLines, expectedLines).enumerated() { if g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 - || e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 - { + || e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count == 0 { continue } let ge = g.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let ee = e.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if ge.commonPrefix(with: ee) != ee { - // print("") - // print("[\(file):\(line)] " + "Difference found on line: \(no + 1)!".red) - // print("Expected @ \(file):\(Int(line) + no + 3 /*formatting*/ + 1):") - // print(e.yellow) - // print("Got instead:") - // print(g.red) - diffLineNumbers.append(no) let sourceLocation = SourceLocation( @@ -57,7 +155,7 @@ func assertOutput( } let hasDiff = diffLineNumbers.count > 0 - if hasDiff || dump{ + if hasDiff || dump { print("") if hasDiff { print("error: Number of not matching lines: \(diffLineNumbers.count)!".red) diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 90541263..2f2c8c8c 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -41,12 +41,12 @@ final class FuncCallbackImportTests { ) st.log.logLevel = .error - try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: Self.class_interfaceFile) + try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "callMe" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil) } assertOutput( @@ -69,8 +69,8 @@ final class FuncCallbackImportTests { callMe_callback_handle$ = callMe_callback_handle$.bindTo(callback); Linker linker = Linker.nativeLinker(); MemorySegment callback$ = linker.upcallStub(callMe_callback_handle$, callMe_callback_desc$, arena); - if (TRACE_DOWNCALLS) { - traceDowncall(callback$); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(callback$); } mh$.invokeExact(callback$); } catch (Throwable ex$) { diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 2f3bccb1..3160d55f 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -53,7 +53,6 @@ final class FunctionDescriptorTests { } """ - @Test func FunctionDescriptor_globalTakeInt() throws { try functionDescriptorTest("globalTakeInt") { output in @@ -141,7 +140,7 @@ extension FunctionDescriptorTests { javaPackage: String = "com.example.swift", swiftModuleName: String = "SwiftModule", logLevel: Logger.Level = .trace, - body: (String) throws -> () + body: (String) throws -> Void ) throws { let st = Swift2JavaTranslator( javaPackage: javaPackage, @@ -149,7 +148,7 @@ extension FunctionDescriptorTests { ) st.log.logLevel = logLevel - try st.analyze(swiftInterfacePath: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == methodIdentifier @@ -168,7 +167,7 @@ extension FunctionDescriptorTests { javaPackage: String = "com.example.swift", swiftModuleName: String = "SwiftModule", logLevel: Logger.Level = .trace, - body: (String) throws -> () + body: (String) throws -> Void ) throws { let st = Swift2JavaTranslator( javaPackage: javaPackage, @@ -176,20 +175,21 @@ extension FunctionDescriptorTests { ) st.log.logLevel = logLevel - try st.analyze(swiftInterfacePath: "/fake/Sample.swiftinterface", text: interfaceFile) + try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) let varDecl: ImportedVariable? = st.importedTypes.values.compactMap { - $0.variables.first { - $0.identifier == identifier - } - }.first + $0.variables.first { + $0.identifier == identifier + } + }.first guard let varDecl else { fatalError("Cannot find descriptor of: \(identifier)") } let getOutput = CodePrinter.toString { printer in - st.printFunctionDescriptorValue(&printer, varDecl.accessorFunc(kind: accessorKind)!, accessorKind: accessorKind) + st.printFunctionDescriptorValue( + &printer, varDecl.accessorFunc(kind: accessorKind)!, accessorKind: accessorKind) } try body(getOutput) diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 3b2ef4f4..c866cb60 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -70,12 +70,12 @@ final class MethodImportTests { ) st.log.logLevel = .error - try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "helloWorld" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil) } assertOutput( @@ -91,8 +91,8 @@ final class MethodImportTests { public static void helloWorld() { var mh$ = helloWorld.HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(); } mh$.invokeExact(); } catch (Throwable ex$) { @@ -111,14 +111,14 @@ final class MethodImportTests { ) st.log.logLevel = .error - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "globalTakeInt" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: nil) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil) } assertOutput( @@ -134,8 +134,8 @@ final class MethodImportTests { public static void globalTakeInt(long i) { var mh$ = globalTakeInt.HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(i); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(i); } mh$.invokeExact(i); } catch (Throwable ex$) { @@ -154,14 +154,14 @@ final class MethodImportTests { ) st.log.logLevel = .error - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "globalTakeIntLongString" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .memorySegment) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) } assertOutput( @@ -177,8 +177,8 @@ final class MethodImportTests { public static void globalTakeIntLongString(int i32, long l, com.example.swift.String s) { var mh$ = globalTakeIntLongString.HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(i32, l, s.$memorySegment()); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(i32, l, s.$memorySegment()); } mh$.invokeExact(i32, l, s.$memorySegment()); } catch (Throwable ex$) { @@ -197,14 +197,14 @@ final class MethodImportTests { ) st.log.logLevel = .error - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "helloMemberFunction" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .memorySegment) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) } assertOutput( @@ -220,8 +220,8 @@ final class MethodImportTests { public static void helloMemberFunction(java.lang.foreign.MemorySegment self$) { var mh$ = helloMemberFunction.HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(self$); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(self$); } mh$.invokeExact(self$); } catch (Throwable ex$) { @@ -240,14 +240,14 @@ final class MethodImportTests { ) st.log.logLevel = .error - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "helloMemberInExtension" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .memorySegment) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) } assertOutput( @@ -263,8 +263,8 @@ final class MethodImportTests { public static void helloMemberInExtension(java.lang.foreign.MemorySegment self$) { var mh$ = helloMemberInExtension.HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(self$); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(self$); } mh$.invokeExact(self$); } catch (Throwable ex$) { @@ -283,14 +283,14 @@ final class MethodImportTests { ) st.log.logLevel = .info - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "helloMemberFunction" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .memorySegment) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) } assertOutput( @@ -306,8 +306,8 @@ final class MethodImportTests { public static void helloMemberFunction(java.lang.foreign.MemorySegment self$) { var mh$ = helloMemberFunction.HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(self$); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(self$); } mh$.invokeExact(self$); } catch (Throwable ex$) { @@ -326,14 +326,14 @@ final class MethodImportTests { ) st.log.logLevel = .info - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "helloMemberFunction" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .wrapper) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .wrapper) } assertOutput( @@ -361,14 +361,14 @@ final class MethodImportTests { ) st.log.logLevel = .info - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { $0.baseIdentifier == "makeInt" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, selfVariant: .wrapper) + st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .wrapper) } assertOutput( @@ -396,14 +396,14 @@ final class MethodImportTests { ) st.log.logLevel = .info - try st.analyze(swiftInterfacePath: "/fake/__FakeModule/SwiftFile.swiftinterface", text: class_interfaceFile) + try st.analyze(file: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.initializers.first { $0.identifier == "init(len:cap:)" }! let output = CodePrinter.toString { printer in - st.printClassInitializerConstructors(&printer, initDecl, parentName: initDecl.parentName!) + st.printClassInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!) } assertOutput( @@ -431,8 +431,8 @@ final class MethodImportTests { public MySwiftClass(SwiftArena arena, long len, long cap) { var mh$ = init_len_cap.HANDLE; try { - if (TRACE_DOWNCALLS) { - traceDowncall(len, cap); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(len, cap); } this.selfMemorySegment = (MemorySegment) mh$.invokeExact(len, cap, TYPE_METADATA.$memorySegment()); if (arena != null) { diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift new file mode 100644 index 00000000..56eedfa5 --- /dev/null +++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import JExtractSwift +import Testing + +final class MethodThunkTests { + let input = + """ + import Swift + + public func globalFunc(a: Int32, b: Int64) {} + public func globalFunc(a: Double, b: Int64) {} + """ + + @Test("Thunk overloads: globalFunc(a: Int32, b: Int64) & globalFunc(i32: Int32, l: Int64)") + func thunk_overloads() throws { + let st = Swift2JavaTranslator( + javaPackage: "com.example.swift", + swiftModuleName: "FakeModule" + ) + st.log.logLevel = .error + + try assertOutput( + st, input: input, .swift, + detectChunkByInitialLines: 1, + expectedChunks: + [ + """ + @_cdecl("swiftjava_FakeModule_globalFunc_a_b") + public func swiftjava_FakeModule_globalFunc_a_b(a: Int32, b: Int64) /* Void */ { + return globalFunc(a: a, b: b) + } + """, + """ + @_cdecl("swiftjava_FakeModule_globalFunc_a_b$1") + public func swiftjava_FakeModule_globalFunc_a_b$1(a: Double, b: Int64) /* Void */ { + return globalFunc(a: a, b: b) + } + """ + ] + ) + } + +} diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index d776b42a..ee562cbd 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -34,138 +34,166 @@ final class VariableImportTests { """ @Test("Import: var counter: Int") - func variable_() throws { + func variable_int() throws { let st = Swift2JavaTranslator( javaPackage: "com.example.swift", - swiftModuleName: "__FakeModule" + swiftModuleName: "FakeModule" ) st.log.logLevel = .error - try st.analyze(swiftInterfacePath: "/fake/Fake.swiftinterface", text: class_interfaceFile) - - let identifier = "counterInt" - let varDecl: ImportedVariable? = - st.importedTypes.values.compactMap { - $0.variables.first { - $0.identifier == identifier - } - }.first - guard let varDecl else { - fatalError("Cannot find: \(identifier)") - } - - let output = CodePrinter.toString { printer in - st.printVariableDowncallMethods(&printer, varDecl) - } - - assertOutput( - dump: true, - output, - expected: - """ - // ==== -------------------------------------------------- - // counterInt - private static class counterInt { - public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( + try assertOutput( + st, input: class_interfaceFile, .java, + detectChunkByInitialLines: 7, + expectedChunks: + [ + """ + private static class counterInt { + public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( /* -> */SWIFT_INT, - SWIFT_POINTER + SWIFT_POINTER ); - public static final MemorySegment ADDR_GET = __FakeModule.findOrThrow("g"); - public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET); - public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( - SWIFT_INT, - SWIFT_POINTER + public static final MemorySegment ADDR_GET = + FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt"); + + public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET); + public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( + SWIFT_INT, + SWIFT_POINTER ); - public static final MemorySegment ADDR_SET = __FakeModule.findOrThrow("s"); - public static final MethodHandle HANDLE_SET = Linker.nativeLinker().downcallHandle(ADDR_SET, DESC_SET); - } - /** - * Function descriptor for: - * - */ - public static FunctionDescriptor counterInt$get$descriptor() { - return counterInt.DESC_GET; - } - /** - * Downcall method handle for: - * - */ - public static MethodHandle counterInt$get$handle() { - return counterInt.HANDLE_GET; - } - /** - * Address for: - * - */ - public static MemorySegment counterInt$get$address() { - return counterInt.ADDR_GET; - } - /** - * Function descriptor for: - * - */ - public static FunctionDescriptor counterInt$set$descriptor() { - return counterInt.DESC_SET; - } - /** - * Downcall method handle for: - * - */ - public static MethodHandle counterInt$set$handle() { - return counterInt.HANDLE_SET; - } - /** - * Address for: - * - */ - public static MemorySegment counterInt$set$address() { - return counterInt.ADDR_SET; - } - /** - * Downcall to Swift: - * - */ - public static long getCounterInt(java.lang.foreign.MemorySegment self$) { - var mh$ = counterInt.HANDLE_GET; + public static final MemorySegment ADDR_SET = + FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt"); + + public static final MethodHandle HANDLE_SET = Linker.nativeLinker().downcallHandle(ADDR_SET, DESC_SET); + } + """, + """ + /** + * Function descriptor for: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static FunctionDescriptor counterInt$get$descriptor() { + return counterInt.DESC_GET; + } + """, + """ + /** + * Downcall method handle for: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static MethodHandle counterInt$get$handle() { + return counterInt.HANDLE_GET; + } + """, + """ + /** + * Address for: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static MemorySegment counterInt$get$address() { + return counterInt.ADDR_GET; + } + """, + """ + /** + * Function descriptor for: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static FunctionDescriptor counterInt$set$descriptor() { + return counterInt.DESC_SET; + } + """ + , + """ + /** + * Downcall method handle for: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static MethodHandle counterInt$set$handle() { + return counterInt.HANDLE_SET; + } + """, + """ + /** + * Address for: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static MemorySegment counterInt$set$address() { + return counterInt.ADDR_SET; + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static long getCounterInt(java.lang.foreign.MemorySegment self$) { + var mh$ = counterInt.HANDLE_GET; + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(self$); + } + return (long) mh$.invokeExact(self$); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public long getCounterInt() { + return (long) getCounterInt($memorySegment()); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public static void setCounterInt(long newValue, java.lang.foreign.MemorySegment self$) { + var mh$ = counterInt.HANDLE_SET; try { - if (TRACE_DOWNCALLS) { - traceDowncall(self$); - } - return (long) mh$.invokeExact(self$); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(newValue, self$); + } + mh$.invokeExact(newValue, self$); } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); + throw new AssertionError("should not reach here", ex$); } - } - /** - * Downcall to Swift: - * - */ - public long getCounterInt() { - return (long) getCounterInt($memorySegment()); - } - /** - * Downcall to Swift: - * - */ - public static void setCounterInt(long newValue, java.lang.foreign.MemorySegment self$) { - var mh$ = counterInt.HANDLE_SET; - try { - if (TRACE_DOWNCALLS) { - traceDowncall(newValue, self$); - } - mh$.invokeExact(newValue, self$); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); } - } - /** - * Downcall to Swift: - * - */ - public void setCounterInt(long newValue) { - setCounterInt(newValue, $memorySegment()); - } - """ + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var counterInt: Int + * } + */ + public void setCounterInt(long newValue) { + setCounterInt(newValue, $memorySegment()); + } + """ + ] ) } } diff --git a/WIP.md b/WIP.md index c8a6255d..e1f58612 100644 --- a/WIP.md +++ b/WIP.md @@ -32,50 +32,3 @@ improve: - [ ] Investigate "unbridged" variants of String, Array, etc. - [ ] Investigate the new [Foreign Function & Memory API](https://bugs.openjdk.org/browse/JDK-8312523) (aka Project Panama) for exposing Swift APIs to Java. - -### jextract-swift - -Separate todo list for the jextract / panama side of the project: - -Calling convention: -- [x] Call swift methods, take parameters, return values -- [ ] How to call a **throwing** Swift function from Java -- [ ] How to call a **generic** Swift function from Java - - [ ] How to pass "call me back" (Callable, Runnable) to Swift, and make an **up-call** -- [ ] How to support passing a struct **inout** `SwiftValue` to Swift so that Java side sees change - -Bridges: -- [ ] Java **Optional** / Swift Optional - depends on generics (!) -- [ ] Efficient **String** / SwiftString wrappers and converters -- [ ] Handle byte buffers and pointers properly -- [ ] Converters for **Array** -- [ ] Converters for **List** and common collections? -- [ ] expose Swift collections as some bridged type implementing java.util.Collection? -- [ ] Import Swift **enums** - -Importer: -- [x] import global functions into the `Module.theFunction` on Java side -- [x] import functions with parameters -- [x] import functions return values -- [ ] import instance member functions using "wrapper" pattern -- [ ] handle types like `[any Thing]?`, we can't parse them right now even -- [ ] support nested types in Swift -- [ ] handle types like `any Thing`, importer does not understand `any` or `some` - -Programming model: -- [ ] Which style of ownership for Java class wrapping a Swift Class - - [x] __allocating_init, how to release/destroy from Java - - [x] Offer explicit swift_**release** / swift_**retain** functions - - [ ] Offer some way to make Immortal class instance - - [ ] **SwiftArena** which retains/destroys underlying Swift class? -- [ ] How to create a Swift struct - -Swift Compiler work: -- [x] Expose **mangled names** of types and methods in .swiftinterface -- [ ] Expose **@layout** of class, struct etc. types in .swiftinterface -- [ ] Expose `demangle` function to human-readable text; it'd be good for usability - -Build: -- [x] Gradle build for Java parts of samples and "SwiftKit" utilities -- [x] Build Swift dependencies when building Java samples automatically -- [ ] JMH benchmarks diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index a61e5793..c18f30c1 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -15,3 +15,12 @@ repositories { mavenCentral() } + +def cleanSwift = tasks.register("cleanSwift", Exec) { + workingDir = layout.projectDirectory + commandLine "swift" + args("package", "clean") +} +tasks.clean { + dependsOn("cleanSwift") +} diff --git a/buildSrc/src/main/groovy/org/swift/swiftkit/gradle/BuildUtils.groovy b/buildSrc/src/main/groovy/org/swift/swiftkit/gradle/BuildUtils.groovy index 2e09d11b..4fcddbee 100644 --- a/buildSrc/src/main/groovy/org/swift/swiftkit/gradle/BuildUtils.groovy +++ b/buildSrc/src/main/groovy/org/swift/swiftkit/gradle/BuildUtils.groovy @@ -23,7 +23,7 @@ final class BuildUtils { def isLinux = osName.toLowerCase(Locale.getDefault()).contains("linux") def base = rootDir == null ? "" : "${rootDir}/" - return [ + def debugPaths = [ isLinux ? /* Linux */ (osArch == "amd64" || osArch == "x86_64" ? "${base}.build/x86_64-unknown-linux-gnu/debug/" : @@ -38,10 +38,21 @@ final class BuildUtils { /* macOS */ (osArch == "aarch64" ? "${base}../../.build/arm64-apple-macosx/debug/" : "${base}../../.build/${osArch}-apple-macosx/debug/"), - isLinux ? - "/usr/lib/swift/linux" : - // assume macOS - "/usr/lib/swift/" ] + def releasePaths = debugPaths.collect { it.replaceAll("debug", "release") } + def systemPaths = + // system paths + isLinux ? + [ + "/usr/lib/swift/linux", + // TODO: should we be Swiftly aware and then use the currently used path? + System.getProperty("user.home") + "/.local/share/swiftly/toolchains/6.0.2/usr/lib/swift/linux" + ] : + [ + // assume macOS + "/usr/lib/swift/" + ] + + return releasePaths + debugPaths + systemPaths } } diff --git a/docker/Dockerfile b/docker/Dockerfile index c4711253..c68ccc3c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -26,5 +26,5 @@ ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" # Install "untested" nightly 'main' Swift # TODO: Only do this if the released Swift is older than what we require -COPY install_untested_nightly_swift.sh . -RUN bash -xc './install_untested_nightly_swift.sh' +#COPY install_untested_nightly_swift.sh . +RUN #bash -xc './install_untested_nightly_swift.sh' diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 0d63eaf9..8aa295ce 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -76,10 +76,7 @@ fi mkdir -p /usr/lib/jvm/ mv jdk.tar.gz /usr/lib/jvm/ cd /usr/lib/jvm/ -ls tar xzvf jdk.tar.gz && rm jdk.tar.gz -ls -find . -depth -maxdepth 1 -type d mv "$(find . -depth -maxdepth 1 -type d | head -n1)" default-jdk echo "JAVA_HOME = /usr/lib/jvm/default-jdk" diff --git a/settings.gradle b/settings.gradle index 3a0bd106..fa0fa5bd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,4 +26,3 @@ new File(rootDir, "Samples").listFiles().each { include ":Samples:${it.name}" } } -