-
Notifications
You must be signed in to change notification settings - Fork 60
Swift Package Manager distribution via prebuilt xcframework (iOS, macOS, Mac Catalyst, visionOS) #1486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
bmehta001
wants to merge
27
commits into
microsoft:main
Choose a base branch
from
bmehta001:bhamehta/spm-xcframework-prototype
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Swift Package Manager distribution via prebuilt xcframework (iOS, macOS, Mac Catalyst, visionOS) #1486
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
0f3e065
Prototype: SPM distribution via prebuilt xcframework (Apple)
bmehta001 5e3687e
Prototype: SPM release workflow + parallel 3-component SemVer tag
bmehta001 ba7ba4c
Fix local SPM xcframework consumption
bmehta001 308e031
Copy only public ObjC headers into xcframework
bmehta001 dd97234
Align SPM package with xcframework contents
bmehta001 accb600
Generate SPM availability from xcframework build
bmehta001 4e03dff
Address SPM prototype review refinements
bmehta001 0edd5f1
Align Apple SPM docs and module availability
bmehta001 6d27fba
Add macOS slice to SPM xcframework
bmehta001 d242830
Add Mac Catalyst slice to SPM xcframework
bmehta001 1d89f6d
Add visionOS slices to SPM xcframework
bmehta001 0246847
Address SPM release review comments
bmehta001 9634894
Address follow-up SPM review comments
bmehta001 5923e5c
Validate SPM Apple platforms in release workflow
bmehta001 ccceaa5
Skip package creation for xcframework slices
bmehta001 5c6f574
Clean up Apple SPM README
bmehta001 79f55fa
Remove status label from Apple SPM README
bmehta001 27049fe
Assert SPM platforms in release workflow
bmehta001 55c4ce5
Clean up xcframework build script comments
bmehta001 9a18330
Use fresh build cache for each xcframework slice
bmehta001 279ffd3
Use portable shell comparison in iOS build
bmehta001 7bc7415
Quote ${IOS_PLAT}/${IOS_ARCH} in Apple if() conditions
bmehta001 9d52266
Rename public Clang module ObjCModule -> MATTelemetryObjC
bmehta001 2b5bdd5
docs(apple): note xcframework expects system sqlite3/zlib
bmehta001 f2c5fc9
Use PIIKind alias in SemanticContext.setUserID signature
bmehta001 06caea2
Fix SemanticContext.setUserID dropping the caller's piiKind
bmehta001 a57c2a1
Address Swift package review refinements
bmehta001 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| name: SPM release (xcframework) | ||
|
|
||
| # On a published SDK release, build MATTelemetry.xcframework, upload it to the | ||
| # GitHub Release, and publish a 3-component SemVer tag for Swift Package Manager | ||
| # whose Package.swift binaryTarget points at the uploaded artifact + checksum. | ||
| # | ||
| # Why a separate tag: the SDK's own release tags are 4-component (vX.Y.Z.W), | ||
| # which is NOT valid SemVer, so Swift Package Manager ignores them. This derives | ||
| # a 3-component tag (X.Y.Z) from the same release that SPM can resolve. | ||
| # | ||
| # Prerequisites: | ||
| # * The root Package.swift (the SPM manifest) must exist at the release tag | ||
| # (i.e. this prototype merged to main before the release is cut). | ||
| # * Uses the default GITHUB_TOKEN (needs contents: write). No extra secrets. | ||
|
|
||
| on: | ||
| release: | ||
| types: [published] | ||
| workflow_dispatch: | ||
| inputs: | ||
| tag: | ||
| description: "4-component release tag to publish for SPM (e.g. v3.10.161.1)" | ||
| required: true | ||
| type: string | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| concurrency: | ||
| group: spm-release-${{ github.event.release.tag_name || inputs.tag }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| spm: | ||
| name: Publish SPM xcframework + tag | ||
| # Skip drafts/pre-releases; always allow manual dispatch. | ||
| if: >- | ||
| ${{ github.event_name == 'workflow_dispatch' || | ||
| (github.event.release.draft == false && github.event.release.prerelease == false) }} | ||
| runs-on: macos-15 # provides Xcode with Apple platform SDKs (xcodebuild, swift) | ||
| env: | ||
| ARTIFACT: MATTelemetry.xcframework.zip | ||
| steps: | ||
| - name: Resolve tag and derive SPM version | ||
| id: ver | ||
| env: | ||
| # Pass untrusted tag values through the environment rather than | ||
| # interpolating ${{ ... }} into the script body. | ||
| RELEASE_TAG: ${{ github.event.release.tag_name }} | ||
| INPUT_TAG: ${{ inputs.tag }} | ||
| run: | | ||
| set -euo pipefail | ||
| TAG="${RELEASE_TAG:-$INPUT_TAG}" | ||
| if [ -z "$TAG" ]; then echo "::error::No release tag could be resolved."; exit 1; fi | ||
| # Only act on 4-component version tags vX.Y.Z.W. | ||
| if ! printf '%s' "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then | ||
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | ||
| echo "::error::Tag '$TAG' is not a 4-component version tag (expected vX.Y.Z.W)." | ||
| exit 1 | ||
| fi | ||
| echo "::notice::Tag '$TAG' is not a 4-component version tag; nothing to publish." | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| VERSION="${TAG#v}" # X.Y.Z.W | ||
| SPM_VERSION="${VERSION%.*}" # X.Y.Z (drop the trailing build component) | ||
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | ||
| echo "spm_version=$SPM_VERSION" >> "$GITHUB_OUTPUT" | ||
| echo "Release $TAG -> SPM tag $SPM_VERSION" | ||
|
|
||
| - name: Checkout the release tag | ||
| if: ${{ steps.ver.outputs.skip != 'true' }} | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ steps.ver.outputs.tag }} | ||
| fetch-depth: 0 | ||
| # The private lib/modules submodule is intentionally NOT fetched; the | ||
| # xcframework ships the core SDK + Obj-C wrappers, matching the vcpkg | ||
| # port (the optional modules are excluded there too). | ||
| submodules: false | ||
|
|
||
| - name: Build MATTelemetry.xcframework | ||
| if: ${{ steps.ver.outputs.skip != 'true' }} | ||
| run: | | ||
| set -euo pipefail | ||
| chmod +x tools/apple/build-xcframework.sh | ||
| tools/apple/build-xcframework.sh release | ||
| test -f "build/apple/$ARTIFACT" | ||
|
|
||
| - name: Validate SwiftPM package consumption | ||
| if: ${{ steps.ver.outputs.skip != 'true' }} | ||
| run: | | ||
| set -euo pipefail | ||
| swift package dump-package > package.json | ||
| python3 - <<'PY' | ||
| import json | ||
| expected = { | ||
| "ios": "12.0", | ||
| "maccatalyst": "14.0", | ||
| "macos": "10.15", | ||
| "visionos": "1.0", | ||
| } | ||
| with open("package.json", encoding="utf-8") as f: | ||
| platforms = { | ||
| item["platformName"]: item["version"] | ||
| for item in json.load(f)["platforms"] | ||
| } | ||
| if platforms != expected: | ||
| raise SystemExit(f"Unexpected Package.swift platforms: {platforms}") | ||
| PY | ||
| swift build | ||
| xcodebuild -scheme OneDSSwift -destination 'generic/platform=iOS Simulator' build | ||
| xcodebuild -scheme OneDSSwift -destination 'platform=macOS,variant=Mac Catalyst' build | ||
| xcodebuild -scheme OneDSSwift -destination 'generic/platform=visionOS Simulator' build | ||
|
|
||
| - name: Compute SPM checksum | ||
| id: sum | ||
| if: ${{ steps.ver.outputs.skip != 'true' }} | ||
| run: | | ||
| set -euo pipefail | ||
| checksum="$(swift package compute-checksum "build/apple/$ARTIFACT")" | ||
| echo "checksum=$checksum" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Upload xcframework to the release | ||
| if: ${{ steps.ver.outputs.skip != 'true' }} | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: gh release upload "${{ steps.ver.outputs.tag }}" "build/apple/$ARTIFACT" --clobber | ||
|
|
||
| - name: Point Package.swift at the released artifact | ||
| if: ${{ steps.ver.outputs.skip != 'true' }} | ||
| env: | ||
| ASSET_URL: https://github.com/${{ github.repository }}/releases/download/${{ steps.ver.outputs.tag }}/MATTelemetry.xcframework.zip | ||
| CHECKSUM: ${{ steps.sum.outputs.checksum }} | ||
| run: | | ||
| set -euo pipefail | ||
| python3 - "$ASSET_URL" "$CHECKSUM" <<'PY' | ||
| import re, sys | ||
| url, checksum = sys.argv[1], sys.argv[2] | ||
| path = "Package.swift" | ||
| src = open(path).read() | ||
| repl = ( | ||
| '.binaryTarget(\n' | ||
| ' name: "MATTelemetry",\n' | ||
| f' url: "{url}",\n' | ||
| f' checksum: "{checksum}")' | ||
| ) | ||
| out = re.sub( | ||
| r'\.binaryTarget\(\s*name:\s*"MATTelemetry",\s*path:\s*"[^"]*"\s*\)', | ||
| repl, src, count=1) | ||
| assert out != src, "binaryTarget(path:) block not found in Package.swift" | ||
| open(path, "w").write(out) | ||
| PY | ||
|
|
||
| - name: Commit manifest and push the 3-component SPM tag | ||
| if: ${{ steps.ver.outputs.skip != 'true' }} | ||
| run: | | ||
| set -euo pipefail | ||
| if git ls-remote --exit-code --tags origin "refs/tags/${{ steps.ver.outputs.spm_version }}" >/dev/null; then | ||
| echo "::notice::SPM tag ${{ steps.ver.outputs.spm_version }} already exists; skipping tag publish." | ||
| exit 0 | ||
| fi | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git add Package.swift tools/apple/MATTelemetryAvailability.json | ||
| git commit -m "[spm] ${{ steps.ver.outputs.spm_version }}: pin xcframework url + checksum" | ||
| # The SPM tag points at this commit (release source + resolved | ||
| # binaryTarget). It is published as a tag only, not merged to a branch. | ||
| git tag -a "${{ steps.ver.outputs.spm_version }}" \ | ||
| -m "Swift Package Manager release ${{ steps.ver.outputs.spm_version }} (from ${{ steps.ver.outputs.tag }})" | ||
| git push origin "refs/tags/${{ steps.ver.outputs.spm_version }}" | ||
| echo "Published SPM tag ${{ steps.ver.outputs.spm_version }}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // swift-tools-version: 5.9 | ||
| // | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| // Swift Package Manager manifest for the 1DS C++ SDK (Microsoft Applications | ||
| // Telemetry) on Apple platforms. | ||
| // | ||
| // PROTOTYPE — distribution model: | ||
| // * The compiled C++ core + Obj-C wrappers ship as a prebuilt binary | ||
| // xcframework (built by tools/apple/build-xcframework.sh). This avoids | ||
| // compiling the CMake/Bond/sqlite/zlib C++ tree through SPM, which is not | ||
| // practical. | ||
| // * The thin Swift wrapper (wrappers/swift/Sources/OneDSSwift) is compiled | ||
| // from source on top of the Obj-C module vended by the xcframework. | ||
| // | ||
| // Local development: | ||
| // 1. Run `tools/apple/build-xcframework.sh release` on macOS with Xcode. | ||
| // It produces ./build/apple/MATTelemetry.xcframework. | ||
| // 2. `swift build` validates macOS consumption; for iOS / Mac Catalyst / | ||
| // visionOS, add this package as a local dependency or build the package | ||
| // with the desired Xcode destination. | ||
| // | ||
| // Release distribution (so consumers can add the repo by URL in Xcode): | ||
| // .github/workflows/spm-release.yml builds and uploads the xcframework, | ||
| // computes the checksum, rewrites the local `.binaryTarget(... path:)` below | ||
| // to `url:`+`checksum:`, and pushes the 3-component SemVer tag that SPM can | ||
| // resolve. | ||
|
|
||
| import PackageDescription | ||
| import Foundation | ||
|
|
||
| let packageDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent() | ||
|
|
||
| func readAvailability() -> [String: Bool] { | ||
| let candidates = [ | ||
| "build/apple/MATTelemetryAvailability.json", | ||
| "tools/apple/MATTelemetryAvailability.json", | ||
| ] | ||
|
|
||
| for relativePath in candidates { | ||
| let url = packageDirectory.appendingPathComponent(relativePath).standardizedFileURL | ||
| guard let data = try? Data(contentsOf: url), | ||
| let object = try? JSONSerialization.jsonObject(with: data) as? [String: Bool] else { | ||
| continue | ||
| } | ||
| return object | ||
| } | ||
|
|
||
| return [:] | ||
| } | ||
|
|
||
| let availability = readAvailability() | ||
| let hasDiagnosticDataViewer = availability["diagnosticDataViewer"] ?? false | ||
| let hasPrivacyGuard = availability["privacyGuard"] ?? false | ||
| let hasSanitizer = availability["sanitizer"] ?? false | ||
|
|
||
| var excludedSources: [String] = [] | ||
| var swiftSettings: [SwiftSetting] = [] | ||
|
|
||
| if !hasDiagnosticDataViewer { | ||
| excludedSources.append("DiagnosticDataViewer.swift") | ||
| } | ||
|
|
||
| if hasPrivacyGuard { | ||
| swiftSettings.append(.define("MATSDK_PRIVACYGUARD_AVAILABLE")) | ||
| } else { | ||
| excludedSources.append(contentsOf: [ | ||
| "PrivacyGuard.swift", | ||
| "PrivacyGuardInitConfig.swift", | ||
| ]) | ||
| } | ||
|
|
||
| if !hasSanitizer { | ||
| excludedSources.append(contentsOf: [ | ||
| "Sanitizer.swift", | ||
| "SanitizerInitConfig.swift", | ||
| ]) | ||
| } | ||
|
|
||
| let package = Package( | ||
| name: "OneDSSwift", | ||
| platforms: [ | ||
| .iOS(.v12), | ||
| .macCatalyst(.v14), | ||
| .macOS(.v10_15), | ||
| .visionOS(.v1), | ||
| ], | ||
|
bmehta001 marked this conversation as resolved.
bmehta001 marked this conversation as resolved.
|
||
| products: [ | ||
| .library(name: "OneDSSwift", targets: ["OneDSSwift"]), | ||
| ], | ||
| targets: [ | ||
| // Prebuilt C++ core + Obj-C wrappers. The xcframework's bundled | ||
| // module map vends the Clang module `MATTelemetryObjC` (see | ||
| // tools/apple/module.modulemap), which the Swift layer imports. | ||
| // | ||
| // For a tagged release, swap the local path for the hosted artifact: | ||
| // | ||
| // .binaryTarget( | ||
| // name: "MATTelemetry", | ||
| // url: "https://github.com/microsoft/cpp_client_telemetry/releases/download/v3.10.161.1/MATTelemetry.xcframework.zip", | ||
| // checksum: "<output of swift package compute-checksum>"), | ||
| .binaryTarget( | ||
| name: "MATTelemetry", | ||
| path: "build/apple/MATTelemetry.xcframework"), | ||
|
|
||
| // Thin Swift API layer (source). Depends on the Obj-C module from the | ||
| // xcframework. The conditional source exclusions above must stay in sync | ||
| // with the headers baked into the xcframework. | ||
| .target( | ||
| name: "OneDSSwift", | ||
| dependencies: ["MATTelemetry"], | ||
| path: "wrappers/swift/Sources/OneDSSwift", | ||
| exclude: excludedSources, | ||
| swiftSettings: swiftSettings, | ||
| linkerSettings: [ | ||
| .linkedLibrary("c++"), | ||
| .linkedLibrary("sqlite3"), | ||
| .linkedLibrary("z"), | ||
| .linkedFramework("CFNetwork", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])), | ||
| .linkedFramework("CoreFoundation", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])), | ||
| .linkedFramework("Foundation", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])), | ||
| .linkedFramework("Network", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])), | ||
| .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])), | ||
| .linkedFramework("IOKit", .when(platforms: [.macOS])), | ||
| .linkedFramework("UIKit", .when(platforms: [.iOS, .macCatalyst, .visionOS])), | ||
| ]), | ||
| ] | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.