diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04a605c0..9a0d45ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,3 +100,31 @@ jobs: build/reports/** meshlink/build/reports/** **/test-results/** + + meshlink-swiftpm-macos: + runs-on: macos-latest + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + + - name: Set up Java + uses: actions/setup-java@v5.2.0 + with: + distribution: temurin + java-version: '21' + cache: gradle + + - name: Build SwiftPM packaging artifact + run: ./scripts/spm/package-meshlink-xcframework.sh + + - name: Upload SwiftPM packaging outputs + if: failure() + uses: actions/upload-artifact@v7.0.1 + with: + name: meshlink-swiftpm-reports + if-no-files-found: ignore + path: | + meshlink/build/swiftpm/** + meshlink/build/XCFrameworks/** diff --git a/.github/workflows/swiftpm-release.yml b/.github/workflows/swiftpm-release.yml new file mode 100644 index 00000000..671c2f55 --- /dev/null +++ b/.github/workflows/swiftpm-release.yml @@ -0,0 +1,42 @@ +name: swiftpm-release + +on: + release: + types: + - published + +permissions: + contents: write + +concurrency: + group: swiftpm-release-${{ github.event.release.tag_name }} + cancel-in-progress: false + +jobs: + package-swiftpm-release: + runs-on: macos-latest + timeout-minutes: 60 + + steps: + - name: Checkout release tag + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Set up Java + uses: actions/setup-java@v5.2.0 + with: + distribution: temurin + java-version: '21' + cache: gradle + + - name: Build SwiftPM release artifacts + run: ./scripts/spm/package-meshlink-xcframework.sh + + - name: Upload SwiftPM release assets + uses: softprops/action-gh-release@v2.2.1 + with: + tag_name: ${{ github.event.release.tag_name }} + files: | + meshlink/build/swiftpm/MeshLink.xcframework.zip + meshlink/build/swiftpm/MeshLink.xcframework.checksum diff --git a/Package.release.swift b/Package.release.swift new file mode 100644 index 00000000..64f0b2a3 --- /dev/null +++ b/Package.release.swift @@ -0,0 +1,19 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "MeshLink", + platforms: [ + .iOS(.v14), + ], + products: [ + .library(name: "MeshLink", targets: ["MeshLink"]), + ], + targets: [ + .binaryTarget( + name: "MeshLink", + url: "https://example.invalid/releases/MeshLink.xcframework.zip", + checksum: "REPLACE_WITH_SWIFT_PACKAGE_CHECKSUM" + ), + ] +) diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..9e0e0398 --- /dev/null +++ b/Package.swift @@ -0,0 +1,18 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "MeshLink", + platforms: [ + .iOS(.v14), + ], + products: [ + .library(name: "MeshLink", targets: ["MeshLink"]), + ], + targets: [ + .binaryTarget( + name: "MeshLink", + path: "meshlink/build/swiftpm/MeshLink.xcframework" + ), + ] +) diff --git a/README.md b/README.md index 6e7ff482..b3644c21 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ MeshLink has not cut a public stable release yet. Today, the repository is still source-distributed from a local checkout. The intended first public release shape is a published `:meshlink` artifact for -Gradle consumers while iOS continues to use the generated Apple framework path. -Swift Package Manager and CocoaPods are not part of that first-release target. +Gradle consumers while iOS can use the generated Apple framework path or the +root-level SwiftPM package. For the current distribution shape and remaining release blockers, use the [release status reference](docs/reference/release-status.md). diff --git a/RELEASING.md b/RELEASING.md index 31997175..647e63b7 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -14,12 +14,12 @@ The intended first public release shape is: - a published `:meshlink` artifact for Gradle consumers - the existing generated Apple framework path for iOS Xcode hosts -- no Swift Package Manager package yet -- no CocoaPods pod yet +- root-level Swift Package Manager support for both checkout and release binary targets -Artifact publication is not yet wired into this repository. That means the -repository can now automate release PRs, tags, GitHub releases, and next- -snapshot bumps, but publishing the SDK artifact remains a separate follow-up. +Artifact publication is partially wired into this repository. The release +workflow now packages the SwiftPM XCFramework zip and checksum on published +GitHub releases, while the Maven/Gradle SDK publication and signing steps remain +separate follow-up work. ## Release automation diff --git a/build.gradle.kts b/build.gradle.kts index aae16cbf..6fc14eeb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,12 @@ import org.gradle.api.Project import org.gradle.api.tasks.Delete import org.gradle.api.tasks.wrapper.Wrapper +buildscript { + dependencies { + classpath("org.slf4j:slf4j-nop:1.7.30") + } +} + private fun isAgpDependencyResolutionWarningListener(listener: Any): Boolean { if (listener::class.java.name.startsWith("com.android.build.gradle.internal.DependencyResolutionChecksKt$")) { return true diff --git a/docs/how-to/add-meshlink-to-your-app.md b/docs/how-to/add-meshlink-to-your-app.md index 40b09b40..f6c62dd3 100644 --- a/docs/how-to/add-meshlink-to-your-app.md +++ b/docs/how-to/add-meshlink-to-your-app.md @@ -19,6 +19,7 @@ Use it when you need to: | already shares the MeshLink Gradle build | depend on `project(":meshlink")` directly | | lives in a separate Gradle repo | use a composite build with dependency substitution | | is a native iOS Xcode app | call `:meshlink:embedAndSignAppleFrameworkForXcode` from an Xcode pre-build script | +| wants SwiftPM integration | use the root-level `Package.swift` for local checkout development or `Package.release.swift` for the release binary target | ## Installation flow at a glance @@ -53,13 +54,17 @@ blockers. This repository does **not** yet publish: - a Maven Central artifact -- a Swift Package Manager package -- a CocoaPods pod + +Swift Package Manager support is available from the repository root via: + +- `Package.swift` for local checkout integration +- `Package.release.swift` as the release binary-target template The supported installation paths today are: - Gradle source integration for Android or shared Kotlin code - a Gradle-built Apple framework linked by Xcode for iOS +- a SwiftPM package at the repository root for local checkout or binary-target use ## 2. Confirm the host requirements first diff --git a/docs/how-to/use-meshlink-from-swift.md b/docs/how-to/use-meshlink-from-swift.md index f5471c99..74c90786 100644 --- a/docs/how-to/use-meshlink-from-swift.md +++ b/docs/how-to/use-meshlink-from-swift.md @@ -1,12 +1,12 @@ # How to use MeshLink from Swift This guide shows you how to call MeshLink from Swift through the generated -`MeshLink` Apple framework. +`MeshLink` Apple framework or the root-level SwiftPM package. Use it when: - your app code is Swift, not shared Kotlin UI or business logic -- your Xcode target already links the generated `MeshLink` framework +- your Xcode target already links the generated `MeshLink` framework or the root-level SwiftPM package - you want the Swift-facing startup, lifecycle, flow-collection, and send patterns diff --git a/docs/reference/release-status.md b/docs/reference/release-status.md index ae757730..06a4ee25 100644 --- a/docs/reference/release-status.md +++ b/docs/reference/release-status.md @@ -52,9 +52,10 @@ Today, supported integration paths are: - direct Gradle source integration for Android or shared Kotlin code - composite-build substitution for a separate Gradle host app - a Gradle-built Apple framework linked by Xcode for iOS +- a root-level SwiftPM local checkout package (`Package.swift` at the repository root) +- a root-level SwiftPM binary-target release template (`Package.release.swift` at the repository root) -There is not yet a published Maven Central artifact, Swift Package Manager -package, or CocoaPods pod. +There is not yet a published Maven Central artifact. ## Intended first public release shape @@ -62,8 +63,7 @@ The planned first public release shape is: - publish `:meshlink` for Gradle consumers - keep the generated Apple framework path for iOS Xcode hosts -- continue without Swift Package Manager for the first release -- continue without CocoaPods for the first release +- ship Swift Package Manager support alongside the iOS framework path ## Release-readiness work already in place @@ -71,6 +71,7 @@ The repository now has: - a maintained CI workflow for the core MeshLink SDK verification bundle - a `release-please` workflow and manifest configuration for cumulative release PRs, tags, and next-snapshot bumps +- a SwiftPM release workflow that packages the XCFramework zip and checksum for published GitHub releases - a changelog for release-facing notes - a security policy for private vulnerability reporting - a maintainer release runbook diff --git a/docs/reference/repository-layout.md b/docs/reference/repository-layout.md index 161ca69a..db2a4cbf 100644 --- a/docs/reference/repository-layout.md +++ b/docs/reference/repository-layout.md @@ -78,6 +78,8 @@ flowchart LR | `:meshlink-reference` | Kotlin Multiplatform library | `:meshlink` | Shared reference-app shell, screens, session model, export logic, and automation support | | `:meshlink-reference:android` | Android application module | `:meshlink-reference` | Android host application for the shared reference-app shell | | `meshlink-reference/ios` | Native Xcode project | exported `MeshLinkReference` framework from `:meshlink-reference` | iOS host application, signing, simulator, device, and UI-test workflows | +| `Package.swift` | Swift Package manifest | `meshlink/build/swiftpm/MeshLink.xcframework` | SwiftPM local checkout package for Xcode and `swift package` consumers | +| `Package.release.swift` | Swift Package manifest template | published XCFramework ZIP artifact | SwiftPM release binary-target packaging | | `:meshlink-proof:android` | Android application module | `:meshlink` | Android proof and benchmark surface for physical validation | | `meshlink-proof/ios` | Native Xcode project | exported `MeshLink` framework from `:meshlink` | iOS proof and benchmark surface for physical validation | | `:benchmarks` | JVM benchmark module | `:meshlink` | Retained JVM performance baselines and benchmark tasks | diff --git a/meshlink/build.gradle.kts b/meshlink/build.gradle.kts index 0bcc52b1..65666df6 100644 --- a/meshlink/build.gradle.kts +++ b/meshlink/build.gradle.kts @@ -2,6 +2,7 @@ import io.gitlab.arturbosch.detekt.Detekt import org.gradle.api.tasks.testing.Test import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework plugins { alias(libs.plugins.kotlin.multiplatform) @@ -16,6 +17,7 @@ plugins { kotlin { explicitApi() + val meshLinkXCFramework = XCFramework("MeshLink") jvm { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) } } android { namespace = "ch.trancee.meshlink" @@ -30,6 +32,7 @@ kotlin { baseName = "MeshLink" isStatic = true binaryOption("bundleId", "ch.trancee.meshlink") + meshLinkXCFramework.add(this) } } applyDefaultHierarchyTemplate() diff --git a/meshlink/src/commonTest/kotlin/ch/trancee/meshlink/integration/MeshRoutingIntegrationTest.kt b/meshlink/src/commonTest/kotlin/ch/trancee/meshlink/integration/MeshRoutingIntegrationTest.kt index bb78d698..571c72f2 100644 --- a/meshlink/src/commonTest/kotlin/ch/trancee/meshlink/integration/MeshRoutingIntegrationTest.kt +++ b/meshlink/src/commonTest/kotlin/ch/trancee/meshlink/integration/MeshRoutingIntegrationTest.kt @@ -471,6 +471,12 @@ class MeshRoutingIntegrationTest { prewarmRoute(sender = sender, recipient = recipient) harness.unlinkPeers(sender, recipient) testDelay(100) + awaitDiagnosticForPeer( + diagnostics = sender.diagnosticSink::events, + code = DiagnosticCode.ROUTE_EXPIRED, + peerIdValue = recipient.peerId.value, + routeAvailable = false, + ) // Act val sendResult = sender.meshLink.send(recipient.peerId, payload) diff --git a/scripts/spm/package-meshlink-xcframework.sh b/scripts/spm/package-meshlink-xcframework.sh new file mode 100755 index 00000000..53134b2c --- /dev/null +++ b/scripts/spm/package-meshlink-xcframework.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +artifact_dir="$repo_root/meshlink/build/swiftpm" +xcframework_name="MeshLink.xcframework" +xcframework_source="$repo_root/meshlink/build/XCFrameworks/release/$xcframework_name" +xcframework_staging="$artifact_dir/$xcframework_name" +zip_path="$artifact_dir/$xcframework_name.zip" +checksum_path="$artifact_dir/$xcframework_name.checksum" + +cd "$repo_root" +./gradlew :meshlink:assembleMeshLinkReleaseXCFramework + +rm -rf "$artifact_dir" +mkdir -p "$artifact_dir" +cp -R "$xcframework_source" "$xcframework_staging" +rm -f "$zip_path" "$checksum_path" + +cd "$artifact_dir" +zip -qry "$xcframework_name.zip" "$xcframework_name" +checksum="$(swift package compute-checksum "$xcframework_name.zip")" +printf '%s\n' "$checksum" > "$checksum_path" + +printf 'XCFramework staged at: %s\n' "$xcframework_staging" +printf 'Archive written to: %s\n' "$zip_path" +printf 'SwiftPM checksum: %s\n' "$checksum"