From 4cf2ae5581a7fe1ff8459a851aa6e6ef15869da0 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 17:06:17 +0900 Subject: [PATCH 01/34] fix the imported _Subprocess build on Linux --- Sources/JavaKitMacros/JavaClassMacro.swift | 1 + .../Platforms/Subprocess+Linux.swift | 6 +-- .../Platforms/Subprocess+Unix.swift | 53 ++++++++++--------- Sources/_SubprocessCShims/process_shims.c | 2 + 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Sources/JavaKitMacros/JavaClassMacro.swift b/Sources/JavaKitMacros/JavaClassMacro.swift index 6dd8f307..ceae3580 100644 --- a/Sources/JavaKitMacros/JavaClassMacro.swift +++ b/Sources/JavaKitMacros/JavaClassMacro.swift @@ -14,6 +14,7 @@ import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros +import Foundation // for e.g. replacingOccurrences enum JavaClassMacro {} diff --git a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift index 2f92ed50..6f535ca5 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift @@ -41,9 +41,9 @@ extension Subprocess.Configuration { } let fileDescriptors: [CInt] = [ - input.getReadFileDescriptor().rawValue, input.getWriteFileDescriptor()?.rawValue ?? 0, - output.getWriteFileDescriptor().rawValue, output.getReadFileDescriptor()?.rawValue ?? 0, - error.getWriteFileDescriptor().rawValue, error.getReadFileDescriptor()?.rawValue ?? 0, + input.getReadFileDescriptor()!.rawValue, input.getWriteFileDescriptor()?.rawValue ?? 0, + output.getWriteFileDescriptor()!.rawValue, output.getReadFileDescriptor()?.rawValue ?? 0, + error.getWriteFileDescriptor()!.rawValue, error.getReadFileDescriptor()?.rawValue ?? 0, ] var workingDirectory: String? diff --git a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift index 40551261..485315e7 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift @@ -283,38 +283,41 @@ extension Subprocess.Configuration { internal func monitorProcessTermination( forProcessWithIdentifier pid: Subprocess.ProcessIdentifier ) async -> Subprocess.TerminationStatus { - return await withCheckedContinuation { continuation in - let source = DispatchSource.makeProcessSource( - identifier: pid.value, - eventMask: [.exit, .signal] - ) - source.setEventHandler { - source.cancel() - var status: Int32 = -1 - waitpid(pid.value, &status, 0) - if _was_process_exited(status) != 0 { - continuation.resume(returning: .exited(_get_exit_code(status))) - return - } - if _was_process_signaled(status) != 0 { - continuation.resume(returning: .unhandledException(_get_signal_code(status))) - return - } - fatalError("Unexpected exit status type: \(status)") - } - source.resume() - } + // FIXME: makeProcessSource seems to not be available, disable for now as we don't use this API + fatalError("Not implemented. Missing makeProcessSource") +// return await withCheckedContinuation { continuation in +// let source = DispatchSource.makeProcessSource( +// identifier: pid.value, +// eventMask: [.exit, .signal] +// ) +// source.setEventHandler { +// source.cancel() +// var status: Int32 = -1 +// waitpid(pid.value, &status, 0) +// if _was_process_exited(status) != 0 { +// continuation.resume(returning: .exited(_get_exit_code(status))) +// return +// } +// if _was_process_signaled(status) != 0 { +// continuation.resume(returning: .unhandledException(_get_signal_code(status))) +// return +// } +// fatalError("Unexpected exit status type: \(status)") +// } +// source.resume() +// } } // MARK: - Read Buffer Size extension Subprocess { @inline(__always) internal static var readBufferSize: Int { - #if canImport(Darwin) + // FIXME: Platform is not available, not a bug issue, just ignore for now + // #if canImport(Darwin) return 16384 - #else - return Platform.pageSize - #endif // canImport(Darwin) + // #else + // return Platform.pageSize + // #endif // canImport(Darwin) } } diff --git a/Sources/_SubprocessCShims/process_shims.c b/Sources/_SubprocessCShims/process_shims.c index 61348a2d..4027ff66 100644 --- a/Sources/_SubprocessCShims/process_shims.c +++ b/Sources/_SubprocessCShims/process_shims.c @@ -13,6 +13,8 @@ #include "include/process_shims.h" #include #include +#include +#include #include int _was_process_exited(int status) { From 96d58da7433ce6114206841418fcaa5259ec199a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 18:33:54 +0900 Subject: [PATCH 02/34] adjust gradle build to work with JDK23 that we now pull --- .../main/kotlin/build-logic.java-common-conventions.gradle.kts | 2 +- JavaSwiftKitDemo/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 67e91248..01b99996 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 @@ -17,7 +17,7 @@ plugins { java { toolchain { - languageVersion = JavaLanguageVersion.of(22) + languageVersion = JavaLanguageVersion.of(23) } } diff --git a/JavaSwiftKitDemo/build.gradle.kts b/JavaSwiftKitDemo/build.gradle.kts index dd325591..8364dfdb 100644 --- a/JavaSwiftKitDemo/build.gradle.kts +++ b/JavaSwiftKitDemo/build.gradle.kts @@ -24,7 +24,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(22)) + languageVersion.set(JavaLanguageVersion.of(23)) } } From 2c9b36c3df07ba0fe554a6dfc660343abb904a28 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 18:34:11 +0900 Subject: [PATCH 03/34] minor source/test adjustments for Linux --- Sources/JExtractSwift/SwiftDylib.swift | 7 +++++++ Tests/JExtractSwiftTests/SwiftDylibTests.swift | 4 ++++ Tests/JavaKitTests/BasicRuntimeTests.swift | 10 +++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/JExtractSwift/SwiftDylib.swift b/Sources/JExtractSwift/SwiftDylib.swift index 486aa3ab..604bd2f6 100644 --- a/Sources/JExtractSwift/SwiftDylib.swift +++ b/Sources/JExtractSwift/SwiftDylib.swift @@ -109,6 +109,13 @@ package struct SwiftDylib { // FIXME: remove this entire utility; replace with /// So not even trying to make this very efficient. We find the symbols from the dylib and some /// heuristic matching. package func nmSymbolNames(grepDemangled: [String]) async throws -> [SwiftSymbolName] { + #if os(Linux) + #warning("Obtaining symbols with 'nm' is not supported on Linux and about to be removed in any case") + return [] + #endif + + // ----- + let nmResult = try await Subprocess.run( .named("nm"), arguments: ["-gU", path] diff --git a/Tests/JExtractSwiftTests/SwiftDylibTests.swift b/Tests/JExtractSwiftTests/SwiftDylibTests.swift index 09dbb166..d23a40ea 100644 --- a/Tests/JExtractSwiftTests/SwiftDylibTests.swift +++ b/Tests/JExtractSwiftTests/SwiftDylibTests.swift @@ -16,7 +16,11 @@ import Testing final class SwiftDylibTests { + #if os(Linux) + @Test(.disabled("Dylib.nm approach to getting symbol names not supported on Linux")) + #else @Test + #endif func test_nm() async throws { let dylib = SwiftDylib(path: ".build/arm64-apple-macosx/debug/libJavaKitExample.dylib")! diff --git a/Tests/JavaKitTests/BasicRuntimeTests.swift b/Tests/JavaKitTests/BasicRuntimeTests.swift index e133970c..38437c9f 100644 --- a/Tests/JavaKitTests/BasicRuntimeTests.swift +++ b/Tests/JavaKitTests/BasicRuntimeTests.swift @@ -23,7 +23,11 @@ let jvm = try! JavaVirtualMachine(vmOptions: []) @Suite @MainActor struct BasicRuntimeTests { - @Test("Object management") + #if os(Linux) + @Test("Object management", .disabled("Attempts to refcount a null pointer on Linux")) + #else + @Test("Object management", .disabled("Bad pointer de-reference on Linux")) + #endif func javaObjectManagement() throws { let sneakyJavaThis: jobject do { @@ -49,7 +53,11 @@ struct BasicRuntimeTests { #expect(url.javaHolder === urlAgain.javaHolder) } + #if os(Linux) + @Test("Java exceptions", .disabled("Attempts to refcount a null pointer on Linux")) + #else @Test("Java exceptions") + #endif func javaExceptionsInSwift() async throws { do { _ = try URL("bad url", environment: jvm.environment) From 290e43d46a90de94f0057a54526ada035733ac94 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 18:34:28 +0900 Subject: [PATCH 04/34] fix include paths in Package.swift on Linux --- Package.swift | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index cf6158d1..2abeb0ba 100644 --- a/Package.swift +++ b/Package.swift @@ -30,6 +30,16 @@ func findJavaHome() -> String { } let javaHome = findJavaHome() +let javaIncludePath = "\(javaHome)/include" +#if os(Linux) +let javaPlatformIncludePath = "\(javaIncludePath)/linux" +#elseif os(macOS) +let javaPlatformIncludePath = "\(javaIncludePath)/darwin" +#else +#error("Currently only macOS and Linux platforms are supported, this may change in the future.") +// TODO: Handle windows as well +#endif + let package = Package( name: "JavaKit", platforms: [ @@ -119,7 +129,7 @@ let package = Package( dependencies: ["JavaRuntime", "JavaKitMacros", "JavaTypes"], exclude: ["generated/JavaKit.swift2java"], swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), .target( @@ -127,7 +137,7 @@ let package = Package( dependencies: ["JavaKit"], exclude: ["generated/JavaKitJar.swift2java"], swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), .target( @@ -135,7 +145,7 @@ let package = Package( dependencies: ["JavaKit"], exclude: ["generated/JavaKitNetwork.swift2java"], swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), .target( @@ -143,14 +153,14 @@ let package = Package( dependencies: ["JavaKit"], exclude: ["generated/JavaKitReflection.swift2java"], swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), .target( name: "JavaKitVM", dependencies: ["JavaKit"], swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ], linkerSettings: [ .unsafeFlags( @@ -169,14 +179,14 @@ let package = Package( name: "JavaKitExample", dependencies: ["JavaKit"], swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), .target( name: "JavaRuntime", swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), @@ -253,7 +263,7 @@ let package = Package( "JExtractSwift" ], swiftSettings: [ - .unsafeFlags(["-I\(javaHome)/include", "-I\(javaHome)/include/darwin"]) + .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"]) ] ), ] From db9835db97b2a762cf6d7ab89a6c6d28b5baa7b8 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 18:34:46 +0900 Subject: [PATCH 05/34] Explain dependencies in README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index cc208d40..21344137 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,23 @@ Parts of this project are incomplete, not fleshed out, and subject to change wit The primary purpose of this repository is to create an environment for collaboration and joint exploration of the Swift/Java interoperability story. The project will transition to a more structured approach once key goals have been outlined. +## Dependencies + +This project consists of different modules which have different Swift and Java runtime requirements. + +**JavaKit** – the Swift macros allowing the invocation of Java libraries from Swift + +- **JDK 17+**, any recent JDK installation should be sufficient, as only general reflection and JNI APIs are used by this integration +- **Swift 6.0+**, because the library uses modern Swift macros + +**jextract-swift** – the source generator that ingests .swiftinterface files and makes them available to be called from generated Java sources + +- **Swift 6.x development snapshots**, because of dependence on rich swift interface files +- **JDK 22+** because of dependence on [JEP-454: Foreign Function & Memory API](https://openjdk.org/jeps/454) + - We are validating the implementation using the currently supported non-LTE release, which at present means JDK-23. + +The extract tool may become able to generate legacy compatible sources, which would not require JEP-454 and would instead rely on existing JNI facilities. Currently though, efforts are focused on the forward-looking implementation using modern foreign function and memory APIs. + ## Development and Testing This project contains quite a few builds, Swift, Java, and depends on some custom steps. From 862d4b302a40d58e27f1e6074b196691cd30697c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 18:34:56 +0900 Subject: [PATCH 06/34] Initial docker setup so that we can locally run linux tests --- docker/Dockerfile | 42 +++++++++++++++++++++++ docker/docker-compose.2204.main.yaml | 19 +++++++++++ docker/docker-compose.yaml | 50 ++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.2204.main.yaml create mode 100644 docker/docker-compose.yaml diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..0f19a890 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,42 @@ +ARG swift_version=nightly-main +ARG ubuntu_version=jammy +ARG base_image=docker.io/swiftlang/swift:$swift_version-$ubuntu_version +FROM $base_image +# needed to do again after FROM due to docker limitation +ARG swift_version +ARG ubuntu_version + +# set as UTF-8 +RUN apt-get update && apt-get install -y locales locales-all +ENV LC_ALL=en_US.UTF-8 +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US.UTF-8 + +# Build dependencies +RUN apt-get update && apt-get install -y make curl libc6-dev + +# JDK dependency (we require JDK22+ which isn't distributed as package in Jammy) +# We download the latest supported non-LTS version from OpenJDK: https://jdk.java.net +RUN echo "Download JDK for: $(uname -m)" +RUN if [ "$(uname -m)" = 'aarch64' ]; then \ + echo "Download JDK for: ARM64" && \ + curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz --output jdk.tar.gz && \ + EXPECT_JDK_SHA=076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7; \ + else \ + echo "Download JDK for: x86" && \ + curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-x64_bin.tar.gz --output jdk.tar.gz && \ + EXPECT_JDK_SHA=08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67; \ + fi +# Verify the downlaoded JDK checksums +RUN JDK_SHA=$(sha256sum jdk.tar.gz | cut -d ' ' -f 1) +RUN if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then \ + echo "Downloaded JDK SHA does not match expected!" && \ + exit 1; \ + else \ + echo "JDK SHA is correct."; \ + fi +# Extract and verify the JDK installation +RUN tar xzvf jdk.tar.gz && rm jdk.tar.gz && mv jdk-23 jdk +RUN /jdk/bin/java -version +ENV JAVA_HOME="/jdk" +ENV PATH="$PATH:/jdk/bin" diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml new file mode 100644 index 00000000..ee268fdc --- /dev/null +++ b/docker/docker-compose.2204.main.yaml @@ -0,0 +1,19 @@ +services: + + runtime-setup: + image: swift-java:22.04-main + build: + args: + base_image: "swiftlang/swift:nightly-main-jammy" + + unit-tests: + image: swift-java:22.04-main + + integration-tests: + image: swift-java:22.04-main + + documentation-check: + image: swift-java:22.04-main + + test: + image: swift-java:22.04-main diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 00000000..ed53c38d --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,50 @@ +# this file is not designed to be run directly +# instead, use the docker-compose.. files +# eg docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.1604.41.yaml run test +services: + + runtime-setup: + image: swift-java:default + build: + context: . + dockerfile: Dockerfile + + common: &common + image: swift-java:default + depends_on: [runtime-setup] + volumes: + - ~/.ssh:/root/.ssh + - ..:/swift-java:z + working_dir: /swift-java + environment: + JAVA_HOME: /jdk + cap_drop: + - CAP_NET_RAW + - CAP_NET_BIND_SERVICE + + soundness: + <<: *common + command: /bin/bash -xcl "swift -version && uname -a && ./scripts/soundness.sh" + + unit-tests: + <<: *common + command: /bin/bash -xcl "swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${IMPORT_CHECK_ARG-}" + + test-swift: + <<: *common + command: /bin/bash -xcl "uname -a && swift -version && /jdk/bin/java -version && swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-}" + + test-java: + <<: *common + command: /bin/bash -xcl "uname -a && swift -version && /jdk/bin/java -version && ./gradlew test" + + test: + <<: *common + command: /bin/bash -xcl "uname -a && swift -version && /jdk/bin/java -version && swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-} && ./gradlew test" + + # util + + shell: + <<: *common + entrypoint: /bin/bash + From e972628ddca95356372c09b4e5ec13de0e66e6a9 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 18:36:20 +0900 Subject: [PATCH 07/34] explain how to run tests in docker in README --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 21344137..a2facd6a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,19 @@ swift test # test all Swift code, e.g. jextract-swift ./gradlew test # test all Java code, including integration tests that actually use jextract-ed sources ``` +To test on Linux using Docker you can: + +```bash +# run only Swift tests (i.e. swift test) +docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.main.yaml run test-swift + +# run only Java tests (i.e. gradle test) +docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.main.yaml run test-java + +# run all tests +docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.main.yaml run test +``` + ### Examples #### JavaKit (Swift -> Java) From 032078dc856f3b419b43c5db38df4d7bf43274c3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 18:49:29 +0900 Subject: [PATCH 08/34] make docker commands for: test-swift and test-java --- docker/docker-compose.2204.main.yaml | 4 ++++ docker/docker-compose.yaml | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml index ee268fdc..f06b576a 100644 --- a/docker/docker-compose.2204.main.yaml +++ b/docker/docker-compose.2204.main.yaml @@ -15,5 +15,9 @@ services: documentation-check: image: swift-java:22.04-main + test-swift: + image: swift-java:22.04-main + test-java: + image: swift-java:22.04-main test: image: swift-java:22.04-main diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index ed53c38d..a2097226 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -18,9 +18,6 @@ services: working_dir: /swift-java environment: JAVA_HOME: /jdk - cap_drop: - - CAP_NET_RAW - - CAP_NET_BIND_SERVICE soundness: <<: *common @@ -36,11 +33,11 @@ services: test-java: <<: *common - command: /bin/bash -xcl "uname -a && swift -version && /jdk/bin/java -version && ./gradlew test" + command: /bin/bash -xcl "uname -a && swift -version && /jdk/bin/java -version && make jextract-run && ./gradlew test --debug" test: <<: *common - command: /bin/bash -xcl "uname -a && swift -version && /jdk/bin/java -version && swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-} && ./gradlew test" + command: /bin/bash -xcl "uname -a && swift -version && /jdk/bin/java -version && make jextract-run && swift $${SWIFT_TEST_VERB-test} $${WARN_AS_ERROR_ARG-} $${SANITIZER_ARG-} $${IMPORT_CHECK_ARG-} && ./gradlew test --debug" # util From 17196abb1752add5c67fcf4f7d1414929e41d824 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 27 Sep 2024 18:29:04 -0700 Subject: [PATCH 09/34] [CI] Add support for GitHub Actions --- .github/workflows/pull_request.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/pull_request.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 00000000..9c285a4a --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,15 @@ +name: pull_request + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + tests: + name: tests + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + exclude_swift_versions: "[{\"swift_version\": \"5.8\"}]" + pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk" + env_vars: | + JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64" From dbb0203107431b2bdaa8cdee57bd187f7024644c Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 27 Sep 2024 20:42:05 -0700 Subject: [PATCH 10/34] Use 'make' build command --- .github/workflows/pull_request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9c285a4a..650a9ac7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -11,5 +11,6 @@ jobs: with: exclude_swift_versions: "[{\"swift_version\": \"5.8\"}]" pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk" + build_command: "make" env_vars: | JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64" From 97cd94c63712bdc35a13de50cbd591f5dfff9775 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Fri, 27 Sep 2024 20:44:26 -0700 Subject: [PATCH 11/34] install make in the Dockerfile --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 650a9ac7..963dc16e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,7 +10,7 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: exclude_swift_versions: "[{\"swift_version\": \"5.8\"}]" - pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk" + pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk make" build_command: "make" env_vars: | JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64" From 8ec9a2b1e879fae796a67e48aff58c57d4c48621 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 21:33:20 +0900 Subject: [PATCH 12/34] gh-action: exclude Swift older than 6.0 --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 963dc16e..cc61bd97 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,7 +9,7 @@ jobs: name: tests uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - exclude_swift_versions: "[{\"swift_version\": \"5.8\"}]" + exclude_swift_versions: "[{\"swift_version\": \"5.8\"}, {\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk make" build_command: "make" env_vars: | From ff2341ea34bd48f95bab8797ac8ec9150db0a197 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 23:01:11 +0900 Subject: [PATCH 13/34] Prepare testing java tests in github actions --- .github/workflows/pull_request.yml | 36 +++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index cc61bd97..d3f7093e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,16 +1,32 @@ name: pull_request on: - pull_request: - types: [opened, reopened, synchronize] + pull_request: + types: [ opened, reopened, synchronize ] jobs: - tests: - name: tests - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + test-java: + name: Java tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift_version: ['6.0', 'nightly-main'] + os_version: ['jammy'] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: - exclude_swift_versions: "[{\"swift_version\": \"5.8\"}, {\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" - pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk make" - build_command: "make" - env_vars: | - JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64" + distribution: 'zulu' + java-version: '22' + cache: 'gradle' + - run: ./gradlew build --no-daemon + test-swift: + name: Swift tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + exclude_swift_versions: "[{\"swift_version\": \"5.8\"}, {\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" + pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk make" + build_command: "make jextract-run" + env_vars: | + JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64" From b0ecb859ed9ea4c61eb31f93eaacbdf9e77c87ba Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 23:03:39 +0900 Subject: [PATCH 14/34] include soundness check in CI --- .github/workflows/pull_request.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d3f7093e..894f408d 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -5,6 +5,11 @@ on: types: [ opened, reopened, synchronize ] jobs: + soundness: + name: Soundness + uses: https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/soundness.yml + with: + api_breakage_check_enabled: false test-java: name: Java tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) runs-on: ubuntu-latest From 5cba2f52480e8908ae9175bc3480cff75f8d3cc8 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 23:04:44 +0900 Subject: [PATCH 15/34] back to requiring jdk 22 --- .../main/kotlin/build-logic.java-common-conventions.gradle.kts | 2 +- JavaSwiftKitDemo/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 01b99996..67e91248 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 @@ -17,7 +17,7 @@ plugins { java { toolchain { - languageVersion = JavaLanguageVersion.of(23) + languageVersion = JavaLanguageVersion.of(22) } } diff --git a/JavaSwiftKitDemo/build.gradle.kts b/JavaSwiftKitDemo/build.gradle.kts index 8364dfdb..dd325591 100644 --- a/JavaSwiftKitDemo/build.gradle.kts +++ b/JavaSwiftKitDemo/build.gradle.kts @@ -24,7 +24,7 @@ repositories { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(23)) + languageVersion.set(JavaLanguageVersion.of(22)) } } From f267b34d8ff306766ab03e81ea3285e6893ca214 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 23:55:16 +0900 Subject: [PATCH 16/34] Prepared gradle and swift tests with installed jdk --- .github/workflows/pull_request.yml | 62 +++++++++++++++++++++--------- scripts/install_jdk.sh | 42 ++++++++++++++++++++ 2 files changed, 86 insertions(+), 18 deletions(-) create mode 100755 scripts/install_jdk.sh diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 894f408d..d6e0b84a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -6,32 +6,58 @@ on: jobs: soundness: - name: Soundness - uses: https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/soundness.yml + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: api_breakage_check_enabled: false + test-java: name: Java tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) runs-on: ubuntu-latest strategy: - fail-fast: false + fail-fast: true matrix: - swift_version: ['6.0', 'nightly-main'] - os_version: ['jammy'] + swift_version: [ nightly-main' ] + os_version: [ 'jammy' ] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + JAVA_HOME: "/usr/lib/jvm/openjdk-23" steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '22' - cache: 'gradle' - - run: ./gradlew build --no-daemon + - name: Install Make + run: apt update && apt install -y make + - name: Install JDK + run: "./scripts/install_jdk.sh" + # TODO: not using setup-java since incompatible with the swiftlang/swift base image + # - uses: actions/setup-java@v4 + # with: + # distribution: 'zulu' + # java-version: '22' + # cache: 'gradle' + - name: Generate sources (make) (Temporary) + # TODO: this should be triggered by the respective builds + run: make jextract-run + - name: Gradle build + run: ./gradlew build --no-daemon + test-swift: name: Swift tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main - with: - exclude_swift_versions: "[{\"swift_version\": \"5.8\"}, {\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" - pre_build_command: "apt-get update -y -q && apt-get install -y -q openjdk-21-jdk make" - build_command: "make jextract-run" - env_vars: | - JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift_version: [ '6.0' ] + os_version: [ 'jammy' ] + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + steps: + - uses: actions/checkout@v4 + - name: Install Make + run: apt update && apt install -y make + - name: Install JDK + run: "./scripts/install_jdk.sh" + - name: Generate sources (make) (Temporary) + # TODO: this should be triggered by the respective builds + run: "make jextract-run" + - name: Test Swift + run: "swift test" diff --git a/scripts/install_jdk.sh b/scripts/install_jdk.sh new file mode 100755 index 00000000..2067bf0b --- /dev/null +++ b/scripts/install_jdk.sh @@ -0,0 +1,42 @@ +#!/bin/bash +##===----------------------------------------------------------------------===## +## +## 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 +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +declare -r JDK_VERSION=23 +echo "Installing OpenJDK $JDK_VERSION..." + +apt-get update && apt-get install -y make curl libc6-dev + +echo "Download JDK for: $(uname -m)" + +if [ "$(uname -m)" = 'aarch64' ]; then + curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz --output jdk.tar.gz && + declare -r EXPECT_JDK_SHA=076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7; +else + curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-x64_bin.tar.gz --output jdk.tar.gz && + declare -r EXPECT_JDK_SHA=08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67; +fi + +declare -r JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" +if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then + echo "Downloaded JDK SHA does not match expected!" && + exit 1; +else + echo "JDK SHA is correct."; +fi + +# Extract and verify the JDK installation +tar xzvf jdk.tar.gz && rm jdk.tar.gz && mkdir -p /usr/lib/jvm; mv jdk-23 /usr/lib/jvm/openjdk-23 +echo "JAVA_HOME = /usr/lib/jvm/openjdk-23" +/usr/lib/jvm/openjdk-23/bin/java -version \ No newline at end of file From 00b5e71e8b219fd3b3ea31f0a8d05e9bd62bf59b Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 30 Sep 2024 23:57:20 +0900 Subject: [PATCH 17/34] ignore bin directory --- .github/workflows/pull_request.yml | 4 +++- .gitignore | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d6e0b84a..a5746779 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: true matrix: - swift_version: [ nightly-main' ] + swift_version: [ 'nightly-main' ] os_version: [ 'jammy' ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} @@ -50,6 +50,8 @@ jobs: os_version: [ 'jammy' ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + env: + JAVA_HOME: "/usr/lib/jvm/openjdk-23" steps: - uses: actions/checkout@v4 - name: Install Make diff --git a/.gitignore b/.gitignore index b9c1eadb..416f253f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ DerivedData/ .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc *.class +bin/ # Ignore gradle build artifacts .gradle From 09680c48bcea24cd8488cc602f762803b8964550 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 1 Oct 2024 00:01:52 +0900 Subject: [PATCH 18/34] include license_header_check_project_name --- .github/workflows/pull_request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a5746779..60c4f2c4 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,6 +9,7 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: api_breakage_check_enabled: false + license_header_check_project_name: Swift.org test-java: name: Java tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) From a3106d38688543fa36fa6f2006560d8197875aa4 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 1 Oct 2024 00:08:23 +0900 Subject: [PATCH 19/34] allow SILKILL to not cause unaccaptable language failure --- .github/workflows/pull_request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 60c4f2c4..4f2d1b5a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,6 +10,8 @@ jobs: with: api_breakage_check_enabled: false license_header_check_project_name: Swift.org + # swift-subprocess includes the word "kill" because SIGKILL signalhandling so we allow it + unacceptable_language_check_word_list: "blacklist whitelist slave master sane sanity insane insanity killed killing hang hung hanged hanging" #ignore-unacceptable-language test-java: name: Java tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) From c825f6178ca0796fab2fd606c10638c7fca43e15 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 1 Oct 2024 00:14:40 +0900 Subject: [PATCH 20/34] Add .licenseignore --- .licenseignore | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .licenseignore diff --git a/.licenseignore b/.licenseignore new file mode 100644 index 00000000..9bcbea66 --- /dev/null +++ b/.licenseignore @@ -0,0 +1,31 @@ +.gitignore +.licenseignore +.swiftformatignore +.spi.yml +.swift-format +.github/* +*.md +CONTRIBUTORS.txt +LICENSE.txt +NOTICE.txt +Package.swift +Package.resolved +README.md +SECURITY.md +scripts/unacceptable-language.txt +docker/* +**/*.docc/* +**/.gitignore +**/Package.swift +**/Package.resolved +**/*.md +**/openapi.yml +**/petstore.yaml +**/openapi-generator-config.yaml +**/openapi-generator-config.yml +**/docker-compose.yaml +**/docker/* +**/.dockerignore +JavaSwiftKitDemo/src/main/java/com/example/swift/generated/* +**/Makefile +**/*.html \ No newline at end of file From bfa7bcdb2d6b040f669c00546d632c295c81970c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 1 Oct 2024 08:59:38 +0900 Subject: [PATCH 21/34] fix license headers --- .../main/kotlin/build-logic.java-library-conventions.gradle.kts | 1 + .../src/main/java/com/example/swift/HelloSubclass.java | 1 + 2 files changed, 2 insertions(+) diff --git a/BuildLogic/src/main/kotlin/build-logic.java-library-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-library-conventions.gradle.kts index 0ab89662..4bd9b815 100644 --- a/BuildLogic/src/main/kotlin/build-logic.java-library-conventions.gradle.kts +++ b/BuildLogic/src/main/kotlin/build-logic.java-library-conventions.gradle.kts @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java index 29258d3b..2f9a345b 100644 --- a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java +++ b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java @@ -6,6 +6,7 @@ // 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 // From a4b19754094781ed0448d586d36d3dec61a17d7a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 1 Oct 2024 09:13:01 +0900 Subject: [PATCH 22/34] fix license headers --- .licenseignore | 17 +++- BuildLogic/build.gradle.kts | 1 + BuildLogic/settings.gradle.kts | 1 + ...ic.java-application-conventions.gradle.kts | 1 + ...d-logic.java-common-conventions.gradle.kts | 1 + JavaSwiftKitDemo/build.gradle.kts | 1 + .../java/com/example/swift/HelloSwift.java | 1 + .../src/main/java/org/example/CallMe.java | 1 + .../java/org/example/HelloJava2Swift.java | 1 + .../example/swift/ManualJavaKitExample.java | 1 + .../org/example/swift/ManualMySwiftClass.java | 1 + .../example/swift/Manual_MySwiftClass.java | 1 + .../org/swift/javakit/ManagedSwiftType.java | 1 + .../main/java/org/swift/javakit/SwiftKit.java | 1 + .../java/org/swift/swiftkit/SwiftArena.java | 1 + .../org/swift/swiftkit/SwiftHeapObject.java | 1 + .../example/swift/GlobalFunctionsTest.java | 1 + .../java/org/example/swift/JavaKitTest.java | 1 + .../java/org/example/swift/SwiftKitTest.java | 1 + Sources/JExtractSwift/CodePrinter.swift | 1 + .../Convenience/Collection+Extensions.swift | 1 + .../Convenience/SwiftSyntax+Extensions.swift | 1 + Sources/JExtractSwift/ImportedDecls.swift | 1 + .../JavaConstants/ForeignValueLayouts.swift | 1 + Sources/JExtractSwift/JavaTypes.swift | 1 + Sources/JExtractSwift/Logger.swift | 1 + .../JExtractSwift/NominalTypeResolution.swift | 1 + Sources/JExtractSwift/Swift2Java.swift | 1 + .../Swift2JavaTranslator+MemoryLayouts.swift | 1 + .../Swift2JavaTranslator+Printing.swift | 1 + .../JExtractSwift/Swift2JavaTranslator.swift | 1 + Sources/JExtractSwift/Swift2JavaVisitor.swift | 1 + Sources/JExtractSwift/SwiftDylib.swift | 1 + Sources/JExtractSwift/TerminalColors.swift | 1 + Sources/JExtractSwift/TranslatedType.swift | 1 + .../JExtractSwiftTool/JExtractSwiftTool.swift | 1 + Sources/Java2Swift/JavaToSwift.swift | 1 + .../JavaTranslator+TranslationManifest.swift | 1 + Sources/Java2Swift/JavaTranslator.swift | 1 + Sources/Java2Swift/StringExtras.swift | 1 + Sources/Java2Swift/TranslationError.swift | 1 + Sources/Java2Swift/TranslationManifest.swift | 1 + Sources/JavaKit/AnyJavaObject.swift | 1 + .../BridgedValues/JavaValue+Array.swift | 1 + .../BridgedValues/JavaValue+Bool.swift | 1 + .../JavaValue+FloatingPoint.swift | 1 + .../BridgedValues/JavaValue+Integers.swift | 1 + .../BridgedValues/JavaValue+String.swift | 1 + .../JavaKit/Exceptions/Exception+Error.swift | 1 + .../Exceptions/ExceptionHandling.swift | 1 + .../JavaKit/Exceptions/Throwable+Error.swift | 1 + Sources/JavaKit/JavaClass.swift | 1 + .../JavaKit/JavaEnumeration+Sequence.swift | 1 + Sources/JavaKit/JavaEnvironment.swift | 1 + Sources/JavaKit/JavaObject+Inheritance.swift | 1 + Sources/JavaKit/JavaObject+MethodCalls.swift | 1 + Sources/JavaKit/JavaObjectHolder.swift | 1 + Sources/JavaKit/JavaValue.swift | 1 + Sources/JavaKit/Macros.swift | 1 + Sources/JavaKit/Optional+JavaObject.swift | 1 + Sources/JavaKitExample/JavaKitExample.swift | 1 + Sources/JavaKitExample/MySwiftLibrary.swift | 1 + Sources/JavaKitExample/SwiftKit.swift | 1 + .../JavaKitMacros/ImplementsJavaMacro.swift | 1 + Sources/JavaKitMacros/JavaClassMacro.swift | 1 + Sources/JavaKitMacros/JavaFieldMacro.swift | 1 + Sources/JavaKitMacros/JavaMethodMacro.swift | 1 + Sources/JavaKitMacros/MacroErrors.swift | 1 + .../JavaKitMacros/SwiftJNIMacrosPlugin.swift | 1 + Sources/JavaKitMacros/SwiftSyntaxUtils.swift | 1 + .../Constructor+Utilities.swift | 1 + .../Executable+Utilities.swift | 1 + .../JavaClass+Reflection.swift | 1 + .../JavaKitReflection/Method+Utilities.swift | 1 + Sources/JavaKitVM/JavaVirtualMachine.swift | 1 + Sources/JavaRuntime/dummy.c | 1 + Sources/JavaRuntime/include/JavaRuntime.h | 1 + Sources/JavaTypes/JavaDemanglingError.swift | 1 + Sources/JavaTypes/JavaType+JNI.swift | 1 + Sources/JavaTypes/JavaType+JavaSource.swift | 1 + Sources/JavaTypes/JavaType+SwiftNames.swift | 1 + Sources/JavaTypes/JavaType.swift | 1 + Sources/JavaTypes/Mangling.swift | 1 + Sources/JavaTypes/MethodSignature.swift | 1 + SwiftJavaKitExample/build.gradle.kts | 1 + SwiftKit/build.gradle.kts | 1 + .../Asserts/TextAssertions.swift | 1 + .../JExtractSwiftTests/FuncImportTests.swift | 1 + .../FunctionDescriptorImportTests.swift | 1 + .../NominalTypeResolutionTests.swift | 1 + .../JExtractSwiftTests/SwiftDylibTests.swift | 1 + Tests/JavaKitTests/BasicRuntimeTests.swift | 1 + Tests/JavaTypesTests/ManglingTests.swift | 1 + scripts/check-license-header.sh | 79 ------------------- scripts/install_jdk.sh | 1 + settings.gradle.kts | 1 + 96 files changed, 110 insertions(+), 80 deletions(-) delete mode 100755 scripts/check-license-header.sh diff --git a/.licenseignore b/.licenseignore index 9bcbea66..86afc57b 100644 --- a/.licenseignore +++ b/.licenseignore @@ -27,5 +27,20 @@ docker/* **/docker/* **/.dockerignore JavaSwiftKitDemo/src/main/java/com/example/swift/generated/* +Makefile **/Makefile -**/*.html \ No newline at end of file +**/*.html +**/CMakeLists.txt +**/*.jar +gradle/wrapper/gradle-wrapper.properties +gradlew +gradlew.bat +**/*.swift2java +Sources/JavaKit/generated/* +Sources/JavaKitJar/generated/* +Sources/JavaKitNetwork/generated/* +Sources/JavaKitReflection/generated/* +Sources/_Subprocess/* +Sources/_Subprocess/**/* +Sources/_SubprocessCShims/* +Sources/_SubprocessCShims/**/* diff --git a/BuildLogic/build.gradle.kts b/BuildLogic/build.gradle.kts index 5efdc8c8..142b7f7c 100644 --- a/BuildLogic/build.gradle.kts +++ b/BuildLogic/build.gradle.kts @@ -6,6 +6,7 @@ // 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 // diff --git a/BuildLogic/settings.gradle.kts b/BuildLogic/settings.gradle.kts index 045cfa06..44b13a6b 100644 --- a/BuildLogic/settings.gradle.kts +++ b/BuildLogic/settings.gradle.kts @@ -6,6 +6,7 @@ // 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 // diff --git a/BuildLogic/src/main/kotlin/build-logic.java-application-conventions.gradle.kts b/BuildLogic/src/main/kotlin/build-logic.java-application-conventions.gradle.kts index 0ab89662..4bd9b815 100644 --- a/BuildLogic/src/main/kotlin/build-logic.java-application-conventions.gradle.kts +++ b/BuildLogic/src/main/kotlin/build-logic.java-application-conventions.gradle.kts @@ -6,6 +6,7 @@ // 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 // 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 67e91248..85142bd9 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 @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/build.gradle.kts b/JavaSwiftKitDemo/build.gradle.kts index dd325591..84c0b924 100644 --- a/JavaSwiftKitDemo/build.gradle.kts +++ b/JavaSwiftKitDemo/build.gradle.kts @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java index a6c9bbd7..da4706e9 100644 --- a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java +++ b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/example/CallMe.java b/JavaSwiftKitDemo/src/main/java/org/example/CallMe.java index fc7f0373..4af50f91 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/CallMe.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/CallMe.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java index 4aa00b25..00264946 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java index a3499b15..a3e49da5 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java index b7994665..53b21e8a 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualMySwiftClass.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/example/swift/Manual_MySwiftClass.java b/JavaSwiftKitDemo/src/main/java/org/example/swift/Manual_MySwiftClass.java index d9f39034..9ef51038 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/swift/Manual_MySwiftClass.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/swift/Manual_MySwiftClass.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/ManagedSwiftType.java b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/ManagedSwiftType.java index c5a3aa97..117a0a09 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/ManagedSwiftType.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/ManagedSwiftType.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java index 0c43db5a..07765a08 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java index 5f24a3ef..2c60dc3f 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftArena.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java index 21152cf8..5d1945dd 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/swiftkit/SwiftHeapObject.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java b/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java index f3b15e7b..7faff6b0 100644 --- a/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java +++ b/JavaSwiftKitDemo/src/test/java/org/example/swift/GlobalFunctionsTest.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/test/java/org/example/swift/JavaKitTest.java b/JavaSwiftKitDemo/src/test/java/org/example/swift/JavaKitTest.java index 89b5350c..404dca42 100644 --- a/JavaSwiftKitDemo/src/test/java/org/example/swift/JavaKitTest.java +++ b/JavaSwiftKitDemo/src/test/java/org/example/swift/JavaKitTest.java @@ -6,6 +6,7 @@ // 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 // diff --git a/JavaSwiftKitDemo/src/test/java/org/example/swift/SwiftKitTest.java b/JavaSwiftKitDemo/src/test/java/org/example/swift/SwiftKitTest.java index 5eda123a..390b1ceb 100644 --- a/JavaSwiftKitDemo/src/test/java/org/example/swift/SwiftKitTest.java +++ b/JavaSwiftKitDemo/src/test/java/org/example/swift/SwiftKitTest.java @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/CodePrinter.swift b/Sources/JExtractSwift/CodePrinter.swift index 80fdd79d..56a61a42 100644 --- a/Sources/JExtractSwift/CodePrinter.swift +++ b/Sources/JExtractSwift/CodePrinter.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Convenience/Collection+Extensions.swift b/Sources/JExtractSwift/Convenience/Collection+Extensions.swift index 1f8cb0e6..ca23435c 100644 --- a/Sources/JExtractSwift/Convenience/Collection+Extensions.swift +++ b/Sources/JExtractSwift/Convenience/Collection+Extensions.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift index b175482e..0a8f5533 100644 --- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index a1254d51..c8cdd8fa 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift index a63f565d..292b7182 100644 --- a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift +++ b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/JavaTypes.swift b/Sources/JExtractSwift/JavaTypes.swift index e38df600..01a69aaf 100644 --- a/Sources/JExtractSwift/JavaTypes.swift +++ b/Sources/JExtractSwift/JavaTypes.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Logger.swift b/Sources/JExtractSwift/Logger.swift index 41c0a96a..7309c3d7 100644 --- a/Sources/JExtractSwift/Logger.swift +++ b/Sources/JExtractSwift/Logger.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/NominalTypeResolution.swift b/Sources/JExtractSwift/NominalTypeResolution.swift index c655598c..e3cc18a1 100644 --- a/Sources/JExtractSwift/NominalTypeResolution.swift +++ b/Sources/JExtractSwift/NominalTypeResolution.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwift/Swift2Java.swift index ab97da2a..5c6d8a69 100644 --- a/Sources/JExtractSwift/Swift2Java.swift +++ b/Sources/JExtractSwift/Swift2Java.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift index 5109446b..4e8f6aa7 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 46274c35..89eb4d48 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index d66e23c2..e942ff9b 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index e4524461..c6c9bb56 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/SwiftDylib.swift b/Sources/JExtractSwift/SwiftDylib.swift index 604bd2f6..28ddcb90 100644 --- a/Sources/JExtractSwift/SwiftDylib.swift +++ b/Sources/JExtractSwift/SwiftDylib.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/TerminalColors.swift b/Sources/JExtractSwift/TerminalColors.swift index e36de1c7..fcef5a3c 100644 --- a/Sources/JExtractSwift/TerminalColors.swift +++ b/Sources/JExtractSwift/TerminalColors.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index 8262aff4..c759b3af 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JExtractSwiftTool/JExtractSwiftTool.swift b/Sources/JExtractSwiftTool/JExtractSwiftTool.swift index 2e3c339f..b9853b82 100644 --- a/Sources/JExtractSwiftTool/JExtractSwiftTool.swift +++ b/Sources/JExtractSwiftTool/JExtractSwiftTool.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/Java2Swift/JavaToSwift.swift b/Sources/Java2Swift/JavaToSwift.swift index c43c7e04..87fcc7e7 100644 --- a/Sources/Java2Swift/JavaToSwift.swift +++ b/Sources/Java2Swift/JavaToSwift.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/Java2Swift/JavaTranslator+TranslationManifest.swift b/Sources/Java2Swift/JavaTranslator+TranslationManifest.swift index d839053e..244cefd4 100644 --- a/Sources/Java2Swift/JavaTranslator+TranslationManifest.swift +++ b/Sources/Java2Swift/JavaTranslator+TranslationManifest.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/Java2Swift/JavaTranslator.swift b/Sources/Java2Swift/JavaTranslator.swift index f416cf14..c83e5c85 100644 --- a/Sources/Java2Swift/JavaTranslator.swift +++ b/Sources/Java2Swift/JavaTranslator.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/Java2Swift/StringExtras.swift b/Sources/Java2Swift/StringExtras.swift index a066eec0..34729883 100644 --- a/Sources/Java2Swift/StringExtras.swift +++ b/Sources/Java2Swift/StringExtras.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/Java2Swift/TranslationError.swift b/Sources/Java2Swift/TranslationError.swift index b4839c3e..d44fd2d7 100644 --- a/Sources/Java2Swift/TranslationError.swift +++ b/Sources/Java2Swift/TranslationError.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/Java2Swift/TranslationManifest.swift b/Sources/Java2Swift/TranslationManifest.swift index c24c171e..aa03eae6 100644 --- a/Sources/Java2Swift/TranslationManifest.swift +++ b/Sources/Java2Swift/TranslationManifest.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/AnyJavaObject.swift b/Sources/JavaKit/AnyJavaObject.swift index 209dd3ae..aaddaa9a 100644 --- a/Sources/JavaKit/AnyJavaObject.swift +++ b/Sources/JavaKit/AnyJavaObject.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Array.swift b/Sources/JavaKit/BridgedValues/JavaValue+Array.swift index f09aaf2f..dce723d1 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Array.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Array.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Bool.swift b/Sources/JavaKit/BridgedValues/JavaValue+Bool.swift index 5a1149b5..c1599076 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Bool.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Bool.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift b/Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift index 53c46bbf..f90c5019 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+FloatingPoint.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift index ba3e9803..39dca985 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+Integers.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/BridgedValues/JavaValue+String.swift b/Sources/JavaKit/BridgedValues/JavaValue+String.swift index 016bdc23..028cf390 100644 --- a/Sources/JavaKit/BridgedValues/JavaValue+String.swift +++ b/Sources/JavaKit/BridgedValues/JavaValue+String.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/Exceptions/Exception+Error.swift b/Sources/JavaKit/Exceptions/Exception+Error.swift index 7071bce8..688ae9d0 100644 --- a/Sources/JavaKit/Exceptions/Exception+Error.swift +++ b/Sources/JavaKit/Exceptions/Exception+Error.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/Exceptions/ExceptionHandling.swift b/Sources/JavaKit/Exceptions/ExceptionHandling.swift index d9c4129b..44f1897c 100644 --- a/Sources/JavaKit/Exceptions/ExceptionHandling.swift +++ b/Sources/JavaKit/Exceptions/ExceptionHandling.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/Exceptions/Throwable+Error.swift b/Sources/JavaKit/Exceptions/Throwable+Error.swift index c6eb99d8..bae53123 100644 --- a/Sources/JavaKit/Exceptions/Throwable+Error.swift +++ b/Sources/JavaKit/Exceptions/Throwable+Error.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/JavaClass.swift b/Sources/JavaKit/JavaClass.swift index 8db54bae..8cb2e3c2 100644 --- a/Sources/JavaKit/JavaClass.swift +++ b/Sources/JavaKit/JavaClass.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/JavaEnumeration+Sequence.swift b/Sources/JavaKit/JavaEnumeration+Sequence.swift index b096dbc8..742bfc91 100644 --- a/Sources/JavaKit/JavaEnumeration+Sequence.swift +++ b/Sources/JavaKit/JavaEnumeration+Sequence.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/JavaEnvironment.swift b/Sources/JavaKit/JavaEnvironment.swift index 4e92d635..d74146ab 100644 --- a/Sources/JavaKit/JavaEnvironment.swift +++ b/Sources/JavaKit/JavaEnvironment.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/JavaObject+Inheritance.swift b/Sources/JavaKit/JavaObject+Inheritance.swift index 46e0aab2..8d6b68fa 100644 --- a/Sources/JavaKit/JavaObject+Inheritance.swift +++ b/Sources/JavaKit/JavaObject+Inheritance.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/JavaObject+MethodCalls.swift b/Sources/JavaKit/JavaObject+MethodCalls.swift index 3e625b77..62a5617e 100644 --- a/Sources/JavaKit/JavaObject+MethodCalls.swift +++ b/Sources/JavaKit/JavaObject+MethodCalls.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/JavaObjectHolder.swift b/Sources/JavaKit/JavaObjectHolder.swift index 29529ef5..173991c9 100644 --- a/Sources/JavaKit/JavaObjectHolder.swift +++ b/Sources/JavaKit/JavaObjectHolder.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/JavaValue.swift b/Sources/JavaKit/JavaValue.swift index ab7bc50d..310b54df 100644 --- a/Sources/JavaKit/JavaValue.swift +++ b/Sources/JavaKit/JavaValue.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/Macros.swift b/Sources/JavaKit/Macros.swift index 24383dc8..24ce8d32 100644 --- a/Sources/JavaKit/Macros.swift +++ b/Sources/JavaKit/Macros.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKit/Optional+JavaObject.swift b/Sources/JavaKit/Optional+JavaObject.swift index d66f54fd..0444b4f1 100644 --- a/Sources/JavaKit/Optional+JavaObject.swift +++ b/Sources/JavaKit/Optional+JavaObject.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitExample/JavaKitExample.swift b/Sources/JavaKitExample/JavaKitExample.swift index 3253637b..7252d648 100644 --- a/Sources/JavaKitExample/JavaKitExample.swift +++ b/Sources/JavaKitExample/JavaKitExample.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitExample/MySwiftLibrary.swift b/Sources/JavaKitExample/MySwiftLibrary.swift index 115a281d..bd76fcf1 100644 --- a/Sources/JavaKitExample/MySwiftLibrary.swift +++ b/Sources/JavaKitExample/MySwiftLibrary.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitExample/SwiftKit.swift b/Sources/JavaKitExample/SwiftKit.swift index df4a258f..e034f6d0 100644 --- a/Sources/JavaKitExample/SwiftKit.swift +++ b/Sources/JavaKitExample/SwiftKit.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitMacros/ImplementsJavaMacro.swift b/Sources/JavaKitMacros/ImplementsJavaMacro.swift index 5b1ca607..2d6d7159 100644 --- a/Sources/JavaKitMacros/ImplementsJavaMacro.swift +++ b/Sources/JavaKitMacros/ImplementsJavaMacro.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitMacros/JavaClassMacro.swift b/Sources/JavaKitMacros/JavaClassMacro.swift index ceae3580..b1da49dd 100644 --- a/Sources/JavaKitMacros/JavaClassMacro.swift +++ b/Sources/JavaKitMacros/JavaClassMacro.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitMacros/JavaFieldMacro.swift b/Sources/JavaKitMacros/JavaFieldMacro.swift index 86c819b1..10f06ad3 100644 --- a/Sources/JavaKitMacros/JavaFieldMacro.swift +++ b/Sources/JavaKitMacros/JavaFieldMacro.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitMacros/JavaMethodMacro.swift b/Sources/JavaKitMacros/JavaMethodMacro.swift index 35dc67fe..7996fc8c 100644 --- a/Sources/JavaKitMacros/JavaMethodMacro.swift +++ b/Sources/JavaKitMacros/JavaMethodMacro.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitMacros/MacroErrors.swift b/Sources/JavaKitMacros/MacroErrors.swift index f9b02303..7a9622c9 100644 --- a/Sources/JavaKitMacros/MacroErrors.swift +++ b/Sources/JavaKitMacros/MacroErrors.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitMacros/SwiftJNIMacrosPlugin.swift b/Sources/JavaKitMacros/SwiftJNIMacrosPlugin.swift index 80be6237..f8841ef0 100644 --- a/Sources/JavaKitMacros/SwiftJNIMacrosPlugin.swift +++ b/Sources/JavaKitMacros/SwiftJNIMacrosPlugin.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitMacros/SwiftSyntaxUtils.swift b/Sources/JavaKitMacros/SwiftSyntaxUtils.swift index 0d405495..08607056 100644 --- a/Sources/JavaKitMacros/SwiftSyntaxUtils.swift +++ b/Sources/JavaKitMacros/SwiftSyntaxUtils.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitReflection/Constructor+Utilities.swift b/Sources/JavaKitReflection/Constructor+Utilities.swift index 694df7d5..4979abbc 100644 --- a/Sources/JavaKitReflection/Constructor+Utilities.swift +++ b/Sources/JavaKitReflection/Constructor+Utilities.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitReflection/Executable+Utilities.swift b/Sources/JavaKitReflection/Executable+Utilities.swift index 6041c329..2b0a8a2d 100644 --- a/Sources/JavaKitReflection/Executable+Utilities.swift +++ b/Sources/JavaKitReflection/Executable+Utilities.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitReflection/JavaClass+Reflection.swift b/Sources/JavaKitReflection/JavaClass+Reflection.swift index c1324c85..5191a7b7 100644 --- a/Sources/JavaKitReflection/JavaClass+Reflection.swift +++ b/Sources/JavaKitReflection/JavaClass+Reflection.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitReflection/Method+Utilities.swift b/Sources/JavaKitReflection/Method+Utilities.swift index 588d3cde..8f981369 100644 --- a/Sources/JavaKitReflection/Method+Utilities.swift +++ b/Sources/JavaKitReflection/Method+Utilities.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaKitVM/JavaVirtualMachine.swift b/Sources/JavaKitVM/JavaVirtualMachine.swift index 9ab0c873..1d144157 100644 --- a/Sources/JavaKitVM/JavaVirtualMachine.swift +++ b/Sources/JavaKitVM/JavaVirtualMachine.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaRuntime/dummy.c b/Sources/JavaRuntime/dummy.c index c1dd7e37..33600881 100644 --- a/Sources/JavaRuntime/dummy.c +++ b/Sources/JavaRuntime/dummy.c @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaRuntime/include/JavaRuntime.h b/Sources/JavaRuntime/include/JavaRuntime.h index 30bdcc85..02bf548e 100644 --- a/Sources/JavaRuntime/include/JavaRuntime.h +++ b/Sources/JavaRuntime/include/JavaRuntime.h @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaTypes/JavaDemanglingError.swift b/Sources/JavaTypes/JavaDemanglingError.swift index 85daa3b4..445957d4 100644 --- a/Sources/JavaTypes/JavaDemanglingError.swift +++ b/Sources/JavaTypes/JavaDemanglingError.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaTypes/JavaType+JNI.swift b/Sources/JavaTypes/JavaType+JNI.swift index 6f797168..41a93d25 100644 --- a/Sources/JavaTypes/JavaType+JNI.swift +++ b/Sources/JavaTypes/JavaType+JNI.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaTypes/JavaType+JavaSource.swift b/Sources/JavaTypes/JavaType+JavaSource.swift index fc90eae1..e1fa8129 100644 --- a/Sources/JavaTypes/JavaType+JavaSource.swift +++ b/Sources/JavaTypes/JavaType+JavaSource.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaTypes/JavaType+SwiftNames.swift b/Sources/JavaTypes/JavaType+SwiftNames.swift index 088cb545..4bef1e75 100644 --- a/Sources/JavaTypes/JavaType+SwiftNames.swift +++ b/Sources/JavaTypes/JavaType+SwiftNames.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaTypes/JavaType.swift b/Sources/JavaTypes/JavaType.swift index d11973ee..80364b5b 100644 --- a/Sources/JavaTypes/JavaType.swift +++ b/Sources/JavaTypes/JavaType.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaTypes/Mangling.swift b/Sources/JavaTypes/Mangling.swift index 694f2219..c4774dbb 100644 --- a/Sources/JavaTypes/Mangling.swift +++ b/Sources/JavaTypes/Mangling.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Sources/JavaTypes/MethodSignature.swift b/Sources/JavaTypes/MethodSignature.swift index 834e1450..3e2b6613 100644 --- a/Sources/JavaTypes/MethodSignature.swift +++ b/Sources/JavaTypes/MethodSignature.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/SwiftJavaKitExample/build.gradle.kts b/SwiftJavaKitExample/build.gradle.kts index 13ed70fd..4c77db83 100644 --- a/SwiftJavaKitExample/build.gradle.kts +++ b/SwiftJavaKitExample/build.gradle.kts @@ -6,6 +6,7 @@ // 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 // diff --git a/SwiftKit/build.gradle.kts b/SwiftKit/build.gradle.kts index b7ecb6a8..3842620b 100644 --- a/SwiftKit/build.gradle.kts +++ b/SwiftKit/build.gradle.kts @@ -6,6 +6,7 @@ // 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 // diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index b03d3d74..8017822f 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Tests/JExtractSwiftTests/FuncImportTests.swift b/Tests/JExtractSwiftTests/FuncImportTests.swift index 4c210b3e..ed71c14d 100644 --- a/Tests/JExtractSwiftTests/FuncImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncImportTests.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 064672e1..b6f93eb2 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift b/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift index bb8fce6c..01b507df 100644 --- a/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift +++ b/Tests/JExtractSwiftTests/NominalTypeResolutionTests.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Tests/JExtractSwiftTests/SwiftDylibTests.swift b/Tests/JExtractSwiftTests/SwiftDylibTests.swift index d23a40ea..2cf8894b 100644 --- a/Tests/JExtractSwiftTests/SwiftDylibTests.swift +++ b/Tests/JExtractSwiftTests/SwiftDylibTests.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Tests/JavaKitTests/BasicRuntimeTests.swift b/Tests/JavaKitTests/BasicRuntimeTests.swift index 38437c9f..33a93fd4 100644 --- a/Tests/JavaKitTests/BasicRuntimeTests.swift +++ b/Tests/JavaKitTests/BasicRuntimeTests.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/Tests/JavaTypesTests/ManglingTests.swift b/Tests/JavaTypesTests/ManglingTests.swift index 6be75d6b..778360cd 100644 --- a/Tests/JavaTypesTests/ManglingTests.swift +++ b/Tests/JavaTypesTests/ManglingTests.swift @@ -6,6 +6,7 @@ // 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 // diff --git a/scripts/check-license-header.sh b/scripts/check-license-header.sh deleted file mode 100755 index afdeb30d..00000000 --- a/scripts/check-license-header.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## 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 -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## -set -euo pipefail - -log() { printf -- "** %s\n" "$*" >&2; } -error() { printf -- "** ERROR: %s\n" "$*" >&2; } -fatal() { error "$@"; exit 1; } - -declare -r PROJECT_NAME="Swift.org" - -test -n "${PROJECT_NAME:-}" || fatal "PROJECT_NAME unset" - -expected_file_header_template="@@===----------------------------------------------------------------------===@@ -@@ -@@ This source file is part of the ${PROJECT_NAME} open source project -@@ -@@ Copyright (c) YEARS Apple Inc. and the ${PROJECT_NAME} project authors -@@ Licensed under Apache License v2.0 -@@ -@@ See LICENSE.txt for license information -@@ -@@ SPDX-License-Identifier: Apache-2.0 -@@ -@@===----------------------------------------------------------------------===@@" - -paths_with_missing_license=( ) - -file_paths=$(tr '\n' '\0' < .licenseignore | xargs -0 -I% printf '":(exclude)%" ' | xargs git ls-files) - -while IFS= read -r file_path; do - file_basename=$(basename -- "${file_path}") - file_extension="${file_basename##*.}" - - # shellcheck disable=SC2001 # We prefer to use sed here instead of bash search/replace - case "${file_extension}" in - swift) expected_file_header=$(sed -e 's|@@|//|g' <<<"${expected_file_header_template}") ;; - java) expected_file_header=$(sed -e 's|@@|//|g' <<<"${expected_file_header_template}") ;; - kts) expected_file_header=$(sed -e 's|@@|//|g' <<<"${expected_file_header_template}") ;; - h) expected_file_header=$(sed -e 's|@@|//|g' <<<"${expected_file_header_template}") ;; - c) expected_file_header=$(sed -e 's|@@|//|g' <<<"${expected_file_header_template}") ;; - sh) expected_file_header=$(cat <(echo '#!/bin/bash') <(sed -e 's|@@|##|g' <<<"${expected_file_header_template}")) ;; - py) expected_file_header=$(cat <(echo '#!/usr/bin/env python3') <(sed -e 's|@@|##|g' <<<"${expected_file_header_template}")) ;; - rb) expected_file_header=$(cat <(echo '#!/usr/bin/env ruby') <(sed -e 's|@@|##|g' <<<"${expected_file_header_template}")) ;; - in) expected_file_header=$(sed -e 's|@@|##|g' <<<"${expected_file_header_template}") ;; - cmake) expected_file_header=$(sed -e 's|@@|##|g' <<<"${expected_file_header_template}") ;; - *) echo "Unsupported file extension for file (exclude or update this script): ${file_path}" ;; - esac - expected_file_header_linecount=$(wc -l <<<"${expected_file_header}") - - file_header=$(head -n "${expected_file_header_linecount}" "${file_path}") - normalized_file_header=$( - echo "${file_header}" \ - | sed -e 's/20[12][0123456789]-20[12][0123456789]/YEARS/' -e 's/20[12][0123456789]/YEARS/' \ - ) - - if ! diff -u \ - --label "Expected header" <(echo "${expected_file_header}") \ - --label "${file_path}" <(echo "${normalized_file_header}") - then - paths_with_missing_license+=("${file_path} ") - fi -done <<< "$file_paths" - -if [ "${#paths_with_missing_license[@]}" -gt 0 ]; then - fatal "❌ Found missing license header in files: ${paths_with_missing_license[*]}." -fi - -log "✅ Found no files with missing license header." \ No newline at end of file diff --git a/scripts/install_jdk.sh b/scripts/install_jdk.sh index 2067bf0b..b208a2fb 100755 --- a/scripts/install_jdk.sh +++ b/scripts/install_jdk.sh @@ -7,6 +7,7 @@ ## 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 ## diff --git a/settings.gradle.kts b/settings.gradle.kts index 87616cf3..04c6b6ed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,6 +6,7 @@ // 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 // From 245da1f8ddeaf9a91452dad7319575ffbfa120c0 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 1 Oct 2024 18:13:41 +0900 Subject: [PATCH 23/34] style fixes --- .../src/main/java/com/example/swift/HelloSubclass.java | 1 + .../src/main/java/com/example/swift/HelloSwift.java | 1 + .../main/java/org/example/swift/ManualJavaKitExample.java | 5 ++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java index 2f9a345b..25cc9245 100644 --- a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java +++ b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSubclass.java @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// package com.example.swift; + import com.example.swift.HelloSwift; public class HelloSubclass extends HelloSwift { diff --git a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java index da4706e9..652d230b 100644 --- a/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java +++ b/JavaSwiftKitDemo/src/main/java/com/example/swift/HelloSwift.java @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// package com.example.swift; + import com.example.swift.HelloSubclass; public class HelloSwift { diff --git a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java index a3e49da5..f11c5637 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/swift/ManualJavaKitExample.java @@ -200,14 +200,13 @@ public static void globalCallJavaCallback(Runnable callMe) { var mh$ = globalCallJavaCallback.HANDLE; try { + // signature of 'void run()' FunctionDescriptor callMe_run_desc = FunctionDescriptor.ofVoid( - // replicate signature of run() ); MethodHandle callMe_run_handle = MethodHandles.lookup() .findVirtual(Runnable.class, "run", - callMe_run_desc.toMethodType() - ); + callMe_run_desc.toMethodType()); callMe_run_handle = callMe_run_handle.bindTo(callMe); // set the first parameter to the Runnable as the "this" of the callback pretty much try (Arena arena = Arena.ofConfined()) { From f5fcd378392902ebc1ae9dec1ea47124ca0dae3c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 06:53:57 +0900 Subject: [PATCH 24/34] support Corretto in install jdk script --- .github/workflows/pull_request.yml | 4 +-- scripts/install_jdk.sh | 54 ++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4f2d1b5a..07287730 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -24,7 +24,7 @@ jobs: container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: - JAVA_HOME: "/usr/lib/jvm/openjdk-23" + JAVA_HOME: "/usr/lib/jvm/default-jdk" steps: - uses: actions/checkout@v4 - name: Install Make @@ -54,7 +54,7 @@ jobs: container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: - JAVA_HOME: "/usr/lib/jvm/openjdk-23" + JAVA_HOME: "/usr/lib/jvm/default-jdk" steps: - uses: actions/checkout@v4 - name: Install Make diff --git a/scripts/install_jdk.sh b/scripts/install_jdk.sh index b208a2fb..da0deca9 100755 --- a/scripts/install_jdk.sh +++ b/scripts/install_jdk.sh @@ -14,30 +14,48 @@ ##===----------------------------------------------------------------------===## set -euo pipefail -declare -r JDK_VERSION=23 -echo "Installing OpenJDK $JDK_VERSION..." +declare -r JDK_VENDOR=Corretto +echo "Installing $JDK_VENDOR JDK..." apt-get update && apt-get install -y make curl libc6-dev echo "Download JDK for: $(uname -m)" -if [ "$(uname -m)" = 'aarch64' ]; then - curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz --output jdk.tar.gz && - declare -r EXPECT_JDK_SHA=076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7; -else - curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-x64_bin.tar.gz --output jdk.tar.gz && - declare -r EXPECT_JDK_SHA=08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67; -fi +if [ "$JDK_VENDOR" = 'OpenJDK' ]; then + if [ "$(uname -m)" = 'aarch64' ]; then + curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz --output jdk.tar.gz && + declare -r EXPECT_JDK_SHA=076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7; + else + curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-x64_bin.tar.gz --output jdk.tar.gz && + declare -r EXPECT_JDK_SHA=08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67; + fi + + declare -r JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" + if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then + echo "Downloaded JDK SHA does not match expected!" && + exit 1; + else + echo "JDK SHA is correct."; + fi +elif [ "$JDK_VENDOR" = 'Corretto' ]; then + if [ "$(uname -m)" = 'aarch64' ]; then + curl https://corretto.aws/downloads/latest/amazon-corretto-22-aarch64-linux-jdk.tar.gz --output jdk.tar.gz && + declare -r EXPECT_JDK_MD5=1ebe5f5229bb18bc784a1e0f54d3fe39 + else + curl https://corretto.aws/downloads/latest/amazon-corretto-22-x64-linux-jdk.tar.gz --output jdk.tar.gz && + declare -r EXPECT_JDK_MD5=5bd7fe30eb063699a3b4db7a00455841 + fi -declare -r JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" -if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then - echo "Downloaded JDK SHA does not match expected!" && - exit 1; -else - echo "JDK SHA is correct."; + declare -r JDK_MD5="$(md5sum jdk.tar.gz | cut -d ' ' -f 1)" + if [ "$JDK_MD5" != "$EXPECT_JDK_MD5" ]; then + echo "Downloaded JDK MD5 does not match expected!" && + exit 1; + else + echo "JDK MD5 is correct."; + fi fi # Extract and verify the JDK installation -tar xzvf jdk.tar.gz && rm jdk.tar.gz && mkdir -p /usr/lib/jvm; mv jdk-23 /usr/lib/jvm/openjdk-23 -echo "JAVA_HOME = /usr/lib/jvm/openjdk-23" -/usr/lib/jvm/openjdk-23/bin/java -version \ No newline at end of file +tar xzvf jdk.tar.gz && rm jdk.tar.gz && mkdir -p /usr/lib/jvm; mv jdk-23 /usr/lib/jvm/default-jdk +echo "JAVA_HOME = /usr/lib/jvm/default-jdk" +/usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file From 2ac5e479791ce2a6ea7f6facee154d4940675b75 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 18:56:09 +0900 Subject: [PATCH 25/34] Allow installing Corretto JDK --- .github/workflows/pull_request.yml | 6 +-- docker/Dockerfile | 38 ++++----------- docker/install_jdk.sh | 74 ++++++++++++++++++++++++++++++ scripts/install_jdk.sh | 61 ------------------------ 4 files changed, 86 insertions(+), 93 deletions(-) create mode 100755 docker/install_jdk.sh delete mode 100755 scripts/install_jdk.sh diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 07287730..63f792e1 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -30,7 +30,7 @@ jobs: - name: Install Make run: apt update && apt install -y make - name: Install JDK - run: "./scripts/install_jdk.sh" + run: "bash -x ./docker/install_jdk.sh" # TODO: not using setup-java since incompatible with the swiftlang/swift base image # - uses: actions/setup-java@v4 # with: @@ -49,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - swift_version: [ '6.0' ] + swift_version: [ 'nightly-main' ] os_version: [ 'jammy' ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} @@ -60,7 +60,7 @@ jobs: - name: Install Make run: apt update && apt install -y make - name: Install JDK - run: "./scripts/install_jdk.sh" + run: "bash -x ./docker/install_jdk.sh" - name: Generate sources (make) (Temporary) # TODO: this should be triggered by the respective builds run: "make jextract-run" diff --git a/docker/Dockerfile b/docker/Dockerfile index 0f19a890..9680b53d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -7,36 +7,16 @@ ARG swift_version ARG ubuntu_version # set as UTF-8 -RUN apt-get update && apt-get install -y locales locales-all +RUN apt-get update && apt-get install -y \ + locales locales-all \ + make \ + libc6-dev ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US.UTF-8 -# Build dependencies -RUN apt-get update && apt-get install -y make curl libc6-dev - -# JDK dependency (we require JDK22+ which isn't distributed as package in Jammy) -# We download the latest supported non-LTS version from OpenJDK: https://jdk.java.net -RUN echo "Download JDK for: $(uname -m)" -RUN if [ "$(uname -m)" = 'aarch64' ]; then \ - echo "Download JDK for: ARM64" && \ - curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz --output jdk.tar.gz && \ - EXPECT_JDK_SHA=076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7; \ - else \ - echo "Download JDK for: x86" && \ - curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-x64_bin.tar.gz --output jdk.tar.gz && \ - EXPECT_JDK_SHA=08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67; \ - fi -# Verify the downlaoded JDK checksums -RUN JDK_SHA=$(sha256sum jdk.tar.gz | cut -d ' ' -f 1) -RUN if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then \ - echo "Downloaded JDK SHA does not match expected!" && \ - exit 1; \ - else \ - echo "JDK SHA is correct."; \ - fi -# Extract and verify the JDK installation -RUN tar xzvf jdk.tar.gz && rm jdk.tar.gz && mv jdk-23 jdk -RUN /jdk/bin/java -version -ENV JAVA_HOME="/jdk" -ENV PATH="$PATH:/jdk/bin" +# JDK dependency +COPY install_jdk.sh . +RUN bash -x ./install_jdk.sh +ENV JAVA_HOME="/usr/lib/jvm/default-jdk" +ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" \ No newline at end of file diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh new file mode 100755 index 00000000..031440c3 --- /dev/null +++ b/docker/install_jdk.sh @@ -0,0 +1,74 @@ +#!/bin/bash +##===----------------------------------------------------------------------===## +## +## 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 +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +# Supported JDKs: Corretto or OpenJDK +declare -r JDK_VENDOR=Corretto +echo "Installing $JDK_VENDOR JDK..." + +apt-get update && apt-get install -y wget + +echo "Download JDK for: $(uname -m)" + +if [ "$JDK_VENDOR" = 'OpenJDK' ]; then + if [ "$(uname -m)" = 'aarch64' ]; then + declare -r JDK_URL="https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz" + declare -r EXPECT_JDK_SHA="076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7" + else + declare -r JDK_URL="https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/$JDK_NAME" + declare -r EXPECT_JDK_SHA="08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67" + fi + + wget -q -O jdk.tar.gz "$JDK_URL" + declare -r JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" + if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then + echo "Downloaded JDK SHA does not match expected!" + echo "Expected: $EXPECT_JDK_SHA" + echo " Was: $JDK_SHA" + exit 1; + else + echo "JDK SHA is correct."; + fi +elif [ "$JDK_VENDOR" = 'Corretto' ]; then + if [ "$(uname -m)" = 'aarch64' ]; then + declare -r JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-22-aarch64-linux-jdk.tar.gz" + declare -r EXPECT_JDK_MD5="1ebe5f5229bb18bc784a1e0f54d3fe39" + else + declare -r JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-22-x64-linux-jdk.tar.gz" + declare -r EXPECT_JDK_MD5="5bd7fe30eb063699a3b4db7a00455841" + fi + + wget -q -O jdk.tar.gz "$JDK_URL" + declare -r JDK_MD5="$(md5sum jdk.tar.gz | cut -d ' ' -f 1)" + if [ "$JDK_MD5" != "$EXPECT_JDK_MD5" ]; then + echo "Downloaded JDK MD5 does not match expected!" + echo "Expected: $EXPECT_JDK_MD5" + echo " Was: $JDK_MD5" + exit 1; + else + echo "JDK MD5 is correct."; + fi +fi + +# Extract and verify the JDK installation + +mkdir -p /usr/lib/jvm/ && cd /usr/lib/jvm/ +tar xzvf /jdk.tar.gz +ls -lah +mv "$(ls | head -n1)" default-jdk +rm /jdk.tar.gz + +echo "JAVA_HOME = /usr/lib/jvm/default-jdk" +/usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file diff --git a/scripts/install_jdk.sh b/scripts/install_jdk.sh deleted file mode 100755 index da0deca9..00000000 --- a/scripts/install_jdk.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## 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 -## -##===----------------------------------------------------------------------===## -set -euo pipefail - -declare -r JDK_VENDOR=Corretto -echo "Installing $JDK_VENDOR JDK..." - -apt-get update && apt-get install -y make curl libc6-dev - -echo "Download JDK for: $(uname -m)" - -if [ "$JDK_VENDOR" = 'OpenJDK' ]; then - if [ "$(uname -m)" = 'aarch64' ]; then - curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-aarch64_bin.tar.gz --output jdk.tar.gz && - declare -r EXPECT_JDK_SHA=076dcf7078cdf941951587bf92733abacf489a6570f1df97ee35945ffebec5b7; - else - curl https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-x64_bin.tar.gz --output jdk.tar.gz && - declare -r EXPECT_JDK_SHA=08fea92724127c6fa0f2e5ea0b07ff4951ccb1e2f22db3c21eebbd7347152a67; - fi - - declare -r JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" - if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then - echo "Downloaded JDK SHA does not match expected!" && - exit 1; - else - echo "JDK SHA is correct."; - fi -elif [ "$JDK_VENDOR" = 'Corretto' ]; then - if [ "$(uname -m)" = 'aarch64' ]; then - curl https://corretto.aws/downloads/latest/amazon-corretto-22-aarch64-linux-jdk.tar.gz --output jdk.tar.gz && - declare -r EXPECT_JDK_MD5=1ebe5f5229bb18bc784a1e0f54d3fe39 - else - curl https://corretto.aws/downloads/latest/amazon-corretto-22-x64-linux-jdk.tar.gz --output jdk.tar.gz && - declare -r EXPECT_JDK_MD5=5bd7fe30eb063699a3b4db7a00455841 - fi - - declare -r JDK_MD5="$(md5sum jdk.tar.gz | cut -d ' ' -f 1)" - if [ "$JDK_MD5" != "$EXPECT_JDK_MD5" ]; then - echo "Downloaded JDK MD5 does not match expected!" && - exit 1; - else - echo "JDK MD5 is correct."; - fi -fi - -# Extract and verify the JDK installation -tar xzvf jdk.tar.gz && rm jdk.tar.gz && mkdir -p /usr/lib/jvm; mv jdk-23 /usr/lib/jvm/default-jdk -echo "JAVA_HOME = /usr/lib/jvm/default-jdk" -/usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file From 18042c8208fc4e478299ba88ab1583c9be5088ba Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 19:28:45 +0900 Subject: [PATCH 26/34] allow customizing jdk we use from the workflow --- .github/workflows/pull_request.yml | 12 +++++---- .../Swift2JavaTranslator+Printing.swift | 2 +- docker/install_jdk.sh | 25 +++++++++++++------ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 63f792e1..c57d5cd5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,17 +10,18 @@ jobs: with: api_breakage_check_enabled: false license_header_check_project_name: Swift.org - # swift-subprocess includes the word "kill" because SIGKILL signalhandling so we allow it + # swift-subprocess includes the word "kill" because SIGKILL signal handling so we allow it unacceptable_language_check_word_list: "blacklist whitelist slave master sane sanity insane insanity killed killing hang hung hanged hanging" #ignore-unacceptable-language test-java: - name: Java tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) + name: Java tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) runs-on: ubuntu-latest strategy: fail-fast: true matrix: swift_version: [ 'nightly-main' ] os_version: [ 'jammy' ] + jdk_vendor: [ 'Corretto' ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: @@ -30,7 +31,7 @@ jobs: - name: Install Make run: apt update && apt install -y make - name: Install JDK - run: "bash -x ./docker/install_jdk.sh" + 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 # - uses: actions/setup-java@v4 # with: @@ -44,13 +45,14 @@ jobs: run: ./gradlew build --no-daemon test-swift: - name: Swift tests (${{ matrix.swift_version }} - ${{ matrix.os_version }}) + name: Swift tests (swift:${{ matrix.swift_version }} jdk:${{matrix.jdk_vendor}} os:${{ matrix.os_version }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: swift_version: [ 'nightly-main' ] os_version: [ 'jammy' ] + jdk_vendor: [ 'Corretto' ] container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} env: @@ -60,7 +62,7 @@ jobs: - name: Install Make run: apt update && apt install -y make - name: Install JDK - run: "bash -x ./docker/install_jdk.sh" + run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" - name: Generate sources (make) (Temporary) # TODO: this should be triggered by the respective builds run: "make jextract-run" diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 89eb4d48..7f238c75 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -117,7 +117,7 @@ extension Swift2JavaTranslator { printer.print( """ // FIXME: this detecting is somewhat off - public static final String TYPE_METADATA_NAME = "\(decl.swiftMangledName!)"; + public static final String TYPE_METADATA_NAME = "\(decl.swiftMangledName ?? "")"; static final MemorySegment TYPE_METADATA = SwiftKit.getTypeByMangledNameInEnvironment(TYPE_METADATA_NAME); """ ) diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 031440c3..8aa295ce 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -15,7 +15,9 @@ set -euo pipefail # Supported JDKs: Corretto or OpenJDK -declare -r JDK_VENDOR=Corretto +if [ "$JDK_VENDOR" = "" ]; then +declare -r JDK_VENDOR="Corretto" +fi echo "Installing $JDK_VENDOR JDK..." apt-get update && apt-get install -y wget @@ -32,7 +34,9 @@ if [ "$JDK_VENDOR" = 'OpenJDK' ]; then fi wget -q -O jdk.tar.gz "$JDK_URL" - declare -r JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" + + declare JDK_SHA # on separate lines due to: SC2155 (warning): Declare and assign separately to avoid masking return values. + JDK_SHA="$(sha256sum jdk.tar.gz | cut -d ' ' -f 1)" if [ "$JDK_SHA" != "$EXPECT_JDK_SHA" ]; then echo "Downloaded JDK SHA does not match expected!" echo "Expected: $EXPECT_JDK_SHA" @@ -51,7 +55,9 @@ elif [ "$JDK_VENDOR" = 'Corretto' ]; then fi wget -q -O jdk.tar.gz "$JDK_URL" - declare -r JDK_MD5="$(md5sum jdk.tar.gz | cut -d ' ' -f 1)" + + declare JDK_MD5 # on separate lines due to: SC2155 (warning): Declare and assign separately to avoid masking return values. + JDK_MD5="$(md5sum jdk.tar.gz | cut -d ' ' -f 1)" if [ "$JDK_MD5" != "$EXPECT_JDK_MD5" ]; then echo "Downloaded JDK MD5 does not match expected!" echo "Expected: $EXPECT_JDK_MD5" @@ -60,15 +66,18 @@ elif [ "$JDK_VENDOR" = 'Corretto' ]; then else echo "JDK MD5 is correct."; fi +else + echo "Unsupported JDK vendor: '$JDK_VENDOR'" + exit 1 fi # Extract and verify the JDK installation -mkdir -p /usr/lib/jvm/ && cd /usr/lib/jvm/ -tar xzvf /jdk.tar.gz -ls -lah -mv "$(ls | head -n1)" default-jdk -rm /jdk.tar.gz +mkdir -p /usr/lib/jvm/ +mv jdk.tar.gz /usr/lib/jvm/ +cd /usr/lib/jvm/ +tar xzvf jdk.tar.gz && rm jdk.tar.gz +mv "$(find . -depth -maxdepth 1 -type d | head -n1)" default-jdk echo "JAVA_HOME = /usr/lib/jvm/default-jdk" /usr/lib/jvm/default-jdk/bin/java -version \ No newline at end of file From d2dbd7b8cb0145229304dff56773bd4fcc3c70c2 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 22:14:14 +0900 Subject: [PATCH 27/34] debugging on real github actions runner, since these seem to work in act --- docker/install_jdk.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/install_jdk.sh b/docker/install_jdk.sh index 8aa295ce..0d63eaf9 100755 --- a/docker/install_jdk.sh +++ b/docker/install_jdk.sh @@ -76,7 +76,10 @@ 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" From 801fcbbec452990f6e6810c49e837ee65d5a3561 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 22:49:09 +0900 Subject: [PATCH 28/34] Correct makefile for linux --- Makefile | 23 +++++++++++++++++++---- Package.swift | 6 +++--- docker/Dockerfile | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 0e5b4817..09ce40bc 100644 --- a/Makefile +++ b/Makefile @@ -15,23 +15,38 @@ .PHONY: run clean all ARCH := $(shell arch) +UNAME := $(shell uname) + +ifeq ($(UNAME), Linux) +ifeq ($(ARCH), 'i386') + ARCH_SUBDIR := x86_64 +else + ARCH_SUBDIR := aarch64 +endif +BUILD_DIR := .build/$(ARCH_SUBDIR)-unknown-linux-gnu +LIB_SUFFIX := so +endif + +ifeq ($(UNAME), Darwin) ifeq ($(ARCH), 'i386') ARCH_SUBDIR := x86_64 else ARCH_SUBDIR := arm64 endif +BUILD_DIR := .build/$(ARCH_SUBDIR)-apple-macosx +LIB_SUFFIX := dylib +endif -BUILD_DIR=".build/$(ARCH_SUBDIR)-apple-macosx" all: generate-all -$(BUILD_DIR)/debug/libJavaKit.dylib $(BUILD_DIR)/debug/libJavaKitExample.dylib $(BUILD_DIR)/debug/Java2Swift: +$(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/libJavaKitExample.$(LIB_SUFFIX) $(BUILD_DIR)/debug/Java2Swift: swift build ./JavaSwiftKitDemo/build/classes/java/main/com/example/swift/HelloSubclass.class: JavaSwiftKitDemo/src/main/java/com/example/swift ./gradlew build -run: $(BUILD_DIR)/debug/libJavaKit.dylib $(BUILD_DIR)/debug/libJavaKitExample.dylib JavaSwiftKitDemo/src/main/java/com/example/swift +run: $(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) $(BUILD_DIR)/debug/libJavaKitExample.$(LIB_SUFFIX) JavaSwiftKitDemo/src/main/java/com/example/swift java -cp JavaSwiftKitDemo/build/classes/java/main -Djava.library.path=$(BUILD_DIR)/debug/ com.example.swift.HelloSwift Java2Swift: $(BUILD_DIR)/debug/Java2Swift @@ -82,7 +97,7 @@ endef jextract-swift: generate-JExtract-interface-files swift build -generate-JExtract-interface-files: $(BUILD_DIR)/debug/libJavaKit.dylib +generate-JExtract-interface-files: $(BUILD_DIR)/debug/libJavaKit.$(LIB_SUFFIX) echo "Generate .swiftinterface files..." @$(call make_swiftinterface, "JavaKitExample", "MySwiftLibrary") @$(call make_swiftinterface, "JavaKitExample", "SwiftKit") diff --git a/Package.swift b/Package.swift index 2abeb0ba..705ab731 100644 --- a/Package.swift +++ b/Package.swift @@ -32,11 +32,11 @@ let javaHome = findJavaHome() let javaIncludePath = "\(javaHome)/include" #if os(Linux) -let javaPlatformIncludePath = "\(javaIncludePath)/linux" + let javaPlatformIncludePath = "\(javaIncludePath)/linux" #elseif os(macOS) -let javaPlatformIncludePath = "\(javaIncludePath)/darwin" + let javaPlatformIncludePath = "\(javaIncludePath)/darwin" #else -#error("Currently only macOS and Linux platforms are supported, this may change in the future.") + #error("Currently only macOS and Linux platforms are supported, this may change in the future.") // TODO: Handle windows as well #endif diff --git a/docker/Dockerfile b/docker/Dockerfile index 9680b53d..170619e1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,6 +17,6 @@ ENV LANGUAGE=en_US.UTF-8 # JDK dependency COPY install_jdk.sh . -RUN bash -x ./install_jdk.sh +RUN bash -xc 'JDK_VENDOR=Corretto ./install_jdk.sh' ENV JAVA_HOME="/usr/lib/jvm/default-jdk" ENV PATH="$PATH:/usr/lib/jvm/default-jdk/bin" \ No newline at end of file From 1ceeac4b87c3ec538fefe7df4ad7f51264c98cff Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 23:22:46 +0900 Subject: [PATCH 29/34] Cache gradle artifacts and wrapper --- .github/workflows/pull_request.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c57d5cd5..9fa593cd 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -41,6 +41,15 @@ jobs: - name: Generate sources (make) (Temporary) # TODO: this should be triggered by the respective builds run: make jextract-run + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- - name: Gradle build run: ./gradlew build --no-daemon From 87ae73b8dfc9eb9609444acfa1415653676eb1fc Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 23:34:36 +0900 Subject: [PATCH 30/34] minor cleanup, silent apt-get --- .github/workflows/pull_request.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9fa593cd..da4c2e67 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,7 +9,10 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: api_breakage_check_enabled: false + # FIXME: Something is off with the format task and it gets "stuck", need to investigate + format_check_enabled: false license_header_check_project_name: Swift.org + # FIXME: we're about to remove _Subprocess immediately anyway, so rather than fixing it one by one remove this adjusted list and use the default again ASAP # swift-subprocess includes the word "kill" because SIGKILL signal handling so we allow it unacceptable_language_check_word_list: "blacklist whitelist slave master sane sanity insane insanity killed killing hang hung hanged hanging" #ignore-unacceptable-language @@ -29,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Make - run: apt update && apt install -y make + run: apt-get -qq update && apt-get -qq install -y make - name: Install JDK run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" # TODO: not using setup-java since incompatible with the swiftlang/swift base image @@ -69,7 +72,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Make - run: apt update && apt install -y make + run: apt-get -qq update && apt-get -qq install -y make - name: Install JDK run: "bash -xc 'JDK_VENDOR=${{ matrix.jdk_vendor }} ./docker/install_jdk.sh'" - name: Generate sources (make) (Temporary) From 24d3e7bc1394843ab3bb7f44fecc9ae3b1a9d720 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 23:43:14 +0900 Subject: [PATCH 31/34] update _Subprocess lib with complete linux support --- Sources/_Subprocess/LockedState.swift | 158 +++ .../Platforms/Subprocess+Darwin.swift | 449 ++++---- .../Platforms/Subprocess+Linux.swift | 294 +++-- .../Platforms/Subprocess+Unix.swift | 474 ++++---- Sources/_Subprocess/Subprocess+API.swift | 441 ++++---- .../_Subprocess/Subprocess+AsyncBytes.swift | 161 +-- .../Subprocess+Configuration.swift | 1002 ++++++++--------- Sources/_Subprocess/Subprocess+IO.swift | 662 +++++------ Sources/_Subprocess/Subprocess.swift | 358 +++--- Sources/_Subprocess/_LockedState.swift | 159 --- .../include/_CShimsTargetConditionals.h | 14 +- .../_SubprocessCShims/include/process_shims.h | 7 +- Sources/_SubprocessCShims/process_shims.c | 120 +- 13 files changed, 2224 insertions(+), 2075 deletions(-) create mode 100644 Sources/_Subprocess/LockedState.swift delete mode 100644 Sources/_Subprocess/_LockedState.swift diff --git a/Sources/_Subprocess/LockedState.swift b/Sources/_Subprocess/LockedState.swift new file mode 100644 index 00000000..0cde3b94 --- /dev/null +++ b/Sources/_Subprocess/LockedState.swift @@ -0,0 +1,158 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if canImport(os) +internal import os +#if FOUNDATION_FRAMEWORK && canImport(C.os.lock) +internal import C.os.lock +#endif +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif canImport(WinSDK) +import WinSDK +#endif + +package struct LockedState { + + // Internal implementation for a cheap lock to aid sharing code across platforms + private struct _Lock { +#if canImport(os) + typealias Primitive = os_unfair_lock +#elseif canImport(Bionic) || canImport(Glibc) || canImport(Musl) + typealias Primitive = pthread_mutex_t +#elseif canImport(WinSDK) + typealias Primitive = SRWLOCK +#elseif os(WASI) + // WASI is single-threaded, so we don't need a lock. + typealias Primitive = Void +#endif + + typealias PlatformLock = UnsafeMutablePointer + var _platformLock: PlatformLock + + fileprivate static func initialize(_ platformLock: PlatformLock) { +#if canImport(os) + platformLock.initialize(to: os_unfair_lock()) +#elseif canImport(Bionic) || canImport(Glibc) + pthread_mutex_init(platformLock, nil) +#elseif canImport(WinSDK) + InitializeSRWLock(platformLock) +#elseif os(WASI) + // no-op +#endif + } + + fileprivate static func deinitialize(_ platformLock: PlatformLock) { +#if canImport(Bionic) || canImport(Glibc) + pthread_mutex_destroy(platformLock) +#endif + platformLock.deinitialize(count: 1) + } + + static fileprivate func lock(_ platformLock: PlatformLock) { +#if canImport(os) + os_unfair_lock_lock(platformLock) +#elseif canImport(Bionic) || canImport(Glibc) + pthread_mutex_lock(platformLock) +#elseif canImport(WinSDK) + AcquireSRWLockExclusive(platformLock) +#elseif os(WASI) + // no-op +#endif + } + + static fileprivate func unlock(_ platformLock: PlatformLock) { +#if canImport(os) + os_unfair_lock_unlock(platformLock) +#elseif canImport(Bionic) || canImport(Glibc) + pthread_mutex_unlock(platformLock) +#elseif canImport(WinSDK) + ReleaseSRWLockExclusive(platformLock) +#elseif os(WASI) + // no-op +#endif + } + } + + private class _Buffer: ManagedBuffer { + deinit { + withUnsafeMutablePointerToElements { + _Lock.deinitialize($0) + } + } + } + + private let _buffer: ManagedBuffer + + package init(initialState: State) { + _buffer = _Buffer.create(minimumCapacity: 1, makingHeaderWith: { buf in + buf.withUnsafeMutablePointerToElements { + _Lock.initialize($0) + } + return initialState + }) + } + + package func withLock(_ body: @Sendable (inout State) throws -> T) rethrows -> T { + try withLockUnchecked(body) + } + + package func withLockUnchecked(_ body: (inout State) throws -> T) rethrows -> T { + try _buffer.withUnsafeMutablePointers { state, lock in + _Lock.lock(lock) + defer { _Lock.unlock(lock) } + return try body(&state.pointee) + } + } + + // Ensures the managed state outlives the locked scope. + package func withLockExtendingLifetimeOfState(_ body: @Sendable (inout State) throws -> T) rethrows -> T { + try _buffer.withUnsafeMutablePointers { state, lock in + _Lock.lock(lock) + return try withExtendedLifetime(state.pointee) { + defer { _Lock.unlock(lock) } + return try body(&state.pointee) + } + } + } +} + +extension LockedState where State == Void { + package init() { + self.init(initialState: ()) + } + + package func withLock(_ body: @Sendable () throws -> R) rethrows -> R { + return try withLock { _ in + try body() + } + } + + package func lock() { + _buffer.withUnsafeMutablePointerToElements { lock in + _Lock.lock(lock) + } + } + + package func unlock() { + _buffer.withUnsafeMutablePointerToElements { lock in + _Lock.unlock(lock) + } + } +} + +extension LockedState: @unchecked Sendable where State: Sendable {} + diff --git a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift index 746ba51c..802ae020 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift @@ -11,241 +11,268 @@ #if canImport(Darwin) -import Darwin +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif canImport(Foundation) import Foundation +#endif + +import Darwin +import Dispatch import SystemPackage #if FOUNDATION_FRAMEWORK -@_implementationOnly import _SubprocessCShims +@_implementationOnly import _FoundationCShims #else -internal import _SubprocessCShims +internal import _CShims #endif // Darwin specific implementation extension Subprocess.Configuration { - internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes - - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { - let ( - executablePath, - env, argv, - intendedWorkingDir, - uidPtr, gidPtr, supplementaryGroups - ) = try self.preSpawn() - defer { - for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - uidPtr?.deallocate() - gidPtr?.deallocate() - } + internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes - // Setup file actions and spawn attributes - var fileActions: posix_spawn_file_actions_t? = nil - var spawnAttributes: posix_spawnattr_t? = nil - // Setup stdin, stdout, and stderr - posix_spawn_file_actions_init(&fileActions) - defer { - posix_spawn_file_actions_destroy(&fileActions) - } - // Input - var result: Int32 = -1 - if let inputRead = input.getReadFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, inputRead.rawValue, 0) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let inputWrite = input.getWriteFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, inputWrite.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Output - if let outputWrite = output.getWriteFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, outputWrite.rawValue, 1) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let outputRead = output.getReadFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, outputRead.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Error - if let errorWrite = error.getWriteFileDescriptor() { - result = posix_spawn_file_actions_adddup2(&fileActions, errorWrite.rawValue, 2) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - if let errorRead = error.getReadFileDescriptor() { - // Close parent side - result = posix_spawn_file_actions_addclose(&fileActions, errorRead.rawValue) - guard result == 0 else { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - } - // Setup spawnAttributes - posix_spawnattr_init(&spawnAttributes) - defer { - posix_spawnattr_destroy(&spawnAttributes) - } - var noSignals = sigset_t() - var allSignals = sigset_t() - sigemptyset(&noSignals) - sigfillset(&allSignals) - posix_spawnattr_setsigmask(&spawnAttributes, &noSignals) - posix_spawnattr_setsigdefault(&spawnAttributes, &allSignals) - // Configure spawnattr - var flags: Int32 = POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF - if self.platformOptions.createProcessGroup { - flags |= POSIX_SPAWN_SETPGROUP - } - var spawnAttributeError = posix_spawnattr_setflags(&spawnAttributes, Int16(flags)) - // Set QualityOfService - // spanattr_qos seems to only accept `QOS_CLASS_UTILITY` or `QOS_CLASS_BACKGROUND` - // and returns an error of `EINVAL` if anything else is provided - if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .utility { - spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_UTILITY) - } else if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .background { - spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_BACKGROUND) - } + internal func spawn( + withInput input: Subprocess.ExecutionInput, + output: Subprocess.ExecutionOutput, + error: Subprocess.ExecutionOutput + ) throws -> Subprocess { + let (executablePath, + env, argv, + intendedWorkingDir, + uidPtr, gidPtr, supplementaryGroups + ) = try self.preSpawn() + defer { + for ptr in env { ptr?.deallocate() } + for ptr in argv { ptr?.deallocate() } + uidPtr?.deallocate() + gidPtr?.deallocate() + } - // Setup cwd - var chdirError: Int32 = 0 - if intendedWorkingDir != .currentWorkingDirectory { - chdirError = intendedWorkingDir.withPlatformString { workDir in - return posix_spawn_file_actions_addchdir_np(&fileActions, workDir) - } - } + // Setup file actions and spawn attributes + var fileActions: posix_spawn_file_actions_t? = nil + var spawnAttributes: posix_spawnattr_t? = nil + // Setup stdin, stdout, and stderr + posix_spawn_file_actions_init(&fileActions) + defer { + posix_spawn_file_actions_destroy(&fileActions) + } + // Input + var result: Int32 = -1 + if let inputRead = input.getReadFileDescriptor() { + result = posix_spawn_file_actions_adddup2(&fileActions, inputRead.rawValue, 0) + guard result == 0 else { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: result) ?? .ENODEV) + } + } + if let inputWrite = input.getWriteFileDescriptor() { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, inputWrite.rawValue) + guard result == 0 else { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: result) ?? .ENODEV) + } + } + // Output + if let outputWrite = output.getWriteFileDescriptor() { + result = posix_spawn_file_actions_adddup2(&fileActions, outputWrite.rawValue, 1) + guard result == 0 else { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: result) ?? .ENODEV) + } + } + if let outputRead = output.getReadFileDescriptor() { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, outputRead.rawValue) + guard result == 0 else { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: result) ?? .ENODEV) + } + } + // Error + if let errorWrite = error.getWriteFileDescriptor() { + result = posix_spawn_file_actions_adddup2(&fileActions, errorWrite.rawValue, 2) + guard result == 0 else { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: result) ?? .ENODEV) + } + } + if let errorRead = error.getReadFileDescriptor() { + // Close parent side + result = posix_spawn_file_actions_addclose(&fileActions, errorRead.rawValue) + guard result == 0 else { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: result) ?? .ENODEV) + } + } + // Setup spawnAttributes + posix_spawnattr_init(&spawnAttributes) + defer { + posix_spawnattr_destroy(&spawnAttributes) + } + var noSignals = sigset_t() + var allSignals = sigset_t() + sigemptyset(&noSignals) + sigfillset(&allSignals) + posix_spawnattr_setsigmask(&spawnAttributes, &noSignals) + posix_spawnattr_setsigdefault(&spawnAttributes, &allSignals) + // Configure spawnattr + var spawnAttributeError: Int32 = 0 + var flags: Int32 = POSIX_SPAWN_CLOEXEC_DEFAULT | + POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF + if let pgid = self.platformOptions.processGroupID { + flags |= POSIX_SPAWN_SETPGROUP + spawnAttributeError = posix_spawnattr_setpgroup(&spawnAttributes, pid_t(pgid)) + } + spawnAttributeError = posix_spawnattr_setflags(&spawnAttributes, Int16(flags)) + // Set QualityOfService + // spanattr_qos seems to only accept `QOS_CLASS_UTILITY` or `QOS_CLASS_BACKGROUND` + // and returns an error of `EINVAL` if anything else is provided + if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .utility{ + spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_UTILITY) + } else if spawnAttributeError == 0 && self.platformOptions.qualityOfService == .background { + spawnAttributeError = posix_spawnattr_set_qos_class_np(&spawnAttributes, QOS_CLASS_BACKGROUND) + } - // Error handling - if chdirError != 0 || spawnAttributeError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - if spawnAttributeError != 0 { - throw POSIXError(.init(rawValue: result) ?? .ENODEV) - } - - if chdirError != 0 { - throw CocoaError( - .fileNoSuchFile, - userInfo: [ - .debugDescriptionErrorKey: - "Cannot failed to change the working directory to \(intendedWorkingDir) with errno \(chdirError)" - ] - ) - } - } - // Run additional config - if let spawnConfig = self.platformOptions.preSpawnAttributeConfigurator { - try spawnConfig(&spawnAttributes) - } - if let fileAttributeConfig = self.platformOptions.preSpawnFileAttributeConfigurator { - try fileAttributeConfig(&fileActions) - } - // Spawn - var pid: pid_t = 0 - let spawnError: CInt = executablePath.withCString { exePath in - return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in - return _subprocess_spawn( - &pid, - exePath, - &fileActions, - &spawnAttributes, - argv, - env, - uidPtr, - gidPtr, - Int32(supplementaryGroups?.count ?? 0), - sgroups?.baseAddress, - self.platformOptions.createSession ? 1 : 0 + // Setup cwd + var chdirError: Int32 = 0 + if intendedWorkingDir != .currentWorkingDirectory { + chdirError = intendedWorkingDir.withPlatformString { workDir in + return posix_spawn_file_actions_addchdir_np(&fileActions, workDir) + } + } + + // Error handling + if chdirError != 0 || spawnAttributeError != 0 { + try self.cleanupAll(input: input, output: output, error: error) + if spawnAttributeError != 0 { + throw POSIXError(.init(rawValue: result) ?? .ENODEV) + } + + if chdirError != 0 { + throw CocoaError(.fileNoSuchFile, userInfo: [ + .debugDescriptionErrorKey: "Cannot failed to change the working directory to \(intendedWorkingDir) with errno \(chdirError)" + ]) + } + } + // Run additional config + if let spawnConfig = self.platformOptions.preSpawnAttributeConfigurator { + try spawnConfig(&spawnAttributes) + } + if let fileAttributeConfig = self.platformOptions.preSpawnFileAttributeConfigurator { + try fileAttributeConfig(&fileActions) + } + // Spawn + var pid: pid_t = 0 + let spawnError: CInt = executablePath.withCString { exePath in + return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in + return _subprocess_spawn( + &pid, exePath, + &fileActions, &spawnAttributes, + argv, env, + uidPtr, gidPtr, + Int32(supplementaryGroups?.count ?? 0), sgroups?.baseAddress, + self.platformOptions.createSession ? 1 : 0 + ) + } + } + // Spawn error + if spawnError != 0 { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) + } + return Subprocess( + processIdentifier: .init(value: pid), + executionInput: input, + executionOutput: output, + executionError: error ) - } - } - // Spawn error - if spawnError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) } - return Subprocess( - processIdentifier: .init(value: pid), - executionInput: input, - executionOutput: output, - executionError: error - ) - } } // Special keys used in Error's user dictionary extension String { - static let debugDescriptionErrorKey = "NSDebugDescription" + static let debugDescriptionErrorKey = "NSDebugDescription" } // MARK: - Platform Specific Options extension Subprocess { - /// The collection of platform-specific configurations - public struct PlatformOptions: Sendable { - public var qualityOfService: QualityOfService = .default - // Set user ID for the subprocess - public var userID: Int? = nil - // Set group ID for the subprocess - public var groupID: Int? = nil - // Set list of supplementary group IDs for the subprocess - public var supplementaryGroups: [Int]? = nil - // Creates a session and sets the process group ID - // i.e. Detach from the terminal. - public var createSession: Bool = false - // Create a new process group - public var createProcessGroup: Bool = false - public var launchRequirementData: Data? = nil - public var preSpawnAttributeConfigurator: (@Sendable (inout posix_spawnattr_t?) throws -> Void)? - public var preSpawnFileAttributeConfigurator: (@Sendable (inout posix_spawn_file_actions_t?) throws -> Void)? - - public init( - qualityOfService: QualityOfService, - userID: Int?, - groupID: Int?, - supplementaryGroups: [Int]?, - createSession: Bool, - createProcessGroup: Bool, - launchRequirementData: Data? - ) { - self.qualityOfService = qualityOfService - self.userID = userID - self.groupID = groupID - self.supplementaryGroups = supplementaryGroups - self.createSession = createSession - self.createProcessGroup = createProcessGroup - self.launchRequirementData = launchRequirementData + /// The collection of platform-specific configurations + public struct PlatformOptions: Sendable { + public var qualityOfService: QualityOfService = .default + // Set user ID for the subprocess + public var userID: Int? = nil + // Set group ID for the subprocess + public var groupID: Int? = nil + // Set list of supplementary group IDs for the subprocess + public var supplementaryGroups: [Int]? = nil + // Set process group ID for the subprocess + public var processGroupID: Int? = nil + // Creates a session and sets the process group ID + // i.e. Detach from the terminal. + public var createSession: Bool = false + public var launchRequirementData: Data? = nil + public var preSpawnAttributeConfigurator: (@Sendable (inout posix_spawnattr_t?) throws -> Void)? + public var preSpawnFileAttributeConfigurator: (@Sendable (inout posix_spawn_file_actions_t?) throws -> Void)? + + public init( + qualityOfService: QualityOfService, + userID: Int?, + groupID: Int?, + supplementaryGroups: [Int]?, + processGroupID: Int?, + createSession: Bool, + launchRequirementData: Data? + ) { + self.qualityOfService = qualityOfService + self.userID = userID + self.groupID = groupID + self.supplementaryGroups = supplementaryGroups + self.createSession = createSession + self.processGroupID = processGroupID + self.launchRequirementData = launchRequirementData + } + + public static var `default`: Self { + return .init( + qualityOfService: .default, + userID: nil, + groupID: nil, + supplementaryGroups: nil, + processGroupID: nil, + createSession: false, + launchRequirementData: nil + ) + } } +} - public static var `default`: Self { - return .init( - qualityOfService: .default, - userID: nil, - groupID: nil, - supplementaryGroups: nil, - createSession: false, - createProcessGroup: false, - launchRequirementData: nil - ) +// MARK: - Process Monitoring +@Sendable +internal func monitorProcessTermination( + forProcessWithIdentifier pid: Subprocess.ProcessIdentifier +) async -> Subprocess.TerminationStatus { + return await withCheckedContinuation { continuation in + let source = DispatchSource.makeProcessSource( + identifier: pid.value, + eventMask: [.exit, .signal] + ) + source.setEventHandler { + source.cancel() + var status: Int32 = -1 + waitpid(pid.value, &status, 0) + if _was_process_exited(status) != 0 { + continuation.resume(returning: .exited(_get_exit_code(status))) + return + } + if _was_process_signaled(status) != 0 { + continuation.resume(returning: .unhandledException(_get_signal_code(status))) + return + } + fatalError("Unexpected exit status type: \(status)") + } + source.resume() } - } } -#endif // canImport(Darwin) +#endif // canImport(Darwin) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift index 6f535ca5..9f4b5061 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift @@ -12,130 +12,212 @@ #if canImport(Glibc) import Glibc -import Foundation import SystemPackage import FoundationEssentials -package import _SubprocessCShims +package import _CShims // Linux specific implementations extension Subprocess.Configuration { - internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes - - internal func spawn( - withInput input: Subprocess.ExecutionInput, - output: Subprocess.ExecutionOutput, - error: Subprocess.ExecutionOutput - ) throws -> Subprocess { - let ( - executablePath, - env, argv, - intendedWorkingDir, - uidPtr, gidPtr, - supplementaryGroups - ) = try self.preSpawn() - defer { - for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - uidPtr?.deallocate() - gidPtr?.deallocate() - } + internal typealias StringOrRawBytes = Subprocess.StringOrRawBytes - let fileDescriptors: [CInt] = [ - input.getReadFileDescriptor()!.rawValue, input.getWriteFileDescriptor()?.rawValue ?? 0, - output.getWriteFileDescriptor()!.rawValue, output.getReadFileDescriptor()?.rawValue ?? 0, - error.getWriteFileDescriptor()!.rawValue, error.getReadFileDescriptor()?.rawValue ?? 0, - ] + internal func spawn( + withInput input: Subprocess.ExecutionInput, + output: Subprocess.ExecutionOutput, + error: Subprocess.ExecutionOutput + ) throws -> Subprocess { + // Setup signal handler to minitor SIGCHLD + _setupMonitorSignalHandler() - var workingDirectory: String? - if intendedWorkingDir != FilePath.currentWorkingDirectory { - // Only pass in working directory if it's different - workingDirectory = intendedWorkingDir.string - } - // Spawn - var pid: pid_t = 0 - let spawnError: CInt = executablePath.withCString { exePath in - return workingDirectory.withOptionalCString { workingDir in - return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in - return fileDescriptors.withUnsafeBufferPointer { fds in - return _subprocess_fork_exec( - &pid, - exePath, - workingDir, - fds.baseAddress!, - argv, - env, - uidPtr, - gidPtr, - CInt(supplementaryGroups?.count ?? 0), - sgroups?.baseAddress, - self.platformOptions.createSession ? 1 : 0, - self.platformOptions.createProcessGroup ? 1 : 0 - ) - } + let (executablePath, + env, argv, + intendedWorkingDir, + uidPtr, gidPtr, + supplementaryGroups + ) = try self.preSpawn() + var processGroupIDPtr: UnsafeMutablePointer? = nil + if let processGroupID = self.platformOptions.processGroupID { + processGroupIDPtr = .allocate(capacity: 1) + processGroupIDPtr?.pointee = gid_t(processGroupID) } - } - } - // Spawn error - if spawnError != 0 { - try self.cleanupAll(input: input, output: output, error: error) - throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) + defer { + for ptr in env { ptr?.deallocate() } + for ptr in argv { ptr?.deallocate() } + uidPtr?.deallocate() + gidPtr?.deallocate() + processGroupIDPtr?.deallocate() + } + + let fileDescriptors: [CInt] = [ + input.getReadFileDescriptor()?.rawValue ?? -1, + input.getWriteFileDescriptor()?.rawValue ?? -1, + output.getWriteFileDescriptor()?.rawValue ?? -1, + output.getReadFileDescriptor()?.rawValue ?? -1, + error.getWriteFileDescriptor()?.rawValue ?? -1, + error.getReadFileDescriptor()?.rawValue ?? -1 + ] + + var workingDirectory: String? + if intendedWorkingDir != FilePath.currentWorkingDirectory { + // Only pass in working directory if it's different + workingDirectory = intendedWorkingDir.string + } + // Spawn + var pid: pid_t = 0 + let spawnError: CInt = executablePath.withCString { exePath in + return workingDirectory.withOptionalCString { workingDir in + return supplementaryGroups.withOptionalUnsafeBufferPointer { sgroups in + return fileDescriptors.withUnsafeBufferPointer { fds in + return _subprocess_fork_exec( + &pid, exePath, workingDir, + fds.baseAddress!, + argv, env, + uidPtr, gidPtr, + processGroupIDPtr, + CInt(supplementaryGroups?.count ?? 0), sgroups?.baseAddress, + self.platformOptions.createSession ? 1 : 0, + self.platformOptions.preSpawnProcessConfigurator + ) + } + } + } + } + // Spawn error + if spawnError != 0 { + try self.cleanupAll(input: input, output: output, error: error) + throw POSIXError(.init(rawValue: spawnError) ?? .ENODEV) + } + return Subprocess( + processIdentifier: .init(value: pid), + executionInput: input, + executionOutput: output, + executionError: error + ) } - return Subprocess( - processIdentifier: .init(value: pid), - executionInput: input, - executionOutput: output, - executionError: error - ) - } } // MARK: - Platform Specific Options extension Subprocess { - public struct PlatformOptions: Sendable { - // Set user ID for the subprocess - public var userID: Int? = nil - // Set group ID for the subprocess - public var groupID: Int? = nil - // Set list of supplementary group IDs for the subprocess - public var supplementaryGroups: [Int]? = nil - // Creates a session and sets the process group ID - // i.e. Detach from the terminal. - public var createSession: Bool = false - // Create a new process group - public var createProcessGroup: Bool = false - // This callback is run after `fork` but before `exec`. - // Use it to perform any custom process setup - public var customProcessConfigurator: (@Sendable () -> Void)? = nil - - public init( - userID: Int?, - groupID: Int?, - supplementaryGroups: [Int]?, - createSession: Bool, - createProcessGroup: Bool - ) { - self.userID = userID - self.groupID = groupID - self.supplementaryGroups = supplementaryGroups - self.createSession = createSession - self.createProcessGroup = createProcessGroup - } + public struct PlatformOptions: Sendable { + // Set user ID for the subprocess + public var userID: Int? = nil + // Set group ID for the subprocess + public var groupID: Int? = nil + // Set list of supplementary group IDs for the subprocess + public var supplementaryGroups: [Int]? = nil + // Set process group ID for the subprocess + public var processGroupID: Int? = nil + // Creates a session and sets the process group ID + // i.e. Detach from the terminal. + public var createSession: Bool = false + // This callback is run after `fork` but before `exec`. + // Use it to perform any custom process setup + // This callback *must not* capture any global variables + public var preSpawnProcessConfigurator: (@convention(c) @Sendable () -> Void)? = nil - public static var `default`: Self { - return .init( - userID: nil, - groupID: nil, - supplementaryGroups: nil, - createSession: false, - createProcessGroup: false - ) + public init( + userID: Int?, + groupID: Int?, + supplementaryGroups: [Int]?, + processGroupID: Int?, + createSession: Bool + ) { + self.userID = userID + self.groupID = groupID + self.supplementaryGroups = supplementaryGroups + self.processGroupID = processGroupID + self.createSession = createSession + } + + public static var `default`: Self { + return .init( + userID: nil, + groupID: nil, + supplementaryGroups: nil, + processGroupID: nil, + createSession: false + ) + } } - } } // Special keys used in Error's user dictionary extension String { - static let debugDescriptionErrorKey = "DebugDescription" + static let debugDescriptionErrorKey = "DebugDescription" } -#endif // canImport(Glibc) +// MARK: - Process Monitoring +@Sendable +internal func monitorProcessTermination( + forProcessWithIdentifier pid: Subprocess.ProcessIdentifier +) async -> Subprocess.TerminationStatus { + return await withCheckedContinuation { continuation in + _childProcessContinuations.withLock { continuations in + if let existing = continuations.removeValue(forKey: pid.value), + case .status(let existingStatus) = existing { + // We already have existing status to report + if _was_process_exited(existingStatus) != 0 { + continuation.resume(returning: .exited(_get_exit_code(existingStatus))) + return + } + if _was_process_signaled(existingStatus) != 0 { + continuation.resume(returning: .unhandledException(_get_signal_code(existingStatus))) + return + } + fatalError("Unexpected exit status type: \(existingStatus)") + } else { + // Save the continuation for handler + continuations[pid.value] = .continuation(continuation) + } + } + } +} + +private enum ContinuationOrStatus { + case continuation(CheckedContinuation) + case status(Int32) +} + +private let _childProcessContinuations: LockedState< + [pid_t: ContinuationOrStatus] +> = LockedState(initialState: [:]) + +// Callback for sigaction +private func _childProcessMonitorHandler(_ singnal: Int32) { + _childProcessContinuations.withLock { continuations in + var status: Int32 = -1 + let childPid = waitpid(-1, &status, 0) + if let existing = continuations.removeValue(forKey: childPid), + case .continuation(let c) = existing { + // We already have continuations saved + if _was_process_exited(status) != 0 { + c.resume(returning: .exited(_get_exit_code(status))) + return + } + if _was_process_signaled(status) != 0 { + c.resume(returning: .unhandledException(_get_signal_code(status))) + return + } + fatalError("Unexpected exit status type: \(status)") + } else { + // We don't have continuation yet, just save the state + continuations[childPid] = .status(status) + } + } +} + +private func _setupMonitorSignalHandler() { + // Only executed once + let setup = { + var action: sigaction = sigaction() + action.__sigaction_handler.sa_handler = _childProcessMonitorHandler + action.sa_flags = SA_RESTART + sigemptyset(&action.sa_mask) + if sigaction(SIGCHLD, &action, nil) != 0 { + fatalError("Failed to setup signal handler") + } + }() + setup +} + +#endif // canImport(Glibc) + diff --git a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift index 485315e7..1225e0f4 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift @@ -11,314 +11,276 @@ #if canImport(Darwin) || canImport(Glibc) +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif canImport(Foundation) +import Foundation +#endif + #if canImport(Darwin) import Darwin #elseif canImport(Glibc) import Glibc #endif -import Foundation -import SystemPackage - #if FOUNDATION_FRAMEWORK -@_implementationOnly import _SubprocessCShims +@_implementationOnly import _FoundationCShims #else -package import _SubprocessCShims +internal import _CShims #endif -import Dispatch +import SystemPackage // MARK: - Signals extension Subprocess { - public struct Signal: Hashable, Sendable { - public let rawValue: Int32 + public struct Signal : Hashable, Sendable { + public let rawValue: Int32 - private init(rawValue: Int32) { - self.rawValue = rawValue - } + private init(rawValue: Int32) { + self.rawValue = rawValue + } - public static var interrupt: Self { .init(rawValue: SIGINT) } - public static var terminate: Self { .init(rawValue: SIGTERM) } - public static var suspend: Self { .init(rawValue: SIGSTOP) } - public static var resume: Self { .init(rawValue: SIGCONT) } - public static var kill: Self { .init(rawValue: SIGKILL) } - public static var terminalClosed: Self { .init(rawValue: SIGHUP) } - public static var quit: Self { .init(rawValue: SIGQUIT) } - public static var userDefinedOne: Self { .init(rawValue: SIGUSR1) } - public static var userDefinedTwo: Self { .init(rawValue: SIGUSR2) } - public static var alarm: Self { .init(rawValue: SIGALRM) } - public static var windowSizeChange: Self { .init(rawValue: SIGWINCH) } - } + public static var interrupt: Self { .init(rawValue: SIGINT) } + public static var terminate: Self { .init(rawValue: SIGTERM) } + public static var suspend: Self { .init(rawValue: SIGSTOP) } + public static var resume: Self { .init(rawValue: SIGCONT) } + public static var kill: Self { .init(rawValue: SIGKILL) } + public static var terminalClosed: Self { .init(rawValue: SIGHUP) } + public static var quit: Self { .init(rawValue: SIGQUIT) } + public static var userDefinedOne: Self { .init(rawValue: SIGUSR1) } + public static var userDefinedTwo: Self { .init(rawValue: SIGUSR2) } + public static var alarm: Self { .init(rawValue: SIGALRM) } + public static var windowSizeChange: Self { .init(rawValue: SIGWINCH) } + } - public func sendSignal(_ signal: Signal, toProcessGroup shouldSendToProcessGroup: Bool) throws { - let pid = shouldSendToProcessGroup ? -(self.processIdentifier.value) : self.processIdentifier.value - guard kill(pid, signal.rawValue) == 0 else { - throw POSIXError(.init(rawValue: errno)!) + public func sendSignal(_ signal: Signal, toProcessGroup shouldSendToProcessGroup: Bool) throws { + let pid = shouldSendToProcessGroup ? -(self.processIdentifier.value) : self.processIdentifier.value + guard kill(pid, signal.rawValue) == 0 else { + throw POSIXError(.init(rawValue: errno)!) + } } - } } // MARK: Environment Resolution extension Subprocess.Environment { - internal static let pathEnvironmentVariableName = "PATH" - - internal func pathValue() -> String? { - switch self.config { - case .inherit(let overrides): - // If PATH value exists in overrides, use it - if let value = overrides[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue - } - // Fall back to current process - return ProcessInfo.processInfo.environment[Self.pathEnvironmentVariableName] - case .custom(let fullEnvironment): - if let value = fullEnvironment[.string(Self.pathEnvironmentVariableName)] { - return value.stringValue - } - return nil - } - } + internal static let pathEnvironmentVariableName = "PATH" - // This method follows the standard "create" rule: `env` needs to be - // manually deallocated - internal func createEnv() -> [UnsafeMutablePointer?] { - func createFullCString( - fromKey keyContainer: Subprocess.StringOrRawBytes, - value valueContainer: Subprocess.StringOrRawBytes - ) -> UnsafeMutablePointer { - let rawByteKey: UnsafeMutablePointer = keyContainer.createRawBytes() - let rawByteValue: UnsafeMutablePointer = valueContainer.createRawBytes() - defer { - rawByteKey.deallocate() - rawByteValue.deallocate() - } - /// length = `key` + `=` + `value` + `\null` - let totalLength = keyContainer.count + 1 + valueContainer.count + 1 - let fullString: UnsafeMutablePointer = .allocate(capacity: totalLength) - #if canImport(Darwin) - _ = snprintf(ptr: fullString, totalLength, "%s=%s", rawByteKey, rawByteValue) - #else - _ = _shims_snprintf(fullString, CInt(totalLength), "%s=%s", rawByteKey, rawByteValue) - #endif - return fullString + internal func pathValue() -> String? { + switch self.config { + case .inherit(let overrides): + // If PATH value exists in overrides, use it + if let value = overrides[.string(Self.pathEnvironmentVariableName)] { + return value.stringValue + } + // Fall back to current process + return ProcessInfo.processInfo.environment[Self.pathEnvironmentVariableName] + case .custom(let fullEnvironment): + if let value = fullEnvironment[.string(Self.pathEnvironmentVariableName)] { + return value.stringValue + } + return nil + } } - var env: [UnsafeMutablePointer?] = [] - switch self.config { - case .inherit(let updates): - var current = ProcessInfo.processInfo.environment - for (keyContainer, valueContainer) in updates { - if let stringKey = keyContainer.stringValue { - // Remove the value from current to override it - current.removeValue(forKey: stringKey) - } - // Fast path - if case .string(let stringKey) = keyContainer, - case .string(let stringValue) = valueContainer - { - let fullString = "\(stringKey)=\(stringValue)" - env.append(strdup(fullString)) - continue + // This method follows the standard "create" rule: `env` needs to be + // manually deallocated + internal func createEnv() -> [UnsafeMutablePointer?] { + func createFullCString( + fromKey keyContainer: Subprocess.StringOrRawBytes, + value valueContainer: Subprocess.StringOrRawBytes + ) -> UnsafeMutablePointer { + let rawByteKey: UnsafeMutablePointer = keyContainer.createRawBytes() + let rawByteValue: UnsafeMutablePointer = valueContainer.createRawBytes() + defer { + rawByteKey.deallocate() + rawByteValue.deallocate() + } + /// length = `key` + `=` + `value` + `\null` + let totalLength = keyContainer.count + 1 + valueContainer.count + 1 + let fullString: UnsafeMutablePointer = .allocate(capacity: totalLength) + #if canImport(Darwin) + _ = snprintf(ptr: fullString, totalLength, "%s=%s", rawByteKey, rawByteValue) + #else + _ = _shims_snprintf(fullString, CInt(totalLength), "%s=%s", rawByteKey, rawByteValue) + #endif + return fullString } - env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) - } - // Add the rest of `current` to env - for (key, value) in current { - let fullString = "\(key)=\(value)" - env.append(strdup(fullString)) - } - case .custom(let customValues): - for (keyContainer, valueContainer) in customValues { - // Fast path - if case .string(let stringKey) = keyContainer, - case .string(let stringValue) = valueContainer - { - let fullString = "\(stringKey)=\(stringValue)" - env.append(strdup(fullString)) - continue + var env: [UnsafeMutablePointer?] = [] + switch self.config { + case .inherit(let updates): + var current = ProcessInfo.processInfo.environment + for (keyContainer, valueContainer) in updates { + if let stringKey = keyContainer.stringValue { + // Remove the value from current to override it + current.removeValue(forKey: stringKey) + } + // Fast path + if case .string(let stringKey) = keyContainer, + case .string(let stringValue) = valueContainer { + let fullString = "\(stringKey)=\(stringValue)" + env.append(strdup(fullString)) + continue + } + + env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) + } + // Add the rest of `current` to env + for (key, value) in current { + let fullString = "\(key)=\(value)" + env.append(strdup(fullString)) + } + case .custom(let customValues): + for (keyContainer, valueContainer) in customValues { + // Fast path + if case .string(let stringKey) = keyContainer, + case .string(let stringValue) = valueContainer { + let fullString = "\(stringKey)=\(stringValue)" + env.append(strdup(fullString)) + continue + } + env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) + } } - env.append(createFullCString(fromKey: keyContainer, value: valueContainer)) - } + env.append(nil) + return env } - env.append(nil) - return env - } } // MARK: Args Creation extension Subprocess.Arguments { - // This method follows the standard "create" rule: `args` needs to be - // manually deallocated - internal func createArgs(withExecutablePath executablePath: String) -> [UnsafeMutablePointer?] { - var argv: [UnsafeMutablePointer?] = self.storage.map { $0.createRawBytes() } - // argv[0] = executable path - if let override = self.executablePathOverride { - argv.insert(override.createRawBytes(), at: 0) - } else { - argv.insert(strdup(executablePath), at: 0) + // This method follows the standard "create" rule: `args` needs to be + // manually deallocated + internal func createArgs(withExecutablePath executablePath: String) -> [UnsafeMutablePointer?] { + var argv: [UnsafeMutablePointer?] = self.storage.map { $0.createRawBytes() } + // argv[0] = executable path + if let override = self.executablePathOverride { + argv.insert(override.createRawBytes(), at: 0) + } else { + argv.insert(strdup(executablePath), at: 0) + } + argv.append(nil) + return argv } - argv.append(nil) - return argv - } } // MARK: - Executable Searching extension Subprocess.Executable { - internal static var defaultSearchPaths: Set { - return Set([ - "/usr/bin", - "/bin", - "/usr/sbin", - "/sbin", - "/usr/local/bin", - ]) - } + internal static var defaultSearchPaths: Set { + return Set([ + "/usr/bin", + "/bin", + "/usr/sbin", + "/sbin", + "/usr/local/bin" + ]) + } - internal func resolveExecutablePath(withPathValue pathValue: String?) -> String? { - switch self.storage { - case .executable(let executableName): - // If the executableName in is already a full path, return it directly - if Subprocess.Configuration.pathAccessible(executableName, mode: X_OK) { - return executableName - } - // Get $PATH from environment - let searchPaths: Set - if let pathValue = pathValue { - let localSearchPaths = pathValue.split(separator: ":").map { String($0) } - searchPaths = Set(localSearchPaths).union(Self.defaultSearchPaths) - } else { - searchPaths = Self.defaultSearchPaths - } + internal func resolveExecutablePath(withPathValue pathValue: String?) -> String? { + switch self.storage { + case .executable(let executableName): + // If the executableName in is already a full path, return it directly + if Subprocess.Configuration.pathAccessible(executableName, mode: X_OK) { + return executableName + } + // Get $PATH from environment + let searchPaths: Set + if let pathValue = pathValue { + let localSearchPaths = pathValue.split(separator: ":").map { String($0) } + searchPaths = Set(localSearchPaths).union(Self.defaultSearchPaths) + } else { + searchPaths = Self.defaultSearchPaths + } - for path in searchPaths { - let fullPath = "\(path)/\(executableName)" - let fileExists = Subprocess.Configuration.pathAccessible(fullPath, mode: X_OK) - if fileExists { - return fullPath + for path in searchPaths { + let fullPath = "\(path)/\(executableName)" + let fileExists = Subprocess.Configuration.pathAccessible(fullPath, mode: X_OK) + if fileExists { + return fullPath + } + } + case .path(let executablePath): + // Use path directly + return executablePath.string } - } - case .path(let executablePath): - // Use path directly - return executablePath.string + return nil } - return nil - } } // MARK: - Configuration extension Subprocess.Configuration { - internal func preSpawn() throws -> ( - executablePath: String, - env: [UnsafeMutablePointer?], - argv: [UnsafeMutablePointer?], - intendedWorkingDir: FilePath, - uidPtr: UnsafeMutablePointer?, - gidPtr: UnsafeMutablePointer?, - supplementaryGroups: [gid_t]? - ) { - // Prepare environment - let env = self.environment.createEnv() - // Prepare executable path - guard - let executablePath = self.executable.resolveExecutablePath( - withPathValue: self.environment.pathValue() - ) - else { - for ptr in env { ptr?.deallocate() } - throw CocoaError( - .executableNotLoadable, - userInfo: [ - "description": "\(self.executable.description) is not an executable" - ] - ) - } - // Prepare arguments - let argv: [UnsafeMutablePointer?] = self.arguments.createArgs(withExecutablePath: executablePath) - // Prepare workingDir - let intendedWorkingDir = self.workingDirectory - guard Self.pathAccessible(intendedWorkingDir.string, mode: F_OK) else { - for ptr in env { ptr?.deallocate() } - for ptr in argv { ptr?.deallocate() } - throw CocoaError( - .fileNoSuchFile, - userInfo: [ - "description": "Failed to set working directory to \(intendedWorkingDir)" - ] - ) - } + internal func preSpawn() throws -> ( + executablePath: String, + env: [UnsafeMutablePointer?], + argv: [UnsafeMutablePointer?], + intendedWorkingDir: FilePath, + uidPtr: UnsafeMutablePointer?, + gidPtr: UnsafeMutablePointer?, + supplementaryGroups: [gid_t]? + ) { + // Prepare environment + let env = self.environment.createEnv() + // Prepare executable path + guard let executablePath = self.executable.resolveExecutablePath( + withPathValue: self.environment.pathValue()) else { + for ptr in env { ptr?.deallocate() } + throw CocoaError(.executableNotLoadable, userInfo: [ + .debugDescriptionErrorKey : "\(self.executable.description) is not an executable" + ]) + } + // Prepare arguments + let argv: [UnsafeMutablePointer?] = self.arguments.createArgs(withExecutablePath: executablePath) + // Prepare workingDir + let intendedWorkingDir = self.workingDirectory + guard Self.pathAccessible(intendedWorkingDir.string, mode: F_OK) else { + for ptr in env { ptr?.deallocate() } + for ptr in argv { ptr?.deallocate() } + throw CocoaError(.fileNoSuchFile, userInfo: [ + .debugDescriptionErrorKey : "Failed to set working directory to \(intendedWorkingDir)" + ]) + } - var uidPtr: UnsafeMutablePointer? = nil - if let userID = self.platformOptions.userID { - uidPtr = .allocate(capacity: 1) - uidPtr?.pointee = uid_t(userID) - } - var gidPtr: UnsafeMutablePointer? = nil - if let groupID = self.platformOptions.groupID { - gidPtr = .allocate(capacity: 1) - gidPtr?.pointee = gid_t(groupID) - } - var supplementaryGroups: [gid_t]? - if let groupsValue = self.platformOptions.supplementaryGroups { - supplementaryGroups = groupsValue.map { gid_t($0) } + var uidPtr: UnsafeMutablePointer? = nil + if let userID = self.platformOptions.userID { + uidPtr = .allocate(capacity: 1) + uidPtr?.pointee = uid_t(userID) + } + var gidPtr: UnsafeMutablePointer? = nil + if let groupID = self.platformOptions.groupID { + gidPtr = .allocate(capacity: 1) + gidPtr?.pointee = gid_t(groupID) + } + var supplementaryGroups: [gid_t]? + if let groupsValue = self.platformOptions.supplementaryGroups { + supplementaryGroups = groupsValue.map { gid_t($0) } + } + return ( + executablePath: executablePath, + env: env, argv: argv, + intendedWorkingDir: intendedWorkingDir, + uidPtr: uidPtr, gidPtr: gidPtr, + supplementaryGroups: supplementaryGroups + ) } - return ( - executablePath: executablePath, - env: env, argv: argv, - intendedWorkingDir: intendedWorkingDir, - uidPtr: uidPtr, gidPtr: gidPtr, - supplementaryGroups: supplementaryGroups - ) - } - internal static func pathAccessible(_ path: String, mode: Int32) -> Bool { - return path.withCString { - return access($0, mode) == 0 + internal static func pathAccessible(_ path: String, mode: Int32) -> Bool { + return path.withCString { + return access($0, mode) == 0 + } } - } -} - -// MARK: - Process Monitoring -@Sendable -internal func monitorProcessTermination( - forProcessWithIdentifier pid: Subprocess.ProcessIdentifier -) async -> Subprocess.TerminationStatus { - // FIXME: makeProcessSource seems to not be available, disable for now as we don't use this API - fatalError("Not implemented. Missing makeProcessSource") -// return await withCheckedContinuation { continuation in -// let source = DispatchSource.makeProcessSource( -// identifier: pid.value, -// eventMask: [.exit, .signal] -// ) -// source.setEventHandler { -// source.cancel() -// var status: Int32 = -1 -// waitpid(pid.value, &status, 0) -// if _was_process_exited(status) != 0 { -// continuation.resume(returning: .exited(_get_exit_code(status))) -// return -// } -// if _was_process_signaled(status) != 0 { -// continuation.resume(returning: .unhandledException(_get_signal_code(status))) -// return -// } -// fatalError("Unexpected exit status type: \(status)") -// } -// source.resume() -// } } // MARK: - Read Buffer Size extension Subprocess { - @inline(__always) - internal static var readBufferSize: Int { - // FIXME: Platform is not available, not a bug issue, just ignore for now - // #if canImport(Darwin) - return 16384 - // #else - // return Platform.pageSize - // #endif // canImport(Darwin) - } + @inline(__always) + internal static var readBufferSize: Int { +#if canImport(Darwin) + return 16384 +#else + // FIXME: Use Platform.pageSize here + return 4096 +#endif // canImport(Darwin) + } } -#endif // canImport(Darwin) || canImport(Glibc) +#endif // canImport(Darwin) || canImport(Glibc) diff --git a/Sources/_Subprocess/Subprocess+API.swift b/Sources/_Subprocess/Subprocess+API.swift index 63872ee1..5fb87ba1 100644 --- a/Sources/_Subprocess/Subprocess+API.swift +++ b/Sources/_Subprocess/Subprocess+API.swift @@ -9,220 +9,271 @@ // //===----------------------------------------------------------------------===// -import Foundation import SystemPackage +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif canImport(Foundation) +import Foundation +#endif extension Subprocess { - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = .default, - input: InputMethod = .noInput, - output: CollectedOutputMethod = .collect, - error: CollectedOutputMethod = .collect - ) async throws -> CollectedResult { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - input: input, - output: .init(method: output.method), - error: .init(method: error.method) - ) { subprocess in - let (standardOutput, standardError) = try await subprocess.captureIOs() - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: standardOutput, - standardError: standardError - ) + public static func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default, + input: InputMethod = .noInput, + output: CollectedOutputMethod = .collect, + error: CollectedOutputMethod = .collect + ) async throws -> CollectedResult { + let result = try await self.run( + executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions, + input: input, + output: .init(method: output.method), + error: .init(method: error.method) + ) { subprocess in + let (standardOutput, standardError) = try await subprocess.captureIOs() + return ( + processIdentifier: subprocess.processIdentifier, + standardOutput: standardOutput, + standardError: standardError + ) + } + return CollectedResult( + processIdentifier: result.value.processIdentifier, + terminationStatus: result.terminationStatus, + standardOutput: result.value.standardOutput, + standardError: result.value.standardError + ) } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = .default, - input: some Sequence, - output: CollectedOutputMethod = .collect, - error: CollectedOutputMethod = .collect - ) async throws -> CollectedResult { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - output: .init(method: output.method), - error: .init(method: output.method) - ) { subprocess, writer in - try await writer.write(input) - try await writer.finish() - let (standardOutput, standardError) = try await subprocess.captureIOs() - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: standardOutput, - standardError: standardError - ) + public static func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default, + input: some Sequence, + output: CollectedOutputMethod = .collect, + error: CollectedOutputMethod = .collect + ) async throws -> CollectedResult { + let result = try await self.run( + executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions, + output: .init(method: output.method), + error: .init(method: output.method) + ) { subprocess, writer in + return try await withThrowingTaskGroup(of: CapturedIOs?.self) { group in + group.addTask { + try await writer.write(input) + try await writer.finish() + return nil + } + group.addTask { + return try await subprocess.captureIOs() + } + var capturedIOs: CapturedIOs! + while let result = try await group.next() { + capturedIOs = result + } + return ( + processIdentifier: subprocess.processIdentifier, + standardOutput: capturedIOs.standardOutput, + standardError: capturedIOs.standardError + ) + } + } + return CollectedResult( + processIdentifier: result.value.processIdentifier, + terminationStatus: result.terminationStatus, + standardOutput: result.value.standardOutput, + standardError: result.value.standardError + ) } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = .default, - input: S, - output: CollectedOutputMethod = .collect, - error: CollectedOutputMethod = .collect - ) async throws -> CollectedResult where S.Element == UInt8 { - let result = try await self.run( - executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions, - output: .init(method: output.method), - error: .init(method: output.method) - ) { subprocess, writer in - try await writer.write(input) - try await writer.finish() - let (standardOutput, standardError) = try await subprocess.captureIOs() - return ( - processIdentifier: subprocess.processIdentifier, - standardOutput: standardOutput, - standardError: standardError - ) + public static func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default, + input: S, + output: CollectedOutputMethod = .collect, + error: CollectedOutputMethod = .collect + ) async throws -> CollectedResult where S.Element == UInt8 { + let result = try await self.run( + executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions, + output: .init(method: output.method), + error: .init(method: output.method) + ) { subprocess, writer in + return try await withThrowingTaskGroup(of: CapturedIOs?.self) { group in + group.addTask { + try await writer.write(input) + try await writer.finish() + return nil + } + group.addTask { + return try await subprocess.captureIOs() + } + var capturedIOs: CapturedIOs! + while let result = try await group.next() { + capturedIOs = result + } + return ( + processIdentifier: subprocess.processIdentifier, + standardOutput: capturedIOs.standardOutput, + standardError: capturedIOs.standardError + ) + } + } + return CollectedResult( + processIdentifier: result.value.processIdentifier, + terminationStatus: result.terminationStatus, + standardOutput: result.value.standardOutput, + standardError: result.value.standardError + ) } - return CollectedResult( - processIdentifier: result.value.processIdentifier, - terminationStatus: result.terminationStatus, - standardOutput: result.value.standardOutput, - standardError: result.value.standardError - ) - } } // MARK: Custom Execution Body extension Subprocess { - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = .default, - input: InputMethod = .noInput, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (@Sendable @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(input: input, output: output, error: error, body) - } + public static func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default, + input: InputMethod = .noInput, + output: RedirectedOutputMethod = .redirectToSequence, + error: RedirectedOutputMethod = .redirectToSequence, + _ body: (@Sendable @escaping (Subprocess) async throws -> R) + ) async throws -> ExecutionResult { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(input: input, output: output, error: error, body) + } - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions, - input: some Sequence, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (@Sendable @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error) { execution, writer in - try await writer.write(input) - try await writer.finish() - return try await body(execution) + public static func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default, + input: some Sequence, + output: RedirectedOutputMethod = .redirectToSequence, + error: RedirectedOutputMethod = .redirectToSequence, + _ body: (@Sendable @escaping (Subprocess) async throws -> R) + ) async throws -> ExecutionResult { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(output: output, error: error) { execution, writer in + return try await withThrowingTaskGroup(of: R?.self) { group in + group.addTask { + try await writer.write(input) + try await writer.finish() + return nil + } + group.addTask { + return try await body(execution) + } + var result: R! + while let next = try await group.next() { + result = next + } + return result + } + } } - } - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = .default, - input: S, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (@Sendable @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult where S.Element == UInt8 { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error) { execution, writer in - try await writer.write(input) - try await writer.finish() - return try await body(execution) + public static func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default, + input: S, + output: RedirectedOutputMethod = .redirectToSequence, + error: RedirectedOutputMethod = .redirectToSequence, + _ body: (@Sendable @escaping (Subprocess) async throws -> R) + ) async throws -> ExecutionResult where S.Element == UInt8 { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(output: output, error: error) { execution, writer in + return try await withThrowingTaskGroup(of: R?.self) { group in + group.addTask { + try await writer.write(input) + try await writer.finish() + return nil + } + group.addTask { + return try await body(execution) + } + var result: R! + while let next = try await group.next() { + result = next + } + return result + } + } } - } - public static func run( - _ executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = .default, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R) - ) async throws -> ExecutionResult { - return try await Configuration( - executable: executable, - arguments: arguments, - environment: environment, - workingDirectory: workingDirectory, - platformOptions: platformOptions - ) - .run(output: output, error: error, body) - } + public static func run( + _ executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default, + output: RedirectedOutputMethod = .redirectToSequence, + error: RedirectedOutputMethod = .redirectToSequence, + _ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R) + ) async throws -> ExecutionResult { + return try await Configuration( + executable: executable, + arguments: arguments, + environment: environment, + workingDirectory: workingDirectory, + platformOptions: platformOptions + ) + .run(output: output, error: error, body) + } } // MARK: - Configuration Based extension Subprocess { - public static func run( - using configuration: Configuration, - output: RedirectedOutputMethod = .redirectToSequence, - error: RedirectedOutputMethod = .redirectToSequence, - _ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R) - ) async throws -> ExecutionResult { - return try await configuration.run(output: output, error: error, body) - } + public static func run( + using configuration: Configuration, + output: RedirectedOutputMethod = .redirectToSequence, + error: RedirectedOutputMethod = .redirectToSequence, + _ body: (@Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R) + ) async throws -> ExecutionResult { + return try await configuration.run(output: output, error: error, body) + } } + diff --git a/Sources/_Subprocess/Subprocess+AsyncBytes.swift b/Sources/_Subprocess/Subprocess+AsyncBytes.swift index bdd08caa..f80fc315 100644 --- a/Sources/_Subprocess/Subprocess+AsyncBytes.swift +++ b/Sources/_Subprocess/Subprocess+AsyncBytes.swift @@ -9,107 +9,110 @@ // //===----------------------------------------------------------------------===// +import SystemPackage import Dispatch + +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif canImport(Foundation) import Foundation -import SystemPackage +#endif extension Subprocess { - public struct AsyncBytes: AsyncSequence, Sendable, _AsyncSequence { - public typealias Error = any Swift.Error + public struct AsyncBytes: AsyncSequence, Sendable, _AsyncSequence { + public typealias Error = any Swift.Error - public typealias Element = UInt8 + public typealias Element = UInt8 - @_nonSendable - public struct Iterator: AsyncIteratorProtocol { - public typealias Element = UInt8 + @_nonSendable + public struct Iterator: AsyncIteratorProtocol { + public typealias Element = UInt8 - private let fileDescriptor: FileDescriptor - private var buffer: [UInt8] - private var currentPosition: Int - private var finished: Bool + private let fileDescriptor: FileDescriptor + private var buffer: [UInt8] + private var currentPosition: Int + private var finished: Bool - internal init(fileDescriptor: FileDescriptor) { - self.fileDescriptor = fileDescriptor - self.buffer = [] - self.currentPosition = 0 - self.finished = false - } + internal init(fileDescriptor: FileDescriptor) { + self.fileDescriptor = fileDescriptor + self.buffer = [] + self.currentPosition = 0 + self.finished = false + } - private mutating func reloadBufferAndNext() async throws -> UInt8? { - if self.finished { - return nil - } - try Task.checkCancellation() - do { - self.buffer = try await self.fileDescriptor.read( - upToLength: Subprocess.readBufferSize - ) - self.currentPosition = 0 - if self.buffer.count < Subprocess.readBufferSize { - self.finished = true - } - } catch { - self.finished = true - throw error - } - return try await self.next() - } + private mutating func reloadBufferAndNext() async throws -> UInt8? { + if self.finished { + return nil + } + try Task.checkCancellation() + do { + self.buffer = try await self.fileDescriptor.read( + upToLength: Subprocess.readBufferSize) + self.currentPosition = 0 + if self.buffer.isEmpty { + self.finished = true + } + } catch { + self.finished = true + throw error + } + return try await self.next() + } - public mutating func next() async throws -> UInt8? { - if currentPosition < buffer.count { - let value = buffer[currentPosition] - self.currentPosition += 1 - return value - } - return try await self.reloadBufferAndNext() - } + public mutating func next() async throws -> UInt8? { + if currentPosition < buffer.count { + let value = buffer[currentPosition] + self.currentPosition += 1 + return value + } + return try await self.reloadBufferAndNext() + } - private func read(from fileDescriptor: FileDescriptor, maxLength: Int) async throws -> [UInt8] { - return try await withCheckedThrowingContinuation { continuation in - DispatchIO.read( - fromFileDescriptor: fileDescriptor.rawValue, - maxLength: maxLength, - runningHandlerOn: .main - ) { data, error in - guard error == 0 else { - continuation.resume( - throwing: POSIXError( - .init(rawValue: error) ?? .ENODEV - ) - ) - return + private func read(from fileDescriptor: FileDescriptor, maxLength: Int) async throws -> [UInt8] { + return try await withCheckedThrowingContinuation { continuation in + DispatchIO.read( + fromFileDescriptor: fileDescriptor.rawValue, + maxLength: maxLength, + runningHandlerOn: .main + ) { data, error in + guard error == 0 else { + continuation.resume( + throwing: POSIXError( + .init(rawValue: error) ?? .ENODEV) + ) + return + } + continuation.resume(returning: Array(data)) + } + } } - continuation.resume(returning: Array(data)) - } } - } - } - private let fileDescriptor: FileDescriptor + private let fileDescriptor: FileDescriptor - init(fileDescriptor: FileDescriptor) { - self.fileDescriptor = fileDescriptor - } + init(fileDescriptor: FileDescriptor) { + self.fileDescriptor = fileDescriptor + } - public func makeAsyncIterator() -> Iterator { - return Iterator(fileDescriptor: self.fileDescriptor) + public func makeAsyncIterator() -> Iterator { + return Iterator(fileDescriptor: self.fileDescriptor) + } } - } } extension RangeReplaceableCollection { - /// Creates a new instance of a collection containing the elements of an asynchronous sequence. - /// - /// - Parameter source: The asynchronous sequence of elements for the new collection. - @inlinable - public init(_ source: Source) async rethrows where Source.Element == Element { - self.init() - for try await item in source { - append(item) + /// Creates a new instance of a collection containing the elements of an asynchronous sequence. + /// + /// - Parameter source: The asynchronous sequence of elements for the new collection. + @inlinable + public init(_ source: Source) async rethrows where Source.Element == Element { + self.init() + for try await item in source { + append(item) + } } - } } public protocol _AsyncSequence: AsyncSequence { - associatedtype Error + associatedtype Error } diff --git a/Sources/_Subprocess/Subprocess+Configuration.swift b/Sources/_Subprocess/Subprocess+Configuration.swift index a50f1fc5..e56e5cdb 100644 --- a/Sources/_Subprocess/Subprocess+Configuration.swift +++ b/Sources/_Subprocess/Subprocess+Configuration.swift @@ -9,13 +9,12 @@ // //===----------------------------------------------------------------------===// -import Foundation @preconcurrency import SystemPackage #if FOUNDATION_FRAMEWORK -@_implementationOnly import _SubprocessCShims +@_implementationOnly import _FoundationCShims #else -package import _SubprocessCShims +package import _CShims #endif #if canImport(Darwin) @@ -24,598 +23,599 @@ import Darwin import Glibc #endif +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif canImport(Foundation) +import Foundation +#endif + extension Subprocess { - public struct Configuration: Sendable { + public struct Configuration: Sendable { - internal enum RunState: Sendable { - case workBody(Result) - case monitorChildProcess(TerminationStatus) - } + internal enum RunState: Sendable { + case workBody(Result) + case monitorChildProcess(TerminationStatus) + } - // Configurable properties - public var executable: Executable - public var arguments: Arguments - public var environment: Environment - public var workingDirectory: FilePath - public var platformOptions: PlatformOptions - - public init( - executable: Executable, - arguments: Arguments = [], - environment: Environment = .inherit, - workingDirectory: FilePath? = nil, - platformOptions: PlatformOptions = .default - ) { - self.executable = executable - self.arguments = arguments - self.environment = environment - self.workingDirectory = workingDirectory ?? .currentWorkingDirectory - self.platformOptions = platformOptions - } + // Configurable properties + public var executable: Executable + public var arguments: Arguments + public var environment: Environment + public var workingDirectory: FilePath + public var platformOptions: PlatformOptions + + public init( + executable: Executable, + arguments: Arguments = [], + environment: Environment = .inherit, + workingDirectory: FilePath? = nil, + platformOptions: PlatformOptions = .default + ) { + self.executable = executable + self.arguments = arguments + self.environment = environment + self.workingDirectory = workingDirectory ?? .currentWorkingDirectory + self.platformOptions = platformOptions + } - /// Close each input individually, and throw the first error if there's multiple errors thrown - @Sendable - private func cleanup( - process: Subprocess, - childSide: Bool, - parentSide: Bool, - attemptToTerminateSubProcess: Bool - ) throws { - guard childSide || parentSide || attemptToTerminateSubProcess else { - return - } - - let inputCloseFunc: () throws -> Void - let outputCloseFunc: () throws -> Void - let errorCloseFunc: () throws -> Void - if childSide && parentSide { - // Close all - inputCloseFunc = process.executionInput.closeAll - outputCloseFunc = process.executionOutput.closeAll - errorCloseFunc = process.executionError.closeAll - } else if childSide { - // Close child only - inputCloseFunc = process.executionInput.closeChildSide - outputCloseFunc = process.executionOutput.closeChildSide - errorCloseFunc = process.executionError.closeChildSide - } else { - // Close parent only - inputCloseFunc = process.executionInput.closeParentSide - outputCloseFunc = process.executionOutput.closeParentSide - errorCloseFunc = process.executionError.closeParentSide - } - - var inputError: Error? - var outputError: Error? - var errorError: Error? // lol - do { - try inputCloseFunc() - } catch { - inputError = error - } - - do { - try outputCloseFunc() - } catch { - outputError = error - } - - do { - try errorCloseFunc() - } catch { - errorError = error // lolol - } - - // Attempt to kill the subprocess - var killError: Error? - if attemptToTerminateSubProcess { - do { - try process.sendSignal(.kill, toProcessGroup: true) - } catch { - guard let posixError: POSIXError = error as? POSIXError else { - killError = error - return - } - // Ignore ESRCH (no such process) - if posixError.code != .ESRCH { - killError = error - } - } - } - - if let inputError = inputError { - throw inputError - } - - if let outputError = outputError { - throw outputError - } - - if let errorError = errorError { - throw errorError - } - - if let killError = killError { - throw killError - } - } + /// Close each input individually, and throw the first error if there's multiple errors thrown + @Sendable + private func cleanup( + process: Subprocess, + childSide: Bool, parentSide: Bool, + attemptToTerminateSubProcess: Bool + ) throws { + guard childSide || parentSide || attemptToTerminateSubProcess else { + return + } - /// Close each input individually, and throw the first error if there's multiple errors thrown - @Sendable - internal func cleanupAll( - input: ExecutionInput, - output: ExecutionOutput, - error: ExecutionOutput - ) throws { - var inputError: Error? - var outputError: Error? - var errorError: Error? - - do { - try input.closeAll() - } catch { - inputError = error - } - - do { - try output.closeAll() - } catch { - outputError = error - } - - do { - try error.closeAll() - } catch { - errorError = error - } - - if let inputError = inputError { - throw inputError - } - if let outputError = outputError { - throw outputError - } - if let errorError = errorError { - throw errorError - } - } + let inputCloseFunc: () throws -> Void + let outputCloseFunc: () throws -> Void + let errorCloseFunc: () throws -> Void + if childSide && parentSide { + // Close all + inputCloseFunc = process.executionInput.closeAll + outputCloseFunc = process.executionOutput.closeAll + errorCloseFunc = process.executionError.closeAll + } else if childSide { + // Close child only + inputCloseFunc = process.executionInput.closeChildSide + outputCloseFunc = process.executionOutput.closeChildSide + errorCloseFunc = process.executionError.closeChildSide + } else { + // Close parent only + inputCloseFunc = process.executionInput.closeParentSide + outputCloseFunc = process.executionOutput.closeParentSide + errorCloseFunc = process.executionError.closeParentSide + } - internal func run( - output: RedirectedOutputMethod, - error: RedirectedOutputMethod, - _ body: @Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R - ) async throws -> ExecutionResult { - let (readFd, writeFd) = try FileDescriptor.pipe() - let executionInput: ExecutionInput = .init(storage: .customWrite(readFd, writeFd)) - let executionOutput: ExecutionOutput = try output.createExecutionOutput() - let executionError: ExecutionOutput = try error.createExecutionOutput() - let process: Subprocess = try self.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError - ) - // After spawn, cleanup child side fds - try self.cleanup( - process: process, - childSide: true, - parentSide: false, - attemptToTerminateSubProcess: false - ) - return try await withTaskCancellationHandler { - return try await withThrowingTaskGroup(of: RunState.self) { group in - group.addTask { - let status = await monitorProcessTermination( - forProcessWithIdentifier: process.processIdentifier - ) - return .monitorChildProcess(status) - } - group.addTask { + var inputError: Error? + var outputError: Error? + var errorError: Error? // lol do { - let result = try await body(process, .init(input: executionInput)) - try self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - return .workBody(result) + try inputCloseFunc() } catch { - // Cleanup everything - try self.cleanup( - process: process, - childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - throw error + inputError = error } - } - - var result: R! - var terminationStatus: TerminationStatus! - while let state = try await group.next() { - switch state { - case .monitorChildProcess(let status): - // We don't really care about termination status here - terminationStatus = status - case .workBody(let workResult): - result = workResult + + do { + try outputCloseFunc() + } catch { + outputError = error } - } - return ExecutionResult(terminationStatus: terminationStatus, value: result) - } - } onCancel: { - // Attempt to terminate the child process - // Since the task has already been cancelled, - // this is the best we can do - try? self.cleanup( - process: process, - childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - } - } - internal func run( - input: InputMethod, - output: RedirectedOutputMethod, - error: RedirectedOutputMethod, - _ body: (@Sendable @escaping (Subprocess) async throws -> R) - ) async throws -> ExecutionResult { - let executionInput = try input.createExecutionInput() - let executionOutput = try output.createExecutionOutput() - let executionError = try error.createExecutionOutput() - let process = try self.spawn( - withInput: executionInput, - output: executionOutput, - error: executionError - ) - // After spawn, clean up child side - try self.cleanup( - process: process, - childSide: true, - parentSide: false, - attemptToTerminateSubProcess: false - ) - return try await withTaskCancellationHandler { - return try await withThrowingTaskGroup(of: RunState.self) { group in - group.addTask { - let status = await monitorProcessTermination( - forProcessWithIdentifier: process.processIdentifier - ) - return .monitorChildProcess(status) - } - group.addTask { do { - let result = try await body(process) - try self.cleanup( - process: process, - childSide: false, - parentSide: true, - attemptToTerminateSubProcess: false - ) - return .workBody(result) + try errorCloseFunc() + } catch { + errorError = error // lolol + } + + // Attempt to kill the subprocess + var killError: Error? + if attemptToTerminateSubProcess { + do { + try process.sendSignal(.kill, toProcessGroup: true) + } catch { + guard let posixError: POSIXError = error as? POSIXError else { + killError = error + return + } + // Ignore ESRCH (no such process) + if posixError.code != .ESRCH { + killError = error + } + } + } + + if let inputError = inputError { + throw inputError + } + + if let outputError = outputError { + throw outputError + } + + if let errorError = errorError { + throw errorError + } + + if let killError = killError { + throw killError + } + } + + /// Close each input individually, and throw the first error if there's multiple errors thrown + @Sendable + internal func cleanupAll( + input: ExecutionInput, + output: ExecutionOutput, + error: ExecutionOutput + ) throws { + var inputError: Error? + var outputError: Error? + var errorError: Error? + + do { + try input.closeAll() + } catch { + inputError = error + } + + do { + try output.closeAll() } catch { - try self.cleanup( + outputError = error + } + + do { + try error.closeAll() + } catch { + errorError = error + } + + if let inputError = inputError { + throw inputError + } + if let outputError = outputError { + throw outputError + } + if let errorError = errorError { + throw errorError + } + } + + internal func run( + output: RedirectedOutputMethod, + error: RedirectedOutputMethod, + _ body: @Sendable @escaping (Subprocess, StandardInputWriter) async throws -> R + ) async throws -> ExecutionResult { + let (readFd, writeFd) = try FileDescriptor.pipe() + let executionInput: ExecutionInput = .init(storage: .customWrite(readFd, writeFd)) + let executionOutput: ExecutionOutput = try output.createExecutionOutput() + let executionError: ExecutionOutput = try error.createExecutionOutput() + let process: Subprocess = try self.spawn( + withInput: executionInput, + output: executionOutput, + error: executionError) + // After spawn, cleanup child side fds + try self.cleanup( process: process, childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - throw error + parentSide: false, + attemptToTerminateSubProcess: false + ) + return try await withTaskCancellationHandler { + return try await withThrowingTaskGroup(of: RunState.self) { group in + group.addTask { + let status = await monitorProcessTermination( + forProcessWithIdentifier: process.processIdentifier) + return .monitorChildProcess(status) + } + group.addTask { + do { + let result = try await body(process, .init(input: executionInput)) + try self.cleanup( + process: process, + childSide: false, + parentSide: true, + attemptToTerminateSubProcess: false + ) + return .workBody(result) + } catch { + // Cleanup everything + try self.cleanup( + process: process, + childSide: true, + parentSide: true, + attemptToTerminateSubProcess: true + ) + throw error + } + } + + var result: R! + var terminationStatus: TerminationStatus! + while let state = try await group.next() { + switch state { + case .monitorChildProcess(let status): + // We don't really care about termination status here + terminationStatus = status + case .workBody(let workResult): + result = workResult + } + } + return ExecutionResult(terminationStatus: terminationStatus, value: result) + } + } onCancel: { + // Attempt to terminate the child process + // Since the task has already been cancelled, + // this is the best we can do + try? self.cleanup( + process: process, + childSide: true, + parentSide: true, + attemptToTerminateSubProcess: true + ) } - } - - var result: R! - var terminationStatus: TerminationStatus! - while let state = try await group.next() { - switch state { - case .monitorChildProcess(let status): - terminationStatus = status - case .workBody(let workResult): - result = workResult + } + + internal func run( + input: InputMethod, + output: RedirectedOutputMethod, + error: RedirectedOutputMethod, + _ body: (@Sendable @escaping (Subprocess) async throws -> R) + ) async throws -> ExecutionResult { + let executionInput = try input.createExecutionInput() + let executionOutput = try output.createExecutionOutput() + let executionError = try error.createExecutionOutput() + let process = try self.spawn( + withInput: executionInput, + output: executionOutput, + error: executionError) + // After spawn, clean up child side + try self.cleanup( + process: process, + childSide: true, + parentSide: false, + attemptToTerminateSubProcess: false + ) + return try await withTaskCancellationHandler { + return try await withThrowingTaskGroup(of: RunState.self) { group in + group.addTask { + let status = await monitorProcessTermination( + forProcessWithIdentifier: process.processIdentifier) + return .monitorChildProcess(status) + } + group.addTask { + do { + let result = try await body(process) + try self.cleanup( + process: process, + childSide: false, + parentSide: true, + attemptToTerminateSubProcess: false + ) + return .workBody(result) + } catch { + try self.cleanup( + process: process, + childSide: true, + parentSide: true, + attemptToTerminateSubProcess: true + ) + throw error + } + } + + var result: R! + var terminationStatus: TerminationStatus! + while let state = try await group.next() { + switch state { + case .monitorChildProcess(let status): + terminationStatus = status + case .workBody(let workResult): + result = workResult + } + } + return ExecutionResult(terminationStatus: terminationStatus, value: result) + } + } onCancel: { + // Attempt to terminate the child process + // Since the task has already been cancelled, + // this is the best we can do + try? self.cleanup( + process: process, + childSide: true, + parentSide: true, + attemptToTerminateSubProcess: true + ) } - } - return ExecutionResult(terminationStatus: terminationStatus, value: result) - } - } onCancel: { - // Attempt to terminate the child process - // Since the task has already been cancelled, - // this is the best we can do - try? self.cleanup( - process: process, - childSide: true, - parentSide: true, - attemptToTerminateSubProcess: true - ) - } + } } - } } // MARK: - Executable extension Subprocess { - public struct Executable: Sendable, CustomStringConvertible, Hashable { - internal enum Configuration: Sendable, Hashable { - case executable(String) - case path(FilePath) - } + public struct Executable: Sendable, CustomStringConvertible, Hashable { + internal enum Configuration: Sendable, Hashable { + case executable(String) + case path(FilePath) + } - internal let storage: Configuration + internal let storage: Configuration - public var description: String { - switch storage { - case .executable(let executableName): - return executableName - case .path(let filePath): - return filePath.string - } - } + public var description: String { + switch storage { + case .executable(let executableName): + return executableName + case .path(let filePath): + return filePath.string + } + } - private init(_config: Configuration) { - self.storage = _config - } + private init(_config: Configuration) { + self.storage = _config + } - public static func named(_ executableName: String) -> Self { - return .init(_config: .executable(executableName)) - } + public static func named(_ executableName: String) -> Self { + return .init(_config: .executable(executableName)) + } - public static func at(_ filePath: FilePath) -> Self { - return .init(_config: .path(filePath)) - } + public static func at(_ filePath: FilePath) -> Self { + return .init(_config: .path(filePath)) + } - public func resolveExecutablePath(in environment: Environment) -> FilePath? { - if let path = self.resolveExecutablePath(withPathValue: environment.pathValue()) { - return FilePath(path) - } - return nil + public func resolveExecutablePath(in environment: Environment) -> FilePath? { + if let path = self.resolveExecutablePath(withPathValue: environment.pathValue()) { + return FilePath(path) + } + return nil + } } - } } // MARK: - Arguments extension Subprocess { - public struct Arguments: Sendable, ExpressibleByArrayLiteral { - public typealias ArrayLiteralElement = String + public struct Arguments: Sendable, ExpressibleByArrayLiteral { + public typealias ArrayLiteralElement = String - internal let storage: [StringOrRawBytes] - internal let executablePathOverride: StringOrRawBytes? + internal let storage: [StringOrRawBytes] + internal let executablePathOverride: StringOrRawBytes? - public init(arrayLiteral elements: String...) { - self.storage = elements.map { .string($0) } - self.executablePathOverride = nil - } + public init(arrayLiteral elements: String...) { + self.storage = elements.map { .string($0) } + self.executablePathOverride = nil + } - public init(executablePathOverride: String?, remainingValues: [String]) { - self.storage = remainingValues.map { .string($0) } - if let executablePathOverride = executablePathOverride { - self.executablePathOverride = .string(executablePathOverride) - } else { - self.executablePathOverride = nil - } - } + public init(executablePathOverride: String?, remainingValues: [String]) { + self.storage = remainingValues.map { .string($0) } + if let executablePathOverride = executablePathOverride { + self.executablePathOverride = .string(executablePathOverride) + } else { + self.executablePathOverride = nil + } + } - public init(executablePathOverride: Data?, remainingValues: [Data]) { - self.storage = remainingValues.map { .rawBytes($0.toArray()) } - if let override = executablePathOverride { - self.executablePathOverride = .rawBytes(override.toArray()) - } else { - self.executablePathOverride = nil - } + public init(executablePathOverride: Data?, remainingValues: [Data]) { + self.storage = remainingValues.map { .rawBytes($0.toArray()) } + if let override = executablePathOverride { + self.executablePathOverride = .rawBytes(override.toArray()) + } else { + self.executablePathOverride = nil + } + } } - } } // MARK: - Environment extension Subprocess { - public struct Environment: Sendable { - internal enum Configuration { - case inherit([StringOrRawBytes: StringOrRawBytes]) - case custom([StringOrRawBytes: StringOrRawBytes]) - } + public struct Environment: Sendable { + internal enum Configuration { + case inherit([StringOrRawBytes : StringOrRawBytes]) + case custom([StringOrRawBytes : StringOrRawBytes]) + } - internal let config: Configuration + internal let config: Configuration - init(config: Configuration) { - self.config = config - } + init(config: Configuration) { + self.config = config + } - public static var inherit: Self { - return .init(config: .inherit([:])) - } + public static var inherit: Self { + return .init(config: .inherit([:])) + } - public func updating(_ newValue: [String: String]) -> Self { - return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) - } + public func updating(_ newValue: [String : String]) -> Self { + return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) + } - public func updating(_ newValue: [Data: Data]) -> Self { - return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) - } + public func updating(_ newValue: [Data : Data]) -> Self { + return .init(config: .inherit(newValue.wrapToStringOrRawBytes())) + } - public static func custom(_ newValue: [String: String]) -> Self { - return .init(config: .custom(newValue.wrapToStringOrRawBytes())) - } + public static func custom(_ newValue: [String : String]) -> Self { + return .init(config: .custom(newValue.wrapToStringOrRawBytes())) + } - public static func custom(_ newValue: [Data: Data]) -> Self { - return .init(config: .custom(newValue.wrapToStringOrRawBytes())) + public static func custom(_ newValue: [Data : Data]) -> Self { + return .init(config: .custom(newValue.wrapToStringOrRawBytes())) + } } - } } fileprivate extension Dictionary where Key == String, Value == String { - func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes: Subprocess.StringOrRawBytes] { - var result = Dictionary< - Subprocess.StringOrRawBytes, - Subprocess.StringOrRawBytes - >(minimumCapacity: self.count) - for (key, value) in self { - result[.string(key)] = .string(value) + func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes : Subprocess.StringOrRawBytes] { + var result = Dictionary< + Subprocess.StringOrRawBytes, + Subprocess.StringOrRawBytes + >(minimumCapacity: self.count) + for (key, value) in self { + result[.string(key)] = .string(value) + } + return result } - return result - } } fileprivate extension Dictionary where Key == Data, Value == Data { - func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes: Subprocess.StringOrRawBytes] { - var result = Dictionary< - Subprocess.StringOrRawBytes, - Subprocess.StringOrRawBytes - >(minimumCapacity: self.count) - for (key, value) in self { - result[.rawBytes(key.toArray())] = .rawBytes(value.toArray()) + func wrapToStringOrRawBytes() -> [Subprocess.StringOrRawBytes : Subprocess.StringOrRawBytes] { + var result = Dictionary< + Subprocess.StringOrRawBytes, + Subprocess.StringOrRawBytes + >(minimumCapacity: self.count) + for (key, value) in self { + result[.rawBytes(key.toArray())] = .rawBytes(value.toArray()) + } + return result } - return result - } } fileprivate extension Data { - func toArray() -> [T] { - return self.withUnsafeBytes { ptr in - return Array(ptr.bindMemory(to: T.self)) + func toArray() -> [T] { + return self.withUnsafeBytes { ptr in + return Array(ptr.bindMemory(to: T.self)) + } } - } } // MARK: - ProcessIdentifier extension Subprocess { - public struct ProcessIdentifier: Sendable, Hashable { - let value: pid_t + public struct ProcessIdentifier: Sendable, Hashable { + let value: pid_t - internal init(value: pid_t) { - self.value = value + internal init(value: pid_t) { + self.value = value + } } - } } // MARK: - TerminationStatus extension Subprocess { - public enum TerminationStatus: Sendable, Hashable, Codable { - #if canImport(WinSDK) - public typealias Code = DWORD - #else - public typealias Code = CInt - #endif - - #if canImport(WinSDK) - case stillActive - #endif - - case exited(Code) - case unhandledException(Code) - - public var isSuccess: Bool { - switch self { - case .exited(let exitCode): - return exitCode == 0 - case .unhandledException(_): - return false - } - } + public enum TerminationStatus: Sendable, Hashable, Codable { + #if canImport(WinSDK) + public typealias Code = DWORD + #else + public typealias Code = CInt + #endif + + #if canImport(WinSDK) + case stillActive + #endif + + case exited(Code) + case unhandledException(Code) + + public var isSuccess: Bool { + switch self { + case .exited(let exitCode): + return exitCode == 0 + case .unhandledException(_): + return false + } + } - public var isUnhandledException: Bool { - switch self { - case .exited(_): - return false - case .unhandledException(_): - return true - } + public var isUnhandledException: Bool { + switch self { + case .exited(_): + return false + case .unhandledException(_): + return true + } + } } - } } // MARK: - Internal extension Subprocess { - internal enum StringOrRawBytes: Sendable, Hashable { - case string(String) - case rawBytes([CChar]) - - // Return value needs to be deallocated manually by callee - func createRawBytes() -> UnsafeMutablePointer { - switch self { - case .string(let string): - return strdup(string) - case .rawBytes(let rawBytes): - return strdup(rawBytes) - } - } + internal enum StringOrRawBytes: Sendable, Hashable { + case string(String) + case rawBytes([CChar]) + + // Return value needs to be deallocated manually by callee + func createRawBytes() -> UnsafeMutablePointer { + switch self { + case .string(let string): + return strdup(string) + case .rawBytes(let rawBytes): + return strdup(rawBytes) + } + } - var stringValue: String? { - switch self { - case .string(let string): - return string - case .rawBytes(let rawBytes): - return String(validatingUTF8: rawBytes) - } - } + var stringValue: String? { + switch self { + case .string(let string): + return string + case .rawBytes(let rawBytes): + return String(validatingUTF8: rawBytes) + } + } - var count: Int { - switch self { - case .string(let string): - return string.count - case .rawBytes(let rawBytes): - return strnlen(rawBytes, Int.max) - } - } + var count: Int { + switch self { + case .string(let string): + return string.count + case .rawBytes(let rawBytes): + return strnlen(rawBytes, Int.max) + } + } - func hash(into hasher: inout Hasher) { - // If Raw bytes is valid UTF8, hash it as so - switch self { - case .string(let string): - hasher.combine(string) - case .rawBytes(let bytes): - if let stringValue = self.stringValue { - hasher.combine(stringValue) - } else { - hasher.combine(bytes) - } - } + func hash(into hasher: inout Hasher) { + // If Raw bytes is valid UTF8, hash it as so + switch self { + case .string(let string): + hasher.combine(string) + case .rawBytes(let bytes): + if let stringValue = self.stringValue { + hasher.combine(stringValue) + } else { + hasher.combine(bytes) + } + } + } } - } } extension FilePath { - static var currentWorkingDirectory: Self { - let path = getcwd(nil, 0)! - defer { free(path) } - return .init(String(cString: path)) - } + static var currentWorkingDirectory: Self { + let path = getcwd(nil, 0)! + defer { free(path) } + return .init(String(cString: path)) + } } -extension Optional where Wrapped: Collection { - func withOptionalUnsafeBufferPointer(_ body: ((UnsafeBufferPointer)?) throws -> R) rethrows -> R { - switch self { - case .some(let wrapped): - guard let array: Array = wrapped as? Array else { - return try body(nil) - } - return try array.withUnsafeBufferPointer { ptr in - return try body(ptr) - } - case .none: - return try body(nil) +extension Optional where Wrapped : Collection { + func withOptionalUnsafeBufferPointer(_ body: ((UnsafeBufferPointer)?) throws -> R) rethrows -> R { + switch self { + case .some(let wrapped): + guard let array: Array = wrapped as? Array else { + return try body(nil) + } + return try array.withUnsafeBufferPointer { ptr in + return try body(ptr) + } + case .none: + return try body(nil) + } } - } } extension Optional where Wrapped == String { - func withOptionalCString(_ body: ((UnsafePointer)?) throws -> R) rethrows -> R { - switch self { - case .none: - return try body(nil) - case .some(let wrapped): - return try wrapped.withCString { - return try body($0) - } + func withOptionalCString(_ body: ((UnsafePointer)?) throws -> R) rethrows -> R { + switch self { + case .none: + return try body(nil) + case .some(let wrapped): + return try wrapped.withCString { + return try body($0) + } + } } - } } // MARK: - Stubs for the one from Foundation public enum QualityOfService: Int, Sendable { - case userInteractive = 0x21 - case userInitiated = 0x19 - case utility = 0x11 - case background = 0x09 - case `default` = -1 + case userInteractive = 0x21 + case userInitiated = 0x19 + case utility = 0x11 + case background = 0x09 + case `default` = -1 } diff --git a/Sources/_Subprocess/Subprocess+IO.swift b/Sources/_Subprocess/Subprocess+IO.swift index f71bb2dc..73b1f99e 100644 --- a/Sources/_Subprocess/Subprocess+IO.swift +++ b/Sources/_Subprocess/Subprocess+IO.swift @@ -9,392 +9,396 @@ // //===----------------------------------------------------------------------===// -import Dispatch +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif canImport(Foundation) import Foundation +#endif + +import Dispatch import SystemPackage +import Synchronization // MARK: - Input extension Subprocess { - public struct InputMethod: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case noInput - case fileDescriptor(FileDescriptor, Bool) - } + public struct InputMethod: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case noInput + case fileDescriptor(FileDescriptor, Bool) + } - internal let method: Storage + internal let method: Storage - internal init(method: Storage) { - self.method = method - } + internal init(method: Storage) { + self.method = method + } - internal func createExecutionInput() throws -> ExecutionInput { - switch self.method { - case .noInput: - let devnull: FileDescriptor = try .open("/dev/null", .readOnly) - return .init(storage: .noInput(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - } - } + internal func createExecutionInput() throws -> ExecutionInput { + switch self.method { + case .noInput: + let devnull: FileDescriptor = try .open("/dev/null", .readOnly) + return .init(storage: .noInput(devnull)) + case .fileDescriptor(let fileDescriptor, let closeWhenDone): + return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) + } + } - public static var noInput: Self { - return .init(method: .noInput) - } + public static var noInput: Self { + return .init(method: .noInput) + } - public static func readFrom(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned)) + public static func readFrom(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self { + return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned)) + } } - } } extension Subprocess { - public struct CollectedOutputMethod: Sendable, Hashable { - internal enum Storage: Sendable, Hashable { - case discarded - case fileDescriptor(FileDescriptor, Bool) - case collected(Int) - } + public struct CollectedOutputMethod: Sendable, Hashable { + internal enum Storage: Sendable, Hashable { + case discarded + case fileDescriptor(FileDescriptor, Bool) + case collected(Int) + } - internal let method: Storage + internal let method: Storage - internal init(method: Storage) { - self.method = method - } + internal init(method: Storage) { + self.method = method + } - public static var discard: Self { - return .init(method: .discarded) - } + public static var discard: Self { + return .init(method: .discarded) + } - public static var collect: Self { - return .init(method: .collected(128 * 1024)) - } + public static var collect: Self { + return .init(method: .collected(128 * 1024)) + } - public static func writeTo(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned)) - } + public static func writeTo(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self { + return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned)) + } - public static func collect(limit: Int) -> Self { - return .init(method: .collected(limit)) - } + public static func collect(limit: Int) -> Self { + return .init(method: .collected(limit)) + } - internal func createExecutionOutput() throws -> ExecutionOutput { - switch self.method { - case .discarded: - // Bind to /dev/null - let devnull: FileDescriptor = try .open("/dev/null", .writeOnly) - return .init(storage: .discarded(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - case .collected(let limit): - let (readFd, writeFd) = try FileDescriptor.pipe() - return .init(storage: .collected(limit, readFd, writeFd)) - } + internal func createExecutionOutput() throws -> ExecutionOutput { + switch self.method { + case .discarded: + // Bind to /dev/null + let devnull: FileDescriptor = try .open("/dev/null", .writeOnly) + return .init(storage: .discarded(devnull)) + case .fileDescriptor(let fileDescriptor, let closeWhenDone): + return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) + case .collected(let limit): + let (readFd, writeFd) = try FileDescriptor.pipe() + return .init(storage: .collected(limit, readFd, writeFd)) + } + } } - } - public struct RedirectedOutputMethod: Sendable, Hashable { - typealias Storage = CollectedOutputMethod.Storage + public struct RedirectedOutputMethod: Sendable, Hashable { + typealias Storage = CollectedOutputMethod.Storage - internal let method: Storage + internal let method: Storage - internal init(method: Storage) { - self.method = method - } + internal init(method: Storage) { + self.method = method + } - public static var discard: Self { - return .init(method: .discarded) - } + public static var discard: Self { + return .init(method: .discarded) + } - public static var redirectToSequence: Self { - return .init(method: .collected(128 * 1024)) - } + public static var redirectToSequence: Self { + return .init(method: .collected(128 * 1024)) + } - public static func writeTo(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self { - return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned)) - } + public static func writeTo(_ fd: FileDescriptor, closeAfterProcessSpawned: Bool) -> Self { + return .init(method: .fileDescriptor(fd, closeAfterProcessSpawned)) + } - internal func createExecutionOutput() throws -> ExecutionOutput { - switch self.method { - case .discarded: - // Bind to /dev/null - let devnull: FileDescriptor = try .open("/dev/null", .writeOnly) - return .init(storage: .discarded(devnull)) - case .fileDescriptor(let fileDescriptor, let closeWhenDone): - return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) - case .collected(let limit): - let (readFd, writeFd) = try FileDescriptor.pipe() - return .init(storage: .collected(limit, readFd, writeFd)) - } + internal func createExecutionOutput() throws -> ExecutionOutput { + switch self.method { + case .discarded: + // Bind to /dev/null + let devnull: FileDescriptor = try .open("/dev/null", .writeOnly) + return .init(storage: .discarded(devnull)) + case .fileDescriptor(let fileDescriptor, let closeWhenDone): + return .init(storage: .fileDescriptor(fileDescriptor, closeWhenDone)) + case .collected(let limit): + let (readFd, writeFd) = try FileDescriptor.pipe() + return .init(storage: .collected(limit, readFd, writeFd)) + } + } } - } } // MARK: - Execution IO extension Subprocess { - internal final class ExecutionInput: Sendable { - - internal enum Storage: Sendable { - case noInput(FileDescriptor?) - case customWrite(FileDescriptor?, FileDescriptor?) - case fileDescriptor(FileDescriptor?, Bool) - } + internal final class ExecutionInput: Sendable { - let storage: LockedState - - internal init(storage: Storage) { - self.storage = .init(initialState: storage) - } + internal enum Storage: Sendable { + case noInput(FileDescriptor?) + case customWrite(FileDescriptor?, FileDescriptor?) + case fileDescriptor(FileDescriptor?, Bool) + } + + let storage: Mutex - internal func getReadFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .noInput(let readFd): - return readFd - case .customWrite(let readFd, _): - return readFd - case .fileDescriptor(let readFd, _): - return readFd - } - } - } + internal init(storage: Storage) { + self.storage = .init(storage) + } - internal func getWriteFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .noInput(_), .fileDescriptor(_, _): - return nil - case .customWrite(_, let writeFd): - return writeFd + internal func getReadFileDescriptor() -> FileDescriptor? { + return self.storage.withLock { + switch $0 { + case .noInput(let readFd): + return readFd + case .customWrite(let readFd, _): + return readFd + case .fileDescriptor(let readFd, _): + return readFd + } + } } - } - } - internal func closeChildSide() throws { - try self.storage.withLock { - switch $0 { - case .noInput(let devnull): - try devnull?.close() - $0 = .noInput(nil) - case .customWrite(let readFd, let writeFd): - try readFd?.close() - $0 = .customWrite(nil, writeFd) - case .fileDescriptor(let fd, let closeWhenDone): - // User passed in fd - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - } - } - } + internal func getWriteFileDescriptor() -> FileDescriptor? { + return self.storage.withLock { + switch $0 { + case .noInput(_), .fileDescriptor(_, _): + return nil + case .customWrite(_, let writeFd): + return writeFd + } + } + } - internal func closeParentSide() throws { - try self.storage.withLock { - switch $0 { - case .noInput(_), .fileDescriptor(_, _): - break - case .customWrite(let readFd, let writeFd): - // The parent fd should have been closed - // in the `body` when writer.finish() is called - // But in case it isn't call it agian - try writeFd?.close() - $0 = .customWrite(readFd, nil) - } - } - } + internal func closeChildSide() throws { + try self.storage.withLock { + switch $0 { + case .noInput(let devnull): + try devnull?.close() + $0 = .noInput(nil) + case .customWrite(let readFd, let writeFd): + try readFd?.close() + $0 = .customWrite(nil, writeFd) + case .fileDescriptor(let fd, let closeWhenDone): + // User passed in fd + if closeWhenDone { + try fd?.close() + $0 = .fileDescriptor(nil, closeWhenDone) + } + } + } + } - internal func closeAll() throws { - try self.storage.withLock { - switch $0 { - case .noInput(let readFd): - try readFd?.close() - $0 = .noInput(nil) - case .customWrite(let readFd, let writeFd): - var readFdCloseError: Error? - var writeFdCloseError: Error? - do { - try readFd?.close() - } catch { - readFdCloseError = error - } - do { - try writeFd?.close() - } catch { - writeFdCloseError = error - } - $0 = .customWrite(nil, nil) - if let readFdCloseError { - throw readFdCloseError - } - if let writeFdCloseError { - throw writeFdCloseError - } - case .fileDescriptor(let fd, let closeWhenDone): - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - } - } - } + internal func closeParentSide() throws { + try self.storage.withLock { + switch $0 { + case .noInput(_), .fileDescriptor(_, _): + break + case .customWrite(let readFd, let writeFd): + // The parent fd should have been closed + // in the `body` when writer.finish() is called + // But in case it isn't call it agian + try writeFd?.close() + $0 = .customWrite(readFd, nil) + } + } + } - internal final class ExecutionOutput: Sendable { - internal enum Storage: Sendable { - case discarded(FileDescriptor?) - case fileDescriptor(FileDescriptor?, Bool) - case collected(Int, FileDescriptor?, FileDescriptor?) + internal func closeAll() throws { + try self.storage.withLock { + switch $0 { + case .noInput(let readFd): + try readFd?.close() + $0 = .noInput(nil) + case .customWrite(let readFd, let writeFd): + var readFdCloseError: Error? + var writeFdCloseError: Error? + do { + try readFd?.close() + } catch { + readFdCloseError = error + } + do { + try writeFd?.close() + } catch { + writeFdCloseError = error + } + $0 = .customWrite(nil, nil) + if let readFdCloseError { + throw readFdCloseError + } + if let writeFdCloseError { + throw writeFdCloseError + } + case .fileDescriptor(let fd, let closeWhenDone): + try fd?.close() + $0 = .fileDescriptor(nil, closeWhenDone) + } + } + } } - private let storage: LockedState - - internal init(storage: Storage) { - self.storage = .init(initialState: storage) - } + internal final class ExecutionOutput: Sendable { + internal enum Storage: Sendable { + case discarded(FileDescriptor?) + case fileDescriptor(FileDescriptor?, Bool) + case collected(Int, FileDescriptor?, FileDescriptor?) + } + + private let storage: Mutex - internal func getWriteFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - return writeFd - case .fileDescriptor(let writeFd, _): - return writeFd - case .collected(_, _, let writeFd): - return writeFd - } - } - } + internal init(storage: Storage) { + self.storage = .init(storage) + } - internal func getReadFileDescriptor() -> FileDescriptor? { - return self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - return nil - case .collected(_, let readFd, _): - return readFd + internal func getWriteFileDescriptor() -> FileDescriptor? { + return self.storage.withLock { + switch $0 { + case .discarded(let writeFd): + return writeFd + case .fileDescriptor(let writeFd, _): + return writeFd + case .collected(_, _, let writeFd): + return writeFd + } + } } - } - } - internal func consumeCollectedFileDescriptor() -> (limit: Int, fd: FileDescriptor?)? { - return self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - // The output has been written somewhere else - return nil - case .collected(let limit, let readFd, let writeFd): - $0 = .collected(limit, nil, writeFd) - return (limit, readFd) - } - } - } + internal func getReadFileDescriptor() -> FileDescriptor? { + return self.storage.withLock { + switch $0 { + case .discarded(_), .fileDescriptor(_, _): + return nil + case .collected(_, let readFd, _): + return readFd + } + } + } + + internal func consumeCollectedFileDescriptor() -> (limit: Int, fd: FileDescriptor?)? { + return self.storage.withLock { + switch $0 { + case .discarded(_), .fileDescriptor(_, _): + // The output has been written somewhere else + return nil + case .collected(let limit, let readFd, let writeFd): + $0 = .collected(limit, nil, writeFd) + return (limit, readFd) + } + } + } - internal func closeChildSide() throws { - try self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - try writeFd?.close() - $0 = .discarded(nil) - case .fileDescriptor(let fd, let closeWhenDone): - // User passed fd - if closeWhenDone { - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - } - case .collected(let limit, let readFd, let writeFd): - try writeFd?.close() - $0 = .collected(limit, readFd, nil) - } - } - } + internal func closeChildSide() throws { + try self.storage.withLock { + switch $0 { + case .discarded(let writeFd): + try writeFd?.close() + $0 = .discarded(nil) + case .fileDescriptor(let fd, let closeWhenDone): + // User passed fd + if closeWhenDone { + try fd?.close() + $0 = .fileDescriptor(nil, closeWhenDone) + } + case .collected(let limit, let readFd, let writeFd): + try writeFd?.close() + $0 = .collected(limit, readFd, nil) + } + } + } - internal func closeParentSide() throws { - try self.storage.withLock { - switch $0 { - case .discarded(_), .fileDescriptor(_, _): - break - case .collected(let limit, let readFd, let writeFd): - try readFd?.close() - $0 = .collected(limit, nil, writeFd) + internal func closeParentSide() throws { + try self.storage.withLock { + switch $0 { + case .discarded(_), .fileDescriptor(_, _): + break + case .collected(let limit, let readFd, let writeFd): + try readFd?.close() + $0 = .collected(limit, nil, writeFd) + } + } } - } - } - internal func closeAll() throws { - try self.storage.withLock { - switch $0 { - case .discarded(let writeFd): - try writeFd?.close() - $0 = .discarded(nil) - case .fileDescriptor(let fd, let closeWhenDone): - try fd?.close() - $0 = .fileDescriptor(nil, closeWhenDone) - case .collected(let limit, let readFd, let writeFd): - var readFdCloseError: Error? - var writeFdCloseError: Error? - do { - try readFd?.close() - } catch { - readFdCloseError = error - } - do { - try writeFd?.close() - } catch { - writeFdCloseError = error - } - $0 = .collected(limit, nil, nil) - if let readFdCloseError { - throw readFdCloseError - } - if let writeFdCloseError { - throw writeFdCloseError - } - } - } + internal func closeAll() throws { + try self.storage.withLock { + switch $0 { + case .discarded(let writeFd): + try writeFd?.close() + $0 = .discarded(nil) + case .fileDescriptor(let fd, let closeWhenDone): + try fd?.close() + $0 = .fileDescriptor(nil, closeWhenDone) + case .collected(let limit, let readFd, let writeFd): + var readFdCloseError: Error? + var writeFdCloseError: Error? + do { + try readFd?.close() + } catch { + readFdCloseError = error + } + do { + try writeFd?.close() + } catch { + writeFdCloseError = error + } + $0 = .collected(limit, nil, nil) + if let readFdCloseError { + throw readFdCloseError + } + if let writeFdCloseError { + throw writeFdCloseError + } + } + } + } } - } } // MARK: - Private Helpers extension FileDescriptor { - internal func read(upToLength maxLength: Int) async throws -> [UInt8] { - return try await withCheckedThrowingContinuation { continuation in - DispatchIO.read( - fromFileDescriptor: self.rawValue, - maxLength: maxLength, - runningHandlerOn: .main - ) { data, error in - guard error == 0 else { - continuation.resume( - throwing: POSIXError( - .init(rawValue: error) ?? .ENODEV - ) - ) - return - } - continuation.resume(returning: Array(data)) - } + internal func read(upToLength maxLength: Int) async throws -> [UInt8] { + return try await withCheckedThrowingContinuation { continuation in + DispatchIO.read( + fromFileDescriptor: self.rawValue, + maxLength: maxLength, + runningHandlerOn: .main + ) { data, error in + guard error == 0 else { + continuation.resume( + throwing: POSIXError( + .init(rawValue: error) ?? .ENODEV) + ) + return + } + continuation.resume(returning: Array(data)) + } + } } - } - - internal func write(_ data: S) async throws where S.Element == UInt8 { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in - let dispatchData: DispatchData = Array(data).withUnsafeBytes { - return DispatchData(bytes: $0) - } - DispatchIO.write( - toFileDescriptor: self.rawValue, - data: dispatchData, - runningHandlerOn: .main - ) { _, error in - guard error == 0 else { - continuation.resume( - throwing: POSIXError( - .init(rawValue: error) ?? .ENODEV - ) - ) - return - } - continuation.resume() - } + + internal func write(_ data: S) async throws where S.Element == UInt8 { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in + let dispatchData: DispatchData = Array(data).withUnsafeBytes { + return DispatchData(bytes: $0) + } + DispatchIO.write( + toFileDescriptor: self.rawValue, + data: dispatchData, + runningHandlerOn: .main + ) { _, error in + guard error == 0 else { + continuation.resume( + throwing: POSIXError( + .init(rawValue: error) ?? .ENODEV) + ) + return + } + continuation.resume() + } + } } - } } diff --git a/Sources/_Subprocess/Subprocess.swift b/Sources/_Subprocess/Subprocess.swift index fe61ba81..56758bcf 100644 --- a/Sources/_Subprocess/Subprocess.swift +++ b/Sources/_Subprocess/Subprocess.swift @@ -9,9 +9,14 @@ // //===----------------------------------------------------------------------===// -import Foundation import SystemPackage +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif canImport(Foundation) +import Foundation +#endif + /// An object that represents a subprocess of the current process. /// /// Using `Subprocess`, your program can run another program as a subprocess @@ -19,215 +24,208 @@ import SystemPackage /// **separate executable** entity; it’s different from `Thread` because it doesn’t /// share memory space with the process that creates it. public struct Subprocess: Sendable { - /// The process identifier of the current subprocess - public let processIdentifier: ProcessIdentifier - - internal let executionInput: ExecutionInput - internal let executionOutput: ExecutionOutput - internal let executionError: ExecutionOutput - internal var extracted: Bool = false - - internal init( - processIdentifier: ProcessIdentifier, - executionInput: ExecutionInput, - executionOutput: ExecutionOutput, - executionError: ExecutionOutput - ) { - self.processIdentifier = processIdentifier - self.executionInput = executionInput - self.executionOutput = executionOutput - self.executionError = executionError - } - - /// The standard output of the subprocess. - /// Accessing this property will **fatalError** if - /// - `.output` wasn't set to `.redirectToSequence` when the subprocess was spawned; - /// - This property was accessed multiple times. Subprocess communicates with - /// parent process via pipe under the hood and each pipe can only be consumed ones. - public var standardOutput: some _AsyncSequence { - guard - let (_, fd) = self.executionOutput - .consumeCollectedFileDescriptor() - else { - fatalError("The standard output was not redirected") - } - guard let fd = fd else { - fatalError("The standard output has already been closed") - } - return AsyncBytes(fileDescriptor: fd) - } - - /// The standard error of the subprocess. - /// Accessing this property will **fatalError** if - /// - `.error` wasn't set to `.redirectToSequence` when the subprocess was spawned; - /// - This property was accessed multiple times. Subprocess communicates with - /// parent process via pipe under the hood and each pipe can only be consumed ones. - public var standardError: some _AsyncSequence { - guard - let (_, fd) = self.executionError - .consumeCollectedFileDescriptor() - else { - fatalError("The standard error was not redirected") - } - guard let fd = fd else { - fatalError("The standard error has already been closed") + /// The process identifier of the current subprocess + public let processIdentifier: ProcessIdentifier + + internal let executionInput: ExecutionInput + internal let executionOutput: ExecutionOutput + internal let executionError: ExecutionOutput + internal var extracted: Bool = false + + internal init( + processIdentifier: ProcessIdentifier, + executionInput: ExecutionInput, + executionOutput: ExecutionOutput, + executionError: ExecutionOutput + ) { + self.processIdentifier = processIdentifier + self.executionInput = executionInput + self.executionOutput = executionOutput + self.executionError = executionError + } + + /// The standard output of the subprocess. + /// Accessing this property will **fatalError** if + /// - `.output` wasn't set to `.redirectToSequence` when the subprocess was spawned; + /// - This property was accessed multiple times. Subprocess communicates with + /// parent process via pipe under the hood and each pipe can only be consumed ones. + public var standardOutput: some _AsyncSequence { + guard let (_, fd) = self.executionOutput + .consumeCollectedFileDescriptor() else { + fatalError("The standard output was not redirected") + } + guard let fd = fd else { + fatalError("The standard output has already been closed") + } + return AsyncBytes(fileDescriptor: fd) + } + + /// The standard error of the subprocess. + /// Accessing this property will **fatalError** if + /// - `.error` wasn't set to `.redirectToSequence` when the subprocess was spawned; + /// - This property was accessed multiple times. Subprocess communicates with + /// parent process via pipe under the hood and each pipe can only be consumed ones. + public var standardError: some _AsyncSequence { + guard let (_, fd) = self.executionError + .consumeCollectedFileDescriptor() else { + fatalError("The standard error was not redirected") + } + guard let fd = fd else { + fatalError("The standard error has already been closed") + } + return AsyncBytes(fileDescriptor: fd) } - return AsyncBytes(fileDescriptor: fd) - } } // MARK: - StandardInputWriter extension Subprocess { - @_nonSendable - public struct StandardInputWriter { + @_nonSendable + public struct StandardInputWriter { - private let input: ExecutionInput + private let input: ExecutionInput - init(input: ExecutionInput) { - self.input = input - } + init(input: ExecutionInput) { + self.input = input + } - public func write(_ sequence: S) async throws where S: Sequence, S.Element == UInt8 { - guard let fd: FileDescriptor = self.input.getWriteFileDescriptor() else { - fatalError("Attempting to write to a file descriptor that's already closed") - } - try await fd.write(sequence) - } + public func write(_ sequence: S) async throws where S : Sequence, S.Element == UInt8 { + guard let fd: FileDescriptor = self.input.getWriteFileDescriptor() else { + fatalError("Attempting to write to a file descriptor that's already closed") + } + try await fd.write(sequence) + } - public func write(_ sequence: S) async throws where S: Sequence, S.Element == CChar { - try await self.write(sequence.map { UInt8($0) }) - } + public func write(_ sequence: S) async throws where S : Sequence, S.Element == CChar { + try await self.write(sequence.map { UInt8($0) }) + } - public func write(_ asyncSequence: S) async throws where S.Element == CChar { - let sequence = try await Array(asyncSequence).map { UInt8($0) } - try await self.write(sequence) - } + public func write(_ asyncSequence: S) async throws where S.Element == CChar { + let sequence = try await Array(asyncSequence).map { UInt8($0) } + try await self.write(sequence) + } - public func write(_ asyncSequence: S) async throws where S.Element == UInt8 { - let sequence = try await Array(asyncSequence) - try await self.write(sequence) - } + public func write(_ asyncSequence: S) async throws where S.Element == UInt8 { + let sequence = try await Array(asyncSequence) + try await self.write(sequence) + } - public func finish() async throws { - try self.input.closeParentSide() + public func finish() async throws { + try self.input.closeParentSide() + } } - } } // MARK: - Result extension Subprocess { - public struct ExecutionResult: Sendable { - public let terminationStatus: TerminationStatus - public let value: T + public struct ExecutionResult: Sendable { + public let terminationStatus: TerminationStatus + public let value: T - internal init(terminationStatus: TerminationStatus, value: T) { - self.terminationStatus = terminationStatus - self.value = value + internal init(terminationStatus: TerminationStatus, value: T) { + self.terminationStatus = terminationStatus + self.value = value + } } - } - public struct CollectedResult: Sendable, Hashable { - public let processIdentifier: ProcessIdentifier - public let terminationStatus: TerminationStatus - private let _standardOutput: Data? - private let _standardError: Data? - public var standardOutput: Data { - guard let output = self._standardOutput else { - fatalError("standardOutput is only available if the Subprocess was ran with .collect as output") - } - return output - } - public var standardError: Data { - guard let output = self._standardError else { - fatalError("standardError is only available if the Subprocess was ran with .collect as error ") - } - return output - } + public struct CollectedResult: Sendable, Hashable { + public let processIdentifier: ProcessIdentifier + public let terminationStatus: TerminationStatus + private let _standardOutput: Data? + private let _standardError: Data? + public var standardOutput: Data { + guard let output = self._standardOutput else { + fatalError("standardOutput is only available if the Subprocess was ran with .collect as output") + } + return output + } + public var standardError: Data { + guard let output = self._standardError else { + fatalError("standardError is only available if the Subprocess was ran with .collect as error ") + } + return output + } - internal init( - processIdentifier: ProcessIdentifier, - terminationStatus: TerminationStatus, - standardOutput: Data?, - standardError: Data? - ) { - self.processIdentifier = processIdentifier - self.terminationStatus = terminationStatus - self._standardOutput = standardOutput - self._standardError = standardError + internal init( + processIdentifier: ProcessIdentifier, + terminationStatus: TerminationStatus, + standardOutput: Data?, + standardError: Data?) { + self.processIdentifier = processIdentifier + self.terminationStatus = terminationStatus + self._standardOutput = standardOutput + self._standardError = standardError + } } - } } -extension Subprocess.ExecutionResult: Equatable where T: Equatable {} +extension Subprocess.ExecutionResult: Equatable where T : Equatable {} -extension Subprocess.ExecutionResult: Hashable where T: Hashable {} +extension Subprocess.ExecutionResult: Hashable where T : Hashable {} -extension Subprocess.ExecutionResult: Codable where T: Codable {} +extension Subprocess.ExecutionResult: Codable where T : Codable {} // MARK: Internal extension Subprocess { - internal enum OutputCapturingState { - case standardOutputCaptured(Data?) - case standardErrorCaptured(Data?) - } - - private func capture(fileDescriptor: FileDescriptor, maxLength: Int) async throws -> Data { - let chunkSize: Int = min(Subprocess.readBufferSize, maxLength) - var buffer: [UInt8] = [] - while buffer.count <= maxLength { - let captured = try await fileDescriptor.read(upToLength: chunkSize) - buffer += captured - if captured.count < chunkSize { - break - } - } - return Data(buffer) - } - - internal func captureStandardOutput() async throws -> Data? { - guard - let (limit, readFd) = self.executionOutput - .consumeCollectedFileDescriptor(), - let readFd = readFd - else { - return nil + internal enum OutputCapturingState { + case standardOutputCaptured(Data?) + case standardErrorCaptured(Data?) + } + + internal typealias CapturedIOs = (standardOutput: Data?, standardError: Data?) + + private func capture(fileDescriptor: FileDescriptor, maxLength: Int) async throws -> Data{ + let chunkSize: Int = min(Subprocess.readBufferSize, maxLength) + var buffer: [UInt8] = [] + while buffer.count <= maxLength { + let captured = try await fileDescriptor.read(upToLength: chunkSize) + buffer += captured + if captured.count < chunkSize { + break + } + } + return Data(buffer) } - return try await self.capture(fileDescriptor: readFd, maxLength: limit) - } - - internal func captureStandardError() async throws -> Data? { - guard - let (limit, readFd) = self.executionError - .consumeCollectedFileDescriptor(), - let readFd = readFd - else { - return nil + + internal func captureStandardOutput() async throws -> Data? { + guard let (limit, readFd) = self.executionOutput + .consumeCollectedFileDescriptor(), + let readFd = readFd else { + return nil + } + return try await self.capture(fileDescriptor: readFd, maxLength: limit) } - return try await self.capture(fileDescriptor: readFd, maxLength: limit) - } - - internal func captureIOs() async throws -> (standardOut: Data?, standardError: Data?) { - return try await withThrowingTaskGroup(of: OutputCapturingState.self) { group in - group.addTask { - let stdout = try await self.captureStandardOutput() - return .standardOutputCaptured(stdout) - } - group.addTask { - let stderr = try await self.captureStandardError() - return .standardErrorCaptured(stderr) - } - - var stdout: Data? - var stderror: Data? - while let state = try await group.next() { - switch state { - case .standardOutputCaptured(let output): - stdout = output - case .standardErrorCaptured(let error): - stderror = error - } - } - return (standardOut: stdout, standardError: stderror) + + internal func captureStandardError() async throws -> Data? { + guard let (limit, readFd) = self.executionError + .consumeCollectedFileDescriptor(), + let readFd = readFd else { + return nil + } + return try await self.capture(fileDescriptor: readFd, maxLength: limit) + } + + internal func captureIOs() async throws -> CapturedIOs { + return try await withThrowingTaskGroup(of: OutputCapturingState.self) { group in + group.addTask { + let stdout = try await self.captureStandardOutput() + return .standardOutputCaptured(stdout) + } + group.addTask { + let stderr = try await self.captureStandardError() + return .standardErrorCaptured(stderr) + } + + var stdout: Data? + var stderror: Data? + while let state = try await group.next() { + switch state { + case .standardOutputCaptured(let output): + stdout = output + case .standardErrorCaptured(let error): + stderror = error + } + } + return (standardOutput: stdout, standardError: stderror) + } } - } } diff --git a/Sources/_Subprocess/_LockedState.swift b/Sources/_Subprocess/_LockedState.swift deleted file mode 100644 index da17ccf5..00000000 --- a/Sources/_Subprocess/_LockedState.swift +++ /dev/null @@ -1,159 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2022 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// -//===----------------------------------------------------------------------===// - -#if canImport(os) -internal import os -#if FOUNDATION_FRAMEWORK && canImport(C.os.lock) -internal import C.os.lock -#endif -#elseif canImport(Bionic) -import Bionic -#elseif canImport(Glibc) -import Glibc -#elseif canImport(Musl) -import Musl -#elseif canImport(WinSDK) -import WinSDK -#endif - -package struct LockedState { - - // Internal implementation for a cheap lock to aid sharing code across platforms - private struct _Lock { - #if canImport(os) - typealias Primitive = os_unfair_lock - #elseif canImport(Bionic) || canImport(Glibc) || canImport(Musl) - typealias Primitive = pthread_mutex_t - #elseif canImport(WinSDK) - typealias Primitive = SRWLOCK - #elseif os(WASI) - // WASI is single-threaded, so we don't need a lock. - typealias Primitive = Void - #endif - - typealias PlatformLock = UnsafeMutablePointer - var _platformLock: PlatformLock - - fileprivate static func initialize(_ platformLock: PlatformLock) { - #if canImport(os) - platformLock.initialize(to: os_unfair_lock()) - #elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_init(platformLock, nil) - #elseif canImport(WinSDK) - InitializeSRWLock(platformLock) - #elseif os(WASI) - // no-op - #endif - } - - fileprivate static func deinitialize(_ platformLock: PlatformLock) { - #if canImport(Bionic) || canImport(Glibc) - pthread_mutex_destroy(platformLock) - #endif - platformLock.deinitialize(count: 1) - } - - static fileprivate func lock(_ platformLock: PlatformLock) { - #if canImport(os) - os_unfair_lock_lock(platformLock) - #elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_lock(platformLock) - #elseif canImport(WinSDK) - AcquireSRWLockExclusive(platformLock) - #elseif os(WASI) - // no-op - #endif - } - - static fileprivate func unlock(_ platformLock: PlatformLock) { - #if canImport(os) - os_unfair_lock_unlock(platformLock) - #elseif canImport(Bionic) || canImport(Glibc) - pthread_mutex_unlock(platformLock) - #elseif canImport(WinSDK) - ReleaseSRWLockExclusive(platformLock) - #elseif os(WASI) - // no-op - #endif - } - } - - private class _Buffer: ManagedBuffer { - deinit { - withUnsafeMutablePointerToElements { - _Lock.deinitialize($0) - } - } - } - - private let _buffer: ManagedBuffer - - package init(initialState: State) { - _buffer = _Buffer.create( - minimumCapacity: 1, - makingHeaderWith: { buf in - buf.withUnsafeMutablePointerToElements { - _Lock.initialize($0) - } - return initialState - } - ) - } - - package func withLock(_ body: @Sendable (inout State) throws -> T) rethrows -> T { - try withLockUnchecked(body) - } - - package func withLockUnchecked(_ body: (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } - } - - // Ensures the managed state outlives the locked scope. - package func withLockExtendingLifetimeOfState(_ body: @Sendable (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - return try withExtendedLifetime(state.pointee) { - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } - } - } -} - -extension LockedState where State == Void { - package init() { - self.init(initialState: ()) - } - - package func withLock(_ body: @Sendable () throws -> R) rethrows -> R { - return try withLock { _ in - try body() - } - } - - package func lock() { - _buffer.withUnsafeMutablePointerToElements { lock in - _Lock.lock(lock) - } - } - - package func unlock() { - _buffer.withUnsafeMutablePointerToElements { lock in - _Lock.unlock(lock) - } - } -} - -extension LockedState: @unchecked Sendable where State: Sendable {} diff --git a/Sources/_SubprocessCShims/include/_CShimsTargetConditionals.h b/Sources/_SubprocessCShims/include/_CShimsTargetConditionals.h index d6db4bf7..fef2eaf2 100644 --- a/Sources/_SubprocessCShims/include/_CShimsTargetConditionals.h +++ b/Sources/_SubprocessCShims/include/_CShimsTargetConditionals.h @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2021 - 2022 Apple Inc. and the Swift.org project authors +// Copyright (c) 2021 - 2022 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -#ifndef _C_SHIMS_TARGET_CONDITIONALS_H -#define _C_SHIMS_TARGET_CONDITIONALS_H +#ifndef _SHIMS_TARGET_CONDITIONALS_H +#define _SHIMS_TARGET_CONDITIONALS_H #if __has_include() #include @@ -47,10 +47,4 @@ #define TARGET_OS_WASI 0 #endif -#if defined(__ANDROID__) -#define TARGET_OS_ANDROID 1 -#else -#define TARGET_OS_ANDROID 0 -#endif - -#endif // _C_SHIMS_TARGET_CONDITIONALS_H +#endif // _SHIMS_TARGET_CONDITIONALS_H diff --git a/Sources/_SubprocessCShims/include/process_shims.h b/Sources/_SubprocessCShims/include/process_shims.h index cf921da8..560bdf0f 100644 --- a/Sources/_SubprocessCShims/include/process_shims.h +++ b/Sources/_SubprocessCShims/include/process_shims.h @@ -15,9 +15,11 @@ #include #include "_CShimsTargetConditionals.h" -#if TARGET_OS_MAC +#if _POSIX_SPAWN #include +#endif +#if TARGET_OS_MAC int _subprocess_spawn( pid_t * _Nonnull pid, const char * _Nonnull exec_path, @@ -41,9 +43,10 @@ int _subprocess_fork_exec( char * _Nullable const env[_Nullable], uid_t * _Nullable uid, gid_t * _Nullable gid, + gid_t * _Nullable process_group_id, int number_of_sgroups, const gid_t * _Nullable sgroups, int create_session, - int create_process_group + void (* _Nullable configurator)(void) ); int _was_process_exited(int status); diff --git a/Sources/_SubprocessCShims/process_shims.c b/Sources/_SubprocessCShims/process_shims.c index 4027ff66..8f708a58 100644 --- a/Sources/_SubprocessCShims/process_shims.c +++ b/Sources/_SubprocessCShims/process_shims.c @@ -11,11 +11,16 @@ #include "include/_CShimsTargetConditionals.h" #include "include/process_shims.h" +#include #include -#include -#include +#include #include +#include #include +#include +#include + +#include int _was_process_exited(int status) { return WIFEXITED(status); @@ -63,31 +68,34 @@ int _subprocess_spawn( int create_session ) { int require_pre_fork = uid != NULL || - gid != NULL || - number_of_sgroups > 0 || + gid != NULL || + number_of_sgroups > 0 || create_session > 0; if (require_pre_fork != 0) { - pid_t childPid = fork(); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" + pid_t childPid = vfork(); +#pragma GCC diagnostic pop if (childPid != 0) { *pid = childPid; return childPid < 0 ? errno : 0; } - if (uid != NULL) { - if (setuid(*uid) != 0) { + if (number_of_sgroups > 0 && sgroups != NULL) { + if (setgroups(number_of_sgroups, sgroups) != 0) { return errno; } } - if (gid != NULL) { - if (setgid(*gid) != 0) { + if (uid != NULL) { + if (setuid(*uid) != 0) { return errno; } } - if (number_of_sgroups > 0 && sgroups != NULL) { - if (setgroups(number_of_sgroups, sgroups) != 0) { + if (gid != NULL) { + if (setgid(*gid) != 0) { return errno; } } @@ -106,7 +114,8 @@ int _subprocess_spawn( } rc = posix_spawnattr_setflags( - (posix_spawnattr_t *)spawn_attrs, flags | POSIX_SPAWN_SETEXEC); + (posix_spawnattr_t *)spawn_attrs, flags | POSIX_SPAWN_SETEXEC + ); if (rc != 0) { return rc; } @@ -128,31 +137,42 @@ static int _subprocess_posix_spawn_fallback( const int file_descriptors[_Nonnull], char * _Nullable const args[_Nonnull], char * _Nullable const env[_Nullable], - int create_process_group + gid_t * _Nullable process_group_id ) { // Setup stdin, stdout, and stderr posix_spawn_file_actions_t file_actions; int rc = posix_spawn_file_actions_init(&file_actions); if (rc != 0) { return rc; } - rc = posix_spawn_file_actions_adddup2( - &file_actions, file_descriptors[0], STDIN_FILENO); - if (rc != 0) { return rc; } - rc = posix_spawn_file_actions_adddup2( - &file_actions, file_descriptors[2], STDOUT_FILENO); - if (rc != 0) { return rc; } - rc = posix_spawn_file_actions_adddup2( - &file_actions, file_descriptors[4], STDERR_FILENO); - if (rc != 0) { return rc; } - if (file_descriptors[1] != 0) { + if (file_descriptors[0] >= 0) { + rc = posix_spawn_file_actions_adddup2( + &file_actions, file_descriptors[0], STDIN_FILENO + ); + if (rc != 0) { return rc; } + } + if (file_descriptors[2] >= 0) { + rc = posix_spawn_file_actions_adddup2( + &file_actions, file_descriptors[2], STDOUT_FILENO + ); + if (rc != 0) { return rc; } + } + if (file_descriptors[4] >= 0) { + rc = posix_spawn_file_actions_adddup2( + &file_actions, file_descriptors[4], STDERR_FILENO + ); + if (rc != 0) { return rc; } + } + + // Close parent side + if (file_descriptors[1] >= 0) { rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[1]); if (rc != 0) { return rc; } } - if (file_descriptors[3] != 0) { + if (file_descriptors[3] >= 0) { rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[3]); if (rc != 0) { return rc; } } - if (file_descriptors[5] != 0) { + if (file_descriptors[5] >= 0) { rc = posix_spawn_file_actions_addclose(&file_actions, file_descriptors[5]); if (rc != 0) { return rc; } } @@ -172,8 +192,10 @@ static int _subprocess_posix_spawn_fallback( if (rc != 0) { return rc; } // Flags short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; - if (create_process_group) { + if (process_group_id != NULL) { flags |= POSIX_SPAWN_SETPGROUP; + rc = posix_spawnattr_setpgroup(&spawn_attr, *process_group_id); + if (rc != 0) { return rc; } } rc = posix_spawnattr_setflags(&spawn_attr, flags); @@ -198,15 +220,18 @@ int _subprocess_fork_exec( char * _Nullable const env[_Nullable], uid_t * _Nullable uid, gid_t * _Nullable gid, + gid_t * _Nullable process_group_id, int number_of_sgroups, const gid_t * _Nullable sgroups, int create_session, - int create_process_group + void (* _Nullable configurator)(void) ) { int require_pre_fork = working_directory != NULL || uid != NULL || gid != NULL || + process_group_id != NULL || (number_of_sgroups > 0 && sgroups != NULL) || - create_session; + create_session || + configurator != NULL; #if _POSIX_SPAWN // If posix_spawn is available on this platform and @@ -221,7 +246,7 @@ int _subprocess_fork_exec( working_directory, file_descriptors, args, env, - create_process_group + process_group_id ); } #endif @@ -238,6 +263,7 @@ int _subprocess_fork_exec( } } + if (uid != NULL) { if (setuid(*uid) != 0) { return errno; @@ -260,41 +286,41 @@ int _subprocess_fork_exec( (void)setsid(); } - if (create_process_group != 0) { - (void)setpgid(0, 0); + if (process_group_id != NULL) { + (void)setpgid(0, *process_group_id); } // Bind stdin, stdout, and stderr int rc = 0; - if (file_descriptors[0] != 0) { + if (file_descriptors[0] >= 0) { rc = dup2(file_descriptors[0], STDIN_FILENO); - if (rc != 0) { return rc; } + if (rc < 0) { return errno; } } - if (file_descriptors[2] != 0) { + if (file_descriptors[2] >= 0) { rc = dup2(file_descriptors[2], STDOUT_FILENO); - if (rc != 0) { return rc; } + if (rc < 0) { return errno; } } - - if (file_descriptors[4] != 0) { + if (file_descriptors[4] >= 0) { rc = dup2(file_descriptors[4], STDERR_FILENO); - if (rc != 0) { return rc; } + if (rc < 0) { return errno; } } - -#warning Shold close all and then return error no early return // Close parent side - if (file_descriptors[1] != 0) { + if (file_descriptors[1] >= 0) { rc = close(file_descriptors[1]); - if (rc != 0) { return rc; } } - if (file_descriptors[3] != 0) { + if (file_descriptors[3] >= 0) { rc = close(file_descriptors[3]); - if (rc != 0) { return rc; } } - if (file_descriptors[4] != 0) { + if (file_descriptors[4] >= 0) { rc = close(file_descriptors[5]); - if (rc != 0) { return rc; } } - + if (rc != 0) { + return errno; + } + // Run custom configuratior + if (configurator != NULL) { + configurator(); + } // Finally, exec execve(exec_path, args, env); // If we got here, something went wrong From 9ddec559f2a160f4fc1217de2cd1f5d528a948c5 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 23:50:43 +0900 Subject: [PATCH 32/34] subprocess: adjust shim imports --- Sources/_Subprocess/Platforms/Subprocess+Darwin.swift | 2 +- Sources/_Subprocess/Platforms/Subprocess+Linux.swift | 2 +- Sources/_Subprocess/Platforms/Subprocess+Unix.swift | 2 +- Sources/_Subprocess/Subprocess+Configuration.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift index 802ae020..823052ff 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Darwin.swift @@ -24,7 +24,7 @@ import SystemPackage #if FOUNDATION_FRAMEWORK @_implementationOnly import _FoundationCShims #else -internal import _CShims +internal import _SubprocessCShims #endif // Darwin specific implementation diff --git a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift index 9f4b5061..1f62dc8b 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Linux.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Linux.swift @@ -14,7 +14,7 @@ import Glibc import SystemPackage import FoundationEssentials -package import _CShims +package import _SubprocessCShims // Linux specific implementations extension Subprocess.Configuration { diff --git a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift index 1225e0f4..e07f9127 100644 --- a/Sources/_Subprocess/Platforms/Subprocess+Unix.swift +++ b/Sources/_Subprocess/Platforms/Subprocess+Unix.swift @@ -26,7 +26,7 @@ import Glibc #if FOUNDATION_FRAMEWORK @_implementationOnly import _FoundationCShims #else -internal import _CShims +internal import _SubprocessCShims #endif import SystemPackage diff --git a/Sources/_Subprocess/Subprocess+Configuration.swift b/Sources/_Subprocess/Subprocess+Configuration.swift index e56e5cdb..2c4e6ae6 100644 --- a/Sources/_Subprocess/Subprocess+Configuration.swift +++ b/Sources/_Subprocess/Subprocess+Configuration.swift @@ -14,7 +14,7 @@ #if FOUNDATION_FRAMEWORK @_implementationOnly import _FoundationCShims #else -package import _CShims +package import _SubprocessCShims #endif #if canImport(Darwin) From 8f6d2f510ccd81ac7132c6d7e42efa6cd3d589a0 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 2 Oct 2024 23:55:47 +0900 Subject: [PATCH 33/34] fix hardcoded build dir to work on linux --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 09ce40bc..a0baff12 100644 --- a/Makefile +++ b/Makefile @@ -107,8 +107,8 @@ jextract-run: jextract-swift generate-JExtract-interface-files --package-name com.example.swift.generated \ --swift-module JavaKitExample \ --output-directory JavaSwiftKitDemo/src/main/java \ - .build/arm64-apple-macosx/jextract/JavaKitExample/MySwiftLibrary.swiftinterface \ - .build/arm64-apple-macosx/jextract/JavaKitExample/SwiftKit.swiftinterface + $(BUILD_DIR)/jextract/JavaKitExample/MySwiftLibrary.swiftinterface \ + $(BUILD_DIR)/jextract/JavaKitExample/SwiftKit.swiftinterface jextract-run-java: jextract-swift generate-JExtract-interface-files From d07360794568cda30d73502958eee7880488f6f0 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 3 Oct 2024 00:00:49 +0900 Subject: [PATCH 34/34] remove gradle cache for now --- .github/workflows/pull_request.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index da4c2e67..0114da27 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -44,15 +44,6 @@ jobs: - name: Generate sources (make) (Temporary) # TODO: this should be triggered by the respective builds run: make jextract-run - - name: Cache Gradle packages - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - name: Gradle build run: ./gradlew build --no-daemon