diff --git a/.github/workflows/canary-upstreams.yml b/.github/workflows/canary-upstreams.yml index 1106ae9..35a0508 100644 --- a/.github/workflows/canary-upstreams.yml +++ b/.github/workflows/canary-upstreams.yml @@ -195,10 +195,16 @@ jobs: --repo "${REPO}" # --------------------------------------------------------------------------- - # opencombine-latest: build against the latest OpenCombine release (host only). + # opencombine-latest: build against the latest OpenCombine release. # # Detects Scheduler/Publisher/Sendable API changes in OpenCombine that would # break the library before users see them. + # + # Since #15 the OpenCombine dependency is platform-conditional: a macOS host + # build links native Combine and compiles ZERO OpenCombine code, so the host + # steps below only validate dependency resolution against the latest tag. + # The wasm build steps (mirroring the jskit-latest job) are what actually + # compile the library against the patched OpenCombine. # --------------------------------------------------------------------------- opencombine-latest: name: Canary — Latest OpenCombine (#12) @@ -251,7 +257,9 @@ jobs: rm -f Package.resolved swift package resolve - - name: Build host — latest OpenCombine + # Host build/test compile against native Combine since #15 (resolution + # smoke check only — no OpenCombine source is built on macOS). + - name: Build host — latest OpenCombine (resolution check) id: build-host run: swift build --target OpenCombineJS @@ -261,6 +269,32 @@ jobs: id: test-host run: swift test || true + # Wasm build: the lane that actually compiles OpenCombine (see job header). + # Same SDK install/skip strategy as the jskit-latest job above. + - name: Install Swift wasm SDK (official, version-matched) + run: | + SWIFT_SEMVER=$(swift --version 2>/dev/null | grep -oE 'Swift version [0-9]+\.[0-9]+(\.[0-9]+)?' | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1) + SWIFT_TAG="swift-${SWIFT_SEMVER}-RELEASE" + # Note: .artifactbundle.tar.gz — there is no .zip variant on download.swift.org. + SDK_URL="https://download.swift.org/swift-${SWIFT_SEMVER}-release/wasm-sdk/${SWIFT_TAG}/${SWIFT_TAG}_wasm.artifactbundle.tar.gz" + echo "Downloading wasm Swift SDK from ${SDK_URL}" + if curl -fsSL "${SDK_URL}" -o /tmp/swift-wasm-sdk.tar.gz; then + swift sdk install /tmp/swift-wasm-sdk.tar.gz || true + else + echo "WARN: no official wasm SDK published for ${SWIFT_TAG}; skipping wasm canary build." + fi + swift sdk list || true + + - name: Build wasm — latest OpenCombine + id: build-wasm + run: | + SDK_ID=$(swift sdk list 2>/dev/null | grep -i wasm | head -1 | awk '{print $1}' || echo "") + if [ -z "$SDK_ID" ]; then + echo "WARN: No wasm SDK found; skipping wasm build in this canary run." + exit 0 + fi + swift build --swift-sdk "${SDK_ID}" --target OpenCombineJS -c debug + - name: File canary-failure issue (if not already open) if: failure() env: diff --git a/CHANGELOG.md b/CHANGELOG.md index af00b35..6e34cb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# Unreleased (0.6.0) + +**Build/packaging change — no public API is removed or deprecated:** + +- The OpenCombine product dependency is now **platform-conditional** + (`.when(platforms: [.wasi, .linux, .android, .windows, .openbsd])`): on Apple platforms — + where the sources already build against native Combine via `#if canImport(Combine)` (#11) — + OpenCombine is no longer compiled or linked at all, giving Apple consumers a + zero-OpenCombine build. All non-Apple platforms keep OpenCombine as a full build-and-link + dependency, so WASI/Linux consumers are unaffected. The package-level + `.package(url: …/OpenCombine.git …)` declaration remains, so SwiftPM still *fetches* + OpenCombine (and its transitive swift-syntax) at resolution time on every platform; only + the build/linkage footprint drops. The OpenCombine upstream canary now exercises the wasm + lane, since a host build no longer compiles OpenCombine + ([#15](https://github.com/IGRSoft/OpenCombineJS/issues/15)) + # 0.5.0 (2026-06-11) **Additions (strictly non-breaking — the public API is additive only):** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 08ac511..e712281 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,9 +37,18 @@ Notes: ## Dependency version policy - Minimum **JavaScriptKit 0.54.1** (ships `JSPromise.value`, required by the async bridge, #13). +- **OpenCombine is platform-conditional** (#15): the product dependency carries + `condition: .when(platforms: [.wasi, .linux, .android, .windows, .openbsd])` — every + non-Apple platform the 6.3 `PackageDescription` exposes. On Apple platforms the sources use + native Combine (`#if canImport(Combine)`, #11) and OpenCombine is neither compiled nor + linked; it is still fetched at resolution time because the package-level declaration stays. + When editing the platform list, keep it in sync with "platforms where `canImport(Combine)` + is false" — dropping one silently breaks that platform's build. Full removal of the + declaration is deferred until the Publisher surface is retired. - Weekly canaries ([canary-upstreams.yml](.github/workflows/canary-upstreams.yml)) build against the latest JavaScriptKit release and OpenCombine main. They are allowed to fail and auto-file - `canary-failure` issues instead of breaking CI. + `canary-failure` issues instead of breaking CI. Note the OpenCombine canary must build the + **wasm lane** — a macOS host build no longer compiles OpenCombine at all. - The moment JavaScriptKit ships a native `TopLevelDecoder` conformance, our retroactive one becomes a duplicate-conformance build error: delete `Sources/OpenCombineJS/JSValueDecoder.swift` and tag a minor release (#9). The JSKit canary exists to catch this early. diff --git a/Package.swift b/Package.swift index 54c3e8d..023e8a7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,6 +1,16 @@ // swift-tools-version:6.3 import PackageDescription +/// Platforms WITHOUT native Combine (`#if canImport(Combine)` is false there, so the +/// sources fall back to `import OpenCombine` — see JSPromise.swift, #11/#15). +/// Must list EVERY non-Apple platform PackageDescription 6.3 exposes, not just .wasi: +/// dropping one (e.g. .linux) would silently unlink OpenCombine for its consumers and +/// break their builds. (.freebsd exists in the 6.3 swiftinterface but is gated +/// @available(_PackageDescription 999.0) and therefore not usable yet.) +/// On Apple platforms the condition keeps OpenCombine out of the build graph entirely: +/// it is still FETCHED at dependency-resolution time, but never compiled or linked. +let openCombinePlatforms: [Platform] = [.wasi, .linux, .android, .windows, .openbsd] + let package = Package( name: "OpenCombineJS", platforms: [ @@ -24,12 +34,22 @@ let package = Package( name: "OpenCombineJSExample", dependencies: [ "OpenCombineJS", + .product( + name: "OpenCombine", + package: "OpenCombine", + condition: .when(platforms: openCombinePlatforms) + ), ] ), .target( name: "OpenCombineJS", dependencies: [ - "JavaScriptKit", "OpenCombine", + "JavaScriptKit", + .product( + name: "OpenCombine", + package: "OpenCombine", + condition: .when(platforms: openCombinePlatforms) + ), ] ), .testTarget( @@ -37,7 +57,11 @@ let package = Package( dependencies: [ "OpenCombineJS", "JavaScriptKit", - "OpenCombine", + .product( + name: "OpenCombine", + package: "OpenCombine", + condition: .when(platforms: openCombinePlatforms) + ), // Installs the JavaScriptEventLoop global executor at startup so async tests can be // resumed by JS timers/promises. Only meaningful (and only linked) on WASI. .product( diff --git a/README.md b/README.md index f7ec592..7d17b1a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ APIs. Currently it provides: - A `publisher` property on [`JSPromise`](https://swiftwasm.github.io/JavaScriptKit/JSPromise/), which converts your [JavaScript `Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) instances to Combine publishers. +On Apple platforms the library builds against the **native Combine framework and links no +OpenCombine at all** — the OpenCombine dependency is declared platform-conditionally, so Apple +consumers get a zero-OpenCombine build (it is still fetched at dependency-resolution time). On +WASI, Linux, and other platforms without Combine it builds against +[OpenCombine](https://github.com/OpenCombine/OpenCombine). + ## Example Here's an example of a timer that fetches a UUID from a remote server every second, parses it diff --git a/Sources/OpenCombineJS/Documentation.docc/OpenCombineJS.md b/Sources/OpenCombineJS/Documentation.docc/OpenCombineJS.md index 90a174c..5321272 100644 --- a/Sources/OpenCombineJS/Documentation.docc/OpenCombineJS.md +++ b/Sources/OpenCombineJS/Documentation.docc/OpenCombineJS.md @@ -28,6 +28,12 @@ Apple platforms it builds against the **native Combine framework**, so `JSPromis symbol-for-symbol — only the module identity differs, so the same consumer code compiles on both backends. +Since 0.6.0 the OpenCombine package dependency is also **platform-conditional**: on Apple +platforms OpenCombine is neither compiled nor linked — Apple consumers get a +zero-OpenCombine build (the package is still fetched when SwiftPM resolves the dependency +graph). On WASI, Linux, and the other non-Apple platforms OpenCombine remains a full +build-and-link dependency, exactly as before. + ### Async/await usage `await`-based APIs require the `JavaScriptEventLoop` global executor on WASI