Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0f3e065
Prototype: SPM distribution via prebuilt xcframework (Apple)
bmehta001 Jun 18, 2026
5e3687e
Prototype: SPM release workflow + parallel 3-component SemVer tag
bmehta001 Jun 18, 2026
ba7ba4c
Fix local SPM xcframework consumption
bmehta001 Jun 18, 2026
308e031
Copy only public ObjC headers into xcframework
bmehta001 Jun 18, 2026
dd97234
Align SPM package with xcframework contents
bmehta001 Jun 18, 2026
accb600
Generate SPM availability from xcframework build
bmehta001 Jun 18, 2026
4e03dff
Address SPM prototype review refinements
bmehta001 Jun 18, 2026
0edd5f1
Align Apple SPM docs and module availability
bmehta001 Jun 18, 2026
6d27fba
Add macOS slice to SPM xcframework
bmehta001 Jun 19, 2026
d242830
Add Mac Catalyst slice to SPM xcframework
bmehta001 Jun 19, 2026
1d89f6d
Add visionOS slices to SPM xcframework
bmehta001 Jun 19, 2026
0246847
Address SPM release review comments
bmehta001 Jun 19, 2026
9634894
Address follow-up SPM review comments
bmehta001 Jun 19, 2026
5923e5c
Validate SPM Apple platforms in release workflow
bmehta001 Jun 19, 2026
ccceaa5
Skip package creation for xcframework slices
bmehta001 Jun 19, 2026
5c6f574
Clean up Apple SPM README
bmehta001 Jun 19, 2026
79f55fa
Remove status label from Apple SPM README
bmehta001 Jun 19, 2026
27049fe
Assert SPM platforms in release workflow
bmehta001 Jun 19, 2026
55c4ce5
Clean up xcframework build script comments
bmehta001 Jun 19, 2026
9a18330
Use fresh build cache for each xcframework slice
bmehta001 Jun 19, 2026
279ffd3
Use portable shell comparison in iOS build
bmehta001 Jun 19, 2026
7bc7415
Quote ${IOS_PLAT}/${IOS_ARCH} in Apple if() conditions
bmehta001 Jun 19, 2026
9d52266
Rename public Clang module ObjCModule -> MATTelemetryObjC
bmehta001 Jun 19, 2026
2b5bdd5
docs(apple): note xcframework expects system sqlite3/zlib
bmehta001 Jun 19, 2026
f2c5fc9
Use PIIKind alias in SemanticContext.setUserID signature
bmehta001 Jun 19, 2026
06caea2
Fix SemanticContext.setUserID dropping the caller's piiKind
bmehta001 Jun 19, 2026
a57c2a1
Address Swift package review refinements
bmehta001 Jun 19, 2026
5d73b5d
Merge branch 'main' into bhamehta/spm-xcframework-prototype
bmehta001 Jun 30, 2026
3bc7555
Harden the SPM release pipeline: propagate build failures, don't clob…
bmehta001 Jul 10, 2026
2f2c7e1
SPM release: fail closed when the "already published" check is indete…
bmehta001 Jul 10, 2026
8dc6659
Report visionOS sysinfo distinctly in SPM builds
bmehta001 Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions .github/workflows/spm-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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-14 # provides Xcode (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: Compute SPM checksum
id: sum
if: ${{ steps.ver.outputs.skip != 'true' }}
run: |
set -euo pipefail
echo "checksum=$(swift package compute-checksum "build/apple/$ARTIFACT")" >> "$GITHUB_OUTPUT"

