Skip to content

Commit 2caba90

Browse files
authored
Merge branch 'main' into bhamehta/jni-resource-safety
2 parents b5ee5c2 + 6571167 commit 2caba90

29 files changed

Lines changed: 921 additions & 35 deletions

‎.github/workflows/build-android.yml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ jobs:
4848
with:
4949
distribution: 'adopt'
5050
java-version: '17'
51-
- name: Remove default github maven configuration
52-
# Workaround for: 'Unable to decrypt local Maven settings credentials'
53-
run: rm $Env:USERPROFILE\.m2\settings.xml
5451
- name: Setup Android SDK
5552
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2
5653
- name: Install NDK

‎.github/workflows/codeql-analysis.yml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,6 @@ jobs:
109109
with:
110110
distribution: 'adopt'
111111
java-version: '17'
112-
- name: Remove default github maven configuration
113-
run: rm $Env:USERPROFILE\.m2\settings.xml
114112
- name: Setup Android SDK
115113
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2
116114
- name: Install NDK

‎.github/workflows/spm-release.yml‎

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
name: SPM release (xcframework)
2+
3+
# On a published SDK release, build MATTelemetry.xcframework, upload it to the
4+
# GitHub Release, and publish a 3-component SemVer tag for Swift Package Manager
5+
# whose Package.swift binaryTarget points at the uploaded artifact + checksum.
6+
#
7+
# Why a separate tag: the SDK's own release tags are 4-component (vX.Y.Z.W),
8+
# which is NOT valid SemVer, so Swift Package Manager ignores them. This derives
9+
# a 3-component tag (X.Y.Z) from the same release that SPM can resolve.
10+
# Only one build per 3-component version can publish: a later vX.Y.Z.W hotfix
11+
# with the same X.Y.Z maps to the existing SPM tag and is skipped/unsupported
12+
# unless this mapping changes.
13+
#
14+
# Prerequisites:
15+
# * The root Package.swift (the SPM manifest) must exist at the release tag
16+
# (i.e. this prototype merged to main before the release is cut).
17+
# * Uses the default GITHUB_TOKEN (needs contents: write). No extra secrets.
18+
19+
on:
20+
release:
21+
types: [published]
22+
workflow_dispatch:
23+
inputs:
24+
tag:
25+
description: "4-component release tag to publish for SPM (e.g. v3.10.161.1)"
26+
required: true
27+
type: string
28+
29+
permissions:
30+
contents: write
31+
32+
concurrency:
33+
# Serialize all SPM publications because different vX.Y.Z.W releases can map
34+
# to the same X.Y.Z tag and release asset.
35+
group: spm-release
36+
cancel-in-progress: false
37+
38+
jobs:
39+
spm:
40+
name: Publish SPM xcframework + tag
41+
# Skip drafts/pre-releases; always allow manual dispatch.
42+
if: >-
43+
${{ github.event_name == 'workflow_dispatch' ||
44+
(github.event.release.draft == false && github.event.release.prerelease == false) }}
45+
runs-on: macos-15 # provides Xcode with Apple platform SDKs (xcodebuild, swift)
46+
env:
47+
ARTIFACT: MATTelemetry.xcframework.zip
48+
steps:
49+
- name: Resolve tag and derive SPM version
50+
id: ver
51+
env:
52+
# Pass untrusted tag values through the environment rather than
53+
# interpolating ${{ ... }} into the script body.
54+
RELEASE_TAG: ${{ github.event.release.tag_name }}
55+
INPUT_TAG: ${{ inputs.tag }}
56+
run: |
57+
set -euo pipefail
58+
TAG="${RELEASE_TAG:-$INPUT_TAG}"
59+
if [ -z "$TAG" ]; then echo "::error::No release tag could be resolved."; exit 1; fi
60+
# Only act on 4-component version tags vX.Y.Z.W.
61+
if ! printf '%s' "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
62+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
63+
echo "::error::Tag '$TAG' is not a 4-component version tag (expected vX.Y.Z.W)."
64+
exit 1
65+
fi
66+
echo "::notice::Tag '$TAG' is not a 4-component version tag; nothing to publish."
67+
echo "skip=true" >> "$GITHUB_OUTPUT"
68+
exit 0
69+
fi
70+
VERSION="${TAG#v}" # X.Y.Z.W
71+
SPM_VERSION="${VERSION%.*}" # X.Y.Z (drop the trailing build component)
72+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
73+
echo "spm_version=$SPM_VERSION" >> "$GITHUB_OUTPUT"
74+
echo "Release $TAG -> SPM tag $SPM_VERSION"
75+
76+
- name: Checkout the release tag
77+
if: ${{ steps.ver.outputs.skip != 'true' }}
78+
uses: actions/checkout@v4
79+
with:
80+
ref: ${{ steps.ver.outputs.tag }}
81+
fetch-depth: 0
82+
# The private lib/modules submodule is intentionally NOT fetched; the
83+
# xcframework ships the core SDK + Obj-C wrappers, matching the vcpkg
84+
# port (the optional modules are excluded there too).
85+
submodules: false
86+
87+
- name: Skip if this SPM version is already published
88+
id: pub
89+
if: ${{ steps.ver.outputs.skip != 'true' }}
90+
run: |
91+
set -euo pipefail
92+
# Distinguish "tag exists" (0) from "connected, no such tag" (2) from a
93+
# transport/auth error (other). Fail closed: on an indeterminate result
94+
# do NOT proceed to the --clobber upload, which could overwrite an
95+
# already-published asset out from under its pinned checksum.
96+
rc=0
97+
git ls-remote --exit-code --tags origin "refs/tags/${{ steps.ver.outputs.spm_version }}" >/dev/null 2>&1 || rc=$?
98+
if [ "$rc" -eq 0 ]; then
99+
echo "::notice::SPM tag ${{ steps.ver.outputs.spm_version }} already exists; leaving its published artifact and checksum untouched (re-uploading a differently-hashed zip would break consumers pinned to the existing checksum)."
100+
echo "published=true" >> "$GITHUB_OUTPUT"
101+
elif [ "$rc" -eq 2 ]; then
102+
echo "published=false" >> "$GITHUB_OUTPUT"
103+
else
104+
echo "::error::Could not determine whether SPM tag ${{ steps.ver.outputs.spm_version }} is already published (git ls-remote exit $rc); refusing to risk clobbering a published asset." 1>&2
105+
exit 1
106+
fi
107+
108+
- name: Build MATTelemetry.xcframework
109+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
110+
run: |
111+
set -euo pipefail
112+
chmod +x tools/apple/build-xcframework.sh
113+
tools/apple/build-xcframework.sh release
114+
test -f "build/apple/$ARTIFACT"
115+
116+
- name: Validate local SwiftPM package consumption
117+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
118+
run: |
119+
set -euo pipefail
120+
swift package dump-package > package.json
121+
python3 - <<'PY'
122+
import json
123+
expected = {
124+
"ios": "13.0",
125+
"maccatalyst": "14.0",
126+
"macos": "10.15",
127+
"visionos": "1.0",
128+
}
129+
with open("package.json", encoding="utf-8") as f:
130+
platforms = {
131+
item["platformName"]: item["version"]
132+
for item in json.load(f)["platforms"]
133+
}
134+
if platforms != expected:
135+
raise SystemExit(f"Unexpected Package.swift platforms: {platforms}")
136+
PY
137+
swift build
138+
xcodebuild -scheme OneDSSwift -destination 'generic/platform=iOS Simulator' build
139+
xcodebuild -scheme OneDSSwift -destination 'platform=macOS,variant=Mac Catalyst' build
140+
xcodebuild -scheme OneDSSwift -destination 'generic/platform=visionOS Simulator' build
141+
142+
- name: Compute SPM checksum
143+
id: sum
144+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
145+
run: |
146+
set -euo pipefail
147+
checksum="$(swift package compute-checksum "build/apple/$ARTIFACT")"
148+
echo "checksum=$checksum" >> "$GITHUB_OUTPUT"
149+
150+
- name: Upload xcframework to the release
151+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
152+
env:
153+
GH_TOKEN: ${{ github.token }}
154+
run: gh release upload "${{ steps.ver.outputs.tag }}" "build/apple/$ARTIFACT" --clobber
155+
156+
- name: Point Package.swift at the released artifact
157+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
158+
env:
159+
ASSET_URL: https://github.com/${{ github.repository }}/releases/download/${{ steps.ver.outputs.tag }}/MATTelemetry.xcframework.zip
160+
CHECKSUM: ${{ steps.sum.outputs.checksum }}
161+
run: |
162+
set -euo pipefail
163+
python3 - "$ASSET_URL" "$CHECKSUM" <<'PY'
164+
import re, sys
165+
url, checksum = sys.argv[1], sys.argv[2]
166+
path = "Package.swift"
167+
src = open(path).read()
168+
repl = (
169+
'.binaryTarget(\n'
170+
' name: "MATTelemetry",\n'
171+
f' url: "{url}",\n'
172+
f' checksum: "{checksum}")'
173+
)
174+
out = re.sub(
175+
r'\.binaryTarget\(\s*name:\s*"MATTelemetry",\s*path:\s*"[^"]*"\s*\)',
176+
repl, src, count=1)
177+
assert out != src, "binaryTarget(path:) block not found in Package.swift"
178+
open(path, "w").write(out)
179+
PY
180+
181+
- name: Create submodule-free SPM tag candidate
182+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
183+
run: |
184+
set -euo pipefail
185+
git config user.name "github-actions[bot]"
186+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
187+
# SwiftPM recursively fetches every submodule in a package checkout.
188+
# Neither the private optional modules nor googletest is needed by the
189+
# released binary/source package, so the SPM-only tag must omit both.
190+
git rm -f .gitmodules lib/modules third_party/googletest
191+
git add Package.swift tools/apple/MATTelemetryAvailability.json
192+
git commit -m "[spm] ${{ steps.ver.outputs.spm_version }}: pin xcframework url + checksum"
193+
if git ls-files --stage | grep -q '^160000 '; then
194+
echo "::error::SPM tag candidate still contains a git submodule." 1>&2
195+
exit 1
196+
fi
197+
git tag -a "${{ steps.ver.outputs.spm_version }}" \
198+
-m "Swift Package Manager release ${{ steps.ver.outputs.spm_version }} (from ${{ steps.ver.outputs.tag }})"
199+
200+
- name: Validate clean package resolution
201+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
202+
env:
203+
SPM_VERSION: ${{ steps.ver.outputs.spm_version }}
204+
run: |
205+
set -euo pipefail
206+
SPM_REPO="$RUNNER_TEMP/cpp_client_telemetry.git"
207+
CONSUMER="$RUNNER_TEMP/MATTelemetrySPMSmoke"
208+
git clone --bare --no-local . "$SPM_REPO"
209+
mkdir -p "$CONSUMER/Sources/Smoke"
210+
cat > "$CONSUMER/Package.swift" <<EOF
211+
// swift-tools-version: 5.9
212+
import PackageDescription
213+
214+
let package = Package(
215+
name: "MATTelemetrySPMSmoke",
216+
platforms: [.macOS(.v10_15)],
217+
dependencies: [
218+
.package(url: "file://$SPM_REPO", exact: "$SPM_VERSION"),
219+
],
220+
targets: [
221+
.executableTarget(
222+
name: "Smoke",
223+
dependencies: [
224+
.product(name: "OneDSSwift", package: "cpp_client_telemetry"),
225+
]),
226+
])
227+
EOF
228+
printf '%s\n' 'import OneDSSwift' > "$CONSUMER/Sources/Smoke/main.swift"
229+
swift build --package-path "$CONSUMER"
230+
231+
- name: Push the 3-component SPM tag
232+
if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }}
233+
run: |
234+
set -euo pipefail
235+
git push origin "refs/tags/${{ steps.ver.outputs.spm_version }}"
236+
echo "Published SPM tag ${{ steps.ver.outputs.spm_version }}"

