diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index 1782d3229..f0fbc7e0c 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -17,6 +17,14 @@ name: SPM release (xcframework) # * Uses the default GITHUB_TOKEN (needs contents: write). No extra secrets. on: + pull_request: + branches: [main] + paths: + - Package.swift + - wrappers/swift/** + - tools/apple/** + - tests/apple/** + - .github/workflows/spm-release.yml release: types: [published] workflow_dispatch: @@ -32,16 +40,30 @@ permissions: concurrency: # Serialize all SPM publications because different vX.Y.Z.W releases can map # to the same X.Y.Z tag and release asset. - group: spm-release - cancel-in-progress: false + group: spm-${{ github.event_name == 'pull_request' && github.event.pull_request.number || 'release' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + package-consumption: + name: Validate released SwiftPM binary + if: ${{ github.event_name == 'pull_request' }} + runs-on: macos-15 + permissions: + contents: read + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - name: Test hosted and local manifest selection + run: python3 tests/apple/test-spm-package.py + - name: Build Swift wrapper against the official release + run: swift build + 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) }} + (github.event_name == 'release' && + 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 @@ -115,6 +137,8 @@ jobs: - name: Validate local SwiftPM package consumption if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }} + env: + MATSDK_SPM_USE_LOCAL_XCFRAMEWORK: "1" run: | set -euo pipefail swift package dump-package > package.json @@ -171,10 +195,10 @@ jobs: 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" + out, count = re.subn( + r'\.binaryTarget\(\s*name:\s*"MATTelemetry",\s*url:\s*"[^"]*",\s*checksum:\s*"[^"]*"\s*\)', + repl, src) + assert count == 1, "Expected exactly one hosted MATTelemetry binary target" open(path, "w").write(out) PY diff --git a/Package.swift b/Package.swift index a7a139c56..dc4a48336 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ // Swift Package Manager manifest for the 1DS C++ SDK (Microsoft Applications // Telemetry) on Apple platforms. // -// PROTOTYPE — distribution model: +// 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 @@ -14,17 +14,20 @@ // * 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: +// Uses the official release xcframework by default; no local C++ build is needed. +// +// Local development (opt-in): // 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 / +// 2. `MATSDK_SPM_USE_LOCAL_XCFRAMEWORK=1 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, validates, and uploads the -// xcframework, rewrites the local `.binaryTarget(... path:)` below to -// `url:`+`checksum:`, removes repository submodules from the tag-only commit, +// xcframework, updates the hosted binary target's URL and checksum below, +// removes repository submodules from the tag-only commit, // validates that commit through a clean consumer, and pushes the // 3-component SemVer tag that SPM can resolve. Because vX.Y.Z.W release tags // map to one X.Y.Z SPM tag, only one build per three-component version can @@ -35,12 +38,12 @@ import PackageDescription import Foundation let packageDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent() +let useLocalXCFramework = ProcessInfo.processInfo.environment["MATSDK_SPM_USE_LOCAL_XCFRAMEWORK"] == "1" func readAvailability() -> [String: Bool] { - let candidates = [ - "build/apple/MATTelemetryAvailability.json", - "tools/apple/MATTelemetryAvailability.json", - ] + let candidates = useLocalXCFramework + ? ["build/apple/MATTelemetryAvailability.json", "tools/apple/MATTelemetryAvailability.json"] + : ["tools/apple/MATTelemetryAvailability.json"] for relativePath in candidates { let url = packageDirectory.appendingPathComponent(relativePath).standardizedFileURL @@ -97,16 +100,12 @@ let package = Package( // 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: ""), - .binaryTarget( + useLocalXCFramework ? .binaryTarget( + name: "MATTelemetry", + path: "build/apple/MATTelemetry.xcframework") : .binaryTarget( name: "MATTelemetry", - path: "build/apple/MATTelemetry.xcframework"), + url: "https://github.com/microsoft/cpp_client_telemetry/releases/download/v3.10.281.1/MATTelemetry.xcframework.zip", + checksum: "799b956dde6c6dccdc46b762b14eca55f677608f81b12879031fbd9c91470812"), // Thin Swift API layer (source). Depends on the Obj-C module from the // xcframework. The conditional source exclusions above must stay in sync diff --git a/lib/include/public/Version.hpp b/lib/include/public/Version.hpp index 662e103f3..9e4c9a200 100644 --- a/lib/include/public/Version.hpp +++ b/lib/include/public/Version.hpp @@ -6,8 +6,8 @@ #define MAT_VERSION_HPP // WARNING: DO NOT MODIFY THIS FILE! // This file has been automatically generated, manual changes will be lost. -#define BUILD_VERSION_STR "3.10.281.1" -#define BUILD_VERSION 3,10,281,1 +#define BUILD_VERSION_STR "3.10.309.1" +#define BUILD_VERSION 3,10,309,1 #ifndef RESOURCE_COMPILER_INVOKED #include "ctmacros.hpp" @@ -18,7 +18,7 @@ namespace MAT_NS_BEGIN { uint64_t const Version = ((uint64_t)3 << 48) | ((uint64_t)10 << 32) | - ((uint64_t)281 << 16) | + ((uint64_t)309 << 16) | ((uint64_t)1); } MAT_NS_END diff --git a/tests/apple/test-spm-package.py b/tests/apple/test-spm-package.py new file mode 100644 index 000000000..9a717b46c --- /dev/null +++ b/tests/apple/test-spm-package.py @@ -0,0 +1,75 @@ +"""Exercise SwiftPM binary selection without building the C++ SDK.""" + +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +class SPMPackageTests(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name) + shutil.copy2(REPO_ROOT / "Package.swift", self.root) + metadata = self.root / "tools" / "apple" + metadata.mkdir(parents=True) + shutil.copy2( + REPO_ROOT / "tools" / "apple" / "MATTelemetryAvailability.json", metadata + ) + self.local = self.root / "build" / "apple" + self.local.mkdir(parents=True) + (self.local / "MATTelemetryAvailability.json").write_text( + json.dumps({ + "diagnosticDataViewer": True, + "privacyGuard": True, + "sanitizer": True, + }), + encoding="utf-8", + ) + + def manifest(self, use_local=False): + environment = dict(os.environ) + environment.pop("MATSDK_SPM_USE_LOCAL_XCFRAMEWORK", None) + if use_local: + environment["MATSDK_SPM_USE_LOCAL_XCFRAMEWORK"] = "1" + result = subprocess.run( + ["swift", "package", "--package-path", str(self.root), "dump-package"], + env=environment, + check=True, + capture_output=True, + text=True, + ) + return {target["name"]: target for target in json.loads(result.stdout)["targets"]} + + def test_default_uses_release_without_local_framework_or_local_modules(self): + targets = self.manifest() + binary = targets["MATTelemetry"] + self.assertRegex( + binary["url"], + r"^https://github\.com/microsoft/cpp_client_telemetry/releases/download/" + r"v\d+\.\d+\.\d+\.\d+/MATTelemetry\.xcframework\.zip$", + ) + self.assertRegex(binary["checksum"], r"^[0-9a-f]{64}$") + self.assertIsNone(binary.get("path")) + self.assertIn("DiagnosticDataViewer.swift", targets["OneDSSwift"]["exclude"]) + self.assertIn("PrivacyGuard.swift", targets["OneDSSwift"]["exclude"]) + self.assertIn("Sanitizer.swift", targets["OneDSSwift"]["exclude"]) + + def test_local_opt_in_uses_matching_framework_and_availability(self): + targets = self.manifest(use_local=True) + binary = targets["MATTelemetry"] + self.assertEqual(binary["path"], "build/apple/MATTelemetry.xcframework") + self.assertIsNone(binary.get("url")) + self.assertIsNone(binary.get("checksum")) + self.assertEqual(targets["OneDSSwift"]["exclude"], []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/apple/README.md b/tools/apple/README.md index 242e5a73e..eee2acd1f 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -73,15 +73,18 @@ tools/apple/build-xcframework.sh release # -> prints the SwiftPM checksum ``` -For local development, `Package.swift` points at -`build/apple/MATTelemetry.xcframework`. `swift build` validates macOS +By default, `Package.swift` downloads the official release xcframework with its +pinned checksum; no local C++ build is needed. To use +`build/apple/MATTelemetry.xcframework` for local development, run +`MATSDK_SPM_USE_LOCAL_XCFRAMEWORK=1 swift build`. This validates macOS consumption; use Xcode destinations for iOS Simulator, Mac Catalyst, and -visionOS Simulator. +visionOS Simulator with the same environment variable. Local availability +metadata is used only in this opt-in mode. ## Consumption -- **Local:** add this repository as a local package dependency after building - `build/apple/MATTelemetry.xcframework`. +- **Checkout:** run `swift build` or add this repository as a local package + dependency to use the official prebuilt binary without building the C++ SDK. - **Released:** add the repository URL in Xcode and pin the parallel 3-component SemVer tag published by the release workflow: @@ -101,7 +104,7 @@ dispatch. It: 2. Validates package consumption for macOS, iOS Simulator, Mac Catalyst, and visionOS Simulator. 3. Uploads the zip to the GitHub Release. -4. Rewrites `Package.swift` from local `path:` to hosted `url:` + `checksum:`. +4. Updates the hosted `url:` + `checksum:` in `Package.swift` for the new release. 5. Creates a self-contained tag commit without the repository's submodules. 6. Resolves and builds that tag from a clean external Swift package. 7. Pushes the 3-component SPM tag.