Comment thread
bmehta001 marked this conversation as resolved.
- 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
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 }}"
Comment thread
bmehta001 marked this conversation as resolved.
echo "Published SPM tag ${{ steps.ver.outputs.spm_version }}"
127 changes: 127 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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` (or add this package as a local dependency).
//
// Release distribution (so consumers can add the repo by URL in Xcode):
// 1. Build the xcframework, zip it, and attach it to the GitHub Release.
// 2. Run `swift package compute-checksum MATTelemetry.xcframework.zip`.
// 3. Replace the `.binaryTarget(... path:)` below with the `url:`+`checksum:`
// form shown in the comment. The vcpkg-release-bump workflow pattern can be
// extended to automate steps 1-3 on each release tag.
Comment thread
bmehta001 marked this conversation as resolved.
Outdated

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: [
"CommonDataContext.swift",
"PrivacyGuard.swift",
"PrivacyGuardInitConfig.swift",
])
Comment thread
bmehta001 marked this conversation as resolved.
}

if !hasSanitizer {
excludedSources.append(contentsOf: [
"Sanitizer.swift",
"SanitizerInitConfig.swift",
])
}

let package = Package(
name: "OneDSSwift",
platforms: [
.iOS(.v12),
],
Comment thread
bmehta001 marked this conversation as resolved.
Comment thread
bmehta001 marked this conversation as resolved.
Comment thread
bmehta001 marked this conversation as resolved.
Comment thread
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 `ObjCModule` (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. NOTE: the conditional source exclusions in
// wrappers/swift/Package.swift (PrivacyGuard / Sanitizer / DataViewer
// when those private modules aren't built) should be carried over here
// and kept in sync with the headers baked into the xcframework.
Comment thread
bmehta001 marked this conversation as resolved.
Outdated
.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])),
.linkedFramework("CoreFoundation", .when(platforms: [.iOS])),
.linkedFramework("Foundation", .when(platforms: [.iOS])),
.linkedFramework("Network", .when(platforms: [.iOS])),
.linkedFramework("SystemConfiguration", .when(platforms: [.iOS])),
.linkedFramework("UIKit", .when(platforms: [.iOS])),
]),
]
)
24 changes: 24 additions & 0 deletions tools/apple/MATTelemetry-umbrella.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Umbrella header for the Obj-C surface vended by MATTelemetry.xcframework.
//
// build-xcframework.sh flattens the ODW*.h headers into the framework's
// Headers/ directory, so these are imported by bare name (not by the
// ../../obj-c/ relative path the in-repo Swift bridging header uses).
//
// This template intentionally lists only always-available Obj-C headers.
// build-xcframework.sh appends imports for optional module headers only when
// those modules were actually built into the binary.

#import <Foundation/Foundation.h>

#import "ODWCommonDataContext.h"
#import "ODWEventProperties.h"
#import "ODWLogConfiguration.h"
#import "ODWLogger.h"
#import "ODWLogManager.h"
#import "ODWPrivacyGuardInitConfig.h"
#import "ODWSanitizerInitConfig.h"
#import "ODWSemanticContext.h"
5 changes: 5 additions & 0 deletions tools/apple/MATTelemetryAvailability.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"diagnosticDataViewer": false,
"privacyGuard": false,
"sanitizer": false
}
81 changes: 81 additions & 0 deletions tools/apple/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Swift Package Manager (xcframework) — prototype

**Status: prototype / not yet validated on macOS.** This is a first-pass scaffold
for distributing the 1DS C++ SDK to Apple app developers via **Swift Package
Manager (SPM)**, the successor to CocoaPods (the CocoaPods trunk goes read-only
on 2 Dec 2026, and there is no official in-repo podspec today).
Comment thread
bmehta001 marked this conversation as resolved.
Outdated

## Approach

SPM cannot practically compile this SDK's C++ tree from source (CMake build,
Bond codegen, vendored sqlite3/zlib, heavy platform conditionals). So:

| Layer | How it ships |
| --- | --- |
| C++ core + Obj-C wrappers (`ODW*`) | **Prebuilt binary** — `MATTelemetry.xcframework` (`.binaryTarget`) |
| Swift API (`OneDSSwift`) | **Source** — `wrappers/swift/Sources/OneDSSwift`, depends on the Obj-C module from the xcframework |

The Obj-C wrappers already compile into `libmat.a` on Apple
(`lib/CMakeLists.txt:217`), and the Swift sources already `import ObjCModule`,
so the xcframework just needs to vend a Clang module named `ObjCModule`
(`tools/apple/module.modulemap` + `MATTelemetry-umbrella.h`).

## Files

| File | Purpose |
| --- | --- |
| `Package.swift` (repo root) | Distributable SPM manifest: `binaryTarget` (xcframework) + `OneDSSwift` source target |
| `tools/apple/build-xcframework.sh` | Builds a static `libmat.a` per Apple slice via `build-ios.sh`, lipo's the simulator archs, and assembles the xcframework with `xcodebuild -create-xcframework` |
| `tools/apple/module.modulemap` | Defines the `ObjCModule` Clang module the Swift layer imports |
| `tools/apple/MATTelemetry-umbrella.h` | Umbrella over the `ODW*.h` headers baked into the xcframework |

## Build (on macOS)

```bash
tools/apple/build-xcframework.sh release
# -> build/apple/MATTelemetry.xcframework
# -> build/apple/MATTelemetry.xcframework.zip (+ prints the SPM checksum)
swift build # resolves Package.swift against the local xcframework
```

## Consume

- **Local:** point a sample app at this package directory (path dependency).
- **Released:** in Xcode *File -> Add Package Dependencies...*, enter the repo
URL and pick a version. SPM only accepts **3-component SemVer**, and the SDK's
own `vX.Y.Z.W` tags are not valid SemVer, so consumers pin the **parallel
3-component tag** the release workflow publishes:

```swift
.package(url: "https://github.com/microsoft/cpp_client_telemetry.git", from: "3.10.161")
```

## Release wiring

`.github/workflows/spm-release.yml` automates distribution on each published
release (a 4-component `vX.Y.Z.W` tag). On a macOS runner it:

1. Builds `MATTelemetry.xcframework` and zips it.
2. Uploads the zip to the GitHub Release.
3. Computes the SPM checksum and rewrites the `Package.swift` `binaryTarget`
from `path:` to `url:`+`checksum:`.
4. Commits that manifest and pushes a **3-component SemVer tag** (`X.Y.Z`,
derived by dropping the trailing build component) that SPM can resolve.

This mirrors the `vcpkg-release-bump` workflow. It requires the root
`Package.swift` to already exist at the release tag (i.e. this prototype merged
before the release is cut).

## Known gaps / TODO (validate on macOS)

- **macOS / Catalyst / visionOS slices** — only iOS device + simulator are wired
up in this first pass; add the macOS slice (see section 3 of the script).
- **Conditional modules** — carry over the `moduleExists()` source exclusions
from `wrappers/swift/Package.swift` (PrivacyGuard / Sanitizer / DataViewer) and
keep the umbrella header in sync with the headers actually built.
- **Header flattening** — confirm the `ODW*.h` headers reference each other by
bare name in the flattened `Headers/` layout (adjust the copy step if not).
- **Static-lib path** — verify `out/lib/libmat.a` is the actual artifact name and
that `-DBUILD_SHARED_LIBS=OFF` yields a static archive for every slice.
- **Code signing** — release xcframeworks are typically signed; add a signing
step before zipping for distribution.
Loading
Loading