‎Package.swift‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// swift-tools-version: 5.9
2+
//
3+
// Copyright (c) Microsoft Corporation. All rights reserved.
4+
// SPDX-License-Identifier: Apache-2.0
5+
//
6+
// Swift Package Manager manifest for the 1DS C++ SDK (Microsoft Applications
7+
// Telemetry) on Apple platforms.
8+
//
9+
// PROTOTYPE — distribution model:
10+
// * The compiled C++ core + Obj-C wrappers ship as a prebuilt binary
11+
// xcframework (built by tools/apple/build-xcframework.sh). This avoids
12+
// compiling the CMake/Bond/sqlite/zlib C++ tree through SPM, which is not
13+
// practical.
14+
// * The thin Swift wrapper (wrappers/swift/Sources/OneDSSwift) is compiled
15+
// from source on top of the Obj-C module vended by the xcframework.
16+
//
17+
// Local development:
18+
// 1. Run `tools/apple/build-xcframework.sh release` on macOS with Xcode.
19+
// It produces ./build/apple/MATTelemetry.xcframework.
20+
// 2. `swift build` validates macOS consumption; for iOS / Mac Catalyst /
21+
// visionOS, add this package as a local dependency or build the package
22+
// with the desired Xcode destination.
23+
//
24+
// Release distribution (so consumers can add the repo by URL in Xcode):
25+
// .github/workflows/spm-release.yml builds, validates, and uploads the
26+
// xcframework, rewrites the local `.binaryTarget(... path:)` below to
27+
// `url:`+`checksum:`, removes repository submodules from the tag-only commit,
28+
// validates that commit through a clean consumer, and pushes the
29+
// 3-component SemVer tag that SPM can resolve. Because vX.Y.Z.W release tags
30+
// map to one X.Y.Z SPM tag, only one build per three-component version can
31+
// publish; later fourth-component hotfixes for the same X.Y.Z are skipped
32+
// unless the mapping changes.
33+
34+
import PackageDescription
35+
import Foundation
36+
37+
let packageDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
38+
39+
func readAvailability() -> [String: Bool] {
40+
let candidates = [
41+
"build/apple/MATTelemetryAvailability.json",
42+
"tools/apple/MATTelemetryAvailability.json",
43+
]
44+
45+
for relativePath in candidates {
46+
let url = packageDirectory.appendingPathComponent(relativePath).standardizedFileURL
47+
guard let data = try? Data(contentsOf: url),
48+
let object = try? JSONSerialization.jsonObject(with: data) as? [String: Bool] else {
49+
continue
50+
}
51+
return object
52+
}
53+
54+
return [:]
55+
}
56+
57+
let availability = readAvailability()
58+
let hasDiagnosticDataViewer = availability["diagnosticDataViewer"] ?? false
59+
let hasPrivacyGuard = availability["privacyGuard"] ?? false
60+
let hasSanitizer = availability["sanitizer"] ?? false
61+
62+
var excludedSources: [String] = []
63+
var swiftSettings: [SwiftSetting] = []
64+
65+
if !hasDiagnosticDataViewer {
66+
excludedSources.append("DiagnosticDataViewer.swift")
67+
}
68+
69+
if hasPrivacyGuard {
70+
swiftSettings.append(.define("MATSDK_PRIVACYGUARD_AVAILABLE"))
71+
} else {
72+
excludedSources.append(contentsOf: [
73+
"PrivacyGuard.swift",
74+
"PrivacyGuardInitConfig.swift",
75+
])
76+
}
77+
78+
if !hasSanitizer {
79+
excludedSources.append(contentsOf: [
80+
"Sanitizer.swift",
81+
"SanitizerInitConfig.swift",
82+
])
83+
}
84+
85+
let package = Package(
86+
name: "OneDSSwift",
87+
platforms: [
88+
.iOS(.v13),
89+
.macCatalyst(.v14),
90+
.macOS(.v10_15),
91+
.visionOS(.v1),
92+
],
93+
products: [
94+
.library(name: "OneDSSwift", targets: ["OneDSSwift"]),
95+
],
96+
targets: [
97+
// Prebuilt C++ core + Obj-C wrappers. The xcframework's bundled
98+
// module map vends the Clang module `MATTelemetryObjC` (see
99+
// tools/apple/module.modulemap), which the Swift layer imports.
100+
//
101+
// For a tagged release, swap the local path for the hosted artifact:
102+
//
103+
// .binaryTarget(
104+
// name: "MATTelemetry",
105+
// url: "https://github.com/microsoft/cpp_client_telemetry/releases/download/v3.10.161.1/MATTelemetry.xcframework.zip",
106+
// checksum: "<output of swift package compute-checksum>"),
107+
.binaryTarget(
108+
name: "MATTelemetry",
109+
path: "build/apple/MATTelemetry.xcframework"),
110+
111+
// Thin Swift API layer (source). Depends on the Obj-C module from the
112+
// xcframework. The conditional source exclusions above must stay in sync
113+
// with the headers baked into the xcframework.
114+
.target(
115+
name: "OneDSSwift",
116+
dependencies: ["MATTelemetry"],
117+
path: "wrappers/swift/Sources/OneDSSwift",
118+
exclude: excludedSources,
119+
swiftSettings: swiftSettings,
120+
linkerSettings: [
121+
.linkedLibrary("c++"),
122+
.linkedLibrary("sqlite3"),
123+
.linkedLibrary("z"),
124+
.linkedFramework("CFNetwork", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])),
125+
.linkedFramework("CoreFoundation", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])),
126+
.linkedFramework("Foundation", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])),
127+
.linkedFramework("Network", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])),
128+
.linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macCatalyst, .macOS, .visionOS])),
129+
.linkedFramework("IOKit", .when(platforms: [.macOS])),
130+
.linkedFramework("UIKit", .when(platforms: [.iOS, .macCatalyst, .visionOS])),
131+
]),
132+
]
133+
)

0 commit comments

Comments
 (0)