From 0f3e0655f5379ea6ff73906f9358b35495ef637a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 01:54:59 -0500 Subject: [PATCH 01/40] Prototype: SPM distribution via prebuilt xcframework (Apple) First-pass scaffold to support Swift Package Manager on Apple platforms (the successor to CocoaPods, whose trunk goes read-only Dec 2 2026; there is no official in-repo podspec today). SPM cannot practically compile the C++ tree (CMake/Bond/sqlite/zlib/platform conditionals), so the compiled C++ core + Obj-C wrappers ship as a prebuilt MATTelemetry.xcframework (.binaryTarget) and the thin Swift layer (wrappers/swift/Sources/OneDSSwift) is compiled from source on top of the Obj-C module the xcframework vends. - Package.swift (root): binaryTarget (xcframework) + OneDSSwift source target; documents the path: -> url:+checksum: switch for releases. - tools/apple/build-xcframework.sh: per-slice static libmat.a via build-ios.sh (iOS device + simulator), lipo, then xcodebuild -create-xcframework; emits a zip + SPM checksum. - tools/apple/module.modulemap + MATTelemetry-umbrella.h: vend the `ObjCModule` Clang module the existing Swift sources already import. - tools/apple/README.md: approach, build/consume steps, release wiring, and the macOS-validation TODOs (macOS/Catalyst slices, conditional modules, header flattening, signing). NOT yet validated on macOS -- needs a mac with Xcode to run the build and adjust the static-lib path / header layout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Package.swift | 65 ++++++++++++++++++++++ tools/apple/MATTelemetry-umbrella.h | 29 ++++++++++ tools/apple/README.md | 70 ++++++++++++++++++++++++ tools/apple/build-xcframework.sh | 84 +++++++++++++++++++++++++++++ tools/apple/module.modulemap | 9 ++++ 5 files changed, 257 insertions(+) create mode 100644 Package.swift create mode 100644 tools/apple/MATTelemetry-umbrella.h create mode 100644 tools/apple/README.md create mode 100755 tools/apple/build-xcframework.sh create mode 100644 tools/apple/module.modulemap diff --git a/Package.swift b/Package.swift new file mode 100644 index 000000000..ad4364e70 --- /dev/null +++ b/Package.swift @@ -0,0 +1,65 @@ +// 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. + +import PackageDescription + +let package = Package( + name: "OneDSSwift", + platforms: [ + .iOS(.v12), + .macOS(.v10_15), + ], + 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: ""), + .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. + .target( + name: "OneDSSwift", + dependencies: ["MATTelemetry"], + path: "wrappers/swift/Sources/OneDSSwift"), + ] +) diff --git a/tools/apple/MATTelemetry-umbrella.h b/tools/apple/MATTelemetry-umbrella.h new file mode 100644 index 000000000..562509962 --- /dev/null +++ b/tools/apple/MATTelemetry-umbrella.h @@ -0,0 +1,29 @@ +// +// 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). +// +// Keep this list in sync with: +// * the ODW*.h headers copied by tools/apple/build-xcframework.sh, and +// * the Swift sources compiled in wrappers/swift/Sources/OneDSSwift. +// +// TODO(validate on macOS): confirm the ODW headers reference each other by bare +// name (or adjust the copy step) so the flattened layout compiles cleanly. + +#import + +#import "ODWDiagnosticDataViewer.h" +#import "ODWEventProperties.h" +#import "ODWLogConfiguration.h" +#import "ODWLogger.h" +#import "ODWLogManager.h" +#import "ODWPrivacyGuard.h" +#import "ODWPrivacyGuardInitConfig.h" +#import "ODWSanitizer.h" +#import "ODWSanitizerInitConfig.h" +#import "ODWSemanticContext.h" diff --git a/tools/apple/README.md b/tools/apple/README.md new file mode 100644 index 000000000..5c5848e8f --- /dev/null +++ b/tools/apple/README.md @@ -0,0 +1,70 @@ +# 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). + +## 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 Packages…* and enter the repo URL once the + release flow below is in place. + +## Release wiring (to make it URL-consumable) + +1. Build the xcframework and zip it (the script does both). +2. Attach `MATTelemetry.xcframework.zip` to the GitHub Release for the tag. +3. Switch the `.binaryTarget` in `Package.swift` from `path:` to + `url:`+`checksum:` (the `compute-checksum` value the script prints). + +This mirrors the `vcpkg-release-bump` workflow: a release-triggered job can build +the xcframework, upload it to the Release, and bump the `binaryTarget` URL + +checksum automatically. + +## 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. diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh new file mode 100755 index 000000000..728a90dae --- /dev/null +++ b/tools/apple/build-xcframework.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# PROTOTYPE: build MATTelemetry.xcframework (1DS C++ core + Obj-C wrappers) for +# Apple platforms, for Swift Package Manager distribution. +# +# Run on macOS with Xcode + CMake installed. Usage: +# tools/apple/build-xcframework.sh [release|debug] +# +# Produces: +# build/apple/MATTelemetry.xcframework +# build/apple/MATTelemetry.xcframework.zip (+ prints the SPM checksum) +# +# Slices built here: iOS device (arm64) and iOS simulator (arm64 + x86_64 fat). +# A macOS slice (and visionOS, Catalyst) can be folded in the same way -- see +# the note in section 3. +# +# NOTE: this is a first-pass scaffold. It has NOT been executed on macOS yet; +# validate on a mac and adjust the static-lib path / header flattening as needed. + +set -euo pipefail + +CONFIG="${1:-release}" +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +OUT="$ROOT/build/apple" +LIB="libmat.a" # mat target; the Obj-C wrappers compile into it (lib/CMakeLists.txt:217) + +# Force a STATIC libmat that includes the Obj-C wrappers, regardless of the +# repo's default library type. +export CMAKE_OPTS="-DBUILD_SHARED_LIBS=OFF -DBUILD_OBJC_WRAPPER=YES ${CMAKE_OPTS:-}" + +rm -rf "$OUT" +mkdir -p "$OUT" + +# --- 1. Public Obj-C headers + module map (vended by the xcframework) -------- +# Flatten the ODW*.h headers + umbrella + modulemap into one Headers dir. The +# module is named `ObjCModule` to match what wrappers/swift sources import. +HDRS="$OUT/Headers" +mkdir -p "$HDRS" +cp "$ROOT"/wrappers/obj-c/ODW*.h "$HDRS/" +cp "$ROOT"/tools/apple/MATTelemetry-umbrella.h "$HDRS/" +cp "$ROOT"/tools/apple/module.modulemap "$HDRS/" + +# --- 2. Build one static lib per (arch, platform) ---------------------------- +build_slice() { # arch platform out-subdir + local arch="$1" plat="$2" sub="$3" + echo "=== building $arch / $plat ($CONFIG) ===" + ( cd "$ROOT" && ./build-ios.sh clean "$CONFIG" "$arch" "$plat" ) + mkdir -p "$OUT/$sub" + cp "$ROOT/out/lib/$LIB" "$OUT/$sub/$LIB" +} + +build_slice arm64 iphoneos ios-arm64 +build_slice arm64 iphonesimulator ios-arm64-sim +build_slice x86_64 iphonesimulator ios-x86_64-sim + +# Fat simulator archive (arm64 + x86_64) -- a single xcframework slice cannot +# mix device and simulator, but it can contain multiple archs for one platform. +mkdir -p "$OUT/ios-simulator" +lipo -create "$OUT/ios-arm64-sim/$LIB" "$OUT/ios-x86_64-sim/$LIB" \ + -output "$OUT/ios-simulator/$LIB" + +# --- 3. (Optional) macOS slice ------------------------------------------------ +# Add a native macOS build (e.g. cmake -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" +# -DBUILD_APPLE_HTTP=YES) here and append another `-library .../libmat.a +# -headers "$HDRS"` pair to the xcodebuild call below. Omitted from this first +# pass to keep the prototype focused on iOS. + +# --- 4. Assemble the xcframework --------------------------------------------- +rm -rf "$OUT/MATTelemetry.xcframework" +xcodebuild -create-xcframework \ + -library "$OUT/ios-arm64/$LIB" -headers "$HDRS" \ + -library "$OUT/ios-simulator/$LIB" -headers "$HDRS" \ + -output "$OUT/MATTelemetry.xcframework" +echo "Created $OUT/MATTelemetry.xcframework" + +# --- 5. Zip + checksum for release distribution ------------------------------ +( cd "$OUT" && rm -f MATTelemetry.xcframework.zip \ + && zip -qry MATTelemetry.xcframework.zip MATTelemetry.xcframework ) +echo "Zipped: $OUT/MATTelemetry.xcframework.zip" +echo -n "SPM checksum (for Package.swift binaryTarget url: form): " +swift package compute-checksum "$OUT/MATTelemetry.xcframework.zip" diff --git a/tools/apple/module.modulemap b/tools/apple/module.modulemap new file mode 100644 index 000000000..0a2c0167a --- /dev/null +++ b/tools/apple/module.modulemap @@ -0,0 +1,9 @@ +// Clang module vended by MATTelemetry.xcframework. Imported by the OneDSSwift +// Swift layer as `import ObjCModule` -- the module name matches what the +// existing wrappers/swift/Sources/OneDSSwift sources already import, so no Swift +// source changes are needed. + +module ObjCModule { + umbrella header "MATTelemetry-umbrella.h" + export * +} From 5e3687e2b016b8b1cbfdeb530748fb5d1a39854d Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 02:36:21 -0500 Subject: [PATCH 02/40] Prototype: SPM release workflow + parallel 3-component SemVer tag Adds .github/workflows/spm-release.yml: on a published 4-component release (vX.Y.Z.W), a macOS-runner job builds MATTelemetry.xcframework, uploads it to the Release, computes the SPM checksum, rewrites the Package.swift binaryTarget from path: to url:+checksum:, and pushes a 3-component SemVer tag (X.Y.Z) that Swift Package Manager can resolve (the SDK's own 4-component tags are not valid SemVer, so SPM ignores them). Also documents the parallel-tag consumption (`from: "3.10.161"`) and the release flow in tools/apple/README.md. Mirrors the vcpkg-release-bump pattern. NOT yet validated -- needs the prototype merged (so Package.swift exists at the release tag) and a macOS runner; the build script itself still needs a first run on a mac. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 141 ++++++++++++++++++++++++++++++ tools/apple/README.md | 37 +++++--- 2 files changed, 165 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/spm-release.yml diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml new file mode 100644 index 000000000..cd8c1c670 --- /dev/null +++ b/.github/workflows/spm-release.yml @@ -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" + + - 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 + 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 }}" diff --git a/tools/apple/README.md b/tools/apple/README.md index 5c5848e8f..90cb53452 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -41,19 +41,30 @@ 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 Packages…* and enter the repo URL once the - release flow below is in place. - -## Release wiring (to make it URL-consumable) - -1. Build the xcframework and zip it (the script does both). -2. Attach `MATTelemetry.xcframework.zip` to the GitHub Release for the tag. -3. Switch the `.binaryTarget` in `Package.swift` from `path:` to - `url:`+`checksum:` (the `compute-checksum` value the script prints). - -This mirrors the `vcpkg-release-bump` workflow: a release-triggered job can build -the xcframework, upload it to the Release, and bump the `binaryTarget` URL + -checksum automatically. +- **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) From ba7ba4cb3c0a1ac3b6d84b466030cfffd9c3a79a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 13:38:08 -0500 Subject: [PATCH 03/40] Fix local SPM xcframework consumption Copy objc_begin/end support headers into the flattened xcframework Headers directory and mirror the existing Swift wrapper optional-module source exclusions in the root package manifest. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Package.swift | 39 +++++++++++++++++++++++++++++++- tools/apple/build-xcframework.sh | 2 ++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index ad4364e70..2e3a51ba7 100644 --- a/Package.swift +++ b/Package.swift @@ -27,6 +27,41 @@ // extended to automate steps 1-3 on each release tag. import PackageDescription +import Foundation + +let packageDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent() + +func moduleExists(_ relativePath: String) -> Bool { + FileManager.default.fileExists(atPath: packageDirectory.appendingPathComponent(relativePath).standardizedFileURL.path) +} + +let hasDiagnosticDataViewer = moduleExists("lib/modules/dataviewer") +let hasPrivacyGuard = moduleExists("lib/modules/privacyguard") +let hasSanitizer = moduleExists("lib/modules/sanitizer") + +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", + ]) +} + +if !hasSanitizer { + excludedSources.append(contentsOf: [ + "Sanitizer.swift", + "SanitizerInitConfig.swift", + ]) +} let package = Package( name: "OneDSSwift", @@ -60,6 +95,8 @@ let package = Package( .target( name: "OneDSSwift", dependencies: ["MATTelemetry"], - path: "wrappers/swift/Sources/OneDSSwift"), + path: "wrappers/swift/Sources/OneDSSwift", + exclude: excludedSources, + swiftSettings: swiftSettings), ] ) diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 728a90dae..342f9b29a 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -40,6 +40,8 @@ mkdir -p "$OUT" HDRS="$OUT/Headers" mkdir -p "$HDRS" cp "$ROOT"/wrappers/obj-c/ODW*.h "$HDRS/" +cp "$ROOT"/wrappers/obj-c/objc_begin.h "$HDRS/" +cp "$ROOT"/wrappers/obj-c/objc_end.h "$HDRS/" cp "$ROOT"/tools/apple/MATTelemetry-umbrella.h "$HDRS/" cp "$ROOT"/tools/apple/module.modulemap "$HDRS/" From 308e03110a3da3fc92d4af3da292e093c03701aa Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 13:51:49 -0500 Subject: [PATCH 04/40] Copy only public ObjC headers into xcframework Avoid vending private wrapper headers from the flattened MATTelemetry.xcframework Headers directory, which otherwise triggers incomplete umbrella warnings when ObjCModule is imported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/apple/build-xcframework.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 342f9b29a..5f4e39634 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -39,7 +39,7 @@ mkdir -p "$OUT" # module is named `ObjCModule` to match what wrappers/swift sources import. HDRS="$OUT/Headers" mkdir -p "$HDRS" -cp "$ROOT"/wrappers/obj-c/ODW*.h "$HDRS/" +find "$ROOT/wrappers/obj-c" -maxdepth 1 -name 'ODW*.h' ! -name '*_private.h' -exec cp {} "$HDRS/" \; cp "$ROOT"/wrappers/obj-c/objc_begin.h "$HDRS/" cp "$ROOT"/wrappers/obj-c/objc_end.h "$HDRS/" cp "$ROOT"/tools/apple/MATTelemetry-umbrella.h "$HDRS/" From dd972345632c70f9e1b919e8c99b45c51f38f850 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 17:47:06 -0500 Subject: [PATCH 05/40] Align SPM package with xcframework contents Generate the ObjC umbrella and availability manifest from the modules built into the xcframework, read that manifest from Package.swift, and guard Swift type aliases for optional modules. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 2 +- Package.swift | 25 ++++++++-- tools/apple/MATTelemetry-umbrella.h | 12 ++--- tools/apple/MATTelemetryAvailability.json | 5 ++ tools/apple/build-xcframework.sh | 46 ++++++++++++++++++- .../swift/Sources/OneDSSwift/ObjCTypes.swift | 2 + 6 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 tools/apple/MATTelemetryAvailability.json diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index cd8c1c670..28eb1f414 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -131,7 +131,7 @@ jobs: 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 + 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. diff --git a/Package.swift b/Package.swift index 2e3a51ba7..773733ba2 100644 --- a/Package.swift +++ b/Package.swift @@ -31,13 +31,28 @@ import Foundation let packageDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent() -func moduleExists(_ relativePath: String) -> Bool { - FileManager.default.fileExists(atPath: packageDirectory.appendingPathComponent(relativePath).standardizedFileURL.path) +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 hasDiagnosticDataViewer = moduleExists("lib/modules/dataviewer") -let hasPrivacyGuard = moduleExists("lib/modules/privacyguard") -let hasSanitizer = moduleExists("lib/modules/sanitizer") +let availability = readAvailability() +let hasDiagnosticDataViewer = availability["diagnosticDataViewer"] ?? false +let hasPrivacyGuard = availability["privacyGuard"] ?? false +let hasSanitizer = availability["sanitizer"] ?? false var excludedSources: [String] = [] var swiftSettings: [SwiftSetting] = [] diff --git a/tools/apple/MATTelemetry-umbrella.h b/tools/apple/MATTelemetry-umbrella.h index 562509962..734ceb07f 100644 --- a/tools/apple/MATTelemetry-umbrella.h +++ b/tools/apple/MATTelemetry-umbrella.h @@ -8,22 +8,16 @@ // Headers/ directory, so these are imported by bare name (not by the // ../../obj-c/ relative path the in-repo Swift bridging header uses). // -// Keep this list in sync with: -// * the ODW*.h headers copied by tools/apple/build-xcframework.sh, and -// * the Swift sources compiled in wrappers/swift/Sources/OneDSSwift. -// -// TODO(validate on macOS): confirm the ODW headers reference each other by bare -// name (or adjust the copy step) so the flattened layout compiles cleanly. +// build-xcframework.sh copies this template and appends imports for optional +// module headers only when those modules were actually built into the binary. #import -#import "ODWDiagnosticDataViewer.h" +#import "ODWCommonDataContext.h" #import "ODWEventProperties.h" #import "ODWLogConfiguration.h" #import "ODWLogger.h" #import "ODWLogManager.h" -#import "ODWPrivacyGuard.h" #import "ODWPrivacyGuardInitConfig.h" -#import "ODWSanitizer.h" #import "ODWSanitizerInitConfig.h" #import "ODWSemanticContext.h" diff --git a/tools/apple/MATTelemetryAvailability.json b/tools/apple/MATTelemetryAvailability.json new file mode 100644 index 000000000..34cb38831 --- /dev/null +++ b/tools/apple/MATTelemetryAvailability.json @@ -0,0 +1,5 @@ +{ + "diagnosticDataViewer": false, + "privacyGuard": false, + "sanitizer": false +} diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 5f4e39634..29e23edf0 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -39,12 +39,54 @@ mkdir -p "$OUT" # module is named `ObjCModule` to match what wrappers/swift sources import. HDRS="$OUT/Headers" mkdir -p "$HDRS" -find "$ROOT/wrappers/obj-c" -maxdepth 1 -name 'ODW*.h' ! -name '*_private.h' -exec cp {} "$HDRS/" \; + +has_dataviewer=false +has_privacyguard=false +has_sanitizer=false +[[ -d "$ROOT/lib/modules/dataviewer" ]] && has_dataviewer=true +[[ -d "$ROOT/lib/modules/privacyguard" ]] && has_privacyguard=true +[[ -d "$ROOT/lib/modules/sanitizer" ]] && has_sanitizer=true + +cat > "$OUT/MATTelemetryAvailability.json" <> "$HDRS/MATTelemetry-umbrella.h" + # --- 2. Build one static lib per (arch, platform) ---------------------------- build_slice() { # arch platform out-subdir local arch="$1" plat="$2" sub="$3" diff --git a/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift b/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift index 1042a42e1..c0c1ad4e0 100644 --- a/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift +++ b/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift @@ -27,4 +27,6 @@ public typealias TransmissionProfile = ODWTransmissionProfile public typealias FlushStatus = ODWStatus // ODWPrivacyGuard.h +#if MATSDK_PRIVACYGUARD_AVAILABLE public typealias DataConcernType = ODWDataConcernType +#endif From accb600cb6f44c3d1f447e114127dda414bd55bc Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 18:00:27 -0500 Subject: [PATCH 06/40] Generate SPM availability from xcframework build Drive Swift source exclusions and ObjC umbrella optional imports from the modules actually built into MATTelemetry.xcframework, remove unsupported macOS package advertising, and add Apple system linker settings for the static binary target. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Package.swift | 14 ++++++++++++-- tools/apple/MATTelemetry-umbrella.h | 5 +++-- tools/apple/build-xcframework.sh | 12 +++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 773733ba2..2ff67d8ee 100644 --- a/Package.swift +++ b/Package.swift @@ -82,7 +82,6 @@ let package = Package( name: "OneDSSwift", platforms: [ .iOS(.v12), - .macOS(.v10_15), ], products: [ .library(name: "OneDSSwift", targets: ["OneDSSwift"]), @@ -112,6 +111,17 @@ let package = Package( dependencies: ["MATTelemetry"], path: "wrappers/swift/Sources/OneDSSwift", exclude: excludedSources, - swiftSettings: swiftSettings), + swiftSettings: swiftSettings, + linkerSettings: [ + .linkedLibrary("sqlite3"), + .linkedLibrary("z"), + .linkedFramework("CFNetwork"), + .linkedFramework("CoreFoundation"), + .linkedFramework("Foundation"), + .linkedFramework("IOKit"), + .linkedFramework("Network"), + .linkedFramework("SystemConfiguration"), + .linkedFramework("UIKit"), + ]), ] ) diff --git a/tools/apple/MATTelemetry-umbrella.h b/tools/apple/MATTelemetry-umbrella.h index 734ceb07f..8d4aaf952 100644 --- a/tools/apple/MATTelemetry-umbrella.h +++ b/tools/apple/MATTelemetry-umbrella.h @@ -8,8 +8,9 @@ // Headers/ directory, so these are imported by bare name (not by the // ../../obj-c/ relative path the in-repo Swift bridging header uses). // -// build-xcframework.sh copies this template and appends imports for optional -// module headers only when those modules were actually built into the binary. +// 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 diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 29e23edf0..36bc5238f 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -88,17 +88,19 @@ cp "$ROOT"/tools/apple/MATTelemetry-umbrella.h "$HDRS/" } >> "$HDRS/MATTelemetry-umbrella.h" # --- 2. Build one static lib per (arch, platform) ---------------------------- -build_slice() { # arch platform out-subdir +build_slice() { # clean-arg arch platform out-subdir + local clean_arg="$1" + shift local arch="$1" plat="$2" sub="$3" echo "=== building $arch / $plat ($CONFIG) ===" - ( cd "$ROOT" && ./build-ios.sh clean "$CONFIG" "$arch" "$plat" ) + ( cd "$ROOT" && ./build-ios.sh $clean_arg "$CONFIG" "$arch" "$plat" ) mkdir -p "$OUT/$sub" cp "$ROOT/out/lib/$LIB" "$OUT/$sub/$LIB" } -build_slice arm64 iphoneos ios-arm64 -build_slice arm64 iphonesimulator ios-arm64-sim -build_slice x86_64 iphonesimulator ios-x86_64-sim +build_slice clean arm64 iphoneos ios-arm64 +build_slice "" arm64 iphonesimulator ios-arm64-sim +build_slice "" x86_64 iphonesimulator ios-x86_64-sim # Fat simulator archive (arm64 + x86_64) -- a single xcframework slice cannot # mix device and simulator, but it can contain multiple archs for one platform. From 4e03dff20e8da5ae54e5bdfca7ca5ea3591d631f Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 18:23:17 -0500 Subject: [PATCH 07/40] Address SPM prototype review refinements Remove macOS package advertising until a macOS slice exists, add iOS linker settings for the static xcframework, and avoid repeating build tool setup for each slice. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Package.swift | 14 +++++++------- tools/apple/build-xcframework.sh | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index 2ff67d8ee..ace48f629 100644 --- a/Package.swift +++ b/Package.swift @@ -113,15 +113,15 @@ let package = Package( exclude: excludedSources, swiftSettings: swiftSettings, linkerSettings: [ + .linkedLibrary("c++"), .linkedLibrary("sqlite3"), .linkedLibrary("z"), - .linkedFramework("CFNetwork"), - .linkedFramework("CoreFoundation"), - .linkedFramework("Foundation"), - .linkedFramework("IOKit"), - .linkedFramework("Network"), - .linkedFramework("SystemConfiguration"), - .linkedFramework("UIKit"), + .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])), ]), ] ) diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 36bc5238f..9f531ce71 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -54,7 +54,9 @@ cat > "$OUT/MATTelemetryAvailability.json" < Date: Thu, 18 Jun 2026 18:45:27 -0500 Subject: [PATCH 08/40] Align Apple SPM docs and module availability Mark the prototype as validated, document remaining gaps, and make optional module availability respect explicit BUILD_PRIVACYGUARD/BUILD_SANITIZER settings passed through CMAKE_OPTS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/apple/README.md | 29 +++++++++++++++++------------ tools/apple/build-xcframework.sh | 30 ++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/tools/apple/README.md b/tools/apple/README.md index 90cb53452..162043957 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -1,9 +1,9 @@ # 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). +**Status: validated prototype.** 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). ## Approach @@ -28,6 +28,7 @@ so the xcframework just needs to vend a Clang module named `ObjCModule` | `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 | +| `tools/apple/MATTelemetryAvailability.json` | Build-time optional-module manifest consumed by `Package.swift` so Swift sources match the xcframework contents | ## Build (on macOS) @@ -66,16 +67,20 @@ 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) +## Validation performed + +- `tools/apple/build-xcframework.sh release` builds the iOS device and simulator + slices and prints the SPM checksum. +- `xcodebuild -scheme OneDSSwift -destination 'generic/platform=iOS Simulator' build` + validates local SwiftPM consumption. +- A small Obj-C module/static-link smoke test was built and run on an iOS + Simulator. + +## Known gaps / TODO - **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. +- **Release workflow validation** — exercise `.github/workflows/spm-release.yml` + end-to-end on an actual published release. diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 9f531ce71..37dfae1c4 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -17,8 +17,8 @@ # A macOS slice (and visionOS, Catalyst) can be folded in the same way -- see # the note in section 3. # -# NOTE: this is a first-pass scaffold. It has NOT been executed on macOS yet; -# validate on a mac and adjust the static-lib path / header flattening as needed. +# NOTE: this is a first-pass scaffold. It has been validated on macOS for iOS +# device + simulator slices; macOS/Catalyst/visionOS slices are still TODO. set -euo pipefail @@ -43,9 +43,31 @@ mkdir -p "$HDRS" has_dataviewer=false has_privacyguard=false has_sanitizer=false + +cmake_option_enabled() { # option-name default-value + local option="$1" + local value="$2" + local token + for token in $CMAKE_OPTS; do + case "$token" in + -D${option}=*) value="${token#*=}" ;; + -D${option}) value=ON ;; + esac + done + value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')" + case "$value" in + 0|false|no|off) return 1 ;; + *) return 0 ;; + esac +} + [[ -d "$ROOT/lib/modules/dataviewer" ]] && has_dataviewer=true -[[ -d "$ROOT/lib/modules/privacyguard" ]] && has_privacyguard=true -[[ -d "$ROOT/lib/modules/sanitizer" ]] && has_sanitizer=true +if [[ -d "$ROOT/lib/modules/privacyguard" ]] && cmake_option_enabled BUILD_PRIVACYGUARD ON; then + has_privacyguard=true +fi +if [[ -d "$ROOT/lib/modules/sanitizer" ]] && cmake_option_enabled BUILD_SANITIZER ON; then + has_sanitizer=true +fi cat > "$OUT/MATTelemetryAvailability.json" < Date: Thu, 18 Jun 2026 21:21:17 -0500 Subject: [PATCH 09/40] Add macOS slice to SPM xcframework Build a universal macOS libmat archive alongside the iOS device and simulator slices, advertise macOS in the root Swift package, and document the expanded validation story. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Package.swift | 15 ++++++----- tools/apple/README.md | 10 +++++--- tools/apple/build-xcframework.sh | 43 +++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/Package.swift b/Package.swift index ace48f629..fe7385189 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,8 @@ // 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). +// 2. `swift build` validates macOS consumption; for iOS, add this package as a +// local dependency or build the package with an iOS Simulator destination. // // 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. @@ -82,6 +83,7 @@ let package = Package( name: "OneDSSwift", platforms: [ .iOS(.v12), + .macOS(.v10_15), ], products: [ .library(name: "OneDSSwift", targets: ["OneDSSwift"]), @@ -116,11 +118,12 @@ let package = Package( .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("CFNetwork", .when(platforms: [.iOS, .macOS])), + .linkedFramework("CoreFoundation", .when(platforms: [.iOS, .macOS])), + .linkedFramework("Foundation", .when(platforms: [.iOS, .macOS])), + .linkedFramework("Network", .when(platforms: [.iOS, .macOS])), + .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS])), + .linkedFramework("IOKit", .when(platforms: [.macOS])), .linkedFramework("UIKit", .when(platforms: [.iOS])), ]), ] diff --git a/tools/apple/README.md b/tools/apple/README.md index 162043957..c2d98771d 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -25,7 +25,7 @@ so the xcframework just needs to vend a Clang module named `ObjCModule` | 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/build-xcframework.sh` | Builds a static `libmat.a` per Apple slice, lipo's the simulator/macOS archs where needed, 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 | | `tools/apple/MATTelemetryAvailability.json` | Build-time optional-module manifest consumed by `Package.swift` so Swift sources match the xcframework contents | @@ -70,7 +70,8 @@ before the release is cut). ## Validation performed - `tools/apple/build-xcframework.sh release` builds the iOS device and simulator - slices and prints the SPM checksum. + slices, plus a universal macOS slice, and prints the SPM checksum. +- `swift build` validates local macOS SwiftPM consumption. - `xcodebuild -scheme OneDSSwift -destination 'generic/platform=iOS Simulator' build` validates local SwiftPM consumption. - A small Obj-C module/static-link smoke test was built and run on an iOS @@ -78,8 +79,9 @@ before the release is cut). ## Known gaps / TODO -- **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). +- **Catalyst / visionOS slices** — iOS device, iOS simulator, and macOS are wired + up in this first pass; Catalyst and visionOS still need separate slice wiring + and validation. - **Code signing** — release xcframeworks are typically signed; add a signing step before zipping for distribution. - **Release workflow validation** — exercise `.github/workflows/spm-release.yml` diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 37dfae1c4..85d272d3d 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -13,12 +13,11 @@ # build/apple/MATTelemetry.xcframework # build/apple/MATTelemetry.xcframework.zip (+ prints the SPM checksum) # -# Slices built here: iOS device (arm64) and iOS simulator (arm64 + x86_64 fat). -# A macOS slice (and visionOS, Catalyst) can be folded in the same way -- see -# the note in section 3. +# Slices built here: iOS device (arm64), iOS simulator (arm64 + x86_64 fat), +# and macOS (arm64 + x86_64 universal). # # NOTE: this is a first-pass scaffold. It has been validated on macOS for iOS -# device + simulator slices; macOS/Catalyst/visionOS slices are still TODO. +# device, simulator, and macOS slices; Catalyst/visionOS slices are still TODO. set -euo pipefail @@ -27,6 +26,15 @@ ROOT="$(cd "$(dirname "$0")/../.." && pwd)" OUT="$ROOT/build/apple" LIB="libmat.a" # mat target; the Obj-C wrappers compile into it (lib/CMakeLists.txt:217) +case "$CONFIG" in + release) CMAKE_BUILD_TYPE="Release" ;; + debug) CMAKE_BUILD_TYPE="Debug" ;; + *) + echo "Usage: $0 [release|debug]" >&2 + exit 1 + ;; +esac + # Force a STATIC libmat that includes the Obj-C wrappers, regardless of the # repo's default library type. export CMAKE_OPTS="-DBUILD_SHARED_LIBS=OFF -DBUILD_OBJC_WRAPPER=YES ${CMAKE_OPTS:-}" @@ -132,17 +140,34 @@ mkdir -p "$OUT/ios-simulator" lipo -create "$OUT/ios-arm64-sim/$LIB" "$OUT/ios-x86_64-sim/$LIB" \ -output "$OUT/ios-simulator/$LIB" -# --- 3. (Optional) macOS slice ------------------------------------------------ -# Add a native macOS build (e.g. cmake -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -# -DBUILD_APPLE_HTTP=YES) here and append another `-library .../libmat.a -# -headers "$HDRS"` pair to the xcodebuild call below. Omitted from this first -# pass to keep the prototype focused on iOS. +# Native universal macOS archive. Build only the `mat` target in an isolated +# CMake build directory so switching away from the iOS toolchain does not +# disturb the already-copied iOS archives. +echo "=== building arm64+x86_64 / macosx ($CONFIG) ===" +MACOS_BUILD="$OUT/macos-build" +MACOS_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-10.15}" +cmake -S "$ROOT" -B "$MACOS_BUILD" \ + -DMAC_ARCH=universal \ + -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET="$MACOS_DEPLOYMENT_TARGET" \ + -DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \ + -DCMAKE_PACKAGE_TYPE=tgz \ + -DBUILD_TEST_TOOL=OFF \ + -DBUILD_UNIT_TESTS=OFF \ + -DBUILD_FUNC_TESTS=OFF \ + -DBUILD_SWIFT_WRAPPER=OFF \ + -DBUILD_PACKAGE=OFF \ + $CMAKE_OPTS +cmake --build "$MACOS_BUILD" --target mat +mkdir -p "$OUT/macos-universal" +cp "$MACOS_BUILD/lib/$LIB" "$OUT/macos-universal/$LIB" # --- 4. Assemble the xcframework --------------------------------------------- rm -rf "$OUT/MATTelemetry.xcframework" xcodebuild -create-xcframework \ -library "$OUT/ios-arm64/$LIB" -headers "$HDRS" \ -library "$OUT/ios-simulator/$LIB" -headers "$HDRS" \ + -library "$OUT/macos-universal/$LIB" -headers "$HDRS" \ -output "$OUT/MATTelemetry.xcframework" echo "Created $OUT/MATTelemetry.xcframework" From d242830e0c64d4e6177e303914a22629ce4bf763 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 22:43:49 -0500 Subject: [PATCH 10/40] Add Mac Catalyst slice to SPM xcframework Teach the Apple build path to produce macabi archives, include a fat Catalyst variant in MATTelemetry.xcframework, advertise Mac Catalyst in the Swift package, and document the expanded validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 13 +++++++++++-- Package.swift | 18 ++++++++++-------- build-ios.sh | 16 +++++++++++++--- tools/apple/README.md | 19 ++++++++++--------- tools/apple/build-xcframework.sh | 14 ++++++++++++-- 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a0ba0e82..6d117cb42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,13 +57,17 @@ if(APPLE) if (${IOS_PLAT} STREQUAL "iphonesimulator") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") - else() + elseif(NOT ${IOS_PLAT} STREQUAL "maccatalyst") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") endif() endif() - if((${IOS_PLAT} STREQUAL "iphoneos") OR (${IOS_PLAT} STREQUAL "iphonesimulator") OR (${IOS_PLAT} STREQUAL "xros") OR (${IOS_PLAT} STREQUAL "xrsimulator")) + if(${IOS_PLAT} STREQUAL "maccatalyst") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${IOS_ARCH}-apple-ios${IOS_DEPLOYMENT_TARGET}-macabi") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${IOS_ARCH}-apple-ios${IOS_DEPLOYMENT_TARGET}-macabi") + set(IOS_PLATFORM "macosx") + elseif((${IOS_PLAT} STREQUAL "iphoneos") OR (${IOS_PLAT} STREQUAL "iphonesimulator") OR (${IOS_PLAT} STREQUAL "xros") OR (${IOS_PLAT} STREQUAL "xrsimulator")) set(IOS_PLATFORM "${IOS_PLAT}") else() message(FATAL_ERROR "Unrecognized iOS platform '${IOS_PLAT}'") @@ -89,6 +93,11 @@ if(APPLE) OUTPUT_VARIABLE CMAKE_OSX_SYSROOT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(${IOS_PLAT} STREQUAL "maccatalyst") + set(IOS_SUPPORT_FRAMEWORKS "${CMAKE_OSX_SYSROOT}/System/iOSSupport/System/Library/Frameworks") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -iframework ${IOS_SUPPORT_FRAMEWORKS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -iframework ${IOS_SUPPORT_FRAMEWORKS}") + endif() message(STATUS "CMAKE_OSX_SYSROOT ${CMAKE_OSX_SYSROOT}") message(STATUS "ARCHITECTURE: ${CMAKE_SYSTEM_PROCESSOR}") message(STATUS "PLATFORM: ${IOS_PLATFORM}") diff --git a/Package.swift b/Package.swift index fe7385189..6144610d0 100644 --- a/Package.swift +++ b/Package.swift @@ -17,8 +17,9 @@ // 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, add this package as a -// local dependency or build the package with an iOS Simulator destination. +// 2. `swift build` validates macOS consumption; for iOS / Mac Catalyst, 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): // 1. Build the xcframework, zip it, and attach it to the GitHub Release. @@ -83,6 +84,7 @@ let package = Package( name: "OneDSSwift", platforms: [ .iOS(.v12), + .macCatalyst(.v14), .macOS(.v10_15), ], products: [ @@ -118,13 +120,13 @@ let package = Package( .linkedLibrary("c++"), .linkedLibrary("sqlite3"), .linkedLibrary("z"), - .linkedFramework("CFNetwork", .when(platforms: [.iOS, .macOS])), - .linkedFramework("CoreFoundation", .when(platforms: [.iOS, .macOS])), - .linkedFramework("Foundation", .when(platforms: [.iOS, .macOS])), - .linkedFramework("Network", .when(platforms: [.iOS, .macOS])), - .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS])), + .linkedFramework("CFNetwork", .when(platforms: [.iOS, .macCatalyst, .macOS])), + .linkedFramework("CoreFoundation", .when(platforms: [.iOS, .macCatalyst, .macOS])), + .linkedFramework("Foundation", .when(platforms: [.iOS, .macCatalyst, .macOS])), + .linkedFramework("Network", .when(platforms: [.iOS, .macCatalyst, .macOS])), + .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macCatalyst, .macOS])), .linkedFramework("IOKit", .when(platforms: [.macOS])), - .linkedFramework("UIKit", .when(platforms: [.iOS])), + .linkedFramework("UIKit", .when(platforms: [.iOS, .macCatalyst])), ]), ] ) diff --git a/build-ios.sh b/build-ios.sh index d316fe2fa..731572865 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -4,7 +4,7 @@ # build-ios.sh [clean] [release|debug] ${ARCH} ${PLATFORM} # where # ARCH = arm64|arm64e|x86_64 -# PLATFORM = iphoneos|iphonesimulator|xros|xrsimulator +# PLATFORM = iphoneos|iphonesimulator|maccatalyst|xros|xrsimulator if [ "$1" == "clean" ]; then echo "build-ios.sh: cleaning previous build artifacts" @@ -37,7 +37,7 @@ elif [ "$1" == "x86_64" ]; then shift fi -# the last param is expected to specify the platform name: iphoneos|iphonesimulator|xros|xrsimulator +# the last param is expected to specify the platform name: iphoneos|iphonesimulator|maccatalyst|xros|xrsimulator # so if it is non-empty and it is not "device", we take it as a valid platform name # otherwise we fall back to old iOS logic which only supported iphoneos|iphonesimulator IOS_PLAT="iphonesimulator" @@ -54,13 +54,23 @@ DEPLOYMENT_TARGET="" if [ "$IOS_PLAT" == "iphoneos" ] || [ "$IOS_PLAT" == "iphonesimulator" ]; then SYS_NAME="iOS" + IOS_SYSROOT="$IOS_PLAT" DEPLOYMENT_TARGET="$IOS_DEPLOYMENT_TARGET" if [ -z "$DEPLOYMENT_TARGET" ]; then DEPLOYMENT_TARGET="12.0" FORCE_RESET_DEPLOYMENT_TARGET=YES fi +elif [ "$IOS_PLAT" == "maccatalyst" ]; then + SYS_NAME="iOS" + IOS_SYSROOT="macosx" + DEPLOYMENT_TARGET="$MACCATALYST_DEPLOYMENT_TARGET" + if [ -z "$DEPLOYMENT_TARGET" ]; then + DEPLOYMENT_TARGET="14.0" + FORCE_RESET_DEPLOYMENT_TARGET=YES + fi elif [ "$IOS_PLAT" == "xros" ] || [ "$IOS_PLAT" == "xrsimulator" ]; then SYS_NAME="visionOS" + IOS_SYSROOT="$IOS_PLAT" DEPLOYMENT_TARGET="$XROS_DEPLOYMENT_TARGET" if [ -z "$DEPLOYMENT_TARGET" ]; then DEPLOYMENT_TARGET="1.0" @@ -92,7 +102,7 @@ cd out CMAKE_PACKAGE_TYPE=tgz -cmake_cmd="cmake -DCMAKE_OSX_SYSROOT=$IOS_PLAT -DCMAKE_SYSTEM_NAME=$SYS_NAME -DCMAKE_IOS_ARCH_ABI=$IOS_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DBUILD_IOS=YES -DIOS_ARCH=$IOS_ARCH -DIOS_PLAT=$IOS_PLAT -DIOS_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PACKAGE_TYPE=$CMAKE_PACKAGE_TYPE -DFORCE_RESET_DEPLOYMENT_TARGET=$FORCE_RESET_DEPLOYMENT_TARGET $CMAKE_OPTS .." +cmake_cmd="cmake -DCMAKE_OSX_SYSROOT=$IOS_SYSROOT -DCMAKE_SYSTEM_NAME=$SYS_NAME -DCMAKE_IOS_ARCH_ABI=$IOS_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DBUILD_IOS=YES -DIOS_ARCH=$IOS_ARCH -DIOS_PLAT=$IOS_PLAT -DIOS_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PACKAGE_TYPE=$CMAKE_PACKAGE_TYPE -DFORCE_RESET_DEPLOYMENT_TARGET=$FORCE_RESET_DEPLOYMENT_TARGET $CMAKE_OPTS .." echo "${cmake_cmd}" eval $cmake_cmd diff --git a/tools/apple/README.md b/tools/apple/README.md index c2d98771d..ca13f40a3 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -25,7 +25,7 @@ so the xcframework just needs to vend a Clang module named `ObjCModule` | 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, lipo's the simulator/macOS archs where needed, and assembles the xcframework with `xcodebuild -create-xcframework` | +| `tools/apple/build-xcframework.sh` | Builds a static `libmat.a` per Apple slice, lipo's the simulator/Catalyst/macOS archs where needed, 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 | | `tools/apple/MATTelemetryAvailability.json` | Build-time optional-module manifest consumed by `Package.swift` so Swift sources match the xcframework contents | @@ -69,19 +69,20 @@ before the release is cut). ## Validation performed -- `tools/apple/build-xcframework.sh release` builds the iOS device and simulator - slices, plus a universal macOS slice, and prints the SPM checksum. +- `tools/apple/build-xcframework.sh release` builds the iOS device, iOS + simulator, Mac Catalyst, and macOS slices, and prints the SPM checksum. - `swift build` validates local macOS SwiftPM consumption. - `xcodebuild -scheme OneDSSwift -destination 'generic/platform=iOS Simulator' build` - validates local SwiftPM consumption. -- A small Obj-C module/static-link smoke test was built and run on an iOS - Simulator. + validates iOS Simulator SwiftPM consumption. +- `xcodebuild -scheme OneDSSwift -destination 'platform=macOS,variant=Mac Catalyst' build` + validates Mac Catalyst SwiftPM consumption. +- Small Obj-C module/static-link smoke tests validate binary module linkability. ## Known gaps / TODO -- **Catalyst / visionOS slices** — iOS device, iOS simulator, and macOS are wired - up in this first pass; Catalyst and visionOS still need separate slice wiring - and validation. +- **visionOS slices** — iOS device, iOS simulator, Mac Catalyst, and macOS are + wired up in this first pass; visionOS still needs separate slice wiring and + validation. - **Code signing** — release xcframeworks are typically signed; add a signing step before zipping for distribution. - **Release workflow validation** — exercise `.github/workflows/spm-release.yml` diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 85d272d3d..961e8a0e3 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -14,10 +14,11 @@ # build/apple/MATTelemetry.xcframework.zip (+ prints the SPM checksum) # # Slices built here: iOS device (arm64), iOS simulator (arm64 + x86_64 fat), -# and macOS (arm64 + x86_64 universal). +# Mac Catalyst (arm64 + x86_64 fat), and macOS (arm64 + x86_64 universal). # # NOTE: this is a first-pass scaffold. It has been validated on macOS for iOS -# device, simulator, and macOS slices; Catalyst/visionOS slices are still TODO. +# device, simulator, Mac Catalyst, and macOS slices; visionOS slices are still +# TODO. set -euo pipefail @@ -133,6 +134,8 @@ build_slice() { # clean-arg arch platform out-subdir build_slice clean arm64 iphoneos ios-arm64 build_slice "" arm64 iphonesimulator ios-arm64-sim build_slice "" x86_64 iphonesimulator ios-x86_64-sim +build_slice "" arm64 maccatalyst maccatalyst-arm64 +build_slice "" x86_64 maccatalyst maccatalyst-x86_64 # Fat simulator archive (arm64 + x86_64) -- a single xcframework slice cannot # mix device and simulator, but it can contain multiple archs for one platform. @@ -140,6 +143,12 @@ mkdir -p "$OUT/ios-simulator" lipo -create "$OUT/ios-arm64-sim/$LIB" "$OUT/ios-x86_64-sim/$LIB" \ -output "$OUT/ios-simulator/$LIB" +# Fat Catalyst archive (arm64 + x86_64), emitted as a separate platform variant +# from both iOS simulator and native macOS. +mkdir -p "$OUT/maccatalyst" +lipo -create "$OUT/maccatalyst-arm64/$LIB" "$OUT/maccatalyst-x86_64/$LIB" \ + -output "$OUT/maccatalyst/$LIB" + # Native universal macOS archive. Build only the `mat` target in an isolated # CMake build directory so switching away from the iOS toolchain does not # disturb the already-copied iOS archives. @@ -167,6 +176,7 @@ rm -rf "$OUT/MATTelemetry.xcframework" xcodebuild -create-xcframework \ -library "$OUT/ios-arm64/$LIB" -headers "$HDRS" \ -library "$OUT/ios-simulator/$LIB" -headers "$HDRS" \ + -library "$OUT/maccatalyst/$LIB" -headers "$HDRS" \ -library "$OUT/macos-universal/$LIB" -headers "$HDRS" \ -output "$OUT/MATTelemetry.xcframework" echo "Created $OUT/MATTelemetry.xcframework" From 1d89f6d463a9b80067cc706e94b64f4e0ef6a28e Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 01:28:10 -0500 Subject: [PATCH 11/40] Add visionOS slices to SPM xcframework Build visionOS device and simulator archives, advertise visionOS in the Swift package, and document the expanded validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 12 ++++++++++-- Package.swift | 19 ++++++++++--------- tools/apple/README.md | 8 ++++---- tools/apple/build-xcframework.sh | 12 +++++++++--- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d117cb42..b67461ba2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,7 @@ if(APPLE) if (${IOS_PLAT} STREQUAL "iphonesimulator") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") - elseif(NOT ${IOS_PLAT} STREQUAL "maccatalyst") + elseif(${IOS_PLAT} STREQUAL "iphoneos") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") endif() @@ -67,7 +67,15 @@ if(APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${IOS_ARCH}-apple-ios${IOS_DEPLOYMENT_TARGET}-macabi") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${IOS_ARCH}-apple-ios${IOS_DEPLOYMENT_TARGET}-macabi") set(IOS_PLATFORM "macosx") - elseif((${IOS_PLAT} STREQUAL "iphoneos") OR (${IOS_PLAT} STREQUAL "iphonesimulator") OR (${IOS_PLAT} STREQUAL "xros") OR (${IOS_PLAT} STREQUAL "xrsimulator")) + elseif(${IOS_PLAT} STREQUAL "xros") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}") + set(IOS_PLATFORM "${IOS_PLAT}") + elseif(${IOS_PLAT} STREQUAL "xrsimulator") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}-simulator") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}-simulator") + set(IOS_PLATFORM "${IOS_PLAT}") + elseif((${IOS_PLAT} STREQUAL "iphoneos") OR (${IOS_PLAT} STREQUAL "iphonesimulator")) set(IOS_PLATFORM "${IOS_PLAT}") else() message(FATAL_ERROR "Unrecognized iOS platform '${IOS_PLAT}'") diff --git a/Package.swift b/Package.swift index 6144610d0..e7b4e56bd 100644 --- a/Package.swift +++ b/Package.swift @@ -17,9 +17,9 @@ // 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, add -// this package as a local dependency or build the package with the desired -// Xcode destination. +// 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): // 1. Build the xcframework, zip it, and attach it to the GitHub Release. @@ -86,6 +86,7 @@ let package = Package( .iOS(.v12), .macCatalyst(.v14), .macOS(.v10_15), + .visionOS(.v1), ], products: [ .library(name: "OneDSSwift", targets: ["OneDSSwift"]), @@ -120,13 +121,13 @@ let package = Package( .linkedLibrary("c++"), .linkedLibrary("sqlite3"), .linkedLibrary("z"), - .linkedFramework("CFNetwork", .when(platforms: [.iOS, .macCatalyst, .macOS])), - .linkedFramework("CoreFoundation", .when(platforms: [.iOS, .macCatalyst, .macOS])), - .linkedFramework("Foundation", .when(platforms: [.iOS, .macCatalyst, .macOS])), - .linkedFramework("Network", .when(platforms: [.iOS, .macCatalyst, .macOS])), - .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macCatalyst, .macOS])), + .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])), + .linkedFramework("UIKit", .when(platforms: [.iOS, .macCatalyst, .visionOS])), ]), ] ) diff --git a/tools/apple/README.md b/tools/apple/README.md index ca13f40a3..2fed02012 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -70,19 +70,19 @@ before the release is cut). ## Validation performed - `tools/apple/build-xcframework.sh release` builds the iOS device, iOS - simulator, Mac Catalyst, and macOS slices, and prints the SPM checksum. + simulator, Mac Catalyst, visionOS device, visionOS simulator, and macOS + slices, and prints the SPM checksum. - `swift build` validates local macOS SwiftPM consumption. - `xcodebuild -scheme OneDSSwift -destination 'generic/platform=iOS Simulator' build` validates iOS Simulator SwiftPM consumption. - `xcodebuild -scheme OneDSSwift -destination 'platform=macOS,variant=Mac Catalyst' build` validates Mac Catalyst SwiftPM consumption. +- `xcodebuild -scheme OneDSSwift -destination 'generic/platform=visionOS Simulator' build` + validates visionOS Simulator SwiftPM consumption. - Small Obj-C module/static-link smoke tests validate binary module linkability. ## Known gaps / TODO -- **visionOS slices** — iOS device, iOS simulator, Mac Catalyst, and macOS are - wired up in this first pass; visionOS still needs separate slice wiring and - validation. - **Code signing** — release xcframeworks are typically signed; add a signing step before zipping for distribution. - **Release workflow validation** — exercise `.github/workflows/spm-release.yml` diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 961e8a0e3..023fe7d3f 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -14,11 +14,11 @@ # build/apple/MATTelemetry.xcframework.zip (+ prints the SPM checksum) # # Slices built here: iOS device (arm64), iOS simulator (arm64 + x86_64 fat), -# Mac Catalyst (arm64 + x86_64 fat), and macOS (arm64 + x86_64 universal). +# Mac Catalyst (arm64 + x86_64 fat), visionOS device/simulator (arm64), and +# macOS (arm64 + x86_64 universal). # # NOTE: this is a first-pass scaffold. It has been validated on macOS for iOS -# device, simulator, Mac Catalyst, and macOS slices; visionOS slices are still -# TODO. +# device, simulator, Mac Catalyst, visionOS, and macOS slices. set -euo pipefail @@ -137,6 +137,10 @@ build_slice "" x86_64 iphonesimulator ios-x86_64-sim build_slice "" arm64 maccatalyst maccatalyst-arm64 build_slice "" x86_64 maccatalyst maccatalyst-x86_64 +# visionOS uses a different CMake system name, so start it from a fresh cache. +build_slice clean arm64 xros visionos-arm64 +build_slice "" arm64 xrsimulator visionos-arm64-sim + # Fat simulator archive (arm64 + x86_64) -- a single xcframework slice cannot # mix device and simulator, but it can contain multiple archs for one platform. mkdir -p "$OUT/ios-simulator" @@ -177,6 +181,8 @@ xcodebuild -create-xcframework \ -library "$OUT/ios-arm64/$LIB" -headers "$HDRS" \ -library "$OUT/ios-simulator/$LIB" -headers "$HDRS" \ -library "$OUT/maccatalyst/$LIB" -headers "$HDRS" \ + -library "$OUT/visionos-arm64/$LIB" -headers "$HDRS" \ + -library "$OUT/visionos-arm64-sim/$LIB" -headers "$HDRS" \ -library "$OUT/macos-universal/$LIB" -headers "$HDRS" \ -output "$OUT/MATTelemetry.xcframework" echo "Created $OUT/MATTelemetry.xcframework" From 024684724006f8be8d835e104bbd54ca00c3afe7 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 01:36:56 -0500 Subject: [PATCH 12/40] Address SPM release review comments Clarify release automation comments, make the checksum workflow step unambiguous, and ensure forced xcframework CMake flags cannot be overridden by caller-provided CMAKE_OPTS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 3 ++- Package.swift | 9 ++++----- tools/apple/build-xcframework.sh | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index 28eb1f414..6c129b4bd 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -92,7 +92,8 @@ jobs: if: ${{ steps.ver.outputs.skip != 'true' }} run: | set -euo pipefail - echo "checksum=$(swift package compute-checksum "build/apple/$ARTIFACT")" >> "$GITHUB_OUTPUT" + 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' }} diff --git a/Package.swift b/Package.swift index e7b4e56bd..1a1806ccb 100644 --- a/Package.swift +++ b/Package.swift @@ -22,11 +22,10 @@ // with the desired Xcode destination. // // 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. +// .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 diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 023fe7d3f..406ed7513 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -38,7 +38,7 @@ esac # Force a STATIC libmat that includes the Obj-C wrappers, regardless of the # repo's default library type. -export CMAKE_OPTS="-DBUILD_SHARED_LIBS=OFF -DBUILD_OBJC_WRAPPER=YES ${CMAKE_OPTS:-}" +export CMAKE_OPTS="${CMAKE_OPTS:-} -DBUILD_SHARED_LIBS=OFF -DBUILD_OBJC_WRAPPER=YES" rm -rf "$OUT" mkdir -p "$OUT" From 9634894be8bc3e2b216b683e86a3decd5db5d4e4 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 01:46:12 -0500 Subject: [PATCH 13/40] Address follow-up SPM review comments Update the package comment for optional Swift sources, fail early for unsupported Apple platforms, and skip SPM tag publishing when the release tag already exists. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 4 ++++ Package.swift | 6 ++---- build-ios.sh | 3 +++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index 6c129b4bd..bd485b04f 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -130,6 +130,10 @@ jobs: 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 diff --git a/Package.swift b/Package.swift index 1a1806ccb..c7dc5346d 100644 --- a/Package.swift +++ b/Package.swift @@ -106,10 +106,8 @@ let package = Package( 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. + // xcframework. The conditional source exclusions above must stay in sync + // with the headers baked into the xcframework. .target( name: "OneDSSwift", dependencies: ["MATTelemetry"], diff --git a/build-ios.sh b/build-ios.sh index 731572865..8fb50094e 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -76,6 +76,9 @@ elif [ "$IOS_PLAT" == "xros" ] || [ "$IOS_PLAT" == "xrsimulator" ]; then DEPLOYMENT_TARGET="1.0" FORCE_RESET_DEPLOYMENT_TARGET=YES fi +else + echo "ERROR: unsupported Apple platform '$IOS_PLAT'. Expected iphoneos, iphonesimulator, maccatalyst, xros, or xrsimulator." 1>&2 + exit 1 fi echo "deployment target = $DEPLOYMENT_TARGET" From 5923e5cfed97e943fdeeacca6caf970041ca7bad Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 01:53:42 -0500 Subject: [PATCH 14/40] Validate SPM Apple platforms in release workflow Run SwiftPM and Xcode package builds for macOS, iOS Simulator, Mac Catalyst, and visionOS Simulator after producing the xcframework artifact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index bd485b04f..ac06c2649 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -37,7 +37,7 @@ jobs: if: >- ${{ github.event_name == 'workflow_dispatch' || (github.event.release.draft == false && github.event.release.prerelease == false) }} - runs-on: macos-14 # provides Xcode (xcodebuild, swift) + runs-on: macos-15 # provides Xcode with Apple platform SDKs (xcodebuild, swift) env: ARTIFACT: MATTelemetry.xcframework.zip steps: @@ -87,6 +87,15 @@ jobs: 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 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' }} From ccceaa5fed519193ba4d56376c80803ee19d9e49 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 02:09:05 -0500 Subject: [PATCH 15/40] Skip package creation for xcframework slices Allow build-ios.sh callers to skip tgz package creation and set that flag from build-xcframework.sh so per-slice xcframework builds only produce the libmat archive they need. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-ios.sh | 6 +++++- tools/apple/build-xcframework.sh | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build-ios.sh b/build-ios.sh index 8fb50094e..c3da652e7 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -111,4 +111,8 @@ eval $cmake_cmd make -make package +if [ "${MATTELEMETRY_SKIP_PACKAGE:-}" == "1" ]; then + echo "MATTELEMETRY_SKIP_PACKAGE=1: skipping package creation" +else + make package +fi diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 406ed7513..036c29c53 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -126,7 +126,7 @@ build_slice() { # clean-arg arch platform out-subdir shift local arch="$1" plat="$2" sub="$3" echo "=== building $arch / $plat ($CONFIG) ===" - ( cd "$ROOT" && ./build-ios.sh $clean_arg "$CONFIG" "$arch" "$plat" ) + ( cd "$ROOT" && MATTELEMETRY_SKIP_PACKAGE=1 ./build-ios.sh $clean_arg "$CONFIG" "$arch" "$plat" ) mkdir -p "$OUT/$sub" cp "$ROOT/out/lib/$LIB" "$OUT/$sub/$LIB" } From 5c6f57414336b6b28c7bf4f69aedacb41b2d310a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 02:22:13 -0500 Subject: [PATCH 16/40] Clean up Apple SPM README Refresh the prototype documentation to match the current multi-platform xcframework, release workflow, validation coverage, and remaining gaps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/apple/README.md | 143 +++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 63 deletions(-) diff --git a/tools/apple/README.md b/tools/apple/README.md index 2fed02012..9c791e48b 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -1,89 +1,106 @@ -# Swift Package Manager (xcframework) — prototype +# Swift Package Manager xcframework prototype -**Status: validated prototype.** 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). +**Status: validated prototype.** This packages the 1DS C++ SDK for Apple +developers through Swift Package Manager (SPM), using a prebuilt xcframework for +the C++ core plus Obj-C wrappers and compiling the Swift API from source. -## Approach +## Package shape -SPM cannot practically compile this SDK's C++ tree from source (CMake build, -Bond codegen, vendored sqlite3/zlib, heavy platform conditionals). So: +SPM is not a good fit for compiling this SDK's full C++ tree directly because +the SDK depends on CMake, Bond codegen, vendored sqlite3/zlib, and platform +conditionals. Instead, the package is split into: -| Layer | How it ships | +| Layer | Packaging | | --- | --- | -| 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 | +| C++ core + Obj-C wrappers (`ODW*`) | `MATTelemetry.xcframework` binary target | +| Swift API (`OneDSSwift`) | Source target in `wrappers/swift/Sources/OneDSSwift` | -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`). +The xcframework vendors a Clang module named `ObjCModule` through +`tools/apple/module.modulemap` and `MATTelemetry-umbrella.h`, matching the +existing Swift sources' `import ObjCModule`. -## Files +## Supported slices + +`tools/apple/build-xcframework.sh release` builds: + +| Platform | Slice | +| --- | --- | +| iOS device | `ios-arm64` | +| iOS Simulator | `ios-arm64_x86_64-simulator` | +| Mac Catalyst | `ios-arm64_x86_64-maccatalyst` | +| macOS | `macos-arm64_x86_64` | +| visionOS device | `xros-arm64` | +| visionOS Simulator | `xros-arm64-simulator` | + +## Important 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, lipo's the simulator/Catalyst/macOS archs where needed, 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 | -| `tools/apple/MATTelemetryAvailability.json` | Build-time optional-module manifest consumed by `Package.swift` so Swift sources match the xcframework contents | +| `Package.swift` | Root SPM manifest: binary target + Swift source target | +| `tools/apple/build-xcframework.sh` | Builds static `libmat.a` slices and assembles `MATTelemetry.xcframework` | +| `tools/apple/module.modulemap` | Defines the `ObjCModule` Clang module | +| `tools/apple/MATTelemetry-umbrella.h` | Base umbrella for always-available Obj-C wrapper headers | +| `tools/apple/MATTelemetryAvailability.json` | Optional-module manifest consumed by `Package.swift` | +| `.github/workflows/spm-release.yml` | Release automation for the hosted xcframework and SPM tag | + +## Local build -## Build (on macOS) +Run on macOS with Xcode and CMake: ```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 +# -> build/apple/MATTelemetry.xcframework.zip +# -> prints the SwiftPM checksum ``` -## Consume +For local development, `Package.swift` points at +`build/apple/MATTelemetry.xcframework`. `swift build` validates macOS +consumption; use Xcode destinations for iOS Simulator, Mac Catalyst, and +visionOS Simulator. -- **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: +## Consumption - ```swift - .package(url: "https://github.com/microsoft/cpp_client_telemetry.git", from: "3.10.161") - ``` +- **Local:** add this repository as a local package dependency after building + `build/apple/MATTelemetry.xcframework`. +- **Released:** add the repository URL in Xcode and pin the parallel + 3-component SemVer tag published by the release workflow: -## Release wiring +```swift +.package(url: "https://github.com/microsoft/cpp_client_telemetry.git", from: "3.10.161") +``` + +The SDK's native release tags are 4-component (`vX.Y.Z.W`), which SPM does not +accept as SemVer. The release workflow publishes the corresponding `X.Y.Z` tag. + +## Release workflow -`.github/workflows/spm-release.yml` automates distribution on each published -release (a 4-component `vX.Y.Z.W` tag). On a macOS runner it: +`.github/workflows/spm-release.yml` runs for published SDK releases and manual +dispatch. 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. +1. Builds and zips `MATTelemetry.xcframework`. +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:`. +5. Commits the resolved manifest and pushes the 3-component SPM tag. -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). +The private `lib/modules` submodule is intentionally not fetched by the release +workflow, so optional module headers and Swift sources are gated by +`MATTelemetryAvailability.json`. ## Validation performed -- `tools/apple/build-xcframework.sh release` builds the iOS device, iOS - simulator, Mac Catalyst, visionOS device, visionOS simulator, and macOS - slices, and prints the SPM checksum. -- `swift build` validates local macOS SwiftPM consumption. -- `xcodebuild -scheme OneDSSwift -destination 'generic/platform=iOS Simulator' build` - validates iOS Simulator SwiftPM consumption. -- `xcodebuild -scheme OneDSSwift -destination 'platform=macOS,variant=Mac Catalyst' build` - validates Mac Catalyst SwiftPM consumption. -- `xcodebuild -scheme OneDSSwift -destination 'generic/platform=visionOS Simulator' build` - validates visionOS Simulator SwiftPM consumption. -- Small Obj-C module/static-link smoke tests validate binary module linkability. - -## Known gaps / TODO - -- **Code signing** — release xcframeworks are typically signed; add a signing - step before zipping for distribution. -- **Release workflow validation** — exercise `.github/workflows/spm-release.yml` - end-to-end on an actual published release. +- Full xcframework build for iOS, iOS Simulator, Mac Catalyst, macOS, visionOS, + and visionOS Simulator. +- SwiftPM/Xcode builds for macOS, iOS Simulator, Mac Catalyst, visionOS + Simulator, and visionOS device. +- External TelemetryTest package consumer builds, including visionOS. +- Obj-C module/static-link smoke tests for representative binary slices. +- Apple Vision Pro simulator runtime installation and boot. + +## Known gaps + +- Release xcframework signing/notarization. +- End-to-end execution of `.github/workflows/spm-release.yml` on a real + published release. From 79f55faaff49f829ced5becefdda8c1a89037eae Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 02:24:19 -0500 Subject: [PATCH 17/40] Remove status label from Apple SPM README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/apple/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/apple/README.md b/tools/apple/README.md index 9c791e48b..a5a7eeddf 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -1,8 +1,8 @@ # Swift Package Manager xcframework prototype -**Status: validated prototype.** This packages the 1DS C++ SDK for Apple -developers through Swift Package Manager (SPM), using a prebuilt xcframework for -the C++ core plus Obj-C wrappers and compiling the Swift API from source. +This packages the 1DS C++ SDK for Apple developers through Swift Package Manager +(SPM), using a prebuilt xcframework for the C++ core plus Obj-C wrappers and +compiling the Swift API from source. ## Package shape From 27049fe003c5603ca9dcf2b9f64530cace354215 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 02:25:33 -0500 Subject: [PATCH 18/40] Assert SPM platforms in release workflow Validate the Package.swift platform list with swift package dump-package before running the platform-specific package builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index ac06c2649..2fbd94ba1 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -91,6 +91,23 @@ jobs: 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 From 55c4ce5dc7f5e010551839ac90a6dd49788fce08 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 02:49:37 -0500 Subject: [PATCH 19/40] Clean up xcframework build script comments Remove a drifting line-number reference and keep section headings sequential. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/apple/build-xcframework.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 036c29c53..30dd95c1c 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -25,7 +25,7 @@ set -euo pipefail CONFIG="${1:-release}" ROOT="$(cd "$(dirname "$0")/../.." && pwd)" OUT="$ROOT/build/apple" -LIB="libmat.a" # mat target; the Obj-C wrappers compile into it (lib/CMakeLists.txt:217) +LIB="libmat.a" # mat target; the Obj-C wrappers compile into it. case "$CONFIG" in release) CMAKE_BUILD_TYPE="Release" ;; @@ -175,7 +175,7 @@ cmake --build "$MACOS_BUILD" --target mat mkdir -p "$OUT/macos-universal" cp "$MACOS_BUILD/lib/$LIB" "$OUT/macos-universal/$LIB" -# --- 4. Assemble the xcframework --------------------------------------------- +# --- 3. Assemble the xcframework --------------------------------------------- rm -rf "$OUT/MATTelemetry.xcframework" xcodebuild -create-xcframework \ -library "$OUT/ios-arm64/$LIB" -headers "$HDRS" \ @@ -187,7 +187,7 @@ xcodebuild -create-xcframework \ -output "$OUT/MATTelemetry.xcframework" echo "Created $OUT/MATTelemetry.xcframework" -# --- 5. Zip + checksum for release distribution ------------------------------ +# --- 4. Zip + checksum for release distribution ------------------------------ ( cd "$OUT" && rm -f MATTelemetry.xcframework.zip \ && zip -qry MATTelemetry.xcframework.zip MATTelemetry.xcframework ) echo "Zipped: $OUT/MATTelemetry.xcframework.zip" From 9a18330a8fa5ca536070edb9f603fac9a0fdaa53 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 03:21:34 -0500 Subject: [PATCH 20/40] Use fresh build cache for each xcframework slice Build each Apple slice from a clean CMake out directory, while preserving the build-tools marker, and restrict xcframework slice builds to the libmat archive target inputs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/apple/build-xcframework.sh | 42 +++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 30dd95c1c..00af27b1d 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -36,9 +36,17 @@ case "$CONFIG" in ;; esac -# Force a STATIC libmat that includes the Obj-C wrappers, regardless of the -# repo's default library type. -export CMAKE_OPTS="${CMAKE_OPTS:-} -DBUILD_SHARED_LIBS=OFF -DBUILD_OBJC_WRAPPER=YES" +# Build only the static libmat archive with Obj-C wrappers; slice builds do not +# need the repo's test, Swift wrapper, or package targets. +CMAKE_OPTS="${CMAKE_OPTS:-}" +CMAKE_OPTS="$CMAKE_OPTS -DBUILD_SHARED_LIBS=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DBUILD_OBJC_WRAPPER=YES" +CMAKE_OPTS="$CMAKE_OPTS -DBUILD_TEST_TOOL=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DBUILD_UNIT_TESTS=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DBUILD_FUNC_TESTS=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DBUILD_SWIFT_WRAPPER=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DBUILD_PACKAGE=OFF" +export CMAKE_OPTS rm -rf "$OUT" mkdir -p "$OUT" @@ -121,25 +129,26 @@ cp "$ROOT"/tools/apple/MATTelemetry-umbrella.h "$HDRS/" } >> "$HDRS/MATTelemetry-umbrella.h" # --- 2. Build one static lib per (arch, platform) ---------------------------- -build_slice() { # clean-arg arch platform out-subdir - local clean_arg="$1" - shift +build_slice() { # arch platform out-subdir local arch="$1" plat="$2" sub="$3" echo "=== building $arch / $plat ($CONFIG) ===" - ( cd "$ROOT" && MATTELEMETRY_SKIP_PACKAGE=1 ./build-ios.sh $clean_arg "$CONFIG" "$arch" "$plat" ) + ( + cd "$ROOT" + rm -f CMakeCache.txt *.cmake + rm -rf out + MATTELEMETRY_SKIP_PACKAGE=1 ./build-ios.sh "$CONFIG" "$arch" "$plat" + ) mkdir -p "$OUT/$sub" cp "$ROOT/out/lib/$LIB" "$OUT/$sub/$LIB" } -build_slice clean arm64 iphoneos ios-arm64 -build_slice "" arm64 iphonesimulator ios-arm64-sim -build_slice "" x86_64 iphonesimulator ios-x86_64-sim -build_slice "" arm64 maccatalyst maccatalyst-arm64 -build_slice "" x86_64 maccatalyst maccatalyst-x86_64 - -# visionOS uses a different CMake system name, so start it from a fresh cache. -build_slice clean arm64 xros visionos-arm64 -build_slice "" arm64 xrsimulator visionos-arm64-sim +build_slice arm64 iphoneos ios-arm64 +build_slice arm64 iphonesimulator ios-arm64-sim +build_slice x86_64 iphonesimulator ios-x86_64-sim +build_slice arm64 maccatalyst maccatalyst-arm64 +build_slice x86_64 maccatalyst maccatalyst-x86_64 +build_slice arm64 xros visionos-arm64 +build_slice arm64 xrsimulator visionos-arm64-sim # Fat simulator archive (arm64 + x86_64) -- a single xcframework slice cannot # mix device and simulator, but it can contain multiple archs for one platform. @@ -169,7 +178,6 @@ cmake -S "$ROOT" -B "$MACOS_BUILD" \ -DBUILD_UNIT_TESTS=OFF \ -DBUILD_FUNC_TESTS=OFF \ -DBUILD_SWIFT_WRAPPER=OFF \ - -DBUILD_PACKAGE=OFF \ $CMAKE_OPTS cmake --build "$MACOS_BUILD" --target mat mkdir -p "$OUT/macos-universal" From 279ffd39a15e28b1645c83395842e99a1e403e8d Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 03:47:18 -0500 Subject: [PATCH 21/40] Use portable shell comparison in iOS build Use POSIX '=' for the MATTELEMETRY_SKIP_PACKAGE check in build-ios.sh. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-ios.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-ios.sh b/build-ios.sh index c3da652e7..d96b93bc4 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -111,7 +111,7 @@ eval $cmake_cmd make -if [ "${MATTELEMETRY_SKIP_PACKAGE:-}" == "1" ]; then +if [ "${MATTELEMETRY_SKIP_PACKAGE:-}" = "1" ]; then echo "MATTELEMETRY_SKIP_PACKAGE=1: skipping package creation" else make package From 7bc7415c175addd5953992e7ad05421af01087dc Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 11:26:25 -0500 Subject: [PATCH 22/40] Quote ${IOS_PLAT}/${IOS_ARCH} in Apple if() conditions Addresses Copilot review on #1486 (CMakeLists.txt:70, :108): the new Apple-slice platform conditionals used unquoted ${IOS_PLAT} (and the adjacent ${IOS_ARCH}) inside if()/elseif(). If the variable is ever empty/undefined, `if(${IOS_PLAT} STREQUAL "...")` expands to `if( STREQUAL "...")`, which is a hard CMake parse error that aborts configuration. Quoting the expansion ("${IOS_PLAT}") keeps the compare well-formed (empty -> false) and matches the existing `if("${MAC_ARCH}" STREQUAL ...)` idiom already used in this file. Quoted all 7 ${IOS_PLAT} comparisons (lines 57/60/66/70/74/78/104) and the 3 ${IOS_ARCH} comparisons (84/88/92). ${CMAKE_SYSTEM_NAME} (line 313) is left as-is: it is always defined by CMake, so it cannot trigger the empty-expansion parse error. Verified with a minimal CMake repro: the quoted form configures cleanly with the variable undefined and still matches when set, whereas the old unquoted form errors out at configure time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b67461ba2..28d43fb7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,42 +54,42 @@ if(APPLE) if(FORCE_RESET_OSX_DEPLOYMENT_TARGET) set(CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE) - if (${IOS_PLAT} STREQUAL "iphonesimulator") + if ("${IOS_PLAT}" STREQUAL "iphonesimulator") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") - elseif(${IOS_PLAT} STREQUAL "iphoneos") + elseif("${IOS_PLAT}" STREQUAL "iphoneos") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") endif() endif() - if(${IOS_PLAT} STREQUAL "maccatalyst") + if("${IOS_PLAT}" STREQUAL "maccatalyst") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${IOS_ARCH}-apple-ios${IOS_DEPLOYMENT_TARGET}-macabi") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${IOS_ARCH}-apple-ios${IOS_DEPLOYMENT_TARGET}-macabi") set(IOS_PLATFORM "macosx") - elseif(${IOS_PLAT} STREQUAL "xros") + elseif("${IOS_PLAT}" STREQUAL "xros") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}") set(IOS_PLATFORM "${IOS_PLAT}") - elseif(${IOS_PLAT} STREQUAL "xrsimulator") + elseif("${IOS_PLAT}" STREQUAL "xrsimulator") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}-simulator") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -target ${IOS_ARCH}-apple-xros${IOS_DEPLOYMENT_TARGET}-simulator") set(IOS_PLATFORM "${IOS_PLAT}") - elseif((${IOS_PLAT} STREQUAL "iphoneos") OR (${IOS_PLAT} STREQUAL "iphonesimulator")) + elseif(("${IOS_PLAT}" STREQUAL "iphoneos") OR ("${IOS_PLAT}" STREQUAL "iphonesimulator")) set(IOS_PLATFORM "${IOS_PLAT}") else() message(FATAL_ERROR "Unrecognized iOS platform '${IOS_PLAT}'") endif() - if(${IOS_ARCH} STREQUAL "x86_64") + if("${IOS_ARCH}" STREQUAL "x86_64") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch x86_64") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64") set(CMAKE_SYSTEM_PROCESSOR x86_64) - elseif(${IOS_ARCH} STREQUAL "arm64") + elseif("${IOS_ARCH}" STREQUAL "arm64") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch arm64") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch arm64") set(CMAKE_SYSTEM_PROCESSOR arm64) - elseif(${IOS_ARCH} STREQUAL "arm64e") + elseif("${IOS_ARCH}" STREQUAL "arm64e") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch arm64e") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch arm64e") set(CMAKE_SYSTEM_PROCESSOR arm64e) @@ -101,7 +101,7 @@ if(APPLE) OUTPUT_VARIABLE CMAKE_OSX_SYSROOT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(${IOS_PLAT} STREQUAL "maccatalyst") + if("${IOS_PLAT}" STREQUAL "maccatalyst") set(IOS_SUPPORT_FRAMEWORKS "${CMAKE_OSX_SYSROOT}/System/iOSSupport/System/Library/Frameworks") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -iframework ${IOS_SUPPORT_FRAMEWORKS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -iframework ${IOS_SUPPORT_FRAMEWORKS}") From 9d522660d0051b0b6454965be749961c0b5721be Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 12:10:01 -0500 Subject: [PATCH 23/40] Rename public Clang module ObjCModule -> MATTelemetryObjC The Obj-C wrapper module vended by MATTelemetry.xcframework (and the local wrappers/swift package) was named `ObjCModule` -- a generic name that consumers `import`. Once #1486 makes this module public via SPM, a generic name risks colliding with another binary/SPM package that also vends an `ObjCModule`, and pollutes the consumer's module namespace. Rename it to the namespaced `MATTelemetryObjC` while the name is still internal (the wrappers/swift package was never distributed), so it is collision-safe before first release. Renamed consistently across both build paths so the same Swift sources compile against both modulemaps: - tools/apple/module.modulemap (xcframework) and wrappers/swift/Modules/module.modulemap (local): `module MATTelemetryObjC`. - All 13 `import ObjCModule` -> `import MATTelemetryObjC` in wrappers/swift/Sources/OneDSSwift/*.swift. - Bridging header file renamed ObjCModule-Bridging-Header.h -> MATTelemetryObjC-Bridging-Header.h and its modulemap reference updated. - Comments/docs (Package.swift, build-xcframework.sh, tools/apple/README.md, examples/swift/README.md) updated. Pure rename; no behavioral change. Needs a macOS `swift build` (local wrappers path) + xcframework SPM build to confirm both paths still resolve the module -- I cannot run those on Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Package.swift | 2 +- examples/swift/README.md | 2 +- tools/apple/README.md | 6 +++--- tools/apple/build-xcframework.sh | 2 +- tools/apple/module.modulemap | 8 ++++---- ...idging-Header.h => MATTelemetryObjC-Bridging-Header.h} | 0 wrappers/swift/Modules/module.modulemap | 4 ++-- wrappers/swift/Sources/OneDSSwift/CommonDataContext.swift | 2 +- .../swift/Sources/OneDSSwift/DiagnosticDataViewer.swift | 2 +- wrappers/swift/Sources/OneDSSwift/EventProperties.swift | 2 +- wrappers/swift/Sources/OneDSSwift/LogConfiguration.swift | 2 +- wrappers/swift/Sources/OneDSSwift/LogManager.swift | 2 +- wrappers/swift/Sources/OneDSSwift/Logger.swift | 2 +- wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift | 4 ++-- wrappers/swift/Sources/OneDSSwift/PrivacyGuard.swift | 2 +- .../swift/Sources/OneDSSwift/PrivacyGuardInitConfig.swift | 2 +- wrappers/swift/Sources/OneDSSwift/Sanitizer.swift | 2 +- .../swift/Sources/OneDSSwift/SanitizerInitConfig.swift | 2 +- wrappers/swift/Sources/OneDSSwift/SemanticContext.swift | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) rename wrappers/swift/Headers/{ObjCModule-Bridging-Header.h => MATTelemetryObjC-Bridging-Header.h} (100%) diff --git a/Package.swift b/Package.swift index c7dc5346d..bf8313af8 100644 --- a/Package.swift +++ b/Package.swift @@ -92,7 +92,7 @@ let package = Package( ], targets: [ // Prebuilt C++ core + Obj-C wrappers. The xcframework's bundled - // module map vends the Clang module `ObjCModule` (see + // 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: diff --git a/examples/swift/README.md b/examples/swift/README.md index 2c7db6c3a..3f66816da 100644 --- a/examples/swift/README.md +++ b/examples/swift/README.md @@ -30,7 +30,7 @@ Details: - OneDSSwift: Package containing swift wrappers - Modules Included - - ObjCModule: Module exposing ObjC headers via module.modulemap file. + - MATTelemetryObjC: Module exposing ObjC headers via module.modulemap file. - Libraries and Frameworks to link to Target - [Same as mentioned in the SampleXcodeApp section](#to-be-linked) \ No newline at end of file diff --git a/tools/apple/README.md b/tools/apple/README.md index a5a7eeddf..c08dd4ac3 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -15,9 +15,9 @@ conditionals. Instead, the package is split into: | C++ core + Obj-C wrappers (`ODW*`) | `MATTelemetry.xcframework` binary target | | Swift API (`OneDSSwift`) | Source target in `wrappers/swift/Sources/OneDSSwift` | -The xcframework vendors a Clang module named `ObjCModule` through +The xcframework vendors a Clang module named `MATTelemetryObjC` through `tools/apple/module.modulemap` and `MATTelemetry-umbrella.h`, matching the -existing Swift sources' `import ObjCModule`. +existing Swift sources' `import MATTelemetryObjC`. ## Supported slices @@ -38,7 +38,7 @@ existing Swift sources' `import ObjCModule`. | --- | --- | | `Package.swift` | Root SPM manifest: binary target + Swift source target | | `tools/apple/build-xcframework.sh` | Builds static `libmat.a` slices and assembles `MATTelemetry.xcframework` | -| `tools/apple/module.modulemap` | Defines the `ObjCModule` Clang module | +| `tools/apple/module.modulemap` | Defines the `MATTelemetryObjC` Clang module | | `tools/apple/MATTelemetry-umbrella.h` | Base umbrella for always-available Obj-C wrapper headers | | `tools/apple/MATTelemetryAvailability.json` | Optional-module manifest consumed by `Package.swift` | | `.github/workflows/spm-release.yml` | Release automation for the hosted xcframework and SPM tag | diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 00af27b1d..21bb9add1 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -53,7 +53,7 @@ mkdir -p "$OUT" # --- 1. Public Obj-C headers + module map (vended by the xcframework) -------- # Flatten the ODW*.h headers + umbrella + modulemap into one Headers dir. The -# module is named `ObjCModule` to match what wrappers/swift sources import. +# module is named `MATTelemetryObjC` to match what wrappers/swift sources import. HDRS="$OUT/Headers" mkdir -p "$HDRS" diff --git a/tools/apple/module.modulemap b/tools/apple/module.modulemap index 0a2c0167a..ec2e84f22 100644 --- a/tools/apple/module.modulemap +++ b/tools/apple/module.modulemap @@ -1,9 +1,9 @@ // Clang module vended by MATTelemetry.xcframework. Imported by the OneDSSwift -// Swift layer as `import ObjCModule` -- the module name matches what the -// existing wrappers/swift/Sources/OneDSSwift sources already import, so no Swift -// source changes are needed. +// Swift layer as `import MATTelemetryObjC`. The module name is kept identical +// to the one in wrappers/swift/Modules/module.modulemap so the same Swift +// sources compile against both the local modulemap and this xcframework. -module ObjCModule { +module MATTelemetryObjC { umbrella header "MATTelemetry-umbrella.h" export * } diff --git a/wrappers/swift/Headers/ObjCModule-Bridging-Header.h b/wrappers/swift/Headers/MATTelemetryObjC-Bridging-Header.h similarity index 100% rename from wrappers/swift/Headers/ObjCModule-Bridging-Header.h rename to wrappers/swift/Headers/MATTelemetryObjC-Bridging-Header.h diff --git a/wrappers/swift/Modules/module.modulemap b/wrappers/swift/Modules/module.modulemap index 2e1856bae..f4fbec71d 100644 --- a/wrappers/swift/Modules/module.modulemap +++ b/wrappers/swift/Modules/module.modulemap @@ -1,6 +1,6 @@ /// Module exporting headers declared in ObjC. Imported by Swift package to have access to the ObjC types. -module ObjCModule { - header "../Headers/ObjCModule-Bridging-Header.h" +module MATTelemetryObjC { + header "../Headers/MATTelemetryObjC-Bridging-Header.h" export * } diff --git a/wrappers/swift/Sources/OneDSSwift/CommonDataContext.swift b/wrappers/swift/Sources/OneDSSwift/CommonDataContext.swift index 88786ac95..74c5bf0d0 100644 --- a/wrappers/swift/Sources/OneDSSwift/CommonDataContext.swift +++ b/wrappers/swift/Sources/OneDSSwift/CommonDataContext.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Wrapper over ODWCommonDataContext class. public final class CommonDataContext { diff --git a/wrappers/swift/Sources/OneDSSwift/DiagnosticDataViewer.swift b/wrappers/swift/Sources/OneDSSwift/DiagnosticDataViewer.swift index 46d3011e9..dc136b076 100644 --- a/wrappers/swift/Sources/OneDSSwift/DiagnosticDataViewer.swift +++ b/wrappers/swift/Sources/OneDSSwift/DiagnosticDataViewer.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Wrapper class over `ODWDiagnosticDataViewer` representing Diagnostic Data Viewer Hook. public final class DiagnosticDataViewer { diff --git a/wrappers/swift/Sources/OneDSSwift/EventProperties.swift b/wrappers/swift/Sources/OneDSSwift/EventProperties.swift index eb346a84e..bee46e573 100644 --- a/wrappers/swift/Sources/OneDSSwift/EventProperties.swift +++ b/wrappers/swift/Sources/OneDSSwift/EventProperties.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /** Represents Event's properties. diff --git a/wrappers/swift/Sources/OneDSSwift/LogConfiguration.swift b/wrappers/swift/Sources/OneDSSwift/LogConfiguration.swift index 0f6bd18bb..1a5812770 100644 --- a/wrappers/swift/Sources/OneDSSwift/LogConfiguration.swift +++ b/wrappers/swift/Sources/OneDSSwift/LogConfiguration.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Class wrapping `ODWLogConfiguration` ObjC class object, representing configuration related to events. public final class LogConfiguration { diff --git a/wrappers/swift/Sources/OneDSSwift/LogManager.swift b/wrappers/swift/Sources/OneDSSwift/LogManager.swift index 11a0af415..5a659795f 100644 --- a/wrappers/swift/Sources/OneDSSwift/LogManager.swift +++ b/wrappers/swift/Sources/OneDSSwift/LogManager.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Wrapper over ODWLogManager which manages the telemetry logging system. public final class LogManager { diff --git a/wrappers/swift/Sources/OneDSSwift/Logger.swift b/wrappers/swift/Sources/OneDSSwift/Logger.swift index 04a9c1497..b901cfa4b 100644 --- a/wrappers/swift/Sources/OneDSSwift/Logger.swift +++ b/wrappers/swift/Sources/OneDSSwift/Logger.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Wrapper class around ObjC Logger class `ODWLogger` used to events. public final class Logger { diff --git a/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift b/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift index c0c1ad4e0..0f0483eee 100644 --- a/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift +++ b/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift @@ -5,12 +5,12 @@ /// Contains alias for the types declared in the ObjC header files to make them available /// as part of the swift package module. -/// To avoid clients not have to import ObjCModule explicitly. +/// To avoid clients not have to import MATTelemetryObjC explicitly. /// Important: Due to objc->swift conventions, Type name is removed, so ODWPiiKindGenericData would be accessed as .genericData in swift. /// Check corresponding header file for the doc of each type. -import ObjCModule +import MATTelemetryObjC // ODWEventProperties.h public typealias EventPriority = ODWEventPriority diff --git a/wrappers/swift/Sources/OneDSSwift/PrivacyGuard.swift b/wrappers/swift/Sources/OneDSSwift/PrivacyGuard.swift index 19ad4787a..b589b82bc 100644 --- a/wrappers/swift/Sources/OneDSSwift/PrivacyGuard.swift +++ b/wrappers/swift/Sources/OneDSSwift/PrivacyGuard.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Wrapper to `ODWPrivacyGuard` representing Privacy Guard Hook. public final class PrivacyGuard { diff --git a/wrappers/swift/Sources/OneDSSwift/PrivacyGuardInitConfig.swift b/wrappers/swift/Sources/OneDSSwift/PrivacyGuardInitConfig.swift index 7f660d9f6..2459c7f08 100644 --- a/wrappers/swift/Sources/OneDSSwift/PrivacyGuardInitConfig.swift +++ b/wrappers/swift/Sources/OneDSSwift/PrivacyGuardInitConfig.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC public final class PrivacyGuardInitConfig { let odwPrivacyGuardInitConfig: ODWPrivacyGuardInitConfig diff --git a/wrappers/swift/Sources/OneDSSwift/Sanitizer.swift b/wrappers/swift/Sources/OneDSSwift/Sanitizer.swift index 4b5035f3c..004166f16 100644 --- a/wrappers/swift/Sources/OneDSSwift/Sanitizer.swift +++ b/wrappers/swift/Sources/OneDSSwift/Sanitizer.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Wrapper to `ODWSanitizer` representing the Sanitizer. public final class Sanitizer { diff --git a/wrappers/swift/Sources/OneDSSwift/SanitizerInitConfig.swift b/wrappers/swift/Sources/OneDSSwift/SanitizerInitConfig.swift index 8eb70fd37..81f23d75b 100644 --- a/wrappers/swift/Sources/OneDSSwift/SanitizerInitConfig.swift +++ b/wrappers/swift/Sources/OneDSSwift/SanitizerInitConfig.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC public final class SanitizerInitConfig { let odwSanitizerInitConfig: ODWSanitizerInitConfig diff --git a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift index 184d19bc4..211983097 100644 --- a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift +++ b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -import ObjCModule +import MATTelemetryObjC /// Wrapper over `ODWSemanticContext` class that manages the inclusion of semantic context values on logged events. public class SemanticContext { From 2b5bdd50a555617931ff092273e39e698f172045 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 14:09:26 -0500 Subject: [PATCH 24/40] docs(apple): note xcframework expects system sqlite3/zlib The SPM xcframework links the platform's libsqlite3/libz (Package.swift .linkedLibrary) rather than bundling them. Document why: bundling a private static sqlite3 would collide with any consumer that also uses SQLite (Core Data/GRDB/FMDB) -> duplicate symbols / two-instance state; system linking yields one shared copy. Notes the contrast with vcpkg (uses vcpkg packages) and Android (bundles, since the NDK has no system copy). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/apple/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/apple/README.md b/tools/apple/README.md index c08dd4ac3..1c97c31b1 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -19,6 +19,21 @@ The xcframework vendors a Clang module named `MATTelemetryObjC` through `tools/apple/module.modulemap` and `MATTelemetry-umbrella.h`, matching the existing Swift sources' `import MATTelemetryObjC`. +## Runtime dependencies (sqlite3 / zlib) + +The xcframework does **not** bundle sqlite3 or zlib. `Package.swift` links the +**system** `libsqlite3` and `libz` that Apple ships on every iOS / macOS / Mac +Catalyst / visionOS target (`.linkedLibrary("sqlite3")` / `.linkedLibrary("z")`), +so consumers do not need to add them. + +This is deliberate. Embedding a private static copy of sqlite3 into the +xcframework would give any app that also uses SQLite (Core Data, GRDB, FMDB, +etc.) two copies of the library in one process — risking duplicate-symbol link +errors or divergent SQLite state. Linking the OS-provided libraries guarantees a +single shared instance. For comparison, the vcpkg build consumes vcpkg's own +sqlite3/zlib packages, and only the Android build bundles them (the NDK ships no +system copy). + ## Supported slices `tools/apple/build-xcframework.sh release` builds: From f2c5fc946ced4b51fa06b67cdcde42f8ff8fca53 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 14:29:31 -0500 Subject: [PATCH 25/40] Use PIIKind alias in SemanticContext.setUserID signature Every other PII-tagged public API in OneDSSwift (EventProperties, Logger, LogManager) already uses the `PIIKind` typealias; only SemanticContext.setUserID exposed the raw Obj-C `ODWPiiKind` in its signature/default. Switch it to `PIIKind` (same underlying type via the ObjCTypes.swift typealias) so consumers of the Swift API never need to reference the MATTelemetryObjC module name. Pure source-level alias swap; no behavioral change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- wrappers/swift/Sources/OneDSSwift/SemanticContext.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift index 211983097..a95d53992 100644 --- a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift +++ b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift @@ -50,9 +50,9 @@ public class SemanticContext { - Parameters: - userID: A `String` that contains the unique user identifier. - withPiiKind: A PIIKind of the userID. Set it to PiiKind_None t odenote it as non-PII. - - Note: Default value is `ODWPiiKind.identity`. + - Note: Default value is `PIIKind.identity`. */ - public func setUserID(_ userID: String, withPiiKind piiKind: ODWPiiKind = ODWPiiKind.identity) { + public func setUserID(_ userID: String, withPiiKind piiKind: PIIKind = PIIKind.identity) { odwSemanticContext.setUserId(userID) } From 06caea2aaafd11d5e9965743c7708b1e480458ec Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 15:17:39 -0500 Subject: [PATCH 26/40] Fix SemanticContext.setUserID dropping the caller's piiKind setUserID accepted a piiKind argument but called the no-piiKind Obj-C overload `setUserId(_:)`, silently discarding the caller's PII classification for the user id. ODWSemanticContext exposes a `setUserId:piiKind:` overload (wrappers/obj-c/ODWSemanticContext.h:47-48) for exactly this. Route the argument through so the requested PII tag is actually applied. Privacy-relevant behavioral fix. Needs a macOS `swift build` to confirm the bridged selector (`setUserId(_:piiKind:)`); cannot run Swift on the Windows session. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- wrappers/swift/Sources/OneDSSwift/SemanticContext.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift index a95d53992..03d731102 100644 --- a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift +++ b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift @@ -53,7 +53,7 @@ public class SemanticContext { - Note: Default value is `PIIKind.identity`. */ public func setUserID(_ userID: String, withPiiKind piiKind: PIIKind = PIIKind.identity) { - odwSemanticContext.setUserId(userID) + odwSemanticContext.setUserId(userID, piiKind: piiKind) } /** From a57c2a1008a64dbdf8f98060e29c48ddcde68998 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 19 Jun 2026 15:42:52 -0500 Subject: [PATCH 27/40] Address Swift package review refinements Keep CommonDataContext available without PrivacyGuard, tighten Swift docs, and scope xcframework slice cleanup to the output directory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Package.swift | 1 - tools/apple/README.md | 6 +++--- tools/apple/build-xcframework.sh | 1 - wrappers/swift/Package.swift | 1 - wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift | 2 +- wrappers/swift/Sources/OneDSSwift/SemanticContext.swift | 2 +- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index bf8313af8..18af3547c 100644 --- a/Package.swift +++ b/Package.swift @@ -66,7 +66,6 @@ if hasPrivacyGuard { swiftSettings.append(.define("MATSDK_PRIVACYGUARD_AVAILABLE")) } else { excludedSources.append(contentsOf: [ - "CommonDataContext.swift", "PrivacyGuard.swift", "PrivacyGuardInitConfig.swift", ]) diff --git a/tools/apple/README.md b/tools/apple/README.md index 1c97c31b1..79d706fd1 100644 --- a/tools/apple/README.md +++ b/tools/apple/README.md @@ -1,8 +1,8 @@ # Swift Package Manager xcframework prototype -This packages the 1DS C++ SDK for Apple developers through Swift Package Manager -(SPM), using a prebuilt xcframework for the C++ core plus Obj-C wrappers and -compiling the Swift API from source. +This package distributes the 1DS C++ SDK to Apple developers through Swift +Package Manager (SPM), using a prebuilt xcframework for the C++ core plus Obj-C +wrappers and compiling the Swift API from source. ## Package shape diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 21bb9add1..7fed4ccf9 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -134,7 +134,6 @@ build_slice() { # arch platform out-subdir echo "=== building $arch / $plat ($CONFIG) ===" ( cd "$ROOT" - rm -f CMakeCache.txt *.cmake rm -rf out MATTELEMETRY_SKIP_PACKAGE=1 ./build-ios.sh "$CONFIG" "$arch" "$plat" ) diff --git a/wrappers/swift/Package.swift b/wrappers/swift/Package.swift index 879943354..5547c5c4d 100644 --- a/wrappers/swift/Package.swift +++ b/wrappers/swift/Package.swift @@ -25,7 +25,6 @@ if hasPrivacyGuard { swiftSettings.append(.define("MATSDK_PRIVACYGUARD_AVAILABLE")) } else { excludedSources.append(contentsOf: [ - "CommonDataContext.swift", "PrivacyGuard.swift", "PrivacyGuardInitConfig.swift", ]) diff --git a/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift b/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift index 0f0483eee..e0e446b00 100644 --- a/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift +++ b/wrappers/swift/Sources/OneDSSwift/ObjCTypes.swift @@ -5,7 +5,7 @@ /// Contains alias for the types declared in the ObjC header files to make them available /// as part of the swift package module. -/// To avoid clients not have to import MATTelemetryObjC explicitly. +/// This lets clients use the Swift package module without importing MATTelemetryObjC explicitly. /// Important: Due to objc->swift conventions, Type name is removed, so ODWPiiKindGenericData would be accessed as .genericData in swift. /// Check corresponding header file for the doc of each type. diff --git a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift index 03d731102..3bf485fdf 100644 --- a/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift +++ b/wrappers/swift/Sources/OneDSSwift/SemanticContext.swift @@ -49,7 +49,7 @@ public class SemanticContext { - Parameters: - userID: A `String` that contains the unique user identifier. - - withPiiKind: A PIIKind of the userID. Set it to PiiKind_None t odenote it as non-PII. + - withPiiKind: A `PIIKind` for the userID. Set it to `PIIKind.none` to denote it as non-PII. - Note: Default value is `PIIKind.identity`. */ public func setUserID(_ userID: String, withPiiKind piiKind: PIIKind = PIIKind.identity) { From 3bc755535175f02f3fe6713028f2000b264571fd Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 10 Jul 2026 10:51:56 -0500 Subject: [PATCH 28/40] Harden the SPM release pipeline: propagate build failures, don't clobber published artifacts Two robustness fixes to the Apple/SPM distribution path: - build-ios.sh ran cmake, make, and make package without checking their exit codes. Under MATTELEMETRY_SKIP_PACKAGE=1 (used by the xcframework build) a failed `make` was masked -- the script printed "skipping package creation" and exited 0, so build-xcframework.sh would proceed with a broken/empty slice. Propagate failures explicitly for cd, cmake, make, and make package. - spm-release.yml uploaded the xcframework with `gh release upload --clobber` and updated Package.swift on every run, gated only on the tag being a valid 4-component version. A re-run for an already-published release replaced the release asset with a differently-hashed rebuild while the existing SPM tag kept the old checksum, breaking consumers pinned to it (SPM binary targets verify the checksum). Add an early "already published" check and gate the checksum/upload/manifest/commit steps on it, so a published SPM version's artifact and checksum are left untouched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 20 ++++++++++++++++---- build-ios.sh | 8 ++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index 2fbd94ba1..1965a5044 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -79,6 +79,18 @@ jobs: # port (the optional modules are excluded there too). submodules: false + - name: Skip if this SPM version is already published + id: pub + 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 2>&1; then + 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)." + echo "published=true" >> "$GITHUB_OUTPUT" + else + echo "published=false" >> "$GITHUB_OUTPUT" + fi + - name: Build MATTelemetry.xcframework if: ${{ steps.ver.outputs.skip != 'true' }} run: | @@ -115,20 +127,20 @@ jobs: - name: Compute SPM checksum id: sum - if: ${{ steps.ver.outputs.skip != 'true' }} + if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != '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' }} + if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != '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' }} + if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }} env: ASSET_URL: https://github.com/${{ github.repository }}/releases/download/${{ steps.ver.outputs.tag }}/MATTelemetry.xcframework.zip CHECKSUM: ${{ steps.sum.outputs.checksum }} @@ -153,7 +165,7 @@ jobs: PY - name: Commit manifest and push the 3-component SPM tag - if: ${{ steps.ver.outputs.skip != 'true' }} + if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }} run: | set -euo pipefail if git ls-remote --exit-code --tags origin "refs/tags/${{ steps.ver.outputs.spm_version }}" >/dev/null; then diff --git a/build-ios.sh b/build-ios.sh index d96b93bc4..5dbb8b547 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -101,18 +101,18 @@ if [ -f /usr/bin/clang ]; then fi mkdir -p out -cd out +cd out || { echo "ERROR: cannot enter build directory 'out'" 1>&2; exit 1; } CMAKE_PACKAGE_TYPE=tgz cmake_cmd="cmake -DCMAKE_OSX_SYSROOT=$IOS_SYSROOT -DCMAKE_SYSTEM_NAME=$SYS_NAME -DCMAKE_IOS_ARCH_ABI=$IOS_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DBUILD_IOS=YES -DIOS_ARCH=$IOS_ARCH -DIOS_PLAT=$IOS_PLAT -DIOS_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PACKAGE_TYPE=$CMAKE_PACKAGE_TYPE -DFORCE_RESET_DEPLOYMENT_TARGET=$FORCE_RESET_DEPLOYMENT_TARGET $CMAKE_OPTS .." echo "${cmake_cmd}" -eval $cmake_cmd +eval $cmake_cmd || { echo "ERROR: cmake configuration failed for $IOS_PLAT/$IOS_ARCH" 1>&2; exit 1; } -make +make || { echo "ERROR: make failed for $IOS_PLAT/$IOS_ARCH" 1>&2; exit 1; } if [ "${MATTELEMETRY_SKIP_PACKAGE:-}" = "1" ]; then echo "MATTELEMETRY_SKIP_PACKAGE=1: skipping package creation" else - make package + make package || { echo "ERROR: make package failed for $IOS_PLAT/$IOS_ARCH" 1>&2; exit 1; } fi From 2f2c7e1bc6a78644224a838056e1ad0ea2509caa Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 10 Jul 2026 11:13:40 -0500 Subject: [PATCH 29/40] SPM release: fail closed when the "already published" check is indeterminate The published-tag check used `git ls-remote --exit-code` and treated any non-zero exit as "not published", conflating "connected, no such tag" (exit 2) with a transport/auth error (exit 128+). A transient failure would set published=false and ungate the `gh release upload --clobber`, replacing an already-published asset with a differently-hashed rebuild while the existing SPM tag kept the old checksum -- the exact breakage this gate prevents. Branch on the exit status: existing tag -> skip, no match -> publish, any other error -> abort the job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index 1965a5044..94551a1f7 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -84,11 +84,20 @@ jobs: 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 2>&1; then + # Distinguish "tag exists" (0) from "connected, no such tag" (2) from a + # transport/auth error (other). Fail closed: on an indeterminate result + # do NOT proceed to the --clobber upload, which could overwrite an + # already-published asset out from under its pinned checksum. + rc=0 + git ls-remote --exit-code --tags origin "refs/tags/${{ steps.ver.outputs.spm_version }}" >/dev/null 2>&1 || rc=$? + if [ "$rc" -eq 0 ]; then 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)." echo "published=true" >> "$GITHUB_OUTPUT" - else + elif [ "$rc" -eq 2 ]; then echo "published=false" >> "$GITHUB_OUTPUT" + else + 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 + exit 1 fi - name: Build MATTelemetry.xcframework From 8dc6659cb83653536ad802cf9da81a7dab5c8357 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sun, 12 Jul 2026 12:13:36 -0500 Subject: [PATCH 30/40] Report visionOS sysinfo distinctly in SPM builds When the SPM xcframework includes visionOS slices, the mobile Apple sysinfo implementation should not report those runs as iOS or leave the device class empty. Teach the iOS/visionOS sysinfo path to return visionOS-specific OS and class values when compiled for visionOS, while preserving the existing iOS values for iOS and iOS Simulator. Also document the current SPM release-tag mapping: one SDK build can publish for each three-component SemVer tag because vX.Y.Z.W maps to X.Y.Z. Validation: - swift package dump-package - iOS/visionOS device and simulator Objective-C++ syntax checks - tools/apple/build-xcframework.sh release - swift build - xcodebuild iOS simulator, visionOS simulator, and Mac Catalyst package builds - git diff --check Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/spm-release.yml | 3 +++ Package.swift | 4 +++- lib/pal/posix/sysinfo_utils_ios.mm | 29 ++++++++++++++++++++++- tests/unittests/SysInfoUtilsTests_iOS.cpp | 23 +++++++++++++++++- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index 94551a1f7..8fdcdb4c6 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -7,6 +7,9 @@ name: SPM release (xcframework) # 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. +# Only one build per 3-component version can publish: a later vX.Y.Z.W hotfix +# with the same X.Y.Z maps to the existing SPM tag and is skipped/unsupported +# unless this mapping changes. # # Prerequisites: # * The root Package.swift (the SPM manifest) must exist at the release tag diff --git a/Package.swift b/Package.swift index 18af3547c..8af9ae484 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,9 @@ // .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. +// resolve. Because vX.Y.Z.W release tags map to one X.Y.Z SPM tag, only one +// build per three-component version can publish; later fourth-component +// hotfixes for the same X.Y.Z are skipped unless the mapping changes. import PackageDescription import Foundation diff --git a/lib/pal/posix/sysinfo_utils_ios.mm b/lib/pal/posix/sysinfo_utils_ios.mm index 3a4e4c0d3..3e3b9389b 100644 --- a/lib/pal/posix/sysinfo_utils_ios.mm +++ b/lib/pal/posix/sysinfo_utils_ios.mm @@ -5,9 +5,22 @@ #include "sysinfo_utils_apple.hpp" #import +#include #import #import +#if defined(TARGET_OS_VISION) && TARGET_OS_VISION +#define MATSDK_TARGET_OS_VISION 1 +#else +#define MATSDK_TARGET_OS_VISION 0 +#endif + +#if defined(__VISION_OS_VERSION_MAX_ALLOWED) || (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 170000)) +#define MATSDK_HAS_UI_USER_INTERFACE_IDIOM_VISION 1 +#else +#define MATSDK_HAS_UI_USER_INTERFACE_IDIOM_VISION 0 +#endif + std::string GetDeviceModel() { @autoreleasepool { @@ -34,7 +47,11 @@ std::string GetDeviceOsName() { +#if MATSDK_TARGET_OS_VISION + return std::string("visionOS"); +#else return std::string("iOS"); +#endif } std::string GetDeviceId() @@ -69,7 +86,13 @@ } std::string GetDeviceClass() { -#if TARGET_IPHONE_SIMULATOR +#if MATSDK_TARGET_OS_VISION +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR + return "visionOS.Emulator"; +#else + return "visionOS.Vision"; +#endif +#elif defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR return "iOS.Emulator"; #else switch (UIDevice.currentDevice.userInterfaceIdiom) { @@ -79,6 +102,10 @@ return "iOS.Tablet"; case UIUserInterfaceIdiomTV: return "iOS.AppleTV"; +#if MATSDK_HAS_UI_USER_INTERFACE_IDIOM_VISION + case UIUserInterfaceIdiomVision: + return "visionOS.Vision"; +#endif default: return {}; } diff --git a/tests/unittests/SysInfoUtilsTests_iOS.cpp b/tests/unittests/SysInfoUtilsTests_iOS.cpp index 2fb5179a5..ee70fe8ca 100644 --- a/tests/unittests/SysInfoUtilsTests_iOS.cpp +++ b/tests/unittests/SysInfoUtilsTests_iOS.cpp @@ -5,12 +5,33 @@ #include "common/Common.hpp" #include "pal/posix/sysinfo_utils_apple.hpp" +#include + +#if defined(TARGET_OS_VISION) && TARGET_OS_VISION +#define MATSDK_TEST_TARGET_OS_VISION 1 +#else +#define MATSDK_TEST_TARGET_OS_VISION 0 +#endif using namespace testing; using namespace MAT; -TEST(SysInfoUtilsTests, GetDeviceOsName_iOS_ReturnsiOS) +TEST(SysInfoUtilsTests, GetDeviceOsName_AppleMobile_ReturnsExpectedName) { +#if MATSDK_TEST_TARGET_OS_VISION + ASSERT_EQ(std::string { "visionOS" }, GetDeviceOsName()); +#else ASSERT_EQ(std::string { "iOS" }, GetDeviceOsName()); +#endif } +#if MATSDK_TEST_TARGET_OS_VISION +TEST(SysInfoUtilsTests, GetDeviceClass_visionOS_ReturnsVisionClass) +{ +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR + ASSERT_EQ(std::string { "visionOS.Emulator" }, GetDeviceClass()); +#else + ASSERT_EQ(std::string { "visionOS.Vision" }, GetDeviceClass()); +#endif +} +#endif From 87d5aa8f3f822ae2f71dd8f053f74a969600aa33 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 31 Jul 2026 15:15:22 -0500 Subject: [PATCH 31/40] Fix iOS sysinfo fallbacks and validate rewritten SPM manifest Guard lib/pal/posix/sysinfo_utils_ios.mm:34 against a missing or empty SIMULATOR_MODEL_IDENTIFIER so simulator launches do not hit std::string(nullptr), and map UIUserInterfaceIdiomMac at lib/pal/posix/sysinfo_utils_ios.mm:123 so Mac Catalyst reports a non-empty device class. Move .github/workflows/spm-release.yml:153 validation to run after the Package.swift rewrite at .github/workflows/spm-release.yml:128 so swift package dump-package and the package builds validate the final url/checksum manifest that will be tagged and published. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 --- .github/workflows/spm-release.yml | 52 +++++++++++++++--------------- lib/pal/posix/sysinfo_utils_ios.mm | 23 ++++++++++++- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/.github/workflows/spm-release.yml b/.github/workflows/spm-release.yml index 8fdcdb4c6..c39394c37 100644 --- a/.github/workflows/spm-release.yml +++ b/.github/workflows/spm-release.yml @@ -111,32 +111,6 @@ jobs: 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' && steps.pub.outputs.published != 'true' }} @@ -176,6 +150,32 @@ jobs: open(path, "w").write(out) PY + - name: Validate SwiftPM package consumption + if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != '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: Commit manifest and push the 3-component SPM tag if: ${{ steps.ver.outputs.skip != 'true' && steps.pub.outputs.published != 'true' }} run: | diff --git a/lib/pal/posix/sysinfo_utils_ios.mm b/lib/pal/posix/sysinfo_utils_ios.mm index 3e3b9389b..9c0b6311b 100644 --- a/lib/pal/posix/sysinfo_utils_ios.mm +++ b/lib/pal/posix/sysinfo_utils_ios.mm @@ -21,12 +21,29 @@ #define MATSDK_HAS_UI_USER_INTERFACE_IDIOM_VISION 0 #endif +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) +#define MATSDK_HAS_UI_USER_INTERFACE_IDIOM_MAC 1 +#else +#define MATSDK_HAS_UI_USER_INTERFACE_IDIOM_MAC 0 +#endif + std::string GetDeviceModel() { @autoreleasepool { #if TARGET_IPHONE_SIMULATOR NSString* modelId = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"]; - return std::string([modelId UTF8String]); + if (modelId.length > 0) + { + return std::string([modelId UTF8String]); + } + + NSString* fallbackModel = [[UIDevice currentDevice] model]; + if (fallbackModel.length > 0) + { + return std::string([fallbackModel UTF8String]); + } + + return {}; #else std::string deviceModel { }; struct utsname systemInfo; @@ -102,6 +119,10 @@ return "iOS.Tablet"; case UIUserInterfaceIdiomTV: return "iOS.AppleTV"; +#if MATSDK_HAS_UI_USER_INTERFACE_IDIOM_MAC + case UIUserInterfaceIdiomMac: + return "iOS.Desktop"; +#endif #if MATSDK_HAS_UI_USER_INTERFACE_IDIOM_VISION case UIUserInterfaceIdiomVision: return "visionOS.Vision"; From 6a2e9ffe4417ea1c248f7ecef32e8f56c29cd1cc Mon Sep 17 00:00:00 2001 From: bmehta001 Date: Mon, 3 Aug 2026 14:05:40 -0500 Subject: [PATCH 32/40] Stabilize timing-sensitive tests (#1513) * Stabilize timing-sensitive tests Use a monotonic injectable clock for kill-switch deadlines, replace the sleep-heavy expiration functional test with deterministic unit coverage, and simulate expired SQLite leases directly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 77649dd9-3b60-4ca4-861d-8c5ac165d53c * Restore temporary kill-switch integration coverage Rewrite killIsTemporary to observe active drops and eventual server delivery instead of sleeping for a fixed expiration window. Keep every wait bounded without adding test-only access to production internals. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 77649dd9-3b60-4ca4-861d-8c5ac165d53c * Fix Windows CI and harden injected clocks Rename the temporary kill-switch logger so MSVC /WX no longer promotes C4458 into C2220 in both Windows pipelines. Files changed: - lib/offline/KillSwitchManager.hpp: fall back from an empty Clock and invoke injected callbacks outside the mutex. - tests/unittests/KillSwitchManagerTests.cpp: cover the empty-clock fallback. - tests/functests/BasicFuncTests.cpp: avoid shadowing the fixture logger member. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94 * Harden temporary kill-switch polling Decode only newly arrived requests after releasing the HTTP callback mutex, avoiding repeated parsing and preventing the polling helper from delaying incoming requests. Treat kill-switch activation as a fatal prerequisite while preserving teardown on failure. Files changed: - tests/functests/BasicFuncTests.cpp: snapshot new requests outside the decode path and fail fast when activation is not observed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94 * Make bad-network teardown test deterministic Replace external endpoints with an injected HTTP client that holds requests until teardown cancellation, then reports NetworkFailure through the required exactly-once callback. This preserves the real cancellation and callback-drain path without simulator or network timing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 77649dd9-3b60-4ca4-861d-8c5ac165d53c * tests: use SentCount() in WaitForRequest instead of m_sent.load() directly WaitForRequest polled m_sent.load() directly while SentCount() was already the named accessor for the same value. Using SentCount() keeps the implementation consistent with the class's own public API and means any future change to the accessor (e.g. different memory order) is automatically picked up. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94 * tests: clean up kill-switch test and reduce lease TTL in offline storage test BasicFuncTests/killIsTemporary: flatten acceptedAfterKillExpires polling loop. - Remove redundant pre-loop waitForEvent (nothing sent yet at that point, so it always returned false). - Remove redundant post-loop grace-period block; absorb the 100 ms into expiryDeadline so the single loop covers both the poll and the grace. OfflineStorageTests_SQLite/ReservedRecordsAreReleasedAfterTimeout: - Reduce lease TTL from 60000 ms to 5000 ms. The value is the storage reservation duration, not a wall-clock wait (the test fast-forwards expiry via SQL). 5 s is clearer to readers and equally correct. KillSwitchManager::expiryFromNow: add precondition comment documenting that seconds > 0 is required and why all callers must guard it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 77649dd9-3b60-4ca4-861d-8c5ac165d53c Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94 --- lib/offline/KillSwitchManager.hpp | 53 +++++-- tests/functests/APITest.cpp | 149 ++++++++++++++---- tests/functests/BasicFuncTests.cpp | 147 ++++++++++------- tests/unittests/KillSwitchManagerTests.cpp | 47 ++++++ .../unittests/OfflineStorageTests_SQLite.cpp | 21 ++- 5 files changed, 309 insertions(+), 108 deletions(-) diff --git a/lib/offline/KillSwitchManager.hpp b/lib/offline/KillSwitchManager.hpp index a70569877..d5f5a1211 100644 --- a/lib/offline/KillSwitchManager.hpp +++ b/lib/offline/KillSwitchManager.hpp @@ -7,11 +7,14 @@ #include "pal/PAL.hpp" +#include +#include #include #include #include #include #include +#include #include #include @@ -21,13 +24,24 @@ namespace MAT_NS_BEGIN { class KillSwitchManager { public: + using Clock = std::function; bool isActive() { return !m_tokenTime.empty(); } - KillSwitchManager() : m_isRetryAfterActive(false), m_retryAfterExpiryTime(0) + KillSwitchManager() + : KillSwitchManager([]() { return static_cast(PAL::getMonotonicTimeMs()); }) + { + } + + explicit KillSwitchManager(Clock clock) + : m_clock(clock + ? std::move(clock) + : Clock([]() { return static_cast(PAL::getMonotonicTimeMs()); })), + m_isRetryAfterActive(false), + m_retryAfterExpiryTime(0) { } @@ -45,8 +59,9 @@ namespace MAT_NS_BEGIN { int64_t timeinSecs = 0; if (tryParseSeconds(timeStr, timeinSecs) && timeinSecs > 0) { + const int64_t expiryTime = expiryFromNow(timeinSecs); std::lock_guard guard(m_lock); - m_retryAfterExpiryTime = PAL::getUtcSystemTime() + timeinSecs; + m_retryAfterExpiryTime = expiryTime; m_isRetryAfterActive = true; } } @@ -101,20 +116,22 @@ namespace MAT_NS_BEGIN { void addToken(const std::string& tokenId, int64_t timeInSeconds) { - std::lock_guard guard(m_lock); if (timeInSeconds > 0) { - m_tokenTime[tokenId] = PAL::getUtcSystemTime() + timeInSeconds; //convert milisec to sec + const int64_t expiryTime = expiryFromNow(timeInSeconds); + std::lock_guard guard(m_lock); + m_tokenTime[tokenId] = expiryTime; } } bool isTokenBlocked(const std::string& tokenId) { + const int64_t now = m_clock(); std::lock_guard guard(m_lock); if (m_isRetryAfterActive) { - if (m_retryAfterExpiryTime > PAL::getUtcSystemTime()) + if (m_retryAfterExpiryTime > now) { return true;//always return true for all tokens } @@ -129,7 +146,7 @@ namespace MAT_NS_BEGIN { {//found, check the time stamp int64_t timeStamp = m_tokenTime[tokenId]; - if (timeStamp > PAL::getUtcSystemTime()) //convert milisec to sec + if (timeStamp > now) { return true; } @@ -169,6 +186,24 @@ namespace MAT_NS_BEGIN { } private: + // Precondition: seconds > 0. All call sites enforce this (handleResponse + // and addToken both guard with `timeinSecs > 0` / `timeInSeconds > 0`). + // Passing a non-positive value is UB: a negative durationMs makes the + // overflow check `now > maxTime - durationMs` wrap (signed overflow), so + // the result is unpredictable — do not relax the call-site guards. + int64_t expiryFromNow(int64_t seconds) const + { + constexpr int64_t millisecondsPerSecond = 1000; + constexpr int64_t maxTime = std::numeric_limits::max(); + const int64_t now = m_clock(); + if (seconds > maxTime / millisecondsPerSecond) + { + return maxTime; + } + const int64_t durationMs = seconds * millisecondsPerSecond; + return now > maxTime - durationMs ? maxTime : now + durationMs; + } + // Parse a count of seconds from a response-header value (Retry-After / // kill-duration). Returns false when the value is malformed or out of // range instead of letting std::stoll throw: the worker thread that drives @@ -225,8 +260,8 @@ namespace MAT_NS_BEGIN { // Either way the std::exception catch below ignores the value rather // than crashing. const long long parsed = std::stoll(value.substr(begin, end - begin)); - // Clamp to a value that cannot overflow when later added to a current - // UTC timestamp (seconds) to compute an expiry time. No legitimate + // Clamp to a value that cannot overflow when later converted to + // milliseconds to compute an expiry time. No legitimate // Retry-After / kill-duration approaches this; an absurd value is // capped instead of wrapping the expiry into the past. const int64_t kMaxSeconds = 100LL * 365 * 24 * 60 * 60; // ~100 years @@ -272,6 +307,7 @@ namespace MAT_NS_BEGIN { return true; } + Clock m_clock; std::map m_tokenTime; std::mutex m_lock; bool m_isRetryAfterActive; @@ -280,4 +316,3 @@ namespace MAT_NS_BEGIN { } MAT_NS_END #endif - diff --git a/tests/functests/APITest.cpp b/tests/functests/APITest.cpp index 0347807f6..baea0112e 100644 --- a/tests/functests/APITest.cpp +++ b/tests/functests/APITest.cpp @@ -210,6 +210,98 @@ class TestDebugEventListener : public DebugEventListener { } }; +// Keep requests in flight until teardown cancels them, then simulate a connection +// reset while honoring IHttpClient's exactly-once callback contract. +class NetworkFailureHttpClient final : public IHttpClient +{ +public: + IHttpRequest* CreateRequest() override + { + return new SimpleHttpRequest("bad-network-" + std::to_string(m_nextRequestId.fetch_add(1))); + } + + void SendRequestAsync(IHttpRequest* request, IHttpResponseCallback* callback) override + { + std::lock_guard lock(m_mutex); + m_pending[request->GetId()] = callback; + m_sent.fetch_add(1); + } + + void CancelRequestAsync(const std::string& id) override + { + IHttpResponseCallback* callback = nullptr; + { + std::lock_guard lock(m_mutex); + auto it = m_pending.find(id); + if (it != m_pending.end()) + { + callback = it->second; + m_pending.erase(it); + } + } + if (callback != nullptr) + { + m_cancelled.fetch_add(1); + CompleteWithNetworkFailure(id, callback); + } + } + + void CancelAllRequests() override + { + std::map pending; + { + std::lock_guard lock(m_mutex); + pending.swap(m_pending); + } + m_cancelled.fetch_add(static_cast(pending.size())); + for (const auto& request : pending) + { + CompleteWithNetworkFailure(request.first, request.second); + } + } + + bool WaitForRequest(unsigned timeoutMs) const + { + const auto deadline = PAL::getMonotonicTimeMs() + timeoutMs; + while (SentCount() == 0 && PAL::getMonotonicTimeMs() < deadline) + { + PAL::sleep(10); + } + return SentCount() > 0; + } + + unsigned SentCount() const + { + return m_sent.load(); + } + + unsigned CancelledCount() const + { + return m_cancelled.load(); + } + + unsigned CompletedCount() const + { + return m_completed.load(); + } + +private: + void CompleteWithNetworkFailure(const std::string& id, IHttpResponseCallback* callback) + { + auto response = new SimpleHttpResponse("failure-" + id); + response->m_result = HttpResult_NetworkFailure; + callback->OnHttpResponse(response); + m_completed.fetch_add(1); + } + + mutable std::mutex m_mutex; + std::map m_pending; + std::atomic m_nextRequestId{0}; + std::atomic m_sent{0}; + std::atomic m_cancelled{0}; + std::atomic m_completed{0}; +}; + /// /// Add all event listeners /// @@ -1204,41 +1296,43 @@ TEST(APITest, LogConfiguration_MsRoot_Check) TEST(APITest, LogManager_BadNetwork_Test) { auto& config = LogManager::GetLogConfiguration(); - - // Clean temp file first const char *cacheFilePath = "bad-network.db"; std::string fileName = MAT::GetTempDirectory(); fileName += cacheFilePath; - printf("remove %s\n", fileName.c_str()); std::remove(fileName.c_str()); std::remove((fileName + "-wal").c_str()); std::remove((fileName + "-shm").c_str()); std::remove((fileName + "-journal").c_str()); - for (auto url : { -#if 0 /* [MG}: Temporary change to avoid GitHub Actions crash #92 */ - "https://0.0.0.0/", - "https://127.0.0.1/", -#endif - "https://mobile.events-sandbox.data.microsoft.com/OneCollector/1.0/", - "https://invalid.host.name.microsoft.com/" - }) - { - printf("--- trying %s", url); - config[CFG_STR_CACHE_FILE_PATH] = cacheFilePath; - config[CFG_INT_TRACE_LEVEL_MASK] = 0; - config[CFG_INT_TRACE_LEVEL_MIN] = ACTTraceLevel_Warn; - config[CFG_INT_SDK_MODE] = SdkModeTypes::SdkModeTypes_CS; - config[CFG_INT_MAX_TEARDOWN_TIME] = 0; - config[CFG_STR_COLLECTOR_URL] = url; - size_t numIterations = 5; - while (numIterations--) - { - printf("."); - EXPECT_GE(StressSingleThreaded(config), MAX_ITERATIONS); - } - printf("\n"); - } + auto httpClient = std::make_shared(); + config.AddModule(CFG_MODULE_HTTP_CLIENT, httpClient); + config[CFG_STR_CACHE_FILE_PATH] = cacheFilePath; + config[CFG_INT_TRACE_LEVEL_MASK] = 0; + config[CFG_INT_TRACE_LEVEL_MIN] = ACTTraceLevel_Warn; + config[CFG_INT_SDK_MODE] = SdkModeTypes::SdkModeTypes_CS; + config[CFG_INT_MAX_TEARDOWN_TIME] = 0; + config[CFG_STR_COLLECTOR_URL] = "https://unused.invalid/"; + + TestDebugEventListener debugListener; + addAllListeners(debugListener); + LogManager::AddEventListener(DebugEventType::EVT_HTTP_FAILURE, debugListener); + auto logger = LogManager::Initialize(TEST_TOKEN, config); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); + logger->LogEvent("badNetworkEvent"); + LogManager::UploadNow(); + + const bool requestStarted = httpClient->WaitForRequest(10000); + LogManager::FlushAndTeardown(); + LogManager::RemoveEventListener(DebugEventType::EVT_HTTP_FAILURE, debugListener); + removeAllListeners(debugListener); + config.AddModule(CFG_MODULE_HTTP_CLIENT, nullptr); + + EXPECT_TRUE(requestStarted); + EXPECT_GE(debugListener.numLogged.load(), 1u); + EXPECT_GE(debugListener.numHttpError.load(), 1u); + EXPECT_GE(httpClient->SentCount(), 1u); + EXPECT_EQ(httpClient->SentCount(), httpClient->CancelledCount()); + EXPECT_EQ(httpClient->CancelledCount(), httpClient->CompletedCount()); } TEST(APITest, LogManager_GetLoggerSameLoggerMultithreaded) @@ -1485,4 +1579,3 @@ TEST(APITest, Custom_Decorator) #endif // HAVE_MAT_DEFAULT_HTTP_CLIENT // TEST_PULL_ME_IN(APITest) - diff --git a/tests/functests/BasicFuncTests.cpp b/tests/functests/BasicFuncTests.cpp index 438411425..bc879d3e6 100644 --- a/tests/functests/BasicFuncTests.cpp +++ b/tests/functests/BasicFuncTests.cpp @@ -541,6 +541,35 @@ class BasicFuncTests : public ::testing::Test, } return result; } + + bool waitForEvent(const std::string& name, unsigned timeoutMs, size_t& nextRequestIndex) + { + const auto deadline = PAL::getMonotonicTimeMs() + timeoutMs; + while (PAL::getMonotonicTimeMs() < deadline) + { + std::vector newRequests; + { + LOCKGUARD(mtx_requests); + while (nextRequestIndex < receivedRequests.size()) + { + newRequests.push_back(receivedRequests[nextRequestIndex]); + ++nextRequestIndex; + } + } + for (const auto& request : newRequests) + { + for (const auto& record : decodeRequest(request, false)) + { + if (record.name == name) + { + return true; + } + } + } + PAL::sleep(10); + } + return false; + } }; @@ -1110,6 +1139,17 @@ public : break; }; } + + bool waitForAtLeast(const std::atomic& counter, unsigned expected, unsigned timeoutMs) + { + const auto deadline = PAL::getMonotonicTimeMs() + timeoutMs; + while (counter.load() < expected && PAL::getMonotonicTimeMs() < deadline) + { + PAL::sleep(10); + } + return counter.load() >= expected; + } + void printStats(){ std::cerr << "[ ] numLogged = " << numLogged << std::endl; std::cerr << "[ ] numSent = " << numSent << std::endl; @@ -1231,84 +1271,71 @@ TEST_F(BasicFuncTests, killSwitchWorks) TEST_F(BasicFuncTests, killIsTemporary) { CleanStorage(); - // Create the configuration to send to fake server auto configuration = LogManager::GetLogConfiguration(); configuration[CFG_INT_TRACE_LEVEL_MASK] = 0xFFFFFFFF; configuration[CFG_INT_TRACE_LEVEL_MIN] = ACTTraceLevel_Warn; configuration[CFG_INT_SDK_MODE] = SdkModeTypes::SdkModeTypes_CS; - configuration[CFG_INT_RAM_QUEUE_SIZE] = 4096 * 20; configuration[CFG_STR_CACHE_FILE_PATH] = TEST_STORAGE_FILENAME; - configuration[CFG_INT_MAX_TEARDOWN_TIME] = 2; // 2 seconds wait on shutdown + configuration[CFG_INT_MAX_TEARDOWN_TIME] = 2; configuration[CFG_STR_COLLECTOR_URL] = serverAddress.c_str(); - configuration[CFG_MAP_HTTP][CFG_BOOL_HTTP_COMPRESSION] = false; // disable compression for now - configuration[CFG_MAP_METASTATS_CONFIG]["interval"] = 30 * 60; // 30 mins - configuration[CFG_MAP_METASTATS_CONFIG]["enabled"] = true; // opt in to stats (disabled by default since #1420) - + configuration[CFG_MAP_HTTP][CFG_BOOL_HTTP_COMPRESSION] = false; + configuration[CFG_MAP_METASTATS_CONFIG]["interval"] = 30 * 60; + configuration[CFG_MAP_METASTATS_CONFIG]["enabled"] = true; configuration["name"] = __FILE__; configuration["version"] = "1.0.0"; - configuration["config"] = { { "host", __FILE__ } }; // Host instance + configuration["config"] = { { "host", __FILE__ } }; - // set the killed token on the server - server.setKilledToken(KILLED_TOKEN, 10); + constexpr unsigned killDurationSec = 5; + server.setKilledToken(KILLED_TOKEN, killDurationSec); KillSwitchListener listener; addListeners(listener); - // Log 100 events from valid and invalid 4 times - int repetitions = 4; - for (int i = 0; i < repetitions; i++) { - // Initialize the logger for the valid token and log 100 events - LogManager::Initialize(TEST_TOKEN, configuration); - LogManager::ResumeTransmission(); - auto myLogger = LogManager::GetLogger(TEST_TOKEN, "killed"); - int numIterations = 100; - while (numIterations--) { - EventProperties event1("fooEvent"); - event1.SetProperty("property", "value"); - myLogger->LogEvent(event1); - } - // Initialize the logger for the killed token and log 100 events - LogManager::Initialize(KILLED_TOKEN, configuration); - LogManager::ResumeTransmission(); - myLogger = LogManager::GetLogger(KILLED_TOKEN, "killed"); - numIterations = 100; - while (numIterations--) { - EventProperties event2("failEvent"); - event2.SetProperty("property", "value"); - myLogger->LogEvent(event2); - } - } - // Try and wait to upload - LogManager::UploadNow(); - PAL::sleep(2000); - // Sleep for 11 seconds so the killed time has expired, clear the killed tokens on server - PAL::sleep(11000); - server.clearKilledTokens(); - // Log 100 events with valid logger - LogManager::Initialize(TEST_TOKEN, configuration); - LogManager::ResumeTransmission(); - auto myLogger = LogManager::GetLogger(TEST_TOKEN, "killed"); - int numIterations = 100; - while (numIterations--) { - EventProperties event1("fooEvent"); - event1.SetProperty("property", "value"); - myLogger->LogEvent(event1); - } LogManager::Initialize(KILLED_TOKEN, configuration); + LogManager::SetTransmitProfile(TransmitProfile_RealTime); LogManager::ResumeTransmission(); - myLogger = LogManager::GetLogger(KILLED_TOKEN, "killed"); - numIterations = 100; - while (numIterations--) { - EventProperties event2("failEvent"); - event2.SetProperty("property", "value"); - myLogger->LogEvent(event2); + + auto killedLogger = LogManager::GetLogger(KILLED_TOKEN, "killed"); + killedLogger->LogEvent("activateKillSwitch"); + LogManager::UploadNow(); + + const bool killSwitchActivated = listener.waitForAtLeast(listener.numHttpOK, 1, 10000); + if (!killSwitchActivated) + { + LogManager::FlushAndTeardown(); + removeListeners(listener); + server.clearKilledTokens(); } - // Expect to 0 events to be dropped - EXPECT_EQ(uint32_t { 0 }, listener.numDropped); - LogManager::FlushAndTeardown(); + ASSERT_TRUE(killSwitchActivated) << "Kill-switch response was not observed before timeout"; + server.clearKilledTokens(); - listener.printStats(); + const unsigned droppedBeforeKill = listener.numDropped.load(); + const auto activeDeadline = PAL::getMonotonicTimeMs() + 2000; + unsigned probe = 0; + while (listener.numDropped.load() == droppedBeforeKill + && PAL::getMonotonicTimeMs() < activeDeadline) + { + killedLogger->LogEvent("blockedWhileKillIsActive" + std::to_string(probe++)); + PAL::sleep(20); + } + EXPECT_GT(listener.numDropped.load(), droppedBeforeKill); + + // Poll until the kill-switch TTL expires and the SDK resumes sending. + // Budget: kill duration + 5 s headroom; the extra 100 ms absorbs any + // request that was dispatched just before the deadline fires. + const auto expiryDeadline = PAL::getMonotonicTimeMs() + (killDurationSec + 5) * 1000 + 100; + size_t nextRequestIndex = 0; + bool acceptedAfterKillExpires = false; + while (!acceptedAfterKillExpires && PAL::getMonotonicTimeMs() < expiryDeadline) + { + killedLogger->LogEvent("acceptedAfterKillExpires"); + LogManager::UploadNow(); + acceptedAfterKillExpires = waitForEvent("acceptedAfterKillExpires", 100, nextRequestIndex); + } + EXPECT_TRUE(acceptedAfterKillExpires); + + LogManager::FlushAndTeardown(); removeListeners(listener); server.clearKilledTokens(); } diff --git a/tests/unittests/KillSwitchManagerTests.cpp b/tests/unittests/KillSwitchManagerTests.cpp index 15aaaee18..ceec1f450 100644 --- a/tests/unittests/KillSwitchManagerTests.cpp +++ b/tests/unittests/KillSwitchManagerTests.cpp @@ -16,6 +16,34 @@ TEST(KillSwitchManagerTests, handleResponse_ValidRetryAfter_ActivatesRetryAfter) ASSERT_TRUE(manager.isRetryAfterActive()); } +TEST(KillSwitchManagerTests, constructor_EmptyClockUsesMonotonicClock) +{ + KillSwitchManager manager(KillSwitchManager::Clock{}); + HttpHeaders headers; + headers.add("Retry-After", "120"); + + ASSERT_NO_THROW(manager.handleResponse(headers)); + EXPECT_TRUE(manager.isTokenBlocked("any-token")); +} + +TEST(KillSwitchManagerTests, handleResponse_RetryAfterExpiresAtDeadline) +{ + int64_t nowMs = 1000; + KillSwitchManager manager([&nowMs]() { return nowMs; }); + HttpHeaders headers; + headers.add("Retry-After", "120"); + + manager.handleResponse(headers); + ASSERT_TRUE(manager.isTokenBlocked("any-token")); + + nowMs += 119999; + EXPECT_TRUE(manager.isTokenBlocked("any-token")); + + nowMs += 1; + EXPECT_FALSE(manager.isTokenBlocked("any-token")); + EXPECT_FALSE(manager.isRetryAfterActive()); +} + TEST(KillSwitchManagerTests, handleResponse_NonNumericRetryAfter_DoesNotThrowAndIsIgnored) { KillSwitchManager manager; @@ -120,6 +148,25 @@ TEST(KillSwitchManagerTests, handleResponse_ValidKillTokenAndDuration_BlocksToke ASSERT_TRUE(manager.isTokenBlocked("tenant-token-1")); } +TEST(KillSwitchManagerTests, handleResponse_KillDurationExpiresAtDeadline) +{ + int64_t nowMs = 1000; + KillSwitchManager manager([&nowMs]() { return nowMs; }); + HttpHeaders headers; + headers.add("kill-tokens", "tenant-token-1"); + headers.add("kill-duration", "10"); + + ASSERT_TRUE(manager.handleResponse(headers)); + ASSERT_TRUE(manager.isTokenBlocked("tenant-token-1")); + + nowMs += 9999; + EXPECT_TRUE(manager.isTokenBlocked("tenant-token-1")); + + nowMs += 1; + EXPECT_FALSE(manager.isTokenBlocked("tenant-token-1")); + EXPECT_FALSE(manager.isActive()); +} + TEST(KillSwitchManagerTests, handleResponse_NonNumericKillDuration_DoesNotThrowAndDoesNotBlock) { KillSwitchManager manager; diff --git a/tests/unittests/OfflineStorageTests_SQLite.cpp b/tests/unittests/OfflineStorageTests_SQLite.cpp index d5aa6808a..015e197d7 100644 --- a/tests/unittests/OfflineStorageTests_SQLite.cpp +++ b/tests/unittests/OfflineStorageTests_SQLite.cpp @@ -312,32 +312,31 @@ TEST_F(OfflineStorageTests_SQLite, ReservedRecordsAreReleasedAfterTimeout) ASSERT_THAT(offlineStorage->StoreRecord({"guid1", "token", EventLatency_Normal, EventPersistence_Normal, 1, {}}), true); ASSERT_THAT(offlineStorage->StoreRecord({"guid2", "token", EventLatency_Normal, EventPersistence_Normal, 1, {}}), true); TestRecordConsumer consumer; - // Reserve first for 2 secs - EXPECT_THAT(offlineStorage->GetAndReserveRecords(consumer, 2000, EventLatency_Unspecified, 1), true); + EXPECT_THAT(offlineStorage->GetAndReserveRecords(consumer, 5000, EventLatency_Unspecified, 1), true); ASSERT_THAT(consumer.records.size(), 1); consumer.records.clear(); - PAL::sleep(500); - - // Reserve second for 1 sec, first still unavailable - EXPECT_THAT(offlineStorage->GetAndReserveRecords(consumer, 1000, EventLatency_Unspecified, 1), true); + // The first record remains reserved, so the second call returns the other record. + EXPECT_THAT(offlineStorage->GetAndReserveRecords(consumer, 5000, EventLatency_Unspecified, 1), true); ASSERT_THAT(consumer.records.size(), 1); consumer.records.clear(); auto records = offlineStorage->GetRecords(true, EventLatency_Unspecified, 0); ASSERT_THAT(records.size(), 2); - int64_t waitUntilMs = 0; for (auto const& record : records) { - waitUntilMs = std::max(waitUntilMs, record.reservedUntil); + EXPECT_GT(record.reservedUntil, 1); } - while (PAL::getUtcSystemTimeMs() <= waitUntilMs + 250) + // Simulate lease expiry without depending on wall-clock sleeps or CI scheduling. + offlineStorage->Execute("UPDATE events SET reserved_until=1"); + records = offlineStorage->GetRecords(true, EventLatency_Unspecified, 0); + ASSERT_THAT(records.size(), 2); + for (auto const& record : records) { - PAL::sleep(50); + EXPECT_EQ(record.reservedUntil, 1); } - // Both records are timed out EXPECT_THAT(offlineStorage->GetAndReserveRecords(consumer, 1000), true); ASSERT_THAT(consumer.records.size(), 2); EXPECT_THAT(consumer.records[0].retryCount, 1); From 3a3a83b4927b6e71839e82f5539e5528b0daf44d Mon Sep 17 00:00:00 2001 From: Microsoft Open Source Security Bot Date: Tue, 4 Aug 2026 13:02:39 -0700 Subject: [PATCH 33/40] Pin GitHub Actions to full-length commit SHAs (#1517) --- .github/dependabot.yml | 11 +++++++++++ .github/workflows/build-android.yml | 8 ++++---- .github/workflows/build-ios-mac.yml | 2 +- .github/workflows/build-posix-latest.yml | 4 ++-- .github/workflows/build-ubuntu-2204.yml | 2 +- .github/workflows/build-windows-vs2022.yaml | 2 +- .github/workflows/codeql-analysis.yml | 16 ++++++++-------- .github/workflows/deploy-docs-pages.yml | 10 +++++----- .github/workflows/spellcheck.yml | 2 +- .github/workflows/test-vcpkg.yml | 10 +++++----- .github/workflows/test-win-latest.yml | 6 +++--- 11 files changed, 42 insertions(+), 31 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..2c48305b7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + groups: + github-actions: + patterns: ["*"] + schedule: + interval: "weekly" + cooldown: + default-days: 7 diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 1235e8dc7..1ce8a3aa9 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -35,7 +35,7 @@ jobs: name: Build for Android steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: submodules: false - name: Update submodules @@ -44,7 +44,7 @@ jobs: git config --global submodule.lib/modules.update none git -c protocol.version=2 submodule update --init --force --depth=1 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'adopt' java-version: '17' @@ -52,7 +52,7 @@ jobs: # Workaround for: 'Unable to decrypt local Maven settings credentials' run: rm $Env:USERPROFILE\.m2\settings.xml - name: Setup Android SDK - uses: android-actions/setup-android@v3 + uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 - name: Install NDK run: | java -version @@ -83,7 +83,7 @@ jobs: working-directory: lib\android_build - name: Upload Reports if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: reports path: lib\android_build\maesdk\build\reports diff --git a/.github/workflows/build-ios-mac.yml b/.github/workflows/build-ios-mac.yml index 7ca85012b..29b3dfc34 100644 --- a/.github/workflows/build-ios-mac.yml +++ b/.github/workflows/build-ios-mac.yml @@ -54,7 +54,7 @@ jobs: - name: Grant write permissions to /usr/local run: | sudo chown -R $USER:staff /usr/local - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: submodules: 'true' continue-on-error: true diff --git a/.github/workflows/build-posix-latest.yml b/.github/workflows/build-posix-latest.yml index 8f9320e57..7a35c5a54 100644 --- a/.github/workflows/build-posix-latest.yml +++ b/.github/workflows/build-posix-latest.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 continue-on-error: true - name: Test ${{ matrix.os }} ${{ matrix.config }} run: ./build-tests.sh ${{ matrix.config }} @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Install clang run: sudo apt-get update && sudo apt-get install -y clang - name: Compile each public header standalone under strict flags diff --git a/.github/workflows/build-ubuntu-2204.yml b/.github/workflows/build-ubuntu-2204.yml index 1fbcc6404..6c779c8b5 100644 --- a/.github/workflows/build-ubuntu-2204.yml +++ b/.github/workflows/build-ubuntu-2204.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 continue-on-error: true - name: Test ${{ matrix.os }} ${{ matrix.config }} run: ./build-tests.sh ${{ matrix.config }} \ No newline at end of file diff --git a/.github/workflows/build-windows-vs2022.yaml b/.github/workflows/build-windows-vs2022.yaml index 222e32e67..e109575e8 100644 --- a/.github/workflows/build-windows-vs2022.yaml +++ b/.github/workflows/build-windows-vs2022.yaml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Build env: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index db7b4870a..a1f7a9c7f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,12 +39,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 continue-on-error: true # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -75,7 +75,7 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 analyze-java: name: Analyze Java @@ -90,7 +90,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 continue-on-error: true - name: Update submodules @@ -100,19 +100,19 @@ jobs: git -c protocol.version=2 submodule update --init --force --depth=1 - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: languages: java - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: 'adopt' java-version: '17' - name: Remove default github maven configuration run: rm $Env:USERPROFILE\.m2\settings.xml - name: Setup Android SDK - uses: android-actions/setup-android@v3 + uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 - name: Install NDK run: | java -version @@ -139,4 +139,4 @@ jobs: working-directory: lib\android_build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 diff --git a/.github/workflows/deploy-docs-pages.yml b/.github/workflows/deploy-docs-pages.yml index 09ecd2d35..a3f13366f 100644 --- a/.github/workflows/deploy-docs-pages.yml +++ b/.github/workflows/deploy-docs-pages.yml @@ -33,10 +33,10 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" @@ -55,7 +55,7 @@ jobs: - name: Upload Pages artifact if: github.event_name != 'pull_request' - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 with: path: docs/public/_build/html @@ -71,8 +71,8 @@ jobs: steps: - name: Configure GitHub Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index eeedb9c62..261ff567f 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 continue-on-error: true - name: install misspell diff --git a/.github/workflows/test-vcpkg.yml b/.github/workflows/test-vcpkg.yml index bdf37bd2e..59961ce53 100644 --- a/.github/workflows/test-vcpkg.yml +++ b/.github/workflows/test-vcpkg.yml @@ -26,7 +26,7 @@ jobs: runs-on: windows-latest name: Windows (x64-windows-static) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Bootstrap vcpkg run: | @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest name: Linux (x64-linux) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Bootstrap vcpkg run: | @@ -60,7 +60,7 @@ jobs: runs-on: macos-latest name: macOS (native) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Bootstrap vcpkg run: | @@ -78,7 +78,7 @@ jobs: runs-on: macos-latest name: iOS (arm64-ios cross-compile) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Bootstrap vcpkg run: | @@ -96,7 +96,7 @@ jobs: runs-on: ubuntu-latest name: Android (arm64-v8a API 23 cross-compile) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Bootstrap vcpkg run: | diff --git a/.github/workflows/test-win-latest.yml b/.github/workflows/test-win-latest.yml index 4928fc71f..2a77d5e2a 100644 --- a/.github/workflows/test-win-latest.yml +++ b/.github/workflows/test-win-latest.yml @@ -43,11 +43,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 continue-on-error: true - name: setup-msbuild - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0 with: vs-version: '[17,)' @@ -60,7 +60,7 @@ jobs: runs-on: windows-2022 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Compile each public header standalone under /W4 /WX shell: cmd run: tests\headers\check_public_headers.cmd From 6c19c2f117916489095d79b7f95b1735806bef4c Mon Sep 17 00:00:00 2001 From: bmehta001 Date: Tue, 4 Aug 2026 22:14:28 -0500 Subject: [PATCH 34/40] Preserve sub-millisecond event timestamp precision (#1516) * Preserve sub-millisecond event timestamp precision Use precise wall-clock time where available and retain nanosecond-derived 100 ns ticks on POSIX so record.time no longer truncates every event to milliseconds. Add regression coverage for POSIX timestamp precision.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 05d1030e-75b0-447f-9856-65091d59a97f * Cache precise Windows clock lookup Resolve GetSystemTimePreciseAsFileTime once instead of repeating module and symbol lookups for every event timestamp.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 05d1030e-75b0-447f-9856-65091d59a97f --- lib/pal/PAL.cpp | 28 +++++++++++++++++++++++----- tests/unittests/PalTests.cpp | 16 ++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/pal/PAL.cpp b/lib/pal/PAL.cpp index 3e667653f..0fc28abfb 100644 --- a/lib/pal/PAL.cpp +++ b/lib/pal/PAL.cpp @@ -430,7 +430,26 @@ namespace PAL_NS_BEGIN { { #ifdef _WIN32 FILETIME tocks; - ::GetSystemTimeAsFileTime(&tocks); + // Resolve the precise API dynamically so the SDK retains its Windows 7 + // runtime compatibility and falls back when the API is unavailable. + using GetSystemTimePreciseAsFileTimeProc = VOID (WINAPI*)(LPFILETIME); + static const GetSystemTimePreciseAsFileTimeProc getSystemTimePreciseAsFileTime = + []() -> GetSystemTimePreciseAsFileTimeProc + { + HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll"); + return kernel32 + ? reinterpret_cast( + ::GetProcAddress(kernel32, "GetSystemTimePreciseAsFileTime")) + : nullptr; + }(); + if (getSystemTimePreciseAsFileTime) + { + getSystemTimePreciseAsFileTime(&tocks); + } + else + { + ::GetSystemTimeAsFileTime(&tocks); + } ULONGLONG ticks = (ULONGLONG(tocks.dwHighDateTime) << 32) | tocks.dwLowDateTime; // number of days from beginning to 1601 multiplied by ticks per day return ticks + 0x701ce1722770000ULL; @@ -440,10 +459,9 @@ namespace PAL_NS_BEGIN { // This UTC epoch contract has been signed in blood since C++20 std::chrono::time_point now = std::chrono::system_clock::now(); auto duration = now.time_since_epoch(); - auto millis = std::chrono::duration_cast(duration).count(); - uint64_t ticks = millis; - ticks *= 10000; // convert millis to ticks (1 tick = 100ns) - ticks += 0x89F7FF5F7B58000ULL; // UTC time 0 in .NET ticks + auto nanos = std::chrono::duration_cast(duration).count(); + int64_t ticks = nanos / 100; // convert nanoseconds to .NET ticks (1 tick = 100ns) + ticks += static_cast(0x89F7FF5F7B58000ULL); // UTC time 0 in .NET ticks return ticks; #endif } diff --git a/tests/unittests/PalTests.cpp b/tests/unittests/PalTests.cpp index ddf1f6dd2..c931ff376 100644 --- a/tests/unittests/PalTests.cpp +++ b/tests/unittests/PalTests.cpp @@ -122,6 +122,22 @@ TEST_F(PalTests, SystemTime) EXPECT_THAT(t1, Lt(t0 + 1000)); } +#if !defined(_WIN32) && !defined(_WIN64) +TEST_F(PalTests, SystemTimeInTicksPreservesSubMillisecondPrecision) +{ + constexpr int64_t TicksPerMillisecond = 10000; + bool observedSubMillisecondTick = false; + + for (int i = 0; i < 1000 && !observedSubMillisecondTick; ++i) + { + observedSubMillisecondTick = + PAL::getUtcSystemTimeinTicks() % TicksPerMillisecond != 0; + } + + EXPECT_TRUE(observedSubMillisecondTick); +} +#endif + TEST_F(PalTests, FormatUtcTimestampMsAsISO8601) { EXPECT_THAT(PAL::formatUtcTimestampMsAsISO8601(0ll), Eq("1970-01-01T00:00:00.000Z")); From 55aa8fa0d0a4d0d320b29a888784dcf4014de4de Mon Sep 17 00:00:00 2001 From: bmehta001 Date: Fri, 7 Aug 2026 17:09:26 -0500 Subject: [PATCH 35/40] Fix Apple test dependency targets (#1518) Attach GoogleTest include paths to test targets and use the canonical SQLite3 target while retaining compatibility with older CMake releases. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 3 +++ lib/CMakeLists.txt | 14 +++++++++----- tests/CMakeLists.txt | 24 +++++++++++++++++++----- tests/functests/CMakeLists.txt | 7 ++++++- tests/unittests/CMakeLists.txt | 7 ++++++- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc36e9da3..94397381b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -454,6 +454,9 @@ if(MATSDK_USE_VCPKG_DEPS) # distribution links them the same way), so the vcpkg sqlite3/zlib packages are # not pulled there -- find the system ones via CMake's standard find modules. find_package(SQLite3 REQUIRED) + if(NOT TARGET SQLite3::SQLite3) + add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) + endif() find_package(ZLIB REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) set(MATSDK_APPLE_SYSTEM_DEPS ON) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 13b4d46d4..ce2df9b89 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -502,12 +502,12 @@ if(MATSDK_USE_VCPKG_DEPS) # These are PUBLIC so static-library consumers get the transitive link set # through the exported MSTelemetry::mat target. if(APPLE) - # macOS/iOS link the system libsqlite3 + libz (SQLite::SQLite3 / ZLIB::ZLIB + # macOS/iOS link the system libsqlite3 + libz (SQLite3::SQLite3 / ZLIB::ZLIB # resolve to the OS libraries via CMake's find modules), so the vcpkg # sqlite3/zlib packages are neither pulled nor linked here. target_link_libraries(mat PUBLIC - SQLite::SQLite3 + SQLite3::SQLite3 ZLIB::ZLIB nlohmann_json::nlohmann_json ${LIBS} @@ -583,15 +583,19 @@ else() target_link_libraries(mat PRIVATE sqlite3 z ${LIBS}) else() # Linux legacy: system zlib + system (or private minimal) sqlite3. ZLIB::ZLIB - # and SQLite::SQLite3 are imported targets that carry their own include dirs. + # and SQLite3::SQLite3 are imported targets that carry their own include dirs. find_package(ZLIB REQUIRED) if(MATSDK_BUNDLE_SQLITE) target_link_libraries(mat PRIVATE sqlite3_bundled ZLIB::ZLIB ${LIBS}) else() # find_package(SQLite3) needs CMake >= 3.14, guaranteed by the project floor; - # SQLite::SQLite3 is an imported target carrying its own include dirs. + # SQLite3::SQLite3 is the canonical imported target. CMake < 4.3 only + # provides the deprecated SQLite::SQLite3 spelling. find_package(SQLite3 REQUIRED) - target_link_libraries(mat PRIVATE SQLite::SQLite3 ZLIB::ZLIB ${LIBS}) + if(NOT TARGET SQLite3::SQLite3) + add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) + endif() + target_link_libraries(mat PRIVATE SQLite3::SQLite3 ZLIB::ZLIB ${LIBS}) endif() endif() endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a1d0a1351..785372186 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,11 +1,25 @@ include_directories(. ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/public ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/../lib/decoder ${CMAKE_CURRENT_SOURCE_DIR}/../sqlite ) -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googletest/include - ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googlemock/include -) +set(MATSDK_GTEST_INCLUDE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googletest/include) +set(MATSDK_GMOCK_INCLUDE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googlemock/include) +if(NOT EXISTS "${MATSDK_GTEST_INCLUDE_DIR}/gtest/gtest.h") + message(FATAL_ERROR + "Tests require the third_party/googletest submodule at " + "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest.") +endif() -include_directories(../lib) +add_library(matsdk_test_includes INTERFACE) +target_include_directories(matsdk_test_includes INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/public + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/mat + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/decoder + ${CMAKE_CURRENT_SOURCE_DIR}/../sqlite + ${MATSDK_GTEST_INCLUDE_DIR} + ${MATSDK_GMOCK_INCLUDE_DIR}) set(TESTS_COMMON_SRCS ../common/Common.cpp diff --git a/tests/functests/CMakeLists.txt b/tests/functests/CMakeLists.txt index d09e31f62..796623789 100644 --- a/tests/functests/CMakeLists.txt +++ b/tests/functests/CMakeLists.txt @@ -84,7 +84,10 @@ else() set (SQLITE3_LIB "/usr/local/opt/sqlite/lib/libsqlite3.a") else() find_package(SQLite3 REQUIRED) - set (SQLITE3_LIB SQLite::SQLite3) + if(NOT TARGET SQLite3::SQLite3) + add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) + endif() + set (SQLITE3_LIB SQLite3::SQLite3) endif() if(TARGET zlib_bundled) @@ -154,4 +157,6 @@ else() endif() +target_link_libraries(FuncTests matsdk_test_includes) + add_test(FuncTests FuncTests "--gtest_output=xml:${PROJECT_BINARY_DIR}/test-reports/FuncTests.xml") diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 7233d2920..a7efe90ae 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -148,7 +148,10 @@ else() set (SQLITE3_LIB "/opt/homebrew/opt/sqlite/lib/libsqlite3.a") else() find_package(SQLite3 REQUIRED) - set (SQLITE3_LIB SQLite::SQLite3) + if(NOT TARGET SQLite3::SQLite3) + add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) + endif() + set (SQLITE3_LIB SQLite3::SQLite3) endif() if(TARGET zlib_bundled) @@ -221,4 +224,6 @@ else() endif() +target_link_libraries(UnitTests matsdk_test_includes) + add_test(UnitTests UnitTests "--gtest_output=xml:${PROJECT_BINARY_DIR}/test-reports/UnitTests.xml") From 49c79d264b073b6fd8471a491601aec58fb7df9a Mon Sep 17 00:00:00 2001 From: bmehta001 Date: Fri, 7 Aug 2026 23:51:30 -0500 Subject: [PATCH 36/40] Modernize CMake embedding and self-contained dependencies (#1511) * Add non-vcpkg CMake embedding target Expose the same MSTelemetry::mat target name for build-tree add_subdirectory/FetchContent consumers so downstream projects can link one target regardless of vcpkg/install vs source embedding. Files changed: - lib/CMakeLists.txt: add MSTelemetry::mat build-tree alias. - CMakeLists.txt, lib/CMakeLists.txt: add optional MATSDK_CURL_TARGET, MATSDK_SQLITE_TARGET, and MATSDK_ZLIB_TARGET overrides for non-vcpkg superbuilds, with a WIN32 zlib guard for the existing act_z_* header path. - docs/embedding-with-cmake.md: document source embedding and dependency target overrides. - tests/embedding/CMakeLists.txt: add add_subdirectory smoke project linking MSTelemetry::mat. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Add fetched static curl for non-vcpkg embedding Allow source-embedding consumers to set MATSDK_CURL_PROVIDER=FETCH so 1DS downloads and builds a pinned static curl dependency on Linux, matching the ORT GenAI model. Details: - Add MATSDK_CURL_PROVIDER and MATSDK_CURL_TLS_BACKEND options, defaulting to package discovery and mbedTLS for fetched curl. - Add pinned curl and mbedTLS URL/SHA cache variables. - Add cmake/MatsdkFetchCurl.cmake to build HTTP(S)-only static curl with mbedTLS or OpenSSL. - Document fetched curl and dependency-target override usage for non-vcpkg embedding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Address Copilot review for fetched curl embedding Use SHA256 URL_HASH pins for fetched curl and mbedTLS instead of SHA1, matching FetchContent's stronger integrity checks. Do not override CURL_CA_BUNDLE/CURL_CA_PATH to none; allow fetched curl to use normal CA discovery so default TLS verification can succeed without every consumer supplying CAINFO. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Harden curl capability handling in embedded builds Select HTTP/2 only when the linked curl runtime advertises support and otherwise request HTTP/1.1, so minimal fetched curl builds do not force an unavailable protocol. Check curl option/getinfo failures, preserve constructor configuration errors, and use the correct response-code/socket types before sending. Also fix the conventional calloc argument ordering in EventProperties while bundling small correctness work with the larger embedding PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Modernize CMake embedding and portability Bundle the remaining embedding work into PR #1511 so downstream projects can consume one stable target without source rewrites or platform-specific dependency glue. Key changes: - use standard CMAKE_OSX_* architecture/sysroot/deployment settings with legacy input compatibility; - add canonical MATSDK_* build options and explicit STATIC/SHARED library selection; - make warnings, Werror, ARC, visibility, and dead-strip policy target-local; - add explicit SYSTEM/MINIMAL/VENDORED SQLite and zlib providers with self-contained static installs; - preserve static/dynamic and pinned-source vcpkg compatibility; - add FetchContent consumer CI for Linux, Windows, macOS universal/arm64, iOS device/simulator, and Android under warnings-as-errors; - update build scripts/docs and route legacy installation through cmake --install. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Use typed libcurl write callbacks Pass curl_write_callback function pointers instead of converting function pointers to void*, and use void* only for callback userdata. This preserves portability on architectures where function and data pointers differ. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Use explicit Apple package-config boolean Substitute a build-platform boolean that is always TRUE or FALSE so generated package configs never depend on an undefined APPLE variable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Finish curl response handling and build argument safety Capture response headers with an explicit typed callback, reject invalid or failed socket waits, and build CMake invocations as argv arrays so custom flags and universal architecture lists retain correct quoting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Simplify recent CMake compatibility surface Remove recent redundant SQLite/vendor compatibility switches in favor of the explicit provider options, while retaining established legacy build inputs. Also reuse parent-provided CURL::libcurl automatically and make bundled Apple-mobile SQLite explicitly disable gethostuuid, matching the remaining useful ONNX Runtime patch behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Standardize CMake embedding on canonical interfaces Reduce downstream integration work by using standard CMake platform/linkage inputs, canonical dependency targets, provider enums, unified build/install exports, and one root Android target. Keep direct builds at the existing CMake floor while preset wrappers require the preset-capable toolchain. Files: - CMakeLists.txt, CMakePresets.json, cmake/: canonical options, dependency providers, package exports, and preset guard - lib/, tests/: target-scoped configuration, unified Android source graph, and simplified test linkage - build*.sh, build-cmake.ps1, tools/setup-buildtools*: preset-based wrappers and compatible tooling - .github/workflows/, docs/, tools/ports/: consumer matrices, embedding guidance, and canonical vcpkg mappings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Make Apple system SQLite/zlib imported targets GLOBAL in package config Fix an inconsistency found in code review: the generated static-package config created SQLite::SQLite3/ZLIB::ZLIB without GLOBAL for the Apple/system case, while the root CMakeLists.txt uses GLOBAL for the identical construct. Non-GLOBAL imported targets are only visible in the directory that creates them and its subdirectories, so a multi-directory consumer that calls find_package(MSTelemetry) in one directory and links MSTelemetry::mat from a sibling directory would fail to resolve these targets at generate time. The existing MSTelemetry::sqlite_dependency/zlib_dependency wrappers were already GLOBAL for this reason; this fix makes the targets they delegate to consistent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Share Apple system SQLite/zlib target creation between build and install Extract the Apple-system-SQLite/zlib imported-target logic (previously copy-pasted between CMakeLists.txt and cmake/MSTelemetryConfig.cmake.in) into a single shared cmake/MatsdkAppleSystemDeps.cmake helper, included from both files and installed alongside MSTelemetryConfig.cmake. This is exactly the class of bug fixed in the previous commit: the two copies had drifted out of sync (one had GLOBAL, one did not) because there was no structural mechanism preventing divergence. With one shared definition, that can no longer happen. Validated: - Windows FetchContent embedding build/run: 10/10 passed. - Linux static package build/install: MatsdkAppleSystemDeps.cmake installs alongside MSTelemetryConfig.cmake in lib/cmake/MSTelemetry/. - Standalone CMake project simulating the generated Apple-branch install config: SQLite::SQLite3 and ZLIB::ZLIB are created with IMPORTED_GLOBAL=TRUE and the correct underlying link libraries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Add minimal backward-compat handling for pre-2026 legacy build inputs Apply the simplest-design-while-kind-to-users principle to three legacy build inputs that predate this PR by more than three months: - USE_CURL (Android): translated to MATSDK_ANDROID_HTTP_CLIENT=CURL when the canonical option was not already set explicitly. This is a real functional switch for deliberate Android-curl consumers (not just a renamed knob), and the translation is a handful of lines following the existing matsdk_bool_option pattern, so it is cheap to keep working. - INSTALL_LIB_DIR and BUILD_STATIC_SQLITE: detected and reported via message(DEPRECATION ...) with no behavioral translation. Both are narrow, internal packaging-path/linkage knobs whose old semantics do not map cleanly onto the new GNUInstallDirs layout or MATSDK_SQLITE_PROVIDER model, so silently reinterpreting them would add real complexity for very few users. Instead of silently ignoring them (CMake's default for an unrecognized -D), an old script now gets an explicit, actionable message instead of a silent layout/linkage change. - The Android AAR CMake target name change (maesdk -> mat, with the .so OUTPUT_NAME already preserved as maesdk) is intentionally NOT given a compatibility target: that target was never installed/exported and was purely internal wiring within one subdirectory, so it was never a public interface external code could reference. Validated: -DUSE_CURL=ON on an Android configure resolves MATSDK_ANDROID_HTTP_CLIENT to CURL and requires curl as expected; -DINSTALL_LIB_DIR=... -DBUILD_STATIC_SQLITE=ON on a Linux configure prints both deprecation warnings and still configures successfully. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Consolidate dependency and build integration Share shell and CMake dependency wiring across native, iOS, test, and package builds while preserving provider-specific behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Align vcpkg iOS deployment target Ensure vcpkg-built Apple libraries match the consumer deployment target and avoid linker warnings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4259-b03b-8eeb87c06837 * Harden Apple packaging integration Propagate the resolved iOS sysroot to embedding builds and keep Apple vendored targets compatible with strict warning settings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 * Migrate Apple builds to canonical CMake variables Remove legacy Apple architecture, platform, and deployment-target inputs so standalone scripts and embedding consumers share CMAKE_OSX_* configuration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 * Harden dependency targets and test curl headers Preserve repeated package dependency configuration and cover curl response header/body capture with a local test server. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 * Let curl detect mbedTLS DES support Do not claim DES ECB support through a stale CMake hint; curl digest authentication is disabled and capability detection should remain authoritative. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 * Fix test compiler flags and SQLite target aliases Keep MSVC from receiving GCC-only warning flags and support both CMake SQLite target spellings used by Apple and legacy consumers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Parse CMake options without shell evaluation Preserve quoted and escaped option values while removing eval from the shared build wrapper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Load Apple dependency helpers only on Apple Avoid loading the Apple system-library helper on non-Apple configurations while retaining lazy package-consumer loading. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Clarify data section compiler flag Apply -fdata-sections only to GCC and non-Apple Clang toolchains. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Honor standard SQLite target in auto detection Treat SQLite3::SQLite3 as an existing system provider before selecting bundled SQLite. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Reject Android Room on non-Android builds Fail during configuration before selecting Android-only Room sources on other platforms. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Align iOS deployment target defaults Use the documented iOS 13 minimum consistently in the wrapper, vcpkg port, and consumer smoke test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Clean up package helper installation Install Apple system dependency helpers only in Apple packages and remove unsolicited legacy-option diagnostics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 * Use canonical CMake options directly Remove the legacy option translation helper and document the canonical Android Room option. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 Copilot-Session: 741fc486-3743-4095-b44d-d65afa131d75 Copilot-Session: 5f341bc5-f8ae-4259-b03b-8eeb87c06837 Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 --- .github/workflows/build-ios-mac.yml | 9 +- .github/workflows/build-posix-latest.yml | 3 +- .github/workflows/build-ubuntu-2204.yml | 3 +- .github/workflows/test-embedding.yml | 233 +++++++ CMakeLists.txt | 603 ++++++------------ CMakePresets.json | 194 ++++++ README.md | 6 + build-cmake.ps1 | 61 ++ build-gtest.sh | 3 +- build-ios.sh | 97 ++- build-tests-ios.sh | 3 +- build-tests.sh | 3 +- build.sh | 110 ++-- cmake/MSTelemetryConfig.cmake.in | 103 ++- cmake/MatsdkAppleSystemDeps.cmake | 18 + cmake/MatsdkDependencyTargets.cmake | 40 ++ cmake/MatsdkFetchCurl.cmake | 143 +++++ cmake/MatsdkOptions.cmake | 181 ++++++ cmake/MatsdkRequirePresetSupport.cmake | 7 + docs/building-with-vcpkg.md | 23 +- docs/cpp-start-android.md | 2 +- docs/cpp-start-ios.md | 19 + docs/cpp-start-macosx.md | 18 +- docs/embedding-with-cmake.md | 81 +++ install.sh | 14 +- lib/CMakeLists.txt | 499 ++++++++------- lib/android_build/app/build.gradle | 4 +- .../app/src/main/cpp/CMakeLists.txt | 48 +- lib/android_build/maesdk/build.gradle | 5 +- .../maesdk/src/main/cpp/CMakeLists.txt | 210 +----- lib/http/HttpClient_Apple.mm | 2 +- lib/http/HttpClient_Curl.hpp | 221 +++++-- lib/include/CMakeLists.txt | 20 +- .../posix/NetworkInformationImpl_Android.cpp | 2 +- lib/system/EventProperties.cpp | 3 +- tests/CMakeLists.txt | 38 +- tests/embedding/CMakeLists.txt | 80 +++ tests/functests/CMakeLists.txt | 130 +--- tests/unittests/CMakeLists.txt | 144 +---- tests/unittests/HttpClientCurlTests.cpp | 57 ++ tests/vcpkg/test-vcpkg-ios.sh | 2 +- tests/vcpkg/test-vcpkg-windows.ps1 | 2 +- tools/build-common.sh | 127 ++++ .../ports/cpp-client-telemetry/portfile.cmake | 69 +- tools/setup-buildtools-apple.sh | 3 - tools/setup-buildtools.sh | 14 +- 46 files changed, 2303 insertions(+), 1354 deletions(-) create mode 100644 .github/workflows/test-embedding.yml create mode 100644 CMakePresets.json create mode 100644 build-cmake.ps1 create mode 100644 cmake/MatsdkAppleSystemDeps.cmake create mode 100644 cmake/MatsdkDependencyTargets.cmake create mode 100644 cmake/MatsdkFetchCurl.cmake create mode 100644 cmake/MatsdkOptions.cmake create mode 100644 cmake/MatsdkRequirePresetSupport.cmake create mode 100644 docs/embedding-with-cmake.md create mode 100644 tests/embedding/CMakeLists.txt create mode 100644 tools/build-common.sh diff --git a/.github/workflows/build-ios-mac.yml b/.github/workflows/build-ios-mac.yml index 29b3dfc34..d7687200d 100644 --- a/.github/workflows/build-ios-mac.yml +++ b/.github/workflows/build-ios-mac.yml @@ -55,14 +55,13 @@ jobs: run: | sudo chown -R $USER:staff /usr/local - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - with: - submodules: 'true' - continue-on-error: true + - name: Initialize googletest + run: git submodule update --init --depth=1 third_party/googletest - name: build run: | if [[ "${{ matrix.os }}" == "macos-14" ]]; then - export IOS_DEPLOYMENT_TARGET=13.0; + export CMAKE_OSX_DEPLOYMENT_TARGET=13.0; elif [[ "${{ matrix.os }}" == "macos-15" ]]; then - export IOS_DEPLOYMENT_TARGET=15.0; + export CMAKE_OSX_DEPLOYMENT_TARGET=15.0; fi ./build-tests-ios.sh ${{ matrix.config }} ${{ matrix.simulator }} diff --git a/.github/workflows/build-posix-latest.yml b/.github/workflows/build-posix-latest.yml index 7a35c5a54..dc45fe14e 100644 --- a/.github/workflows/build-posix-latest.yml +++ b/.github/workflows/build-posix-latest.yml @@ -44,7 +44,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - continue-on-error: true + - name: Initialize googletest + run: git submodule update --init --depth=1 third_party/googletest - name: Test ${{ matrix.os }} ${{ matrix.config }} run: ./build-tests.sh ${{ matrix.config }} diff --git a/.github/workflows/build-ubuntu-2204.yml b/.github/workflows/build-ubuntu-2204.yml index 6c779c8b5..ca21ad8c4 100644 --- a/.github/workflows/build-ubuntu-2204.yml +++ b/.github/workflows/build-ubuntu-2204.yml @@ -44,6 +44,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - continue-on-error: true + - name: Initialize googletest + run: git submodule update --init --depth=1 third_party/googletest - name: Test ${{ matrix.os }} ${{ matrix.config }} run: ./build-tests.sh ${{ matrix.config }} \ No newline at end of file diff --git a/.github/workflows/test-embedding.yml b/.github/workflows/test-embedding.yml new file mode 100644 index 000000000..0565a1c86 --- /dev/null +++ b/.github/workflows/test-embedding.yml @@ -0,0 +1,233 @@ +name: Source embedding matrix + +on: + push: + branches: + - main + - master + - dev + pull_request: + branches: + - main + - master + - dev + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - dependencies: system + library-type: STATIC + shared: OFF + fetchcontent: OFF + preload-curl: ON + preload-storage: ON + - dependencies: system + library-type: SHARED + shared: ON + fetchcontent: ON + preload-curl: OFF + preload-storage: OFF + - dependencies: self-contained + library-type: STATIC + shared: OFF + fetchcontent: ON + preload-curl: OFF + preload-storage: OFF + steps: + - uses: actions/checkout@v4 + - name: Install system dependencies + if: matrix.dependencies == 'system' + run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev libsqlite3-dev ninja-build zlib1g-dev + - name: Configure + shell: bash + run: | + options=( + -G Ninja + -S tests/embedding + -B build-embedding + -DCMAKE_BUILD_TYPE=Release + -DMATSDK_EMBEDDING_USE_FETCHCONTENT=${{ matrix.fetchcontent }} + -DMATSDK_EMBEDDING_PRELOAD_CURL=${{ matrix.preload-curl }} + -DMATSDK_EMBEDDING_PRELOAD_STORAGE_DEPS=${{ matrix.preload-storage }} + -DBUILD_SHARED_LIBS=${{ matrix.shared }} + ) + if [[ "${{ matrix.dependencies }}" == "self-contained" ]]; then + options+=( + -DMATSDK_CURL_PROVIDER=FETCH + -DMATSDK_CURL_TLS_BACKEND=MBEDTLS + -DMATSDK_SQLITE_PROVIDER=MINIMAL + -DMATSDK_ZLIB_PROVIDER=VENDORED + ) + else + options+=( + -DMATSDK_SQLITE_PROVIDER=SYSTEM + -DMATSDK_ZLIB_PROVIDER=SYSTEM + ) + fi + cmake "${options[@]}" + - name: Build and run + run: | + cmake --build build-embedding --target embedding_test --parallel 4 + ./build-embedding/embedding_test + + windows: + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + - name: Configure + run: > + cmake -S tests/embedding -B build-embedding -A x64 + -DMATSDK_EMBEDDING_USE_FETCHCONTENT=ON + -DMATSDK_SQLITE_PROVIDER=VENDORED + -DMATSDK_ZLIB_PROVIDER=VENDORED + - name: Build and run + shell: pwsh + run: | + cmake --build build-embedding --config Release --target embedding_test -- /m + & .\build-embedding\Release\embedding_test.exe + + installed-package-linux: + runs-on: ubuntu-latest + strategy: + matrix: + mode: [system, fetched] + steps: + - uses: actions/checkout@v4 + - name: Install system dependencies + if: matrix.mode == 'system' + run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev libsqlite3-dev zlib1g-dev + - name: Configure, install, and consume + shell: bash + run: | + options=( + -G Ninja + -S . + -B build-package + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHARED_LIBS=OFF + -DMATSDK_BUILD_UNIT_TESTS=OFF + -DMATSDK_BUILD_FUNC_TESTS=OFF + -DMATSDK_BUILD_PACKAGE=OFF + -DMATSDK_BUILD_OBJC_WRAPPER=OFF + -DMATSDK_BUILD_SWIFT_WRAPPER=OFF + -DCMAKE_INSTALL_PREFIX="${RUNNER_TEMP}/matsdk" + ) + if [[ "${{ matrix.mode }}" == "fetched" ]]; then + options+=( + -DMATSDK_CURL_PROVIDER=FETCH + -DMATSDK_CURL_TLS_BACKEND=MBEDTLS + -DMATSDK_SQLITE_PROVIDER=MINIMAL + -DMATSDK_ZLIB_PROVIDER=VENDORED + ) + else + options+=( + -DMATSDK_SQLITE_PROVIDER=SYSTEM + -DMATSDK_ZLIB_PROVIDER=SYSTEM + ) + fi + cmake "${options[@]}" + cmake --build build-package --target mat --parallel 4 + cmake --install build-package + cmake -G Ninja -S tests/vcpkg -B build-consumer \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/matsdk" + cmake --build build-consumer --parallel 4 + ./build-consumer/vcpkg_test + + installed-package-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Configure, install, and consume + run: | + cmake -G Ninja -S . -B build-package \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DMATSDK_BUILD_UNIT_TESTS=OFF \ + -DMATSDK_BUILD_FUNC_TESTS=OFF \ + -DMATSDK_BUILD_PACKAGE=OFF \ + -DMATSDK_BUILD_OBJC_WRAPPER=OFF \ + -DMATSDK_BUILD_SWIFT_WRAPPER=OFF \ + -DMATSDK_SQLITE_PROVIDER=SYSTEM \ + -DMATSDK_ZLIB_PROVIDER=SYSTEM \ + -DCMAKE_INSTALL_PREFIX="${RUNNER_TEMP}/matsdk" + cmake --build build-package --target mat --parallel 4 + cmake --install build-package + cmake -G Ninja -S tests/vcpkg -B build-consumer \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/matsdk" + cmake --build build-consumer --parallel 4 + ./build-consumer/vcpkg_test + + macos: + runs-on: macos-latest + strategy: + matrix: + architectures: [arm64, "arm64;x86_64"] + steps: + - uses: actions/checkout@v4 + - name: Configure + run: > + cmake -G Ninja -S tests/embedding -B build-embedding + -DCMAKE_BUILD_TYPE=Release + "-DCMAKE_OSX_ARCHITECTURES=${{ matrix.architectures }}" + -DMATSDK_EMBEDDING_USE_FETCHCONTENT=ON + -DMATSDK_SQLITE_PROVIDER=SYSTEM + -DMATSDK_ZLIB_PROVIDER=SYSTEM + - name: Build + run: cmake --build build-embedding --target embedding_test --parallel 4 + + ios: + runs-on: macos-latest + strategy: + matrix: + include: + - sdk: iphoneos + sqlite-provider: SYSTEM + zlib-provider: SYSTEM + - sdk: iphonesimulator + sqlite-provider: VENDORED + zlib-provider: VENDORED + steps: + - uses: actions/checkout@v4 + - name: Configure + run: > + cmake -G Xcode -S tests/embedding -B build-embedding + -DCMAKE_SYSTEM_NAME=iOS + -DCMAKE_OSX_SYSROOT=${{ matrix.sdk }} + -DCMAKE_OSX_ARCHITECTURES=arm64 + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 + -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO + -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO + -DMATSDK_EMBEDDING_USE_FETCHCONTENT=ON + -DMATSDK_SQLITE_PROVIDER=${{ matrix.sqlite-provider }} + -DMATSDK_ZLIB_PROVIDER=${{ matrix.zlib-provider }} + - name: Build + run: cmake --build build-embedding --config Release --target embedding_test --parallel 4 + + android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure + run: > + cmake -G Ninja -S tests/embedding -B build-embedding + -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_LATEST_HOME}/build/cmake/android.toolchain.cmake + -DANDROID_ABI=arm64-v8a + -DANDROID_PLATFORM=23 + -DCMAKE_BUILD_TYPE=Release + -DMATSDK_EMBEDDING_USE_FETCHCONTENT=ON + -DMATSDK_SQLITE_PROVIDER=VENDORED + -DMATSDK_ZLIB_PROVIDER=VENDORED + - name: Build + run: cmake --build build-embedding --target embedding_test --parallel 4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 94397381b..6d3a0c9c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,25 +1,26 @@ cmake_minimum_required(VERSION 3.15...3.31) + project(MSTelemetry LANGUAGES C CXX) +if(APPLE) + set(MATSDK_BUILD_PLATFORM_APPLE TRUE) +else() + set(MATSDK_BUILD_PLATFORM_APPLE FALSE) +endif() ################################################################################################ -# Vcpkg dependency mode: detect early so it can guard platform-specific flag logic +# Package-manager detection (internal; dependency selection is target/provider based) ################################################################################################ +set(MATSDK_USING_VCPKG OFF) if(DEFINED VCPKG_TOOLCHAIN OR DEFINED VCPKG_TARGET_TRIPLET) - option(MATSDK_USE_VCPKG_DEPS "Use vcpkg-provided dependencies via find_package()" ON) -else() - option(MATSDK_USE_VCPKG_DEPS "Use vcpkg-provided dependencies via find_package()" OFF) + set(MATSDK_USING_VCPKG ON) +endif() +message(STATUS "MATSDK_USING_VCPKG: ${MATSDK_USING_VCPKG}") + +include(cmake/MatsdkOptions.cmake) +include(cmake/MatsdkDependencyTargets.cmake) +if(APPLE) + include(cmake/MatsdkAppleSystemDeps.cmake) endif() -message(STATUS "MATSDK_USE_VCPKG_DEPS: ${MATSDK_USE_VCPKG_DEPS}") - -# Build a private, feature-stripped copy of the vendored SQLite amalgamation -# instead of linking an external SQLite. The SDK uses SQLite only for its offline -# event-storage cache, so the minimal build (see lib/CMakeLists.txt -# MATSDK_SQLITE_MINIMAL_DEFS) omits every optional SQLite subsystem the SDK does -# not use, shrinking the SQLite code ~10% and removing the external sqlite3 -# dependency. Off by default to preserve the existing external/system-SQLite -# behavior; the Android NDK path always bundles SQLite regardless. -option(MATSDK_MINIMAL_SQLITE "Build a feature-stripped vendored SQLite instead of an external one" OFF) -message(STATUS "MATSDK_MINIMAL_SQLITE: ${MATSDK_MINIMAL_SQLITE}") # Begin Uncomment for i386 build #set(CMAKE_SYSTEM_PROCESSOR i386) @@ -34,104 +35,17 @@ if (NOT TARGET_ARCH) set(TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) endif() -# Enable ARC for obj-c on Apple -# Initialize platform options before conditional blocks (needed for config templates) -if(NOT DEFINED BUILD_IOS) - set(BUILD_IOS OFF) -endif() -if(NOT APPLE AND NOT DEFINED BUILD_APPLE_HTTP) - set(BUILD_APPLE_HTTP OFF) -endif() - if(APPLE) - message(STATUS "BUILD_IOS: ${BUILD_IOS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-arc") - - # iOS build options - option(BUILD_IOS "Build for iOS" NO) - option(FORCE_RESET_OSX_DEPLOYMENT_TARGET "Clear the OSX Deployment Target Set" YES) - if (DEFINED FORCE_RESET_DEPLOYMENT_TARGET) - set(FORCE_RESET_OSX_DEPLOYMENT_TARGET ${FORCE_RESET_DEPLOYMENT_TARGET}) - endif() - - # When building via vcpkg, the toolchain file handles architecture, sysroot, - # deployment target, and platform flags. Skip manual flag configuration. - if(NOT MATSDK_USE_VCPKG_DEPS) - if(BUILD_IOS) - set(TARGET_ARCH "APPLE") - set(IOS True) - set(APPLE True) - - if(FORCE_RESET_OSX_DEPLOYMENT_TARGET) - set(CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE) - if (${IOS_PLAT} STREQUAL "iphonesimulator") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}") - else() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}") - endif() - endif() - - if((${IOS_PLAT} STREQUAL "iphoneos") OR (${IOS_PLAT} STREQUAL "iphonesimulator") OR (${IOS_PLAT} STREQUAL "xros") OR (${IOS_PLAT} STREQUAL "xrsimulator")) - set(IOS_PLATFORM "${IOS_PLAT}") - else() - message(FATAL_ERROR "Unrecognized iOS platform '${IOS_PLAT}'") - endif() - - if(${IOS_ARCH} STREQUAL "x86_64") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch x86_64") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64") - set(CMAKE_SYSTEM_PROCESSOR x86_64) - elseif(${IOS_ARCH} STREQUAL "arm64") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch arm64") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch arm64") - set(CMAKE_SYSTEM_PROCESSOR arm64) - elseif(${IOS_ARCH} STREQUAL "arm64e") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch arm64e") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch arm64e") - set(CMAKE_SYSTEM_PROCESSOR arm64e) - else() - message(FATAL_ERROR "Unrecognized iOS architecture '${IOS_ARCH}'") - endif() - - execute_process(COMMAND xcodebuild -version -sdk ${IOS_PLATFORM} ONLY_ACTIVE_ARCH=NO Path - OUTPUT_VARIABLE CMAKE_OSX_SYSROOT - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - message(STATUS "CMAKE_OSX_SYSROOT ${CMAKE_OSX_SYSROOT}") - message(STATUS "ARCHITECTURE: ${CMAKE_SYSTEM_PROCESSOR}") - message(STATUS "PLATFORM: ${IOS_PLATFORM}") - else() - if("${MAC_ARCH}" STREQUAL "x86_64") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch x86_64") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64") - set(CMAKE_SYSTEM_PROCESSOR x86_64) - set(TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) - set(CMAKE_OSX_ARCHITECTURES ${MAC_ARCH}) - set(APPLE True) - elseif("${MAC_ARCH}" STREQUAL "arm64") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch arm64") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch arm64") - set(CMAKE_SYSTEM_PROCESSOR arm64) - set(TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) - set(CMAKE_OSX_ARCHITECTURES ${MAC_ARCH}) - set(APPLE True) - else() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch x86_64 -arch arm64") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64 -arch arm64") - endif() - message(STATUS "MAC_ARCH: ${MAC_ARCH}") - endif() - else() - # vcpkg mode: just set internal flags from what the toolchain provides - if(BUILD_IOS OR CMAKE_SYSTEM_NAME STREQUAL "iOS") - set(BUILD_IOS ON) - set(TARGET_ARCH "APPLE") - set(IOS True) - endif() - message(STATUS "vcpkg toolchain managing architecture and platform flags") + if(MATSDK_PLATFORM_IOS) + set(TARGET_ARCH "APPLE") + set(IOS TRUE) + elseif(CMAKE_OSX_ARCHITECTURES) + set(TARGET_ARCH "${CMAKE_OSX_ARCHITECTURES}") endif() + message(STATUS "MATSDK_PLATFORM_IOS: ${MATSDK_PLATFORM_IOS}") + message(STATUS "CMAKE_OSX_ARCHITECTURES: ${CMAKE_OSX_ARCHITECTURES}") + message(STATUS "CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT}") + message(STATUS "CMAKE_OSX_DEPLOYMENT_TARGET: ${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") @@ -144,126 +58,41 @@ message(STATUS "CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") include(tools/ParseOsRelease.cmake) -# When building via vcpkg, let the toolchain manage compiler flags. -# Only apply project-specific flags for non-vcpkg (legacy) builds. -if(NOT MATSDK_USE_VCPKG_DEPS) - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - set(WARN_FLAGS "/W4 /WX") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # -Wno-unknown-warning-option is Clang-only, omitted here - set(WARN_FLAGS "-Wall -Werror -Wextra -Wno-unused-parameter -Wno-unused-but-set-variable") -else() - # Clang / AppleClang - set(WARN_FLAGS "-Wall -Werror -Wextra -Wno-unused-parameter -Wno-unknown-warning-option -Wno-unused-but-set-variable") -endif() - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # Using GCC with -s and -Wl linker flags. -ffunction-sections/-fdata-sections - # are set once for all dep modes by the global block further below. - set(REL_FLAGS "-s -Wl,--gc-sections -Os ${WARN_FLAGS} -fmerge-all-constants") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - set(REL_FLAGS "${WARN_FLAGS}") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - set(REL_FLAGS "-Os ${WARN_FLAGS} -fmerge-all-constants") -else() - # Using clang - strip unsupported GCC options (-ffunction-sections is set by - # the global block further below). - set(REL_FLAGS "-Os ${WARN_FLAGS} -fmerge-all-constants") -endif() - -## Uncomment this to reduce the volume of note warnings on RPi4 w/gcc-8 Ref. https://gcc.gnu.org/ml/gcc/2017-05/msg00073.html -#if (CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7l") -# set(WARN_FLAGS "${WARN_FLAGS} -Wno-psabi" -#endif() - -# Use libtcmalloc for Debug builds memory leaks detection -set(DBG_FLAGS "-ggdb -gdwarf-2 -O0 ${WARN_FLAGS} -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free") - -if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - #TODO: -fno-rtti - message(STATUS "Building Release ...") - set(CMAKE_C_FLAGS "$ENV{CFLAGS} ${CMAKE_C_FLAGS} -std=c11 ${REL_FLAGS}") - set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} ${CMAKE_CXX_FLAGS} -std=c++11 ${REL_FLAGS}") -else() - set(USE_TCMALLOC 1) - message(STATUS "Building Debug ...") - include(tools/FindTcmalloc.cmake) - set(CMAKE_C_FLAGS "$ENV{CFLAGS} ${CMAKE_C_FLAGS} -std=c11 ${DBG_FLAGS}") - set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} ${CMAKE_CXX_FLAGS} -std=c++11 ${DBG_FLAGS}") -endif() - -#Remove /Zi for Win32 debug compiler issue -if(MSVC) - string( TOLOWER "${CMAKE_VS_PLATFORM_NAME}" PLATFORM_NAME_LOWER ) - if (PLATFORM_NAME_LOWER STREQUAL "win32") - string(REGEX REPLACE "/Z[iI7]" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - string(REGEX REPLACE "/Z[iI7]" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") - endif() -endif() - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - # using Clang -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # using GCC - # Prefer to generate position-independent code - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") - # using Intel C++ -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - # using Visual Studio C++ -endif() - -endif() # NOT MATSDK_USE_VCPKG_DEPS (compiler flags) - -# --- Dead-strip enablement (applies in BOTH vendored and vcpkg modes) --------- -# Deliberate exception to the "let the toolchain manage compiler flags" note -# above (the NOT MATSDK_USE_VCPKG_DEPS block): these flags are NOT optimization -# or dependency choices the vcpkg toolchain owns -- they only split functions and -# data into separate COMDATs/sections so a *consumer's* linker can drop -# unreferenced SDK code (MSVC /OPT:REF + /OPT:ICF, GNU/Clang --gc-sections, Apple -# ld -dead_strip). The toolchain does not set them, and the vcpkg-packaged -# library (and every MSVC build, which never gets /Gy from the block above) would -# otherwise link whole .obj files instead of individual functions. Applying them -# here in both modes closes that gap and matches the MSBuild Release projects, -# which already enable FunctionLevelLinking + OptimizeReferences + COMDATFolding. +# SDK-owned compiler policy. This interface target is linked PRIVATE by SDK +# targets, so add_subdirectory()/FetchContent consumers and vendored dependency +# targets never inherit the SDK's warning-as-error or optimization policy. +add_library(matsdk_build_options INTERFACE) if(MSVC) - # /Gy (function-level linking) is supported by both cl.exe and clang-cl. - add_compile_options(/Gy) - # /Gw (whole-program global data) is cl.exe-only; the ClangCL toolset (for - # which MSVC is also true) does not support it. - if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - add_compile_options(/Gw) - endif() -elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - # On Mach-O, clang emits .subsections_via_symbols, so ld64's -dead_strip - # already removes unreferenced code at per-symbol (function) granularity - # without -ffunction-sections; we add it only for cross-toolchain - # consistency. -fdata-sections is omitted because it historically conflicted - # with bitcode on AppleClang. - add_compile_options(-ffunction-sections) + target_compile_options(matsdk_build_options INTERFACE + /W4 + $<$:/WX> + /Gy + $<$:/Gw>) else() - # GCC / Clang (Linux, Android, MinGW) - add_compile_options(-ffunction-sections -fdata-sections) -endif() - -# Hidden symbol visibility (non-Windows): export only the MATSDK_LIBABI-decorated -# public API (classes + the C API), hiding SDK internals and the bundled -# sqlite3/zlib. This shrinks the dynamic symbol table (faster dynamic -# linking/loading, smaller binaries) and enables more inlining + dead-code -# elimination -- the non-Windows analog of what /Gy plus the consumer's /OPT:REF -# achieve on MSVC. All Windows toolchains (MSVC, MinGW, ClangCL) restrict exports -# via __declspec(dllexport) on MATSDK_LIBABI (lib/include/public/ctmacros.hpp), -# so this is gated on NOT WIN32 (not NOT MSVC, which would also catch MinGW/ -# Clang-GNU Windows builds and apply ELF-style visibility that does not belong on -# a PE/COFF target). -if(NOT WIN32) - # -fvisibility=hidden applies to C and C++; -fvisibility-inlines-hidden is a - # C++-only option, so scope it to CXX. (Applying it to C sources -- e.g. the - # bundled sqlite3/zlib on the legacy Android path -- makes Clang emit an - # "unused argument" warning that becomes an error under the project's -Werror.) - add_compile_options(-fvisibility=hidden $<$:-fvisibility-inlines-hidden>) + target_compile_options(matsdk_build_options INTERFACE + -Wall + -Wextra + -Wno-unused-parameter + -Wno-unused-but-set-variable + $<$:-Werror> + $<$:-Wno-unknown-warning-option> + $<$:-Wno-unknown-warning-option> + $<$:-ggdb> + $<$:-gdwarf-2> + $<$:-O0> + $<$:-fno-builtin-malloc> + $<$:-fno-builtin-calloc> + $<$:-fno-builtin-realloc> + $<$:-fno-builtin-free> + $<$>:-Os> + $<$>:-fmerge-all-constants> + -ffunction-sections + $<$:-fdata-sections>) + if(NOT WIN32) + target_compile_options(matsdk_build_options INTERFACE + -fvisibility=hidden + $<$:-fvisibility-inlines-hidden>) + endif() endif() include(tools/Utils.cmake) @@ -283,35 +112,38 @@ set(PAL_IMPLEMENTATION ${DEFAULT_PAL_IMPLEMENTATION}) message(STATUS "PAL implementation: ${PAL_IMPLEMENTATION}") string(TOUPPER ${PAL_IMPLEMENTATION} PAL_IMPLEMENTATION_UPPER) -add_definitions(-DMATSDK_PAL_${PAL_IMPLEMENTATION_UPPER}=1) +add_library(matsdk_internal_config INTERFACE) +target_compile_definitions(matsdk_internal_config INTERFACE + MATSDK_PAL_${PAL_IMPLEMENTATION_UPPER}=1 + NOMINMAX) option(GCC5_CXX11_ABI_WORKAROUND "Workaround: Use legacy C++11 ABI (for GCC 5 compatibility)" OFF) if(GCC5_CXX11_ABI_WORKAROUND) - add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + target_compile_definitions(matsdk_internal_config INTERFACE + _GLIBCXX_USE_CXX11_ABI=0) endif() option(USE_ONEDS_BOUNDCHECK_METHODS "Use bound check methods for C99 functions" OFF) if (USE_ONEDS_BOUNDCHECK_METHODS) - add_definitions(-DHAVE_ONEDS_BOUNDCHECK_METHODS) + target_compile_definitions(matsdk_internal_config INTERFACE + HAVE_ONEDS_BOUNDCHECK_METHODS) endif() option(USE_ONEDS_SECURE_MEM_FUNCTIONS "Use secure memory functions for sqlite" OFF) -if(USE_ONEDS_SECURE_MEM_FUNCTIONS) - add_definitions(-DUSE_ONEDS_SECURE_MEM_FUNCTIONS) -endif() - -if(PAL_IMPLEMENTATION STREQUAL "WIN32" AND NOT MATSDK_USE_VCPKG_DEPS) - add_definitions(-DZLIB_WINAPI) -endif() - -add_definitions(-DNOMINMAX) - ################################################################################################ # Build prefix and version ################################################################################################ set(SDK_VERSION_PREFIX "EVT") -add_definitions("-DMATSDK_VERSION_PREFIX=\"${SDK_VERSION_PREFIX}\"") +target_compile_definitions(matsdk_internal_config INTERFACE + "MATSDK_VERSION_PREFIX=\"${SDK_VERSION_PREFIX}\"") +if(MATSDK_ANDROID_USE_ROOM) + target_compile_definitions(matsdk_internal_config INTERFACE USE_ROOM) +endif() +if(MATSDK_ENABLE_CAPI_HTTP_CLIENT) + target_compile_definitions(matsdk_internal_config INTERFACE + ENABLE_CAPI_HTTP_CLIENT) +endif() set(MATSDK_API_VERSION "3.10") string(TIMESTAMP DAYNUMBER "%j") @@ -332,74 +164,39 @@ endif() message(STATUS "SDK version: ${SDK_VERSION_PREFIX}-${MATSDK_BUILD_VERSION}") ################################################################################################ -# User options (must be before HTTP stack section for BUILD_APPLE_HTTP) +# Embedding/dependency options ################################################################################################ -option(BUILD_HEADERS "Build API headers" YES) -option(BUILD_LIBRARY "Build library" YES) -option(BUILD_TEST_TOOL "Build console test tool" YES) -# Default the test suites ON only when this repository is the top-level project -# (developer/CI build), and OFF when it is consumed via add_subdirectory()/ -# FetchContent, so downstream projects don't build the tests or require the -# third_party/googletest submodule. PROJECT_IS_TOP_LEVEL exists on CMake >= 3.21; -# fall back to comparing the source dirs on older CMake (floor is 3.15). -if(DEFINED PROJECT_IS_TOP_LEVEL) - set(MATSDK_TESTS_DEFAULT ${PROJECT_IS_TOP_LEVEL}) -elseif(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - set(MATSDK_TESTS_DEFAULT ON) -else() - set(MATSDK_TESTS_DEFAULT OFF) -endif() -option(BUILD_UNIT_TESTS "Build unit tests" ${MATSDK_TESTS_DEFAULT}) -option(BUILD_FUNC_TESTS "Build functional tests" ${MATSDK_TESTS_DEFAULT}) -option(BUILD_JNI_WRAPPER "Build JNI wrapper" NO) -option(BUILD_OBJC_WRAPPER "Build Obj-C wrapper" YES) -option(BUILD_SWIFT_WRAPPER "Build Swift Wrappers" YES) -option(BUILD_PACKAGE "Build package" YES) -option(BUILD_PRIVACYGUARD "Build Privacy Guard" YES) -option(BUILD_CDS "Build CDS - Common Diagnostic Stack" YES) -option(BUILD_LIVEEVENTINSPECTOR "Build Live Event Inspector" YES) -option(BUILD_SIGNALS "Build Signals" YES) -option(BUILD_SANITIZER "Build Sanitizer" YES) -option(LINK_STATIC_DEPENDS "Link dependencies for static build" YES) - -set(MATSDK_ANDROID_HTTP_CLIENT "AUTO" CACHE STRING "Android HTTP client: AUTO, JAVA, or CURL") -set_property(CACHE MATSDK_ANDROID_HTTP_CLIENT PROPERTY STRINGS AUTO JAVA CURL) -string(TOUPPER "${MATSDK_ANDROID_HTTP_CLIENT}" MATSDK_ANDROID_HTTP_CLIENT_UPPER) -if(NOT MATSDK_ANDROID_HTTP_CLIENT_UPPER STREQUAL "AUTO" - AND NOT MATSDK_ANDROID_HTTP_CLIENT_UPPER STREQUAL "JAVA" - AND NOT MATSDK_ANDROID_HTTP_CLIENT_UPPER STREQUAL "CURL") - message(FATAL_ERROR - "MATSDK_ANDROID_HTTP_CLIENT must be AUTO, JAVA, or CURL; got " - "'${MATSDK_ANDROID_HTTP_CLIENT}'.") +set(MATSDK_CURL_PROVIDER "SYSTEM" CACHE STRING + "How builds resolve libcurl: SYSTEM (canonical target/find_package) or FETCH") +set_property(CACHE MATSDK_CURL_PROVIDER PROPERTY STRINGS SYSTEM FETCH) +set(MATSDK_CURL_TLS_BACKEND "MBEDTLS" CACHE STRING + "TLS backend for MATSDK_CURL_PROVIDER=FETCH: MBEDTLS or OPENSSL") +set_property(CACHE MATSDK_CURL_TLS_BACKEND PROPERTY STRINGS MBEDTLS OPENSSL) +set(MATSDK_CURL_URL "https://github.com/curl/curl/releases/download/curl-8_21_0/curl-8.21.0.tar.xz" CACHE STRING + "URL for MATSDK_CURL_PROVIDER=FETCH") +set(MATSDK_CURL_SHA256 "aa1b66a70eace83dc624508745646c08ae561de512ab403adffb93ac87fc72e6" CACHE STRING + "SHA256 for MATSDK_CURL_URL") +set(MATSDK_MBEDTLS_URL "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.7/mbedtls-3.6.7.tar.bz2" CACHE STRING + "URL for the mbedTLS dependency used by MATSDK_CURL_PROVIDER=FETCH and MATSDK_CURL_TLS_BACKEND=MBEDTLS") +set(MATSDK_MBEDTLS_SHA256 "a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6" CACHE STRING + "SHA256 for MATSDK_MBEDTLS_URL") +string(TOUPPER "${MATSDK_CURL_PROVIDER}" MATSDK_CURL_PROVIDER_UPPER) +if(NOT MATSDK_CURL_PROVIDER_UPPER STREQUAL "SYSTEM" AND NOT MATSDK_CURL_PROVIDER_UPPER STREQUAL "FETCH") + message(FATAL_ERROR "MATSDK_CURL_PROVIDER must be SYSTEM or FETCH; got '${MATSDK_CURL_PROVIDER}'.") endif() - -set(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED "") -set(MATSDK_ANDROID_USES_CURL OFF) -set(MATSDK_ANDROID_USES_JAVA_HTTP OFF) -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - if(MATSDK_ANDROID_HTTP_CLIENT_UPPER STREQUAL "AUTO") - set(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED "JAVA") - else() - set(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED "${MATSDK_ANDROID_HTTP_CLIENT_UPPER}") - endif() - - if(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED STREQUAL "CURL") - set(MATSDK_ANDROID_USES_CURL ON) - elseif(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED STREQUAL "JAVA") - set(MATSDK_ANDROID_USES_JAVA_HTTP ON) - endif() - message(STATUS "MATSDK_ANDROID_HTTP_CLIENT: ${MATSDK_ANDROID_HTTP_CLIENT} -> ${MATSDK_ANDROID_HTTP_CLIENT_RESOLVED}") +string(TOUPPER "${MATSDK_CURL_TLS_BACKEND}" MATSDK_CURL_TLS_BACKEND_UPPER) +if(MATSDK_CURL_PROVIDER_UPPER STREQUAL "FETCH" + AND NOT MATSDK_CURL_TLS_BACKEND_UPPER STREQUAL "MBEDTLS" + AND NOT MATSDK_CURL_TLS_BACKEND_UPPER STREQUAL "OPENSSL") + message(FATAL_ERROR "MATSDK_CURL_TLS_BACKEND must be MBEDTLS or OPENSSL; got '${MATSDK_CURL_TLS_BACKEND}'.") endif() - -# Enable Azure Monitor / Application Insights end-point support -option(BUILD_AZMON "Build for Azure Monitor" YES) - -if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - option(BUILD_APPLE_HTTP "Build Apple HTTP client" YES) +if(MATSDK_USING_VCPKG AND MATSDK_CURL_PROVIDER_UPPER STREQUAL "FETCH") + message(FATAL_ERROR + "MATSDK_CURL_PROVIDER=FETCH is a non-vcpkg dependency mode. " + "Use the vcpkg curl-openssl/curl-mbedtls feature instead.") endif() - -if(BUILD_APPLE_HTTP) - add_definitions(-DAPPLE_HTTP=1) +if(MATSDK_BUILD_APPLE_HTTP) + target_compile_definitions(matsdk_internal_config INTERFACE APPLE_HTTP=1) endif() ################################################################################################ @@ -408,120 +205,133 @@ endif() # Only use custom curl if compiling with CPP11 PAL set(MATSDK_NEEDS_CURL OFF) +set(MATSDK_CURL_FETCHED OFF) +set(MATSDK_CURL_LINK_TARGET "") if(PAL_IMPLEMENTATION STREQUAL "CPP11" - AND NOT BUILD_IOS + AND NOT MATSDK_PLATFORM_IOS AND (NOT CMAKE_SYSTEM_NAME STREQUAL "Android" OR MATSDK_ANDROID_USES_CURL) - AND NOT BUILD_APPLE_HTTP) + AND NOT MATSDK_BUILD_APPLE_HTTP) set(MATSDK_NEEDS_CURL ON) - add_definitions(-DHAVE_MAT_CURL_HTTP_CLIENT) - if(MATSDK_USE_VCPKG_DEPS) - # The TLS backend (OpenSSL/mbedTLS) is selected by the vcpkg port's - # curl-openssl (default) / curl-mbedtls features; the SDK just links libcurl. - # Force CONFIG mode so the vcpkg-provided CURLConfig (which defines the - # CURL::libcurl imported target) is used rather than the module FindCURL, - # which on some CMake versions does not define that target. - find_package(CURL CONFIG QUIET) - if(NOT TARGET CURL::libcurl) - message(FATAL_ERROR - "libcurl was not found. The vcpkg port provides the curl HTTP client " - "through the curl-openssl (default) or curl-mbedtls feature. Install " - "cpp-client-telemetry with its default features, or, under the [core,...] " - "form (which drops the default curl-openssl and system-sqlite features), " - "re-select a curl backend and a SQLite backend together, e.g. " - "[core,curl-openssl,system-sqlite] or [core,curl-mbedtls,minimal-sqlite].") - endif() - list(APPEND LIBS CURL::libcurl) + target_compile_definitions(matsdk_internal_config INTERFACE + HAVE_MAT_CURL_HTTP_CLIENT) + if(TARGET CURL::libcurl) + set(MATSDK_CURL_LINK_TARGET CURL::libcurl) + elseif(MATSDK_CURL_PROVIDER_UPPER STREQUAL "FETCH") + include(cmake/MatsdkFetchCurl.cmake) + matsdk_fetch_curl(_matsdk_curl_target) + set(MATSDK_CURL_LINK_TARGET "${_matsdk_curl_target}") + set(MATSDK_CURL_FETCHED ON) else() find_package(CURL REQUIRED) - # Prefer the imported target, which carries curl's include dirs and link - # flags. Fall back to the find-module variables on CMake < 3.12, where - # find_package(CURL) does not define CURL::libcurl. - if(TARGET CURL::libcurl) - list(APPEND LIBS CURL::libcurl) - else() - include_directories(${CURL_INCLUDE_DIRS}) - list(APPEND LIBS "${CURL_LIBRARIES}") + if(NOT TARGET CURL::libcurl) + message(FATAL_ERROR + "find_package(CURL) did not create the required CURL::libcurl target.") endif() + set(MATSDK_CURL_LINK_TARGET CURL::libcurl) endif() endif() ################################################################################################ -# Dependency resolution (vcpkg mode vs vendored) +# Canonical dependency targets ################################################################################################ -if(MATSDK_USE_VCPKG_DEPS) - if(APPLE) - # macOS/iOS ship libsqlite3 and libz as system libraries (the SDK's SPM - # distribution links them the same way), so the vcpkg sqlite3/zlib packages are - # not pulled there -- find the system ones via CMake's standard find modules. - find_package(SQLite3 REQUIRED) - if(NOT TARGET SQLite3::SQLite3) - add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) - endif() - find_package(ZLIB REQUIRED) - find_package(nlohmann_json CONFIG REQUIRED) - set(MATSDK_APPLE_SYSTEM_DEPS ON) - message(STATUS "Apple: using system SQLite3 + zlib; vcpkg-provided nlohmann-json") +if(TARGET SQLite3::SQLite3 AND NOT TARGET SQLite::SQLite3) + add_library(SQLite::SQLite3 ALIAS SQLite3::SQLite3) +endif() +if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "SYSTEM" AND NOT TARGET SQLite::SQLite3) + if(APPLE AND NOT MATSDK_USING_VCPKG) + matsdk_add_apple_system_library(SQLite::SQLite3 sqlite3) else() - set(MATSDK_APPLE_SYSTEM_DEPS OFF) - # SQLite is provided by the private minimal build when MATSDK_MINIMAL_SQLITE is - # ON, so only require the external vcpkg sqlite3 package otherwise. - if(NOT MATSDK_MINIMAL_SQLITE) - find_package(unofficial-sqlite3 CONFIG QUIET) - if(NOT unofficial-sqlite3_FOUND) - message(FATAL_ERROR - "SQLite was not found and the minimal SQLite is not enabled. The vcpkg " - "port provides SQLite through one of two features: 'system-sqlite' " - "(default, links the external sqlite3 package) or 'minimal-sqlite' " - "(builds a private feature-stripped SQLite). Install " - "cpp-client-telemetry with its default features, or with " - "[core,system-sqlite] or [core,minimal-sqlite]. For a direct CMake build, pass " - "-DMATSDK_MINIMAL_SQLITE=ON or ensure unofficial-sqlite3 is discoverable.") - endif() + find_package(SQLite3 QUIET) + if(NOT TARGET SQLite::SQLite3 AND MATSDK_USING_VCPKG) + find_package(unofficial-sqlite3 CONFIG REQUIRED) + matsdk_add_interface_dependency( + SQLite::SQLite3 unofficial::sqlite3::sqlite3) endif() - find_package(ZLIB REQUIRED) - find_package(nlohmann_json CONFIG REQUIRED) - if(MATSDK_MINIMAL_SQLITE) - message(STATUS "Using vcpkg-provided zlib, nlohmann-json; private minimal SQLite") - else() - message(STATUS "Using vcpkg-provided sqlite3, zlib, nlohmann-json") + if(NOT TARGET SQLite::SQLite3) + message(FATAL_ERROR + "MATSDK_SQLITE_PROVIDER=SYSTEM requires SQLite::SQLite3. Install SQLite, " + "define the canonical target before adding 1DS, or choose MINIMAL/VENDORED.") endif() endif() -else() - # Include repo root to allow includes of vendored sqlite, zlib, and nlohmann. - # Use CMAKE_CURRENT_SOURCE_DIR (this repo's root) rather than CMAKE_SOURCE_DIR - # so the vendored headers still resolve when the SDK is consumed as a subproject - # (add_subdirectory/FetchContent), where CMAKE_SOURCE_DIR is the consumer's root. - include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - message(STATUS "Using vendored sqlite3, zlib, nlohmann-json") endif() - -if(BUILD_UNIT_TESTS OR BUILD_FUNC_TESTS) - message(STATUS "Adding gtest") - add_library(gtest STATIC IMPORTED GLOBAL) - message(STATUS "Adding gmock") - add_library(gmock STATIC IMPORTED GLOBAL) +if(TARGET SQLite::SQLite3 AND NOT TARGET SQLite3::SQLite3) + get_target_property(_matsdk_sqlite_target SQLite::SQLite3 ALIASED_TARGET) + if(_matsdk_sqlite_target) + add_library(SQLite3::SQLite3 ALIAS ${_matsdk_sqlite_target}) + else() + add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) + endif() endif() -# Bond Lite subdirectories -include_directories(bondlite/include) +if(MATSDK_ZLIB_PROVIDER_RESOLVED STREQUAL "SYSTEM" AND NOT TARGET ZLIB::ZLIB) + if(APPLE AND NOT MATSDK_USING_VCPKG) + matsdk_add_apple_system_library(ZLIB::ZLIB z) + else() + find_package(ZLIB REQUIRED) + endif() +endif() -include_directories(lib/pal) +set(MATSDK_USES_NLOHMANN_TARGET OFF) +if(TARGET nlohmann_json::nlohmann_json) + set(MATSDK_USES_NLOHMANN_TARGET ON) +elseif(MATSDK_USING_VCPKG) + find_package(nlohmann_json CONFIG REQUIRED) + set(MATSDK_USES_NLOHMANN_TARGET ON) +else() + # nlohmann JSON remains header-only and vendored for source embedding. + set(MATSDK_USES_VENDORED_NLOHMANN ON) +endif() +message(STATUS + "Dependencies: SQLite=${MATSDK_SQLITE_PROVIDER_RESOLVED}, " + "zlib=${MATSDK_ZLIB_PROVIDER_RESOLVED}, " + "nlohmann-target=${MATSDK_USES_NLOHMANN_TARGET}") + +if(MATSDK_BUILD_UNIT_TESTS OR MATSDK_BUILD_FUNC_TESTS) + if(NOT TARGET gtest OR NOT TARGET gmock) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/CMakeLists.txt") + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + set(BUILD_GMOCK ON CACHE BOOL "" FORCE) + if(MSVC) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + endif() + set(_matsdk_saved_build_shared_libs "${BUILD_SHARED_LIBS}") + set(BUILD_SHARED_LIBS OFF) + add_subdirectory(third_party/googletest EXCLUDE_FROM_ALL) + set(BUILD_SHARED_LIBS "${_matsdk_saved_build_shared_libs}") + # Checked-in iOS test projects consume these archive paths directly. + set(_matsdk_gtest_archive_dir + "${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/build/lib") + foreach(_matsdk_gtest_target IN ITEMS gtest gmock) + set_target_properties(${_matsdk_gtest_target} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${_matsdk_gtest_archive_dir}") + foreach(_matsdk_gtest_config IN ITEMS DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) + set_target_properties(${_matsdk_gtest_target} PROPERTIES + "ARCHIVE_OUTPUT_DIRECTORY_${_matsdk_gtest_config}" + "${_matsdk_gtest_archive_dir}") + endforeach() + endforeach() + else() + message(FATAL_ERROR + "Tests require the third_party/googletest submodule. " + "Run git submodule update --init third_party/googletest.") + endif() + endif() +endif() #if(BUILD_UNIT_TESTS) # message("Adding bondlite tests") # enable_testing() # add_subdirectory(bondlite/tests) #endif() -if(BUILD_HEADERS) +if(MATSDK_BUILD_HEADERS) add_subdirectory(lib/include) endif() -include_directories(lib/include) -if(BUILD_LIBRARY) +if(MATSDK_BUILD_LIBRARY) add_subdirectory(lib) endif() -if(BUILD_UNIT_TESTS OR BUILD_FUNC_TESTS) +if(MATSDK_BUILD_UNIT_TESTS OR MATSDK_BUILD_FUNC_TESTS) message(STATUS "Building tests") enable_testing() add_subdirectory(tests) @@ -531,14 +341,19 @@ endif() # Packaging ################################################################################################ -if (BUILD_PACKAGE) - if ("${CMAKE_PACKAGE_TYPE}" STREQUAL "deb") +if(DEFINED CMAKE_PACKAGE_TYPE AND NOT DEFINED CPACK_GENERATOR) + string(TOUPPER "${CMAKE_PACKAGE_TYPE}" CPACK_GENERATOR) + message(DEPRECATION + "CMAKE_PACKAGE_TYPE is deprecated; use standard CPACK_GENERATOR.") +endif() +if(MATSDK_BUILD_PACKAGE) + if("DEB" IN_LIST CPACK_GENERATOR) include(tools/MakeDeb.cmake) endif() - if ("${CMAKE_PACKAGE_TYPE}" STREQUAL "rpm") + if("RPM" IN_LIST CPACK_GENERATOR) include(tools/MakeRpm.cmake) endif() - if ("${CMAKE_PACKAGE_TYPE}" STREQUAL "tgz") + if("TGZ" IN_LIST CPACK_GENERATOR) # TODO: [MG] - fix path... should we simply use /usr/local/lib without CPU? # TODO: [MG] - Windows path is not ideal -- C:/Program Files (x86)/MSTelemetry/* - what should we use instead? include(tools/MakeTgz.cmake) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..6a6110015 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,194 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "matsdk-common", + "hidden": true, + "cacheVariables": { + "BUILD_SHARED_LIBS": "OFF", + "CPACK_GENERATOR": "TGZ", + "MATSDK_BUILD_PACKAGE": "ON", + "MATSDK_BUILD_UNIT_TESTS": "OFF", + "MATSDK_BUILD_FUNC_TESTS": "OFF" + } + }, + { + "name": "matsdk-unix", + "hidden": true, + "inherits": "matsdk-common", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out" + }, + { + "name": "matsdk-debug", + "inherits": "matsdk-unix", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "matsdk-release", + "inherits": "matsdk-unix", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "matsdk-macos-arm64", + "inherits": "matsdk-release", + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "arm64" + } + }, + { + "name": "matsdk-macos-universal", + "inherits": "matsdk-release", + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "arm64;x86_64" + } + }, + { + "name": "matsdk-windows", + "hidden": true, + "inherits": "matsdk-common", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/windows" + }, + { + "name": "matsdk-windows-debug", + "inherits": "matsdk-windows", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "matsdk-windows-release", + "inherits": "matsdk-windows", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "matsdk-ios", + "hidden": true, + "inherits": "matsdk-common", + "generator": "Unix Makefiles", + "binaryDir": "${sourceDir}/out", + "cacheVariables": { + "CMAKE_SYSTEM_NAME": "iOS", + "CMAKE_OSX_DEPLOYMENT_TARGET": "13.0" + } + }, + { + "name": "matsdk-ios-device-base", + "hidden": true, + "inherits": "matsdk-ios", + "cacheVariables": { + "CMAKE_OSX_SYSROOT": "iphoneos", + "CMAKE_OSX_ARCHITECTURES": "arm64" + } + }, + { + "name": "matsdk-ios-simulator-base", + "hidden": true, + "inherits": "matsdk-ios", + "cacheVariables": { + "CMAKE_OSX_SYSROOT": "iphonesimulator", + "CMAKE_OSX_ARCHITECTURES": "arm64" + } + }, + { + "name": "matsdk-ios-device-debug", + "inherits": "matsdk-ios-device-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "matsdk-ios-device-release", + "inherits": "matsdk-ios-device-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "matsdk-ios-simulator-debug", + "inherits": "matsdk-ios-simulator-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "matsdk-ios-simulator-release", + "inherits": "matsdk-ios-simulator-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "matsdk-android-arm64", + "inherits": "matsdk-common", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/android-arm64", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake", + "ANDROID_ABI": "arm64-v8a", + "ANDROID_PLATFORM": "android-23", + "CMAKE_BUILD_TYPE": "Release", + "MATSDK_SQLITE_PROVIDER": "VENDORED", + "MATSDK_ZLIB_PROVIDER": "VENDORED" + } + } + ], + "buildPresets": [ + { + "name": "matsdk-debug", + "configurePreset": "matsdk-debug" + }, + { + "name": "matsdk-release", + "configurePreset": "matsdk-release" + }, + { + "name": "matsdk-macos-arm64", + "configurePreset": "matsdk-macos-arm64" + }, + { + "name": "matsdk-macos-universal", + "configurePreset": "matsdk-macos-universal" + }, + { + "name": "matsdk-windows-debug", + "configurePreset": "matsdk-windows-debug" + }, + { + "name": "matsdk-windows-release", + "configurePreset": "matsdk-windows-release" + }, + { + "name": "matsdk-ios-device-debug", + "configurePreset": "matsdk-ios-device-debug" + }, + { + "name": "matsdk-ios-device-release", + "configurePreset": "matsdk-ios-device-release" + }, + { + "name": "matsdk-ios-simulator-debug", + "configurePreset": "matsdk-ios-simulator-debug" + }, + { + "name": "matsdk-ios-simulator-release", + "configurePreset": "matsdk-ios-simulator-release" + }, + { + "name": "matsdk-android-arm64", + "configurePreset": "matsdk-android-arm64" + } + ] +} diff --git a/README.md b/README.md index 3ddcbb580..181530a47 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,12 @@ Platform specific build instructions: * [Linux](docs/cpp-start-linux.md). [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) or [Docker](https://www.docker.com/products/docker-desktop) can be used to build for various Linux distros. Please refer to [build-docker.cmd](build-docker.cmd) script and [the list of supported containers](docker/). Docker build script accepts the container name as first argument. * [iOS/iPadOS](docs/cpp-start-ios.md) * [Android](docs/cpp-start-android.md) +* [CMake source embedding / FetchContent](docs/embedding-with-cmake.md) + +Standard configure/build presets are listed with `cmake --list-presets`. +`build.sh`, `build-ios.sh`, and `build-cmake.ps1` are thin compatibility +wrappers around those presets. Presets require CMake 3.21+; direct CMake builds +retain the project's CMake 3.15 floor. Other resources to learn how to setup the build system: diff --git a/build-cmake.ps1 b/build-cmake.ps1 new file mode 100644 index 000000000..284920141 --- /dev/null +++ b/build-cmake.ps1 @@ -0,0 +1,61 @@ +param( + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Release", + [switch]$Shared, + [switch]$Clean, + [switch]$Package, + [string[]]$CMakeArgs = @() +) + +$ErrorActionPreference = "Stop" +$RepoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $RepoRoot + +& cmake -P (Join-Path $RepoRoot "cmake\MatsdkRequirePresetSupport.cmake") +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +if (-not (Get-Command cl.exe -ErrorAction SilentlyContinue)) { + $vswhere = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { + throw "Visual Studio vswhere.exe was not found." + } + $vsInstall = & $vswhere -latest -property installationPath + if (-not $vsInstall) { + throw "Visual Studio was not found." + } + $vsDevCmd = Join-Path $vsInstall "Common7\Tools\VsDevCmd.bat" + & cmd /d /s /c "`"$vsDevCmd`" -no_logo && set" | ForEach-Object { + $name, $value = $_ -split "=", 2 + if ($name -and $null -ne $value) { + Set-Item -Path "Env:$name" -Value $value + } + } +} + +$Preset = "matsdk-windows-$($Configuration.ToLowerInvariant())" +$BuildDir = Join-Path $RepoRoot "out\windows" +if ($Clean -and (Test-Path $BuildDir)) { + Remove-Item -LiteralPath $BuildDir -Recurse -Force +} + +$configureArgs = @("--preset", $Preset) +$configureArgs += if ($Shared) { + "-DBUILD_SHARED_LIBS=ON" +} else { + "-DBUILD_SHARED_LIBS=OFF" +} +$configureArgs += $CMakeArgs +if ($Package) { + $configureArgs += "-DCPACK_GENERATOR=TGZ" +} + +& cmake @configureArgs +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +& cmake --build --preset $Preset +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +if ($Package) { + & cmake --build --preset $Preset --target package + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} diff --git a/build-gtest.sh b/build-gtest.sh index 4c73f3382..4dca08f06 100755 --- a/build-gtest.sh +++ b/build-gtest.sh @@ -39,9 +39,8 @@ if(BUILD_IOS) set(CMAKE_OSX_DEPLOYMENT_TARGET "12.2" CACHE STRING "Force set of the deployment target for iOS" FORCE) set(CMAKE_C_FLAGS "\${CMAKE_C_FLAGS} -miphoneos-version-min=10.0") set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -miphoneos-version-min=10.0 -std=c++11") - set(IOS_PLATFORM "iphonesimulator") set(CMAKE_SYSTEM_PROCESSOR x86_64) - execute_process(COMMAND xcodebuild -version -sdk \${IOS_PLATFORM} Path + execute_process(COMMAND xcodebuild -version -sdk iphonesimulator Path OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_OUT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) diff --git a/build-ios.sh b/build-ios.sh index d316fe2fa..1ee6326c7 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -1,4 +1,10 @@ -#!/bin/sh +#!/bin/bash + +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$DIR" +. "$DIR/tools/build-common.sh" # The expected iOS build invocation is: # build-ios.sh [clean] [release|debug] ${ARCH} ${PLATFORM} @@ -7,11 +13,7 @@ # PLATFORM = iphoneos|iphonesimulator|xros|xrsimulator if [ "$1" == "clean" ]; then - echo "build-ios.sh: cleaning previous build artifacts" - rm -f CMakeCache.txt *.cmake - rm -rf out - rm -rf .buildtools -# make clean + matsdk_clean_build_outputs "build-ios.sh" shift fi @@ -25,77 +27,72 @@ elif [ "$1" == "debug" ]; then fi # Set Architecture: arm64, arm64e or x86_64 -IOS_ARCH=$(/usr/bin/uname -m) +APPLE_ARCH=$(/usr/bin/uname -m) if [ "$1" == "arm64" ]; then - IOS_ARCH="arm64" + APPLE_ARCH="arm64" shift elif [ "$1" == "arm64e" ]; then - IOS_ARCH="arm64e" + APPLE_ARCH="arm64e" shift elif [ "$1" == "x86_64" ]; then - IOS_ARCH="x86_64" + APPLE_ARCH="x86_64" shift fi # the last param is expected to specify the platform name: iphoneos|iphonesimulator|xros|xrsimulator # so if it is non-empty and it is not "device", we take it as a valid platform name # otherwise we fall back to old iOS logic which only supported iphoneos|iphonesimulator -IOS_PLAT="iphonesimulator" +APPLE_PLATFORM="iphonesimulator" if [ -n "$1" ] && [ "$1" != "device" ]; then - IOS_PLAT="$1" + APPLE_PLATFORM="$1" elif [ "$1" == "device" ]; then - IOS_PLAT="iphoneos" + APPLE_PLATFORM="iphoneos" fi -echo "IOS_ARCH = $IOS_ARCH, IOS_PLAT = $IOS_PLAT, BUILD_TYPE = $BUILD_TYPE" +echo "architecture = $APPLE_ARCH, platform = $APPLE_PLATFORM, build type = $BUILD_TYPE" -FORCE_RESET_DEPLOYMENT_TARGET=NO DEPLOYMENT_TARGET="" -if [ "$IOS_PLAT" == "iphoneos" ] || [ "$IOS_PLAT" == "iphonesimulator" ]; then +if [ "$APPLE_PLATFORM" == "iphoneos" ] || [ "$APPLE_PLATFORM" == "iphonesimulator" ]; then SYS_NAME="iOS" - DEPLOYMENT_TARGET="$IOS_DEPLOYMENT_TARGET" + DEPLOYMENT_TARGET="$CMAKE_OSX_DEPLOYMENT_TARGET" if [ -z "$DEPLOYMENT_TARGET" ]; then - DEPLOYMENT_TARGET="12.0" - FORCE_RESET_DEPLOYMENT_TARGET=YES + DEPLOYMENT_TARGET="13.0" fi -elif [ "$IOS_PLAT" == "xros" ] || [ "$IOS_PLAT" == "xrsimulator" ]; then +elif [ "$APPLE_PLATFORM" == "xros" ] || [ "$APPLE_PLATFORM" == "xrsimulator" ]; then SYS_NAME="visionOS" - DEPLOYMENT_TARGET="$XROS_DEPLOYMENT_TARGET" + DEPLOYMENT_TARGET="$CMAKE_OSX_DEPLOYMENT_TARGET" if [ -z "$DEPLOYMENT_TARGET" ]; then DEPLOYMENT_TARGET="1.0" - FORCE_RESET_DEPLOYMENT_TARGET=YES fi fi echo "deployment target = $DEPLOYMENT_TARGET" -echo "force reset deployment target = $FORCE_RESET_DEPLOYMENT_TARGET" # Install build tools and recent sqlite3 -FILE=".buildtools" -if [ ! -f $FILE ]; then - tools/setup-buildtools-apple.sh ios - # Assume that the build tools have been successfully installed - echo > $FILE -fi - -if [ -f /usr/bin/gcc ]; then - echo "gcc version: `gcc --version`" -fi - -if [ -f /usr/bin/clang ]; then - echo "clang version: `clang --version`" -fi - -mkdir -p out -cd out - -CMAKE_PACKAGE_TYPE=tgz - -cmake_cmd="cmake -DCMAKE_OSX_SYSROOT=$IOS_PLAT -DCMAKE_SYSTEM_NAME=$SYS_NAME -DCMAKE_IOS_ARCH_ABI=$IOS_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DBUILD_IOS=YES -DIOS_ARCH=$IOS_ARCH -DIOS_PLAT=$IOS_PLAT -DIOS_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PACKAGE_TYPE=$CMAKE_PACKAGE_TYPE -DFORCE_RESET_DEPLOYMENT_TARGET=$FORCE_RESET_DEPLOYMENT_TARGET $CMAKE_OPTS .." -echo "${cmake_cmd}" -eval $cmake_cmd - -make - -make package +BUILD_TOOLS_MARKER=".buildtools" +matsdk_install_buildtools_once "$BUILD_TOOLS_MARKER" tools/setup-buildtools-apple.sh ios + +matsdk_print_compiler_versions +matsdk_require_cmake_preset_support + +CPACK_GENERATOR=TGZ +case "$APPLE_PLATFORM" in + *simulator) PLATFORM_PRESET="matsdk-ios-simulator" ;; + *) PLATFORM_PRESET="matsdk-ios-device" ;; +esac +PRESET="${PLATFORM_PRESET}-$(echo "$BUILD_TYPE" | tr '[:upper:]' '[:lower:]')" + +cmake_args=( + cmake --preset "$PRESET" + "-DCMAKE_SYSTEM_NAME=$SYS_NAME" + "-DCMAKE_OSX_SYSROOT=$APPLE_PLATFORM" + "-DCMAKE_OSX_ARCHITECTURES=$APPLE_ARCH" + "-DCMAKE_OSX_DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET" + "-DCMAKE_BUILD_TYPE=$BUILD_TYPE" + "-DCPACK_GENERATOR=$CPACK_GENERATOR" +) +matsdk_append_cmake_opts_to_cmake_args +matsdk_run_logged_command "${cmake_args[@]}" + +matsdk_build_and_package_preset "$PRESET" diff --git a/build-tests-ios.sh b/build-tests-ios.sh index 3e4a40f46..68f3366d3 100755 --- a/build-tests-ios.sh +++ b/build-tests-ios.sh @@ -5,7 +5,8 @@ SIMULATOR=${2:-iPhone 8} set -e -./build-ios.sh ${SKU} +CMAKE_OPTS="${CMAKE_OPTS} -DMATSDK_BUILD_UNIT_TESTS=ON -DMATSDK_BUILD_FUNC_TESTS=ON" \ + ./build-ios.sh ${SKU} cd tests/unittests diff --git a/build-tests.sh b/build-tests.sh index 15b07fed6..eecd7fd87 100755 --- a/build-tests.sh +++ b/build-tests.sh @@ -2,7 +2,8 @@ cd "${0%/*}" SKU=${1:-release} echo Building and running $SKU tests... -./build.sh ${SKU} +CMAKE_OPTS="${CMAKE_OPTS} -DMATSDK_BUILD_UNIT_TESTS=ON -DMATSDK_BUILD_FUNC_TESTS=ON" \ + ./build.sh ${SKU} # Fail on test errors set -e cd out diff --git a/build.sh b/build.sh index 52a5081b2..f701610db 100755 --- a/build.sh +++ b/build.sh @@ -32,6 +32,7 @@ export PATH=/usr/local/bin:$PATH DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" echo "Current directory: $DIR" cd $DIR +. "$DIR/tools/build-common.sh" export NOROOT=$NOROOT @@ -61,16 +62,16 @@ while [[ $# -gt 0 ]]; do echo "BUILD_TYPE = $BUILD_TYPE" ;; arm64|x86_64|universal) - if [[ -n "$MAC_ARCH" ]]; then - echo "Error: MAC_ARCH is already set to '$MAC_ARCH'. Cannot overwrite with $ARG." 1>&2 + if [[ -n "$APPLE_ARCH" ]]; then + echo "Error: APPLE_ARCH is already set to '$APPLE_ARCH'. Cannot overwrite with $ARG." 1>&2 exit 1 else - MAC_ARCH="$ARG" + APPLE_ARCH="$ARG" fi - echo "MAC_ARCH = $MAC_ARCH" + echo "APPLE_ARCH = $APPLE_ARCH" ;; CUSTOM_BUILD_FLAGS*) - CUSTOM_CMAKE_CXX_FLAG="\"${ARG:19:999}\"" + CUSTOM_CMAKE_CXX_FLAG="${ARG:19:999}" echo "custom compiler flags = $CUSTOM_CMAKE_CXX_FLAG" ;; *) @@ -91,9 +92,9 @@ if [[ -z "$BUILD_TYPE" ]]; then echo "Assuming default BUILD_TYPE = Debug" fi -if [[ -z "$MAC_ARCH" ]]; then - MAC_ARCH=$(/usr/bin/uname -m) - echo "Using current machine MAC_ARCH = $MAC_ARCH" +if [[ -z "$APPLE_ARCH" ]]; then + APPLE_ARCH=$(/usr/bin/uname -m) + echo "Using current machine APPLE_ARCH = $APPLE_ARCH" fi # Evaluate switches @@ -123,11 +124,7 @@ if [[ $# -gt 0 ]]; then fi if [[ "$CLEAN" == "true" ]]; then - echo "Cleaning previous build artifacts" - rm -f CMakeCache.txt *.cmake - rm -rf out - rm -rf .buildtools - # make clean + matsdk_clean_build_outputs "build.sh" fi echo "CMAKE_OPTS from caller: $CMAKE_OPTS" @@ -137,74 +134,77 @@ if [ "$LINK_TYPE" == "shared" ]; then fi # Set target MacOS minver -default_mac_os_target=$([ "$MAC_ARCH" == "arm64" ] && echo "11.10" || echo "10.10") +default_mac_os_target=$([ "$APPLE_ARCH" == "arm64" ] && echo "11.10" || echo "10.10") [ -z $MACOSX_DEPLOYMENT_TARGET ] && export MACOSX_DEPLOYMENT_TARGET=${default_mac_os_target} echo "macosx deployment target="$MACOSX_DEPLOYMENT_TARGET # Install build tools and recent sqlite3 -FILE=.buildtools +BUILD_TOOLS_MARKER=.buildtools OS_NAME=`uname -a` -if [ ! -f $FILE ]; then +if [ ! -f "$BUILD_TOOLS_MARKER" ]; then + buildtools_cmd=() case "$OS_NAME" in - *Darwin*) CMD="tools/setup-buildtools-apple.sh $MAC_ARCH" ;; - *Linux*) CMD="tools/setup-buildtools.sh" ;; - *) CMD=""; echo "WARNING: unsupported OS $OS_NAME, skipping build tools installation.." ;; + *Darwin*) buildtools_cmd=(tools/setup-buildtools-apple.sh "$APPLE_ARCH") ;; + *Linux*) buildtools_cmd=(tools/setup-buildtools.sh) ;; + *) echo "WARNING: unsupported OS $OS_NAME, skipping build tools installation.." ;; esac - [[ -n "$CMD" ]] && { [[ -z "$NOROOT" ]] && sudo $CMD || echo "No root: skipping build tools installation."; } - echo > $FILE + if [[ ${#buildtools_cmd[@]} -gt 0 ]]; then + if [[ -z "$NOROOT" ]]; then + matsdk_try_buildtools_once "$BUILD_TOOLS_MARKER" \ + "No root: skipping build tools installation." \ + sudo "${buildtools_cmd[@]}" + else + echo "No root: skipping build tools installation." + matsdk_mark_buildtools_checked "$BUILD_TOOLS_MARKER" + fi + else + matsdk_mark_buildtools_checked "$BUILD_TOOLS_MARKER" + fi fi -if [ -f /usr/bin/gcc ]; then - echo "gcc version: `gcc --version`" -fi - -if [ -f /usr/bin/clang ]; then - echo "clang version: `clang --version`" -fi +matsdk_print_compiler_versions +matsdk_require_cmake_preset_support # Skip Version.hpp changes # git update-index --skip-worktree lib/include/public/Version.hpp -#rm -rf out -mkdir -p out -cd out - # .tgz package -CMAKE_PACKAGE_TYPE=tgz +CPACK_GENERATOR=TGZ if [ -f /usr/bin/dpkg ]; then # .deb package - export CMAKE_PACKAGE_TYPE=deb + export CPACK_GENERATOR=DEB elif [ -f /usr/bin/rpmbuild ]; then # .rpm package - export CMAKE_PACKAGE_TYPE=rpm + export CPACK_GENERATOR=RPM fi # Fail on error set -e -# TODO: should this be improved to verify if the platform is Apple? Right now we unconditionally pass -DMAC_ARCH even if building for Windows or Linux. -cmake_cmd="cmake -DMAC_ARCH=$MAC_ARCH -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PACKAGE_TYPE=$CMAKE_PACKAGE_TYPE -DCMAKE_CXX_FLAGS="${CUSTOM_CMAKE_CXX_FLAG}" $CMAKE_OPTS .." -echo $cmake_cmd -eval $cmake_cmd - -# TODO: strip symbols to minimize (release-only) - -# Build all -# TODO: what are the pros and cons of using 'make' vs 'cmake --build' ? -#make -cmake --build . - -# No fail on error -set +e - -# Remove old package -rm -f *.deb *.rpm +PRESET="matsdk-$(echo "$BUILD_TYPE" | tr '[:upper:]' '[:lower:]')" +cmake_args=(cmake --preset "$PRESET") +if [[ "$OS_NAME" == *Darwin* ]]; then + if [[ "$APPLE_ARCH" == "universal" ]]; then + cmake_args+=("-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64") + else + cmake_args+=("-DCMAKE_OSX_ARCHITECTURES=$APPLE_ARCH") + fi +fi +cmake_args+=( + "-DCPACK_GENERATOR=$CPACK_GENERATOR" +) +if [[ -n "$CUSTOM_CMAKE_CXX_FLAG" ]]; then + cmake_args+=("-DCMAKE_CXX_FLAGS=$CUSTOM_CMAKE_CXX_FLAG") +fi +matsdk_append_cmake_opts_to_cmake_args +matsdk_run_logged_command "${cmake_args[@]}" -# Build new package -make package +rm -f out/*.deb out/*.rpm +matsdk_build_and_package_preset "$PRESET" +cd out # Install newly generated package if [ -f /usr/bin/dpkg ]; then @@ -221,7 +221,7 @@ fi ## strip --strip-unneeded out/lib/libmat.so ## strip -S --strip-unneeded --remove-section=.note.gnu.gold-version --remove-section=.comment --remove-section=.note --remove-section=.note.gnu.build-id --remove-section=.note.ABI-tag out/lib/libmat.so -if [ "$CMAKE_PACKAGE_TYPE" == "tgz" ]; then +if [ "$CPACK_GENERATOR" == "TGZ" ]; then cd .. MATSDK_INSTALL_DIR="${MATSDK_INSTALL_DIR:-/usr/local}" echo "+-----------------------------------------------------------------------------------+" diff --git a/cmake/MSTelemetryConfig.cmake.in b/cmake/MSTelemetryConfig.cmake.in index 8d63ac1f0..41ba63c0a 100644 --- a/cmake/MSTelemetryConfig.cmake.in +++ b/cmake/MSTelemetryConfig.cmake.in @@ -1,31 +1,29 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) +include("${CMAKE_CURRENT_LIST_DIR}/MatsdkDependencyTargets.cmake") -# Re-find dependencies that consumers need. -# On Apple the SDK links the system libsqlite3 (SQLite::SQLite3); elsewhere it uses -# the vcpkg sqlite3 package unless a private minimal SQLite is bundled. -if(@MATSDK_APPLE_SYSTEM_DEPS@) - find_dependency(SQLite3) -elseif(NOT @MATSDK_BUNDLE_SQLITE@) - find_dependency(unofficial-sqlite3 CONFIG) -endif() -find_dependency(ZLIB) -find_dependency(nlohmann_json CONFIG) - -# Curl is re-found only when the SDK was built with the curl HTTP client -# (Linux, explicit Android curl builds, and macOS built without Apple HTTP). -# Windows (WinInet), default Android Java/JNI HTTP, iOS, and -# macOS-with-Apple-HTTP do not link curl. -# We bake the build-time decision into a boolean rather than re-deriving it, -# because the macOS BUILD_APPLE_HTTP choice can't be inferred from -# CMAKE_SYSTEM_NAME alone. -if(@MATSDK_NEEDS_CURL@) - # Force CONFIG mode so the vcpkg-provided CURLConfig (which defines the - # CURL::libcurl imported target referenced by MSTelemetryTargets.cmake) is - # used, rather than module-mode FindCURL, which on some CMake versions does - # not define that target. - find_dependency(CURL CONFIG) +# Recreate dependencies only when a static package needs them at the final link. +if(@MATSDK_CONFIG_STATIC_PACKAGE@) + if(@MATSDK_BUILD_PLATFORM_APPLE@) + set(_matsdk_package_sqlite_args APPLE_SYSTEM APPLE_LIBRARY sqlite3) + set(_matsdk_package_zlib_args APPLE_SYSTEM APPLE_LIBRARY z) + else() + set(_matsdk_package_sqlite_args) + set(_matsdk_package_zlib_args) + endif() + matsdk_add_package_system_dependency( + MSTelemetry::sqlite_dependency + SQLite::SQLite3 + "@MATSDK_SQLITE_PROVIDER_RESOLVED@" + SQLite3 + ${_matsdk_package_sqlite_args}) + matsdk_add_package_system_dependency( + MSTelemetry::zlib_dependency + ZLIB::ZLIB + "@MATSDK_ZLIB_PROVIDER_RESOLVED@" + ZLIB + ${_matsdk_package_zlib_args}) endif() if("@MATSDK_ANDROID_HTTP_CLIENT_RESOLVED@" STREQUAL "") @@ -45,6 +43,63 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") find_dependency(Threads) endif() +if(@MATSDK_CONFIG_STATIC_PACKAGE@ AND @MATSDK_NEEDS_CURL@) + if(@MATSDK_CURL_FETCHED@) + function(_matsdk_import_static target_name archive_name) + if(NOT TARGET "${target_name}") + add_library("${target_name}" STATIC IMPORTED GLOBAL) + set_target_properties("${target_name}" PROPERTIES + IMPORTED_LOCATION + "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@/${archive_name}") + endif() + endfunction() + + if("@MATSDK_CURL_TLS_BACKEND_UPPER@" STREQUAL "MBEDTLS") + set(_matsdk_mbedcrypto_support) + foreach(_matsdk_support_archive IN ITEMS everest p256m) + if(EXISTS + "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@/lib${_matsdk_support_archive}.a") + _matsdk_import_static( + "MSTelemetry::${_matsdk_support_archive}" + "lib${_matsdk_support_archive}.a") + list(APPEND _matsdk_mbedcrypto_support + "MSTelemetry::${_matsdk_support_archive}") + endif() + endforeach() + _matsdk_import_static(MSTelemetry::mbedcrypto libmbedcrypto.a) + _matsdk_import_static(MSTelemetry::mbedx509 libmbedx509.a) + _matsdk_import_static(MSTelemetry::mbedtls libmbedtls.a) + set_property(TARGET MSTelemetry::mbedcrypto PROPERTY + INTERFACE_LINK_LIBRARIES "${_matsdk_mbedcrypto_support}") + set_property(TARGET MSTelemetry::mbedx509 PROPERTY + INTERFACE_LINK_LIBRARIES MSTelemetry::mbedcrypto) + set_property(TARGET MSTelemetry::mbedtls PROPERTY + INTERFACE_LINK_LIBRARIES + "MSTelemetry::mbedx509;MSTelemetry::mbedcrypto") + set(_matsdk_curl_tls_targets + "MSTelemetry::mbedtls;MSTelemetry::mbedx509;MSTelemetry::mbedcrypto") + else() + find_dependency(OpenSSL) + set(_matsdk_curl_tls_targets "OpenSSL::SSL;OpenSSL::Crypto") + endif() + + _matsdk_import_static(MSTelemetry::curl_archive libcurl.a) + set_property(TARGET MSTelemetry::curl_archive PROPERTY + INTERFACE_LINK_LIBRARIES "${_matsdk_curl_tls_targets}") + else() + if(NOT TARGET CURL::libcurl) + find_dependency(CURL) + endif() + endif() + if(@MATSDK_CURL_FETCHED@) + set(_matsdk_curl_dependency_target MSTelemetry::curl_archive) + else() + set(_matsdk_curl_dependency_target CURL::libcurl) + endif() + matsdk_add_interface_dependency( + MSTelemetry::curl_dependency "${_matsdk_curl_dependency_target}") +endif() + include("${CMAKE_CURRENT_LIST_DIR}/MSTelemetryTargets.cmake") check_required_components(MSTelemetry) diff --git a/cmake/MatsdkAppleSystemDeps.cmake b/cmake/MatsdkAppleSystemDeps.cmake new file mode 100644 index 000000000..2fdb7d70c --- /dev/null +++ b/cmake/MatsdkAppleSystemDeps.cmake @@ -0,0 +1,18 @@ +# Apple ships system SQLite and zlib but no CMake package config for either, so +# there is no find_package() to call. This defines the canonical imported +# target as a thin wrapper around the raw linker library name (e.g. "sqlite3", +# "z"). +# +# This file is shared between the root CMakeLists.txt (build time) and the +# installed MSTelemetryConfig.cmake (consume time, via install(FILES...) in +# lib/CMakeLists.txt) so the two never drift out of sync -- in particular the +# GLOBAL keyword below, which is required so a consumer that calls +# find_package(MSTelemetry) in one directory can link MSTelemetry::mat from a +# sibling/non-descendant directory. +function(matsdk_add_apple_system_library target_name library_name) + if(NOT TARGET "${target_name}") + add_library("${target_name}" INTERFACE IMPORTED GLOBAL) + set_property(TARGET "${target_name}" PROPERTY + INTERFACE_LINK_LIBRARIES "${library_name}") + endif() +endfunction() diff --git a/cmake/MatsdkDependencyTargets.cmake b/cmake/MatsdkDependencyTargets.cmake new file mode 100644 index 000000000..c21c338de --- /dev/null +++ b/cmake/MatsdkDependencyTargets.cmake @@ -0,0 +1,40 @@ +include_guard() +set(_MATSDK_DEPENDENCY_TARGETS_DIR "${CMAKE_CURRENT_LIST_DIR}") + +function(matsdk_add_interface_dependency target_name) + if(ARGC LESS 2) + message(FATAL_ERROR + "matsdk_add_interface_dependency requires a target and at least one link dependency.") + endif() + + if(NOT TARGET "${target_name}") + add_library("${target_name}" INTERFACE IMPORTED GLOBAL) + endif() + set_property(TARGET "${target_name}" APPEND PROPERTY + INTERFACE_LINK_LIBRARIES "${ARGN}") +endfunction() + +function(matsdk_add_package_system_dependency dependency_target canonical_target provider_value package_name) + if(NOT "${provider_value}" STREQUAL "SYSTEM") + return() + endif() + + set(options APPLE_SYSTEM) + set(one_value_args APPLE_LIBRARY) + cmake_parse_arguments(MATSDK_PACKAGE_DEP "${options}" "${one_value_args}" "" ${ARGN}) + + if(MATSDK_PACKAGE_DEP_APPLE_SYSTEM) + if(NOT DEFINED MATSDK_PACKAGE_DEP_APPLE_LIBRARY + OR MATSDK_PACKAGE_DEP_APPLE_LIBRARY STREQUAL "") + message(FATAL_ERROR + "APPLE_LIBRARY is required for Apple system dependencies.") + endif() + include("${_MATSDK_DEPENDENCY_TARGETS_DIR}/MatsdkAppleSystemDeps.cmake") + matsdk_add_apple_system_library( + "${canonical_target}" "${MATSDK_PACKAGE_DEP_APPLE_LIBRARY}") + elseif(NOT TARGET "${canonical_target}") + find_dependency(${package_name}) + endif() + + matsdk_add_interface_dependency("${dependency_target}" "${canonical_target}") +endfunction() diff --git a/cmake/MatsdkFetchCurl.cmake b/cmake/MatsdkFetchCurl.cmake new file mode 100644 index 000000000..ea10d86d7 --- /dev/null +++ b/cmake/MatsdkFetchCurl.cmake @@ -0,0 +1,143 @@ +include(FetchContent) + +function(matsdk_configure_fetched_static_target target_name) + if(NOT TARGET "${target_name}") + message(FATAL_ERROR "Fetched dependency target not found: ${target_name}") + endif() + set_target_properties("${target_name}" PROPERTIES + POSITION_INDEPENDENT_CODE ON + C_VISIBILITY_PRESET hidden) + target_compile_options("${target_name}" PRIVATE + $<$:-ffunction-sections;-fdata-sections>) +endfunction() + +function(matsdk_fetch_curl out_target) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + message(FATAL_ERROR + "MATSDK_CURL_PROVIDER=FETCH is currently supported only on Linux. " + "Use MATSDK_CURL_PROVIDER=SYSTEM for this platform.") + endif() + if(TARGET CURL::libcurl) + message(FATAL_ERROR + "MATSDK_CURL_PROVIDER=FETCH requires owning the CURL::libcurl target, " + "but a target with that name already exists. Use MATSDK_CURL_PROVIDER=SYSTEM.") + endif() + + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) + + foreach(option IN ITEMS + BUILD_SHARED_LIBS + BUILD_TESTING + ENABLE_PROGRAMS + ENABLE_TESTING + GEN_FILES + UNSAFE_BUILD + INSTALL_MBEDTLS_HEADERS + MBEDTLS_FATAL_WARNINGS + USE_SHARED_MBEDTLS_LIBRARY + LINK_WITH_PTHREAD + BUILD_CURL_EXE + BUILD_EXAMPLES + BUILD_LIBCURL_DOCS + BUILD_MISC_DOCS + ENABLE_CURL_MANUAL + CURL_ENABLE_EXPORT_TARGET + CURL_USE_OPENSSL + CURL_USE_PKGCONFIG + CURL_USE_CMAKECONFIG + CURL_ZLIB + CURL_BROTLI + CURL_ZSTD + USE_LIBIDN2 + CURL_USE_LIBPSL + CURL_USE_LIBSSH2 + CURL_USE_LIBSSH + CURL_USE_GSSAPI + CURL_USE_GSASL + USE_NGHTTP2 + USE_NGTCP2 + USE_QUICHE + ENABLE_ARES + ENABLE_UNIX_SOCKETS) + set(${option} OFF) + endforeach() + + foreach(option IN ITEMS + BUILD_STATIC_LIBS + DISABLE_PACKAGE_CONFIG_AND_INSTALL + CURL_DISABLE_INSTALL + HTTP_ONLY + CURL_DISABLE_ALTSVC + CURL_DISABLE_HSTS + CURL_DISABLE_COOKIES + CURL_DISABLE_NETRC + CURL_DISABLE_MIME + CURL_DISABLE_DOH + CURL_DISABLE_AWS + CURL_DISABLE_BEARER_AUTH + CURL_DISABLE_DIGEST_AUTH + CURL_DISABLE_KERBEROS_AUTH + CURL_DISABLE_NEGOTIATE_AUTH) + set(${option} ON) + endforeach() + + if(MATSDK_CURL_TLS_BACKEND_UPPER STREQUAL "MBEDTLS") + set(USE_STATIC_MBEDTLS_LIBRARY ON) + set(CURL_USE_MBEDTLS ON) + set(MBEDTLS_CONFIG_FILE "") + set(MBEDTLS_USER_CONFIG_FILE "") + + FetchContent_Declare( + matsdk_mbedtls + URL ${MATSDK_MBEDTLS_URL} + URL_HASH SHA256=${MATSDK_MBEDTLS_SHA256}) + FetchContent_MakeAvailable(matsdk_mbedtls) + + foreach(target mbedtls mbedx509 mbedcrypto) + matsdk_configure_fetched_static_target("${target}") + endforeach() + + set(MBEDTLS_INCLUDE_DIR "${matsdk_mbedtls_SOURCE_DIR}/include") + set(MBEDTLS_LIBRARY mbedtls) + set(MBEDX509_LIBRARY mbedx509) + set(MBEDCRYPTO_LIBRARY mbedcrypto) + set(MBEDTLS_USE_STATIC_LIBS ON) + foreach(_matsdk_mbedtls_target mbedtls mbedx509 mbedcrypto) + if(TARGET ${_matsdk_mbedtls_target} + AND NOT TARGET MbedTLS::${_matsdk_mbedtls_target}) + add_library(MbedTLS::${_matsdk_mbedtls_target} + ALIAS ${_matsdk_mbedtls_target}) + endif() + endforeach() + elseif(MATSDK_CURL_TLS_BACKEND_UPPER STREQUAL "OPENSSL") + set(CURL_USE_OPENSSL ON) + find_package(OpenSSL REQUIRED) + endif() + + FetchContent_Declare( + matsdk_curl + URL ${MATSDK_CURL_URL} + URL_HASH SHA256=${MATSDK_CURL_SHA256}) + FetchContent_MakeAvailable(matsdk_curl) + + if(NOT TARGET CURL::libcurl OR NOT TARGET libcurl_static) + message(FATAL_ERROR "The embedded static CURL::libcurl target was not created.") + endif() + + matsdk_configure_fetched_static_target(libcurl_static) + + set(_matsdk_fetched_curl_targets libcurl_static) + if(MATSDK_CURL_TLS_BACKEND_UPPER STREQUAL "MBEDTLS") + list(APPEND _matsdk_fetched_curl_targets mbedtls mbedx509 mbedcrypto) + foreach(_matsdk_mbedtls_support_target everest p256m) + if(TARGET ${_matsdk_mbedtls_support_target}) + list(APPEND _matsdk_fetched_curl_targets + ${_matsdk_mbedtls_support_target}) + endif() + endforeach() + endif() + set(MATSDK_FETCHED_CURL_TARGETS + "${_matsdk_fetched_curl_targets}" PARENT_SCOPE) + set(${out_target} CURL::libcurl PARENT_SCOPE) +endfunction() diff --git a/cmake/MatsdkOptions.cmake b/cmake/MatsdkOptions.cmake new file mode 100644 index 000000000..a56e468c6 --- /dev/null +++ b/cmake/MatsdkOptions.cmake @@ -0,0 +1,181 @@ +if(DEFINED PROJECT_IS_TOP_LEVEL) + set(MATSDK_PROJECT_IS_TOP_LEVEL "${PROJECT_IS_TOP_LEVEL}") +elseif(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(MATSDK_PROJECT_IS_TOP_LEVEL ON) +else() + set(MATSDK_PROJECT_IS_TOP_LEVEL OFF) +endif() + +option(MATSDK_BUILD_HEADERS + "Build API headers" ON) +option(MATSDK_BUILD_LIBRARY + "Build the SDK library" ON) +option(MATSDK_BUILD_TEST_TOOL + "Build the console test tool" "${MATSDK_PROJECT_IS_TOP_LEVEL}") +option(MATSDK_BUILD_UNIT_TESTS + "Build unit tests" "${MATSDK_PROJECT_IS_TOP_LEVEL}") +option(MATSDK_BUILD_FUNC_TESTS + "Build functional tests" "${MATSDK_PROJECT_IS_TOP_LEVEL}") +option(MATSDK_BUILD_JNI_WRAPPER + "Build the JNI wrapper" OFF) +option(MATSDK_ANDROID_USE_ROOM + "Use Android Room for offline storage" OFF) +option(MATSDK_ENABLE_CAPI_HTTP_CLIENT + "Enable the C API HTTP client on Android" OFF) +option(MATSDK_BUILD_OBJC_WRAPPER + "Build the Objective-C wrapper" ON) +option(MATSDK_BUILD_SWIFT_WRAPPER + "Build Swift wrappers" ON) +option(MATSDK_BUILD_PACKAGE + "Build an SDK package" "${MATSDK_PROJECT_IS_TOP_LEVEL}") +option(MATSDK_BUILD_PRIVACYGUARD + "Build Privacy Guard" ON) +option(MATSDK_BUILD_CDS + "Build Common Diagnostic Stack" ON) +option(MATSDK_BUILD_LIVEEVENTINSPECTOR + "Build Live Event Inspector" ON) +option(MATSDK_BUILD_SIGNALS + "Build Signals" ON) +option(MATSDK_BUILD_SANITIZER + "Build Sanitizer" ON) +option(MATSDK_BUILD_AZMON + "Build Azure Monitor / Application Insights support" ON) +option(MATSDK_BUILD_APPLE_HTTP + "Build the Apple-native HTTP client" "${APPLE}") + +set(_matsdk_android_http_client_predefined OFF) +if(DEFINED MATSDK_ANDROID_HTTP_CLIENT) + set(_matsdk_android_http_client_predefined ON) +endif() +set(MATSDK_ANDROID_HTTP_CLIENT "AUTO" CACHE STRING + "Android HTTP client: AUTO, JAVA, or CURL") +set_property(CACHE MATSDK_ANDROID_HTTP_CLIENT PROPERTY STRINGS AUTO JAVA CURL) + +# Legacy alias: USE_CURL=ON selected the native curl transport on Android +# before MATSDK_ANDROID_HTTP_CLIENT existed. Translate it (once, unless the +# canonical option was already set explicitly) rather than dropping it, since +# it is a real behavioral switch for deliberate Android curl consumers, not +# just a renamed knob. +if(DEFINED USE_CURL AND USE_CURL) + if(_matsdk_android_http_client_predefined + AND NOT MATSDK_ANDROID_HTTP_CLIENT STREQUAL "CURL") + message(DEPRECATION + "USE_CURL is deprecated and conflicts with MATSDK_ANDROID_HTTP_CLIENT; " + "MATSDK_ANDROID_HTTP_CLIENT=${MATSDK_ANDROID_HTTP_CLIENT} takes precedence.") + elseif(NOT _matsdk_android_http_client_predefined) + set(MATSDK_ANDROID_HTTP_CLIENT "CURL" CACHE STRING + "Android HTTP client: AUTO, JAVA, or CURL" FORCE) + endif() +endif() + +string(TOUPPER "${MATSDK_ANDROID_HTTP_CLIENT}" MATSDK_ANDROID_HTTP_CLIENT_UPPER) +if(NOT MATSDK_ANDROID_HTTP_CLIENT_UPPER MATCHES "^(AUTO|JAVA|CURL)$") + message(FATAL_ERROR + "MATSDK_ANDROID_HTTP_CLIENT must be AUTO, JAVA, or CURL; got " + "'${MATSDK_ANDROID_HTTP_CLIENT}'.") +endif() + +set(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED "") +set(MATSDK_ANDROID_USES_CURL OFF) +set(MATSDK_ANDROID_USES_JAVA_HTTP OFF) +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + if(MATSDK_ANDROID_HTTP_CLIENT_UPPER STREQUAL "AUTO") + set(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED "JAVA") + else() + set(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED + "${MATSDK_ANDROID_HTTP_CLIENT_UPPER}") + endif() + + if(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED STREQUAL "CURL") + set(MATSDK_ANDROID_USES_CURL ON) + else() + set(MATSDK_ANDROID_USES_JAVA_HTTP ON) + endif() + message(STATUS + "MATSDK_ANDROID_HTTP_CLIENT: ${MATSDK_ANDROID_HTTP_CLIENT} -> " + "${MATSDK_ANDROID_HTTP_CLIENT_RESOLVED}") +endif() + +option(BUILD_IOS "Deprecated: use CMAKE_SYSTEM_NAME=iOS or visionOS" OFF) +set(MATSDK_PLATFORM_IOS OFF) +if(BUILD_IOS + OR CMAKE_SYSTEM_NAME STREQUAL "iOS" + OR CMAKE_SYSTEM_NAME STREQUAL "visionOS") + set(MATSDK_PLATFORM_IOS ON) +endif() + +option(MATSDK_WARNINGS_AS_ERRORS + "Treat warnings in SDK-owned targets as errors" "${MATSDK_PROJECT_IS_TOP_LEVEL}") +option(LINK_STATIC_DEPENDS + "Deprecated no-op retained for compatibility with legacy build scripts" ON) + +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +set(MATSDK_SQLITE_PROVIDER "AUTO" CACHE STRING + "SQLite dependency provider: AUTO, SYSTEM, MINIMAL, VENDORED, or NONE") +set_property(CACHE MATSDK_SQLITE_PROVIDER PROPERTY STRINGS + AUTO SYSTEM MINIMAL VENDORED NONE) +set(MATSDK_ZLIB_PROVIDER "AUTO" CACHE STRING + "zlib dependency provider: AUTO, SYSTEM, or VENDORED") +set_property(CACHE MATSDK_ZLIB_PROVIDER PROPERTY STRINGS AUTO SYSTEM VENDORED) + +string(TOUPPER "${MATSDK_SQLITE_PROVIDER}" MATSDK_SQLITE_PROVIDER_RESOLVED) +string(TOUPPER "${MATSDK_ZLIB_PROVIDER}" MATSDK_ZLIB_PROVIDER_RESOLVED) + +if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "AUTO") + if(MATSDK_ANDROID_USE_ROOM AND CMAKE_SYSTEM_NAME STREQUAL "Android") + set(MATSDK_SQLITE_PROVIDER_RESOLVED NONE) + elseif(TARGET SQLite::SQLite3 OR TARGET SQLite3::SQLite3) + set(MATSDK_SQLITE_PROVIDER_RESOLVED SYSTEM) + elseif(NOT MATSDK_USING_VCPKG + AND (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Android")) + set(MATSDK_SQLITE_PROVIDER_RESOLVED VENDORED) + else() + set(MATSDK_SQLITE_PROVIDER_RESOLVED SYSTEM) + endif() +endif() + +if(MATSDK_ZLIB_PROVIDER_RESOLVED STREQUAL "AUTO") + if(TARGET ZLIB::ZLIB) + set(MATSDK_ZLIB_PROVIDER_RESOLVED SYSTEM) + elseif(NOT MATSDK_USING_VCPKG + AND (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Android")) + set(MATSDK_ZLIB_PROVIDER_RESOLVED VENDORED) + else() + set(MATSDK_ZLIB_PROVIDER_RESOLVED SYSTEM) + endif() +endif() + +if(MATSDK_ANDROID_USE_ROOM AND NOT CMAKE_SYSTEM_NAME STREQUAL "Android") + message(FATAL_ERROR + "MATSDK_ANDROID_USE_ROOM is supported only when CMAKE_SYSTEM_NAME is Android.") +endif() + +if(NOT MATSDK_SQLITE_PROVIDER_RESOLVED MATCHES "^(SYSTEM|MINIMAL|VENDORED|NONE)$") + message(FATAL_ERROR + "MATSDK_SQLITE_PROVIDER must be AUTO, SYSTEM, MINIMAL, VENDORED, or NONE; " + "got '${MATSDK_SQLITE_PROVIDER}'.") +endif() +if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "NONE" + AND NOT MATSDK_ANDROID_USE_ROOM) + message(FATAL_ERROR + "MATSDK_SQLITE_PROVIDER=NONE is valid only with MATSDK_ANDROID_USE_ROOM=ON.") +endif() +if(NOT MATSDK_ZLIB_PROVIDER_RESOLVED MATCHES "^(SYSTEM|VENDORED)$") + message(FATAL_ERROR + "MATSDK_ZLIB_PROVIDER must be AUTO, SYSTEM, or VENDORED; " + "got '${MATSDK_ZLIB_PROVIDER}'.") +endif() +set(MATSDK_BUNDLE_SQLITE OFF) +if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "MINIMAL" + OR MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "VENDORED") + set(MATSDK_BUNDLE_SQLITE ON) +endif() +set(MATSDK_BUNDLE_ZLIB OFF) +if(MATSDK_ZLIB_PROVIDER_RESOLVED STREQUAL "VENDORED") + set(MATSDK_BUNDLE_ZLIB ON) +endif() + +message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") +message(STATUS "MATSDK_SQLITE_PROVIDER: ${MATSDK_SQLITE_PROVIDER} -> ${MATSDK_SQLITE_PROVIDER_RESOLVED}") +message(STATUS "MATSDK_ZLIB_PROVIDER: ${MATSDK_ZLIB_PROVIDER} -> ${MATSDK_ZLIB_PROVIDER_RESOLVED}") diff --git a/cmake/MatsdkRequirePresetSupport.cmake b/cmake/MatsdkRequirePresetSupport.cmake new file mode 100644 index 000000000..18b682ad0 --- /dev/null +++ b/cmake/MatsdkRequirePresetSupport.cmake @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.15) + +if(CMAKE_VERSION VERSION_LESS 3.21) + message(FATAL_ERROR + "The 1DS build wrappers require CMake 3.21 or newer for CMakePresets.json " + "support. Direct CMake builds retain the CMake 3.15 minimum.") +endif() diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index fed2dbdcf..8cdb27c69 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -418,7 +418,7 @@ unused) but does not save the dependency. For a plain (non-vcpkg) CMake build, pass the option directly: ```bash -cmake -DMATSDK_MINIMAL_SQLITE=ON .. +cmake -DMATSDK_SQLITE_PROVIDER=MINIMAL .. ``` The strip is **amalgamation-safe**: it changes no SQLite grammar/parser, so no @@ -438,22 +438,13 @@ unchanged against the minimal build. > that case, prefer the default `system-sqlite` feature so the whole graph shares a > single SQLite. -## How It Works: MATSDK_USE_VCPKG_DEPS +## How It Works -When the SDK detects it is being built via vcpkg (by checking for -`VCPKG_TOOLCHAIN` or `VCPKG_TARGET_TRIPLET`), it automatically sets -`MATSDK_USE_VCPKG_DEPS=ON`. This switches dependency resolution from -vendored sources to vcpkg-provided packages via `find_package()`. Android HTTP -transport selection is controlled separately by `MATSDK_ANDROID_HTTP_CLIENT`, -which defaults to `JAVA` on Android. - -You can also set this explicitly for custom CMake workflows: - -```bash -cmake -DMATSDK_USE_VCPKG_DEPS=ON \ - -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake \ - .. -``` +The SDK consumes canonical CMake dependency targets. The vcpkg toolchain +provides those targets through normal `find_package()` discovery; no separate +SDK-specific dependency-mode switch is required. Android transport selection is +separate: `MATSDK_ANDROID_HTTP_CLIENT=AUTO` resolves to the Java/JNI transport, +while the explicit Android curl features select the native curl transport. ## Migrating from the older overlay port diff --git a/docs/cpp-start-android.md b/docs/cpp-start-android.md index 2281a5e31..8f02b05e7 100644 --- a/docs/cpp-start-android.md +++ b/docs/cpp-start-android.md @@ -12,7 +12,7 @@ You will ideally build the SDK using the same versions of the Android SDK, NDK, The Gradle wrapper in ```android_build``` builds two modules, ```app``` and ```maesdk```. The ```maesdk``` module is the SDK packaged as an AAR, with both the Java and C++ components included. The AAR includes C++ shared libraries for four ABIs (two ARM ABIs for devices and two Intel ABIs for the emulator). Android Gradle (as usual) supports debug and release builds, and the Gradle task ```maesdk:assemble``` should build both flavors of AAR. -On Android, there are two database implementations to choose from. By default (the main branch on Github), the SDK will use the Android-supported androidx.Room database package. This reduces APK size because we don't need to compile and link in a copy of SQLite in native code (SQLite is hundreds of kB per ABI of APK file size). Room does have a slight CPU performance disadvantage since database transactions cross the JNI boundary when native code uses it. If you wish to change from Room to the native SQLite implementation, you should change the two module ```build.gradle``` files (app and maesdk). In those files, you will see an argument to CMake to select Room: ```"-DUSE_ROOM=1"```. Change this to ```"-DUSE_ROOM=0``` to select the native SQLite. +On Android, there are two database implementations to choose from. By default (the main branch on Github), the SDK will use the Android-supported androidx.Room database package. This reduces APK size because we don't need to compile and link in a copy of SQLite in native code (SQLite is hundreds of kB per ABI of APK file size). Room does have a slight CPU performance disadvantage since database transactions cross the JNI boundary when native code uses it. If you wish to change from Room to the native SQLite implementation, you should change the two module ```build.gradle``` files (app and maesdk). In those files, you will see an argument to CMake to select Room: ```"-DMATSDK_ANDROID_USE_ROOM=ON"```. Change this to ```"-DMATSDK_ANDROID_USE_ROOM=OFF``` to select the native SQLite. When using the Room implementation, the ```maesdk``` AAR brings ```androidx.room``` as a transitive dependency, pinned in ```lib/android_build/maesdk/build.gradle``` (currently ```2.8.4```). The SDK's native (JNI) code is compiled and tested against this version and the Room-generated schema. Because Gradle resolves a single ```androidx.room``` version for the entire app, if your app (or one of its dependencies) selects a different version, the SDK's native code runs against it. **Do not force ```androidx.room``` below the version the SDK is built against**, and prefer aligning your app on the bundled version (or a compatible newer one). A significantly different Room version can change the shape of query results that cross the JNI boundary and has historically caused native crashes in record retrieval (issue #1227); the SDK now guards against null results defensively, but version alignment avoids subtle behavior differences. diff --git a/docs/cpp-start-ios.md b/docs/cpp-start-ios.md index ad80866e0..6adf7b5cc 100644 --- a/docs/cpp-start-ios.md +++ b/docs/cpp-start-ios.md @@ -37,6 +37,25 @@ If Xcode reports that the requested simulator runtime is missing, install it from Xcode > Settings > Components or run `xcodebuild -downloadPlatform iOS -architectureVariant arm64`. +For direct CMake integration, use the standard Apple variables rather than +SDK-specific architecture flags: + +```sh +cmake -S . -B out \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_OSX_SYSROOT=iphonesimulator \ + -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \ + -DCMAKE_BUILD_TYPE=Release \ + -DMATSDK_BUILD_UNIT_TESTS=OFF \ + -DMATSDK_BUILD_FUNC_TESTS=OFF \ + -DMATSDK_BUILD_OBJC_WRAPPER=OFF \ + -DMATSDK_BUILD_SWIFT_WRAPPER=OFF +``` + +Use `iphoneos` for a device build. Legacy `IOS_ARCH`/`IOS_PLAT` inputs remain +accepted temporarily, but new integrations should use `CMAKE_OSX_*`. + ## 3. Integrate the SDK into your C++ project SDK package contains headers and library installed at the following locations diff --git a/docs/cpp-start-macosx.md b/docs/cpp-start-macosx.md index ec9ef9f71..b0312507b 100644 --- a/docs/cpp-start-macosx.md +++ b/docs/cpp-start-macosx.md @@ -35,22 +35,18 @@ If you do not have those credentials, generate them and use the username and pas ### 2. Run the file build.sh to build the SDK, this will build the SDK along with Unit and Functional Tests -To disable building the tests go to the **CMakeLists.txt** file in the root of the SDK directory and change +To disable tests without editing SDK sources, pass the namespaced CMake options: -```console -option(BUILD_UNIT_TESTS "Build unit tests" YES) -option(BUILD_FUNC_TESTS "Build functional tests" YES) -``` - -to - -```console -option(BUILD_UNIT_TESTS "Build unit tests" NO) -option(BUILD_FUNC_TESTS "Build functional tests" NO) +```sh +CMAKE_OPTS="-DMATSDK_BUILD_UNIT_TESTS=OFF -DMATSDK_BUILD_FUNC_TESTS=OFF" ./build.sh ``` _**Note:** In order to build from scratch all dependencies along with the SDK you need to run: `./build.sh clean`_ +For direct CMake builds, use `CMAKE_OSX_ARCHITECTURES` (`arm64`, `x86_64`, or +`arm64;x86_64`) and `CMAKE_OSX_DEPLOYMENT_TARGET`. The SDK no longer injects +global `-arch` or deployment-target flags. + ### 3. The SDK will be installed under `usr/local/lib/libmat.a` ## **Instrument your code to send a telemetry event** diff --git a/docs/embedding-with-cmake.md b/docs/embedding-with-cmake.md new file mode 100644 index 000000000..3d9f1c591 --- /dev/null +++ b/docs/embedding-with-cmake.md @@ -0,0 +1,81 @@ +# Embedding 1DS with CMake + +Consumers that build the SDK from source with `add_subdirectory()` or +`FetchContent` can link the same target name used by installed/vcpkg builds: + +```cmake +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_TEST_TOOL OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_FUNC_TESTS OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_PACKAGE OFF CACHE BOOL "" FORCE) + +add_subdirectory(cpp_client_telemetry) +target_link_libraries(your_target PRIVATE MSTelemetry::mat) +``` + +For a static SDK build, CMake carries the SDK's link dependencies through the +`MSTelemetry::mat` target, so the consuming target should not need to name the +SDK's internal dependencies directly. + +Use standard `BUILD_SHARED_LIBS=OFF|ON` to select static or shared output. +SDK-specific behavior continues to use namespaced `MATSDK_*` options. + +`MATSDK_WARNINGS_AS_ERRORS` defaults to `ON` for standalone SDK builds and +`OFF` when the SDK is embedded. Its warning policy is private to SDK-owned +targets and never propagates to the parent consumer or vendored dependencies. +Set it explicitly to `ON` in consumer CI to test new toolchains strictly. + +## SQLite and zlib providers + +Source builds can select dependency modes without patching 1DS sources: + +```cmake +set(MATSDK_SQLITE_PROVIDER MINIMAL CACHE STRING "" FORCE) # SYSTEM, MINIMAL, VENDORED +set(MATSDK_ZLIB_PROVIDER VENDORED CACHE STRING "" FORCE) # SYSTEM or VENDORED +``` + +`MINIMAL` builds the feature-stripped SQLite amalgamation. `VENDORED` builds the +unstripped vendored dependency. `SYSTEM` consumes the canonical +`SQLite::SQLite3` / `ZLIB::ZLIB` targets or uses `find_package()`. `AUTO` +preserves platform defaults: system dependencies on desktop/Apple source builds +and vendored dependencies on Windows/Android source builds. + +## Non-vcpkg dependency selection + +When the CPP11 PAL uses the curl HTTP transport outside vcpkg, the SDK normally +calls `find_package(CURL)` and links `CURL::libcurl` when that imported target is +available. On Linux, set `MATSDK_CURL_PROVIDER=FETCH` to let the SDK download and +build a pinned static curl dependency instead: + +```cmake +set(MATSDK_CURL_PROVIDER FETCH CACHE STRING "" FORCE) +set(MATSDK_CURL_TLS_BACKEND MBEDTLS CACHE STRING "" FORCE) # or OPENSSL +add_subdirectory(cpp_client_telemetry) + +target_link_libraries(your_target PRIVATE MSTelemetry::mat) +``` + +The default fetched backend is mbedTLS and is fully self-contained. Selecting +`OPENSSL` builds curl from source but still requires the parent build environment +to provide OpenSSL through `find_package(OpenSSL)`. + +Non-vcpkg Linux builds similarly use `find_package()` for zlib and SQLite unless +an explicit vendored/minimal provider is selected. + +To make a superbuild choose dependency implementations without changing the +leaf consumer target, define the standard CMake targets before adding the SDK: + +```cmake +# These may be real targets or aliases to targets owned by your superbuild. +add_library(CURL::libcurl ALIAS my_curl_target) +add_library(ZLIB::ZLIB ALIAS my_zlib_target) +add_library(SQLite::SQLite3 ALIAS my_sqlite_target) +add_subdirectory(cpp_client_telemetry) + +target_link_libraries(your_target PRIVATE MSTelemetry::mat) +``` + +For a fully self-contained source build, use `MATSDK_SQLITE_PROVIDER=MINIMAL` +and `MATSDK_ZLIB_PROVIDER=VENDORED`; the vendored targets are PIC, hidden, and +compiled without inheriting the SDK's warnings-as-errors policy. diff --git a/install.sh b/install.sh index 4dcddc197..c8b1df24f 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,11 @@ #!/bin/sh -MATSDK_INSTALL_DIR=$1 +set -e + +MATSDK_INSTALL_DIR=${1:-/usr/local} +if [ ! -f out/cmake_install.cmake ]; then + echo "ERROR: out/cmake_install.cmake not found; configure and build the SDK first." >&2 + exit 1 +fi + echo "Install SDK to $MATSDK_INSTALL_DIR" -mkdir -p $MATSDK_INSTALL_DIR/lib -cp out/lib/libmat.* $MATSDK_INSTALL_DIR/lib -mkdir -p $MATSDK_INSTALL_DIR/include/mat -cp lib/include/public/* $MATSDK_INSTALL_DIR/include/mat +cmake --install out --prefix "$MATSDK_INSTALL_DIR" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index ce2df9b89..b08fd4537 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,18 +1,6 @@ # Honor visibility properties for all target types cmake_policy(SET CMP0063 NEW) -# Legacy (non-target) include paths that apply globally within this directory and -# are used by build.sh / MSBuild / standalone CMake workflows. They do NOT propagate -# to downstream consumers via find_package() (see target_include_directories below). -include_directories( . ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/pal ${CMAKE_CURRENT_SOURCE_DIR}/utils ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds ${CMAKE_CURRENT_SOURCE_DIR}/modules/signals ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer ) - -# Legacy builds may need system-installed deps from /usr/local/include. Excluded on -# iOS: /usr/local/include is a host (macOS) path, and injecting it into an iOS -# cross-compile's search path can shadow the iOS SDK's own headers. -if(NOT MATSDK_USE_VCPKG_DEPS AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS") - include_directories(/usr/local/include) -endif() - set(SRCS decorators/BaseDecorator.cpp packager/BondSplicer.cpp packager/Packager.cpp @@ -55,7 +43,6 @@ set(SRCS decorators/BaseDecorator.cpp offline/StorageObserver.cpp offline/OfflineStorageFactory.cpp offline/MemoryStorage.cpp - offline/OfflineStorage_SQLite.cpp offline/OfflineStorageHandler.cpp offline/LogSessionDataProvider.cpp backoff/IBackoff.cpp @@ -65,8 +52,38 @@ set(SRCS decorators/BaseDecorator.cpp decoder/PayloadDecoder.cpp ) +if(MATSDK_ANDROID_USE_ROOM) + list(APPEND SRCS offline/OfflineStorage_Room.cpp) +else() + list(APPEND SRCS offline/OfflineStorage_SQLite.cpp) +endif() + +if(MATSDK_BUILD_JNI_WRAPPER) + list(APPEND SRCS + jni/JniConvertors.cpp + jni/LogManager_jni.cpp + jni/Logger_jni.cpp + jni/SemanticContext_jni.cpp + jni/Utils_jni.cpp) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer/") + list(APPEND SRCS jni/LogManagerDDVController_jni.cpp) + endif() + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" + AND MATSDK_BUILD_PRIVACYGUARD) + list(APPEND SRCS jni/PrivacyGuard_jni.cpp) + endif() + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/signals/" + AND MATSDK_BUILD_SIGNALS) + list(APPEND SRCS jni/Signals_jni.cpp) + endif() + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" + AND MATSDK_BUILD_SANITIZER) + list(APPEND SRCS jni/Sanitizer_jni.cpp) + endif() +endif() + # Support for Azure Monitor / Application Insights -if(BUILD_AZMON) +if(MATSDK_BUILD_AZMON) include(modules/azmon/CMakeLists.txt OPTIONAL) endif() @@ -91,7 +108,7 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer/") ) endif() -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND BUILD_PRIVACYGUARD) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND MATSDK_BUILD_PRIVACYGUARD) list(APPEND SRCS modules/privacyguard/PrivacyGuard.cpp modules/privacyguard/RegisteredFileTypes.cpp @@ -99,29 +116,29 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND BUILD_PRIVACYG ) endif() -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector/" AND BUILD_LIVEEVENTINSPECTOR) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector/" AND MATSDK_BUILD_LIVEEVENTINSPECTOR) list(APPEND SRCS modules/liveeventinspector/LiveEventInspector.cpp modules/liveeventinspector/LiveEventInspector.hpp ) endif() -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/cds/" AND BUILD_CDS) - add_definitions(-DHAVE_MAT_CDS) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/cds/" AND MATSDK_BUILD_CDS) + target_compile_definitions(matsdk_internal_config INTERFACE HAVE_MAT_CDS) list(APPEND SRCS modules/cds/CdsFactory.hpp modules/cds/CdsFactory.cpp ) endif() -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/signals/" AND BUILD_SIGNALS) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/signals/" AND MATSDK_BUILD_SIGNALS) list(APPEND SRCS modules/signals/Signals.cpp modules/signals/SignalsEncoder.cpp ) endif() -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" AND BUILD_SANITIZER) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" AND MATSDK_BUILD_SANITIZER) list(APPEND SRCS modules/sanitizer/detectors/EmailAddressDetector.cpp modules/sanitizer/detectors/JwtDetector.cpp @@ -153,7 +170,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") list(APPEND SRCS pal/posix/sysinfo_utils_apple.cpp ) - if(BUILD_IOS) + if(MATSDK_PLATFORM_IOS) list(APPEND SRCS pal/posix/sysinfo_utils_ios.mm ) @@ -178,7 +195,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") endif() if(APPLE) - if(BUILD_APPLE_HTTP OR BUILD_IOS) + if(MATSDK_BUILD_APPLE_HTTP OR MATSDK_PLATFORM_IOS) list(APPEND SRCS http/HttpClient_Apple.mm ) @@ -216,7 +233,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") ) endif() endif() - if(APPLE AND BUILD_OBJC_WRAPPER) + if(APPLE AND MATSDK_BUILD_OBJC_WRAPPER) message(STATUS "Include ObjC Wrappers") set(OBJC_WRAPPER_SRCS ../wrappers/obj-c/ODWLogger.mm @@ -233,13 +250,13 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") ../wrappers/obj-c/ODWDiagnosticDataViewer.mm ) endif() - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND BUILD_PRIVACYGUARD) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND MATSDK_BUILD_PRIVACYGUARD) set(MATSDK_OBJC_PRIVACYGUARD_AVAILABLE ON) list(APPEND OBJC_WRAPPER_SRCS ../wrappers/obj-c/ODWPrivacyGuard.mm ) endif() - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" AND BUILD_SANITIZER) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" AND MATSDK_BUILD_SANITIZER) set(MATSDK_OBJC_SANITIZER_AVAILABLE ON) list(APPEND OBJC_WRAPPER_SRCS ../wrappers/obj-c/ODWSanitizer.mm @@ -248,7 +265,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") list(APPEND SRCS ${OBJC_WRAPPER_SRCS}) endif() - if(APPLE AND BUILD_SWIFT_WRAPPER) + if(APPLE AND MATSDK_BUILD_SWIFT_WRAPPER) message(STATUS "Building Swift Wrappers") # Run swift build for the Swift Wrappers Package string(TOLOWER ${CMAKE_BUILD_TYPE} LOWER_BUILD_TYPE) @@ -271,11 +288,17 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") elseif(PAL_IMPLEMENTATION STREQUAL "WIN32") # Win32 Desktop for now. # TODO: define a separate PAL for Win10 cmake build -if(NOT MATSDK_USE_VCPKG_DEPS) - include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../zlib ${CMAKE_CURRENT_SOURCE_DIR}/../sqlite) -endif() -add_definitions(-D_UNICODE -DUNICODE -DWIN32 -DMATSDK_PLATFORM_WINDOWS=1 -D_UTC_SDK -DUSE_BOND -D_WINDOWS -D_USRDLL -DWINVER=_WIN32_WINNT_WIN7) -remove_definitions(-D_MBCS) +target_compile_definitions(matsdk_internal_config INTERFACE + _UNICODE + UNICODE + WIN32 + MATSDK_PLATFORM_WINDOWS=1 + _UTC_SDK + USE_BOND + _WINDOWS + _USRDLL + WINVER=_WIN32_WINNT_WIN7) +target_compile_options(matsdk_internal_config INTERFACE /U_MBCS) list(APPEND SRCS http/HttpClient_WinInet.cpp http/HttpClient_WinInet.hpp @@ -310,6 +333,15 @@ endif() create_source_files_groups_per_folder(${SRCS}) +if(APPLE) + set(_matsdk_objcxx_sources ${SRCS}) + list(FILTER _matsdk_objcxx_sources INCLUDE REGEX "\\.mm$") + if(_matsdk_objcxx_sources) + set_source_files_properties(${_matsdk_objcxx_sources} + PROPERTIES COMPILE_OPTIONS "-fobjc-arc;-Wno-error=shorten-64-to-32") + endif() +endif() + # Linux and Android require pthreads if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") find_package(Threads REQUIRED) @@ -325,6 +357,27 @@ else() message(STATUS "Building static SDK library") add_library(mat STATIC ${SRCS}) endif() +target_link_libraries(mat PRIVATE + $ + $) +set_target_properties(mat PROPERTIES POSITION_INDEPENDENT_CODE ON) +if(BUILD_SHARED_LIBS) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_options(mat PRIVATE + $<$>:-s> + $<$>:-Wl,--gc-sections>) + elseif(APPLE) + target_link_options(mat PRIVATE + $<$>:-Wl,-dead_strip>) + endif() +endif() +if(APPLE) + target_compile_options(mat PRIVATE + $<$:-Wno-error=shorten-64-to-32>) +endif() +if(NOT TARGET MSTelemetry::mat) + add_library(MSTelemetry::mat ALIAS mat) +endif() # Public-API export decoration (MATSDK_LIBABI in lib/include/public/ctmacros.hpp). # The SDK has no .def file, so __declspec(dllexport)/(dllimport) on Windows and @@ -369,13 +422,25 @@ target_include_directories(mat ) target_include_directories(mat PRIVATE + ${PROJECT_SOURCE_DIR}/bondlite/include ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/pal ${CMAKE_CURRENT_SOURCE_DIR}/utils + ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp + ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer + ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard + ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector + ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds + ${CMAKE_CURRENT_SOURCE_DIR}/modules/signals + ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer ) +if(NOT MATSDK_USES_NLOHMANN_TARGET) + target_include_directories(mat PRIVATE ${PROJECT_SOURCE_DIR}) +endif() -if(APPLE AND BUILD_OBJC_WRAPPER) +if(APPLE AND MATSDK_BUILD_OBJC_WRAPPER) if(BUILD_SHARED_LIBS AND OBJC_WRAPPER_SRCS) # The root CMakeLists.txt applies -fvisibility=hidden globally to shrink the # exported symbol table of the core C++ SDK. For Objective-C that also hides @@ -407,7 +472,7 @@ endif() # The SDK uses SQLite only for its offline event-storage cache: plain tables, # indexes, transactions, WAL, autovacuum/VACUUM, a handful of PRAGMAs, and one # custom UTF-8 SQL function. None of SQLite's optional subsystems are needed, so -# when MATSDK_MINIMAL_SQLITE is set the bundled SQLite is compiled with these +# when MATSDK_SQLITE_PROVIDER=MINIMAL the bundled SQLite is compiled with these # options to strip out everything the SDK does not use (~10% smaller SQLite code). # They are all amalgamation-safe (no grammar/parser regeneration) and validated # against the offline-storage unit tests. @@ -447,22 +512,6 @@ set(MATSDK_SQLITE_MINIMAL_DEFS SQLITE_UNTESTABLE ) -# Bundle a vendored SQLite (built from sqlite/sqlite3.c) when MATSDK_MINIMAL_SQLITE -# is requested, or on the Android NDK legacy path (which has no system SQLite and -# has always built the vendored amalgamation). Otherwise an external/system SQLite -# is used. The feature-strip definitions above are applied ONLY when -# MATSDK_MINIMAL_SQLITE is ON, so the default Android legacy build keeps its -# existing (unstripped) bundled SQLite behavior. -set(MATSDK_BUNDLE_SQLITE OFF) -if(MATSDK_MINIMAL_SQLITE AND NOT APPLE) - # On Apple the SDK links the system libsqlite3/libz (see the Apple branch below), - # so MATSDK_MINIMAL_SQLITE has no effect there. - set(MATSDK_BUNDLE_SQLITE ON) -elseif(NOT MATSDK_USE_VCPKG_DEPS AND CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android NDK ships no system SQLite, so the vendored amalgamation is always bundled. - set(MATSDK_BUNDLE_SQLITE ON) -endif() - if(MATSDK_BUNDLE_SQLITE AND NOT TARGET sqlite3_bundled) add_library(sqlite3_bundled STATIC "${CMAKE_CURRENT_SOURCE_DIR}/../sqlite/sqlite3.c") # Consumers of MSTelemetry::mat never include sqlite3.h (it is an internal @@ -470,141 +519,148 @@ if(MATSDK_BUNDLE_SQLITE AND NOT TARGET sqlite3_bundled) # SDK itself -- wrap it in BUILD_INTERFACE so install(EXPORT) stays valid. target_include_directories(sqlite3_bundled PUBLIC "$") - set_target_properties(sqlite3_bundled PROPERTIES POSITION_INDEPENDENT_CODE ON) - if(MATSDK_MINIMAL_SQLITE) + set_target_properties(sqlite3_bundled PROPERTIES + POSITION_INDEPENDENT_CODE ON + C_VISIBILITY_PRESET hidden) + if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "MINIMAL") # Feature-stripped build: apply the minimal definitions. target_compile_definitions(sqlite3_bundled PRIVATE ${MATSDK_SQLITE_MINIMAL_DEFS}) endif() + if(USE_ONEDS_SECURE_MEM_FUNCTIONS) + target_compile_definitions(sqlite3_bundled PRIVATE + USE_ONEDS_SECURE_MEM_FUNCTIONS) + endif() + if(APPLE AND MATSDK_PLATFORM_IOS) + # SQLite already resolves this to false on Apple mobile platforms. Define it + # explicitly so the amalgamation does not emit its gethostuuid warning under + # consumer-provided warnings-as-errors. + target_compile_definitions(sqlite3_bundled PRIVATE HAVE_GETHOSTUUID=0) + endif() if(MSVC) # Silence the vendored amalgamation's warnings (/w) and turn off # warning-as-error (/WX-) for this third-party translation unit, so the SDK's # /WX does not promote any amalgamation warning that survives /w to an error. - target_compile_options(sqlite3_bundled PRIVATE /w /WX-) - elseif(MATSDK_MINIMAL_SQLITE) + target_compile_options(sqlite3_bundled PRIVATE + /w /WX- /Gy + $<$:/Gw>) + elseif(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "MINIMAL") # -w disables all warnings for this vendored translation unit so the SDK's # -Werror does not fire on amalgamation code (the OMIT_* options leave some # debug-build macros expanding to empty/unused statements). -fno-finite-math-only: # the amalgamation relies on the INFINITY macro, which -ffast-math / # -ffinite-math-only would break. - target_compile_options(sqlite3_bundled PRIVATE -w -fno-finite-math-only) + target_compile_options(sqlite3_bundled PRIVATE + -w -fno-finite-math-only -ffunction-sections + $<$>:-fdata-sections>) else() # Unstripped vendored build (Android legacy): keep the existing narrower # warning suppression. -fno-finite-math-only guards the INFINITY macro. - target_compile_options(sqlite3_bundled PRIVATE -fno-finite-math-only -Wno-unused-function) + target_compile_options(sqlite3_bundled PRIVATE + -fno-finite-math-only -Wno-unused-function -ffunction-sections + $<$>:-fdata-sections>) endif() endif() +if(MATSDK_BUNDLE_SQLITE AND NOT TARGET SQLite::SQLite3) + add_library(SQLite::SQLite3 ALIAS sqlite3_bundled) +endif() -# TODO: allow adding "${Tcmalloc_LIBRARIES}" to target_link_libraries for memory leak debugging -# (USE_TCMALLOC / FindTcmalloc.cmake are configured for Debug builds in the root CMakeLists.txt, -# but the library is not yet linked here). -if(MATSDK_USE_VCPKG_DEPS) - # vcpkg mode: all deps resolved via find_package() in root CMakeLists.txt - # These are PUBLIC so static-library consumers get the transitive link set - # through the exported MSTelemetry::mat target. - if(APPLE) - # macOS/iOS link the system libsqlite3 + libz (SQLite3::SQLite3 / ZLIB::ZLIB - # resolve to the OS libraries via CMake's find modules), so the vcpkg - # sqlite3/zlib packages are neither pulled nor linked here. - target_link_libraries(mat - PUBLIC - SQLite3::SQLite3 - ZLIB::ZLIB - nlohmann_json::nlohmann_json - ${LIBS} - ) +if(MATSDK_BUNDLE_ZLIB AND NOT TARGET zlib_bundled) + add_library(zlib_bundled STATIC + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/adler32.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/compress.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/crc32.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/deflate.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzclose.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzlib.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzread.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzwrite.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/infback.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/inffast.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/inflate.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/inftrees.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/trees.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/uncompr.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/zutil.c" + ) + target_include_directories(zlib_bundled PUBLIC + "$") + set_target_properties(zlib_bundled PROPERTIES + POSITION_INDEPENDENT_CODE ON + C_VISIBILITY_PRESET hidden) + if(NOT WIN32) + target_compile_definitions(zlib_bundled PRIVATE Z_HAVE_UNISTD_H) else() - if(MATSDK_BUNDLE_SQLITE) - # Private minimal SQLite instead of the vcpkg sqlite3 package. PRIVATE so its - # include dirs / compile definitions are not propagated as a public usage - # requirement. A static mat still propagates the archive itself for linking - # (via $), so it is added to the export set for static builds - # below; a shared mat absorbs it and propagates nothing. - target_link_libraries(mat PRIVATE sqlite3_bundled) - else() - target_link_libraries(mat PUBLIC unofficial::sqlite3::sqlite3) - endif() - target_link_libraries(mat - PUBLIC - ZLIB::ZLIB - nlohmann_json::nlohmann_json - ${LIBS} - ) + target_compile_definitions(zlib_bundled PRIVATE ZLIB_WINAPI) + target_compile_definitions(zlib_bundled INTERFACE + $) endif() -else() - # Legacy mode: use vendored or system-installed deps - if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Build zlib from bundled source: the Android NDK ships no system zlib, and the - # vendored zlib renames its exports to act_z_* (via zlib/names.h). SQLite is - # provided by sqlite3_bundled, created above (MATSDK_BUNDLE_SQLITE is ON for - # the Android NDK path). - add_library(zlib_bundled STATIC - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/adler32.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/compress.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/crc32.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/deflate.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzclose.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzlib.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzread.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/gzwrite.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/infback.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/inffast.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/inflate.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/inftrees.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/trees.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/uncompr.c" - "${CMAKE_CURRENT_SOURCE_DIR}/../zlib/zutil.c" - ) - target_include_directories(zlib_bundled PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../zlib") - set_target_properties(zlib_bundled PROPERTIES POSITION_INDEPENDENT_CODE ON) - # Bundled zlib compiles the pristine sources without zlib's configure step, - # so tell it is available (Android is POSIX). This gives gz*.c the - # real POSIX declarations for read/write/lseek/close instead of relying on - # implicit (int-returning) declarations. - target_compile_definitions(zlib_bundled PRIVATE Z_HAVE_UNISTD_H) - - target_link_libraries(mat PRIVATE sqlite3_bundled zlib_bundled ${LIBS}) - elseif(PAL_IMPLEMENTATION STREQUAL "WIN32") - # Windows legacy: vendored sqlite/zlib headers are included via - # include_directories in the PAL section above; link only ${LIBS} - # (e.g. CURL if needed — sqlite/zlib come from .vcxproj references), plus the - # private minimal SQLite when MATSDK_MINIMAL_SQLITE is enabled. - if(MATSDK_BUNDLE_SQLITE) - target_link_libraries(mat PRIVATE sqlite3_bundled ${LIBS}) - else() - target_link_libraries(mat PRIVATE ${LIBS}) - endif() - elseif(APPLE) - # macOS and iOS both ship system libsqlite3 and libz. Link them by portable - # names -- matching the SDK's own iOS Xcode projects (libsqlite3.tbd + libz.tbd - # from the SDKROOT), Package.swift (.linkedLibrary sqlite3/z), and the vcpkg - # Apple path -- so nothing is bundled and exported static packages stay - # relocatable. On Apple, #include / resolve from the SDK - # sysroot, so no explicit include dir or find_package is needed. - target_link_libraries(mat PRIVATE sqlite3 z ${LIBS}) + if(MSVC) + target_compile_options(zlib_bundled PRIVATE + /w /WX- /Gy + $<$:/Gw>) else() - # Linux legacy: system zlib + system (or private minimal) sqlite3. ZLIB::ZLIB - # and SQLite3::SQLite3 are imported targets that carry their own include dirs. - find_package(ZLIB REQUIRED) - if(MATSDK_BUNDLE_SQLITE) - target_link_libraries(mat PRIVATE sqlite3_bundled ZLIB::ZLIB ${LIBS}) - else() - # find_package(SQLite3) needs CMake >= 3.14, guaranteed by the project floor; - # SQLite3::SQLite3 is the canonical imported target. CMake < 4.3 only - # provides the deprecated SQLite::SQLite3 spelling. - find_package(SQLite3 REQUIRED) - if(NOT TARGET SQLite3::SQLite3) - add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) - endif() - target_link_libraries(mat PRIVATE SQLite3::SQLite3 ZLIB::ZLIB ${LIBS}) - endif() + target_compile_options(zlib_bundled PRIVATE + -w -ffunction-sections + $<$>:-fdata-sections>) endif() endif() +if(MATSDK_BUNDLE_ZLIB AND NOT TARGET ZLIB::ZLIB) + add_library(ZLIB::ZLIB ALIAS zlib_bundled) +endif() + +if(NOT MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "NONE" + AND NOT TARGET SQLite::SQLite3) + message(FATAL_ERROR + "SQLite::SQLite3 was not resolved for provider ${MATSDK_SQLITE_PROVIDER_RESOLVED}.") +endif() +if(NOT TARGET ZLIB::ZLIB) + message(FATAL_ERROR + "ZLIB::ZLIB was not resolved for provider ${MATSDK_ZLIB_PROVIDER_RESOLVED}.") +endif() + +if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "NONE") + # Room provides Android offline storage; no SQLite dependency is needed. +elseif(MATSDK_BUNDLE_SQLITE) + target_link_libraries(mat PRIVATE sqlite3_bundled) +else() + matsdk_add_interface_dependency( + matsdk_sqlite_dependency SQLite::SQLite3) + target_link_libraries(mat PRIVATE + "$" + "$") +endif() +if(MATSDK_BUNDLE_ZLIB) + target_link_libraries(mat PRIVATE zlib_bundled) +else() + matsdk_add_interface_dependency( + matsdk_zlib_dependency ZLIB::ZLIB) + target_link_libraries(mat PRIVATE + "$" + "$") +endif() +if(MATSDK_CURL_LINK_TARGET) + matsdk_add_interface_dependency( + matsdk_curl_dependency ${MATSDK_CURL_LINK_TARGET}) + target_link_libraries(mat PRIVATE + "$" + "$") +endif() +if(MATSDK_USES_NLOHMANN_TARGET) + matsdk_add_interface_dependency( + matsdk_nlohmann_dependency nlohmann_json::nlohmann_json) + target_link_libraries(mat PRIVATE + "$") +endif() + +# TODO: allow adding "${Tcmalloc_LIBRARIES}" to target_link_libraries for memory leak debugging +# (USE_TCMALLOC / FindTcmalloc.cmake are configured for Debug builds in the root CMakeLists.txt, +# but the library is not yet linked here). # Platform-specific link dependencies if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") - target_link_libraries(mat PUBLIC "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}") - if(THREADS_HAVE_PTHREAD_ARG) - target_compile_options(mat PUBLIC "-pthread") + target_link_libraries(mat PUBLIC Threads::Threads "${CMAKE_DL_LIBS}") + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7l") + target_link_libraries(mat PUBLIC atomic) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Android") target_link_libraries(mat PUBLIC log) @@ -619,7 +675,7 @@ elseif(APPLE) "-framework Network" "-framework SystemConfiguration" ) - if(BUILD_IOS OR CMAKE_SYSTEM_NAME STREQUAL "iOS") + if(MATSDK_PLATFORM_IOS) target_link_libraries(mat PUBLIC "-framework UIKit") else() target_link_libraries(mat PUBLIC "-framework IOKit") @@ -629,75 +685,86 @@ endif() ################################################################################################ # Installation ################################################################################################ -# The CMake package config / export workflow is used by vcpkg and any CMake-based -# consumer that does find_package(MSTelemetry). Legacy (non-vcpkg) builds install -# via install.sh or MSBuild output directories and don't need this. -if(MATSDK_USE_VCPKG_DEPS) - # A static libmat propagates its PRIVATE static dependencies through its link - # interface (as $), so the bundled SQLite must be part of the same - # export set and installed alongside mat for downstream find_package() consumers - # to link. A shared libmat absorbs sqlite3_bundled into the .so/.dylib/.dll and - # does not propagate the PRIVATE dep, so exporting the archive there is - # unnecessary (and risks a consumer linking a second SQLite copy) -- only export - # it for a static mat. - set(MATSDK_INSTALL_TARGETS mat) +# A static libmat propagates its private static dependencies at the final link, +# so install bundled archives beside it in both source and vcpkg workflows. +set(MATSDK_EXPORT_TARGETS mat) +set(MATSDK_AUX_INSTALL_TARGETS) +get_target_property(_mat_type mat TYPE) +if(_mat_type STREQUAL "STATIC_LIBRARY") if(MATSDK_BUNDLE_SQLITE AND TARGET sqlite3_bundled) - get_target_property(_mat_type mat TYPE) - if(_mat_type STREQUAL "STATIC_LIBRARY") - list(APPEND MATSDK_INSTALL_TARGETS sqlite3_bundled) - endif() + list(APPEND MATSDK_EXPORT_TARGETS sqlite3_bundled) + endif() + if(MATSDK_BUNDLE_ZLIB AND TARGET zlib_bundled) + list(APPEND MATSDK_EXPORT_TARGETS zlib_bundled) endif() - install(TARGETS ${MATSDK_INSTALL_TARGETS} - EXPORT MSTelemetryTargets + foreach(_matsdk_fetched_target IN LISTS MATSDK_FETCHED_CURL_TARGETS) + if(TARGET ${_matsdk_fetched_target}) + list(APPEND MATSDK_AUX_INSTALL_TARGETS ${_matsdk_fetched_target}) + endif() + endforeach() +endif() + +set(MATSDK_CONFIG_STATIC_PACKAGE FALSE) +if(_mat_type STREQUAL "STATIC_LIBRARY") + set(MATSDK_CONFIG_STATIC_PACKAGE TRUE) +endif() + +install(TARGETS ${MATSDK_EXPORT_TARGETS} + EXPORT MSTelemetryTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) +if(MATSDK_AUX_INSTALL_TARGETS) + install(TARGETS ${MATSDK_AUX_INSTALL_TARGETS} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) +endif() - message(STATUS "Library will be installed to ${CMAKE_INSTALL_LIBDIR}") +message(STATUS "Library will be installed to ${CMAKE_INSTALL_LIBDIR}") - # Generate and install CMake package config files - install(EXPORT MSTelemetryTargets - FILE MSTelemetryTargets.cmake - NAMESPACE MSTelemetry:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MSTelemetry - ) +# Generate and install CMake package config files for every CMake build. +install(EXPORT MSTelemetryTargets + FILE MSTelemetryTargets.cmake + NAMESPACE MSTelemetry:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MSTelemetry +) - configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/MSTelemetryConfig.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfig.cmake" - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MSTelemetry - ) +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/MSTelemetryConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MSTelemetry +) - if(NOT DEFINED MATSDK_BUILD_VERSION OR MATSDK_BUILD_VERSION STREQUAL "") - message(FATAL_ERROR "MATSDK_BUILD_VERSION is not set. Cannot generate package version file.") - endif() +if(NOT DEFINED MATSDK_BUILD_VERSION OR MATSDK_BUILD_VERSION STREQUAL "") + message(FATAL_ERROR "MATSDK_BUILD_VERSION is not set. Cannot generate package version file.") +endif() - write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfigVersion.cmake" - VERSION ${MATSDK_BUILD_VERSION} - COMPATIBILITY AnyNewerVersion - ) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfigVersion.cmake" + VERSION ${MATSDK_BUILD_VERSION} + COMPATIBILITY AnyNewerVersion +) - install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfigVersion.cmake" - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MSTelemetry - ) +set(_matsdk_package_config_files + "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfigVersion.cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/MatsdkDependencyTargets.cmake") +if(APPLE) + list(APPEND _matsdk_package_config_files + "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/MatsdkAppleSystemDeps.cmake") +endif() +install(FILES ${_matsdk_package_config_files} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MSTelemetry +) - if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND MATSDK_ANDROID_USES_JAVA_HTTP) - install(FILES - "${CMAKE_CURRENT_SOURCE_DIR}/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java" - "${CMAKE_CURRENT_SOURCE_DIR}/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java" - DESTINATION "${CMAKE_INSTALL_DATADIR}/cpp-client-telemetry/android/java/com/microsoft/applications/events" - ) - endif() -else() - # Legacy install: just put the library and headers in standard locations - install(TARGETS mat - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND MATSDK_ANDROID_USES_JAVA_HTTP) + install(FILES + "${CMAKE_CURRENT_SOURCE_DIR}/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClient.java" + "${CMAKE_CURRENT_SOURCE_DIR}/android_build/maesdk/src/main/java/com/microsoft/applications/events/HttpClientRequest.java" + DESTINATION + "${CMAKE_INSTALL_DATADIR}/cpp-client-telemetry/android/java/com/microsoft/applications/events" ) - message(STATUS "Library will be installed to ${CMAKE_INSTALL_LIBDIR}") endif() diff --git a/lib/android_build/app/build.gradle b/lib/android_build/app/build.gradle index 28417a2c5..544239e58 100644 --- a/lib/android_build/app/build.gradle +++ b/lib/android_build/app/build.gradle @@ -11,7 +11,9 @@ android { externalNativeBuild { cmake { // Passes optional arguments to CMake. - arguments "-DANDROID_STL=c++_shared", "-DUSE_ROOM=1" + arguments "-DANDROID_STL=c++_shared", + "-DBUILD_SHARED_LIBS=OFF", + "-DMATSDK_ANDROID_USE_ROOM=ON" } } } diff --git a/lib/android_build/app/src/main/cpp/CMakeLists.txt b/lib/android_build/app/src/main/cpp/CMakeLists.txt index c8c397ea8..feb165ec7 100644 --- a/lib/android_build/app/src/main/cpp/CMakeLists.txt +++ b/lib/android_build/app/src/main/cpp/CMakeLists.txt @@ -4,6 +4,7 @@ # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.15...3.31) +project(MaesdkAndroidTests LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -13,21 +14,6 @@ string(REPLACE "/lib/android_build/app/src/main/cpp" "" SDK_ROOT ${CMAKE_SOURCE_ set (gmock_dir ${SDK_ROOT}/third_party/googletest/googlemock) set (gtest_dir ${SDK_ROOT}/third_party/googletest/googletest) -include_directories(AFTER - ${SDK_ROOT}/lib - ${SDK_ROOT}/lib/include/public - ${SDK_ROOT}/lib/include - ${SDK_ROOT}/lib/include/mat - ${SDK_ROOT}/sqlite - ${SDK_ROOT}lib/pal - ${SDK_ROOT} - "${gmock_dir}/include" - "${gmock_dir}" - "${gtest_dir}/include" - # This directory is needed to build directly from Google - # Test sources. - "${gtest_dir}") - set(TESTS_COMMON_SRCS ${SDK_ROOT}/tests/common/Common.cpp ${SDK_ROOT}/tests/common/Mocks.cpp @@ -75,13 +61,8 @@ set(TESTS_SRCS ${SDK_ROOT}/tests/unittests/UtilsTests.cpp ) -find_package( ZLIB REQUIRED ) -include_directories( - ${ZLIB_INCLUDE_DIRS} - ${SDK_ROOT}/tests -) - -find_library(zlib-path z) +# The test app builds SQLite storage alongside the Room-backed SDK. +set(MATSDK_SQLITE_PROVIDER VENDORED CACHE STRING "" FORCE) #Add maesdk as a dependency add_subdirectory(../../../../maesdk/src/main/cpp maesdk) @@ -89,10 +70,9 @@ add_subdirectory(../../../../maesdk/src/main/cpp maesdk) # include the other flavor of database: if maesdk builds with Room include sqlite # if maesdk builds with native sqlite, include Room -if (USE_ROOM) +if (MATSDK_ANDROID_USE_ROOM) set(OTHER_OFFLINE_SRCS ${SDK_ROOT}/lib/offline/OfflineStorage_SQLite.cpp - ${SDK_ROOT}/sqlite/sqlite3.c ) else() set(OTHER_OFFLINE_SRCS @@ -128,6 +108,20 @@ add_library( # Sets the name of the library. ${TESTS_SRCS} ) +target_include_directories(native-lib PRIVATE + ${SDK_ROOT} + ${SDK_ROOT}/lib + ${SDK_ROOT}/lib/include/public + ${SDK_ROOT}/lib/include + ${SDK_ROOT}/lib/include/mat + ${SDK_ROOT}/lib/pal + ${SDK_ROOT}/sqlite + ${SDK_ROOT}/tests + "${gmock_dir}/include" + "${gmock_dir}" + "${gtest_dir}/include" + "${gtest_dir}") + # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this @@ -135,10 +129,12 @@ add_library( # Sets the name of the library. target_link_libraries( # Specifies the target library. native-lib - maesdk + mat + matsdk_internal_config + SQLite::SQLite3 + ZLIB::ZLIB # Links the target library to the log library # included in the NDK. ${log-lib} - ${zlib-path} ) diff --git a/lib/android_build/maesdk/build.gradle b/lib/android_build/maesdk/build.gradle index 714dac0df..5569c0373 100644 --- a/lib/android_build/maesdk/build.gradle +++ b/lib/android_build/maesdk/build.gradle @@ -18,8 +18,9 @@ android { String cxxFlag = project.findProperty("CXXFLAGS") ?: System.getenv("CXXFLAGS") ?: "" ArrayList args = new ArrayList() args.add("-DANDROID_STL=c++_shared") - args.add("-DBUILD_SHARED_LIBS=1") - args.add("-DUSE_ROOM=1") + args.add("-DBUILD_SHARED_LIBS=ON") + args.add("-DMATSDK_ANDROID_USE_ROOM=ON") + args.add("-DMATSDK_BUILD_JNI_WRAPPER=ON") args.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON") String linkerFlag = project.findProperty("CMAKE_SHARED_LINKER_FLAGS") ?: "" linkerFlag = "-DCMAKE_SHARED_LINKER_FLAGS=" + linkerFlag diff --git a/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt b/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt index fc2dad035..6712289dd 100644 --- a/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt +++ b/lib/android_build/maesdk/src/main/cpp/CMakeLists.txt @@ -1,203 +1,17 @@ -# For more information about using CMake with Android Studio, read the -# documentation: https://d.android.com/studio/projects/add-native-code.html - -# Sets the minimum version of CMake required to build the native library. - cmake_minimum_required(VERSION 3.15...3.31) -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Enable Azure Monitor / Application Insights end-point support -option(BUILD_AZMON "Build for Azure Monitor" YES) -option(BUILD_PRIVACYGUARD "Build Privacy Guard" YES) -option(BUILD_SIGNALS "Build Signals" YES) -option(BUILD_SANITIZER "Build Sanitizer" YES) - -if(ENABLE_CAPI_HTTP_CLIENT) - add_definitions(-DENABLE_CAPI_HTTP_CLIENT) -endif() - -string(REPLACE "/lib/android_build/maesdk/src/main/cpp" "" SDK_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) - -if (USE_CURL) - add_definitions(-DHAVE_MAT_CURL_HTTP_CLIENT) - find_package(CURL REQUIRED) -endif() - -set(TARGETNAME maesdk) - -set(SRCS - ${SDK_ROOT}/lib/api/AllowedLevelsCollection.cpp - ${SDK_ROOT}/lib/api/AuthTokensController.cpp - ${SDK_ROOT}/lib/api/ContextFieldsProvider.cpp - ${SDK_ROOT}/lib/api/CorrelationVector.cpp - ${SDK_ROOT}/lib/api/DataViewerCollection.cpp - ${SDK_ROOT}/lib/api/ILogConfiguration.cpp - ${SDK_ROOT}/lib/api/LogConfiguration.cpp - ${SDK_ROOT}/lib/api/LogManager.cpp - ${SDK_ROOT}/lib/api/LogManagerFactory.cpp - ${SDK_ROOT}/lib/api/LogManagerImpl.cpp - ${SDK_ROOT}/lib/api/LogManagerProvider.cpp - ${SDK_ROOT}/lib/api/LogSessionData.cpp - ${SDK_ROOT}/lib/api/Logger.cpp - ${SDK_ROOT}/lib/api/capi.cpp - ${SDK_ROOT}/lib/backoff/IBackoff.cpp - ${SDK_ROOT}/lib/bond/BondSerializer.cpp - ${SDK_ROOT}/lib/callbacks/DebugSource.cpp - ${SDK_ROOT}/lib/compression/HttpDeflateCompression.cpp - ${SDK_ROOT}/lib/decoder/PayloadDecoder.cpp - ${SDK_ROOT}/lib/decorators/BaseDecorator.cpp - ${SDK_ROOT}/lib/filter/EventFilterCollection.cpp - ${SDK_ROOT}/lib/http/HttpClientFactory.cpp - ${SDK_ROOT}/lib/http/HttpClientManager.cpp - ${SDK_ROOT}/lib/http/HttpRequestEncoder.cpp - ${SDK_ROOT}/lib/http/HttpResponseDecoder.cpp - ${SDK_ROOT}/lib/jni/JniConvertors.cpp - ${SDK_ROOT}/lib/jni/LogManager_jni.cpp - ${SDK_ROOT}/lib/jni/Logger_jni.cpp - ${SDK_ROOT}/lib/jni/SemanticContext_jni.cpp - ${SDK_ROOT}/lib/jni/Utils_jni.cpp - ${SDK_ROOT}/lib/offline/MemoryStorage.cpp - ${SDK_ROOT}/lib/offline/LogSessionDataProvider.cpp - ${SDK_ROOT}/lib/offline/OfflineStorageFactory.cpp - ${SDK_ROOT}/lib/offline/OfflineStorageHandler.cpp - ${SDK_ROOT}/lib/offline/StorageObserver.cpp - ${SDK_ROOT}/lib/packager/BondSplicer.cpp - ${SDK_ROOT}/lib/packager/Packager.cpp - ${SDK_ROOT}/lib/pal/InformationProviderImpl.cpp - ${SDK_ROOT}/lib/pal/PAL.cpp - ${SDK_ROOT}/lib/pal/TaskDispatcher_CAPI.cpp - ${SDK_ROOT}/lib/pal/WorkerThread.cpp - ${SDK_ROOT}/lib/pal/posix/DeviceInformationImpl_Android.cpp - ${SDK_ROOT}/lib/pal/posix/NetworkInformationImpl_Android.cpp - ${SDK_ROOT}/lib/pal/posix/SystemInformationImpl_Android.cpp - ${SDK_ROOT}/lib/pal/posix/sysinfo_sources.cpp - ${SDK_ROOT}/lib/stats/MetaStats.cpp - ${SDK_ROOT}/lib/stats/Statistics.cpp - ${SDK_ROOT}/lib/system/EventProperties.cpp - ${SDK_ROOT}/lib/system/EventProperty.cpp - ${SDK_ROOT}/lib/system/TelemetrySystem.cpp - ${SDK_ROOT}/lib/tpm/DeviceStateHandler.cpp - ${SDK_ROOT}/lib/tpm/TransmissionPolicyManager.cpp - ${SDK_ROOT}/lib/tpm/TransmitProfiles.cpp - ${SDK_ROOT}/lib/utils/FileUtils.cpp - ${SDK_ROOT}/lib/utils/StringUtils.cpp - ${SDK_ROOT}/lib/utils/ZlibUtils.cpp - ${SDK_ROOT}/lib/utils/Utils.cpp -) - -# Support for Azure Monitor / Application Insights -if (BUILD_AZMON) - include(${SDK_ROOT}/lib/modules/azmon/CMakeLists.txt OPTIONAL) -endif() - -if(EXISTS ${SDK_ROOT}/lib/modules/dataviewer/) - list(APPEND SRCS - ${SDK_ROOT}/lib/jni/LogManagerDDVController_jni.cpp - ${SDK_ROOT}/lib/modules/dataviewer/DefaultDataViewer.cpp - ${SDK_ROOT}/lib/modules/dataviewer/OnDisableNotificationCollection.cpp - ) -endif() - -if(EXISTS ${SDK_ROOT}/lib/modules/privacyguard/ AND BUILD_PRIVACYGUARD) - list(APPEND SRCS - ${SDK_ROOT}/lib/jni/PrivacyGuard_jni.cpp - ${SDK_ROOT}/lib/modules/privacyguard/SummaryStatistics.cpp - ${SDK_ROOT}/lib/modules/privacyguard/PrivacyGuard.cpp - ${SDK_ROOT}/lib/modules/privacyguard/RegisteredFileTypes.cpp - ) -endif() - -if (EXISTS ${SDK_ROOT}/lib/modules/signals/ AND BUILD_SIGNALS) - list(APPEND SRCS - ${SDK_ROOT}/lib/jni/Signals_jni.cpp - ${SDK_ROOT}/lib/modules/signals/Signals.cpp - ${SDK_ROOT}/lib/modules/signals/SignalsEncoder.cpp - ) -endif() - -if (EXISTS ${SDK_ROOT}/lib/modules/sanitizer/ AND BUILD_SANITIZER) - list(APPEND SRCS - ${SDK_ROOT}/lib/jni/Sanitizer_jni.cpp - ${SDK_ROOT}/lib/modules/sanitizer/detectors/EmailAddressDetector.cpp - ${SDK_ROOT}/lib/modules/sanitizer/detectors/JwtDetector.cpp - ${SDK_ROOT}/lib/modules/sanitizer/detectors/SPOPassword.cpp - ${SDK_ROOT}/lib/modules/sanitizer/detectors/UrlDetector.cpp - ${SDK_ROOT}/lib/modules/sanitizer/Sanitizer.cpp - ${SDK_ROOT}/lib/modules/sanitizer/SanitizerProvider.cpp - ${SDK_ROOT}/lib/modules/sanitizer/SanitizerStringUtils.cpp - ${SDK_ROOT}/lib/modules/sanitizer/SanitizerTargets.cpp - ${SDK_ROOT}/lib/modules/sanitizer/SanitizerTrie.cpp - ${SDK_ROOT}/lib/modules/sanitizer/SanitizerTrieNode.cpp - ) -endif() - -if (USE_ROOM) - add_definitions("-DUSE_ROOM") - list(APPEND SRCS ${SDK_ROOT}/lib/offline/OfflineStorage_Room.cpp) -else() - list(APPEND SRCS - ${SDK_ROOT}/lib/offline/OfflineStorage_SQLite.cpp - ${SDK_ROOT}/sqlite/sqlite3.c - ) -endif() - -if (USE_CURL) - list(APPEND SRCS ${SDK_ROOT}/lib/http/HttpClient_Curl.cpp) -else() - list(APPEND SRCS ${SDK_ROOT}/lib/http/HttpClient_Android.cpp) -endif() - -if (ENABLE_CAPI_HTTP_CLIENT) - list(APPEND SRCS ${SDK_ROOT}/lib/http/HttpClient_CAPI.cpp) -endif() - -add_library(${TARGETNAME} ${SRCS}) - -target_include_directories(${TARGETNAME} PUBLIC - ${SDK_ROOT}/lib - ${SDK_ROOT}/lib/include/public - ${SDK_ROOT}/lib/include - ${SDK_ROOT}/lib/include/mat - ${SDK_ROOT}/sqlite - ${SDK_ROOT}lib/pal - ${SDK_ROOT} - ${SDK_ROOT}/lib/modules/sanitizer/detectors - ${SDK_ROOT}/lib/modules/sanitizer - ${CURL_INCLUDE_DIRS}) - - -# Creates and names a library, sets it as either STATIC -# or SHARED, and provides the relative paths to its source code. -# You can define multiple libraries, and CMake builds them for you. -# Gradle automatically packages shared libraries with your APK. - -# Searches for a specified prebuilt library and stores the path as a -# variable. Because CMake includes system libraries in the search path by -# default, you only need to specify the name of the public NDK library -# you want to add. CMake verifies that the library exists before -# completing its build. - -find_library( # Sets the name of the path variable. - log-lib +project(MaesdkAndroid LANGUAGES C CXX) - # Specifies the name of the NDK library that - # you want CMake to locate. - log ) +get_filename_component(SDK_ROOT + "${CMAKE_CURRENT_LIST_DIR}/../../../../../.." ABSOLUTE) -find_library( - zlib - z -) +set(BUILD_SHARED_LIBS ON CACHE BOOL "") +set(MATSDK_BUILD_JNI_WRAPPER ON CACHE BOOL "" FORCE) +set(MATSDK_ANDROID_USE_ROOM ON CACHE BOOL "" FORCE) +set(MATSDK_BUILD_PACKAGE OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_FUNC_TESTS OFF CACHE BOOL "" FORCE) -# Specifies libraries CMake should link to your target library. You -# can link multiple libraries, such as libraries you define in this -# build script, prebuilt third-party libraries, or system libraries. +add_subdirectory("${SDK_ROOT}" "${CMAKE_CURRENT_BINARY_DIR}/matsdk") -target_link_libraries(maesdk PUBLIC - # Links the target library to the log library - # included in the NDK. - ${log-lib} - ${zlib} - ${CURL_LIBRARIES} - ) +# Preserve the Java/AAR runtime name: System.loadLibrary("maesdk"). +set_target_properties(mat PROPERTIES OUTPUT_NAME maesdk) diff --git a/lib/http/HttpClient_Apple.mm b/lib/http/HttpClient_Apple.mm index b7d6646a4..1a047f5d6 100644 --- a/lib/http/HttpClient_Apple.mm +++ b/lib/http/HttpClient_Apple.mm @@ -207,7 +207,7 @@ void HandleResponse(NSData* data, NSURLResponse* response, NSError* error) NSHTTPURLResponse *httpResp = static_cast(response); auto simpleResponse = new SimpleHttpResponse { NextRespId() }; - simpleResponse->m_statusCode = httpResp.statusCode; + simpleResponse->m_statusCode = static_cast(httpResp.statusCode); NSDictionary *responseHeaders = [httpResp allHeaderFields]; for (id key in responseHeaders) diff --git a/lib/http/HttpClient_Curl.hpp b/lib/http/HttpClient_Curl.hpp index 533c522e3..7d599dec9 100644 --- a/lib/http/HttpClient_Curl.hpp +++ b/lib/http/HttpClient_Curl.hpp @@ -71,6 +71,13 @@ class HttpClient_Curl : public IHttpClient { class CurlHttpOperation { public: + static long GetPreferredHttpVersion() + { + const curl_version_info_data* versionInfo = curl_version_info(CURLVERSION_NOW); + return (versionInfo != nullptr && (versionInfo->features & CURL_VERSION_HTTP2) != 0) + ? CURL_HTTP_VERSION_2_0 + : CURL_HTTP_VERSION_1_1; + } void DispatchEvent(HttpStateEvent type) { @@ -134,21 +141,35 @@ class CurlHttpOperation { #if 0 // Be verbose - curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if (!SetOption(CURLOPT_VERBOSE, 1L)) #else - curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); + if (!SetOption(CURLOPT_VERBOSE, 0L)) #endif + { + DispatchEvent(OnCreateFailed); + return; + } // Specify target URL - curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str()); + if (!SetOption(CURLOPT_URL, m_url.c_str()) + || !SetOption(CURLOPT_SSL_VERIFYPEER, sslVerify ? 1L : 0L) + || !SetOption(CURLOPT_SSL_VERIFYHOST, sslVerify ? 2L : 0L)) + { + DispatchEvent(OnCreateFailed); + return; + } + + if (!m_sslCaInfo.empty() && !SetOption(CURLOPT_CAINFO, m_sslCaInfo.c_str())) + { + DispatchEvent(OnCreateFailed); + return; + } - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, sslVerify ? 1L : 0L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, sslVerify ? 2L : 0L); - if (!m_sslCaInfo.empty()) { - curl_easy_setopt(curl, CURLOPT_CAINFO, m_sslCaInfo.c_str()); + if (!SetOption(CURLOPT_HTTP_VERSION, GetPreferredHttpVersion())) + { + DispatchEvent(OnCreateFailed); + return; } - // HTTP/2 please, fallback to HTTP/1.1 if not supported - curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); // Headers are copied into m_headersChunk during construction and the // curl_slist is kept alive until destruction, so the original map does @@ -156,15 +177,24 @@ class CurlHttpOperation { for (const auto& kv : requestHeaders) { std::string header = kv.first + ": " + kv.second; - m_headersChunk = curl_slist_append(m_headersChunk, header.c_str()); + curl_slist* appended = curl_slist_append(m_headersChunk, header.c_str()); + if (appended == nullptr) + { + res = CURLE_OUT_OF_MEMORY; + DispatchEvent(OnCreateFailed); + return; + } + m_headersChunk = appended; } - if(m_headersChunk != nullptr) + if(m_headersChunk != nullptr && !SetOption(CURLOPT_HTTPHEADER, m_headersChunk)) { - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, m_headersChunk); + DispatchEvent(OnCreateFailed); + return; } TRACE("method=%s, url=%s\n", this->m_method.c_str(), this->m_url.c_str()); + m_isConfigured = true; DispatchEvent(OnCreated); } @@ -181,7 +211,10 @@ class CurlHttpOperation { } DispatchEvent(OnDestroy); res = CURLE_OK; - curl_easy_cleanup(curl); + if (curl != nullptr) + { + curl_easy_cleanup(curl); + } curl_slist_free_all(m_headersChunk); ReleaseResponse(); } @@ -197,10 +230,14 @@ class CurlHttpOperation { // Request buffer const void *request = requestBody.empty() ? nullptr : requestBody.data(); const size_t reqSize = requestBody.size(); + int socketWaitResult = 0; - if(!curl) + if(!curl || !m_isConfigured) { - res = CURLE_FAILED_INIT; + if (res == CURLE_OK) + { + res = CURLE_FAILED_INIT; + } DispatchEvent(OnSendFailed); goto cleanup; } @@ -209,37 +246,49 @@ class CurlHttpOperation { // curl_easy_setopt(curl, CURLOPT_LOCALPORT, dcf_port); // Perform initial connect, handling the timeout if needed - curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); - DispatchEvent(OnConnecting); - res = curl_easy_perform(curl); - if(CURLE_OK != res) + if (!SetOption(CURLOPT_CONNECT_ONLY, 1L)) { - DispatchEvent(OnConnectFailed); // couldn't connect - stage 1 - TRACE("Error #1: %s\n", curl_easy_strerror(res)); + DispatchEvent(OnConnectFailed); goto cleanup; } + DispatchEvent(OnConnecting); + { + const CURLcode curlResult = curl_easy_perform(curl); + res = static_cast(curlResult); + if(CURLE_OK != curlResult) + { + DispatchEvent(OnConnectFailed); // couldn't connect - stage 1 + TRACE("Error #1: %s\n", curl_easy_strerror(curlResult)); + goto cleanup; + } + } - /* Extract the socket from the curl handle - we'll need it for waiting. - * Note that this API takes a pointer to a 'long' while we use - * curl_socket_t for sockets otherwise. - */ - + { + CURLcode infoResult; #if LIBCURL_VERSION_NUM >= 0x072D00 // Version 7.45.00 - res = curl_easy_getinfo(curl, CURLINFO_ACTIVESOCKET, &sockextr); + infoResult = curl_easy_getinfo(curl, CURLINFO_ACTIVESOCKET, &sockextr); #else - res = curl_easy_getinfo(curl, CURLINFO_LASTSOCKET, &sockextr); + long lastSocket = -1; + infoResult = curl_easy_getinfo(curl, CURLINFO_LASTSOCKET, &lastSocket); + if (infoResult == CURLE_OK) + { + sockextr = static_cast(lastSocket); + } #endif - - if(CURLE_OK != res) - { - DispatchEvent(OnConnectFailed); // couldn't connect - stage 2 - TRACE("Error #2: %s\n", curl_easy_strerror(res)); - goto cleanup; + if(CURLE_OK != infoResult || sockextr == CURL_SOCKET_BAD) + { + res = static_cast( + infoResult != CURLE_OK ? infoResult : CURLE_COULDNT_CONNECT); + DispatchEvent(OnConnectFailed); // couldn't connect - stage 2 + TRACE("Error #2: %s\n", curl_easy_strerror(static_cast(res))); + goto cleanup; + } } /* wait for the socket to become ready for sending */ sockfd = sockextr; - if( !WaitOnSocket(sockfd, 0, HTTP_CONN_TIMEOUT * 1000L) || isAborted) + socketWaitResult = WaitOnSocket(sockfd, 0, HTTP_CONN_TIMEOUT * 1000L); + if(socketWaitResult <= 0 || isAborted) { TRACE("Error #3: timeout, aborted=%u\n", isAborted.load() ); res = CURLE_OPERATION_TIMEDOUT; @@ -248,27 +297,46 @@ class CurlHttpOperation { } // once connection is there - switch back to easy perform for HTTP post - curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 0); + if (!SetOption(CURLOPT_CONNECT_ONLY, 0L)) + { + DispatchEvent(OnSendFailed); + goto cleanup; + } // send all data to our callback function if (rawResponse) { - curl_easy_setopt(curl, CURLOPT_HEADER, true); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (void *)&WriteMemoryCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response); - } else { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (void *)&WriteVectorCallback); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&respHeaders); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&respBody); + if (!SetOption(CURLOPT_HEADER, 1L) + || !SetOption(CURLOPT_WRITEFUNCTION, + static_cast(&WriteMemoryCallback)) + || !SetOption(CURLOPT_WRITEDATA, static_cast(&response))) + { + DispatchEvent(OnSendFailed); + goto cleanup; + } + } + else if (!SetOption(CURLOPT_WRITEFUNCTION, + static_cast(&WriteVectorCallback)) + || !SetOption(CURLOPT_HEADERFUNCTION, + static_cast(&WriteVectorCallback)) + || !SetOption(CURLOPT_HEADERDATA, static_cast(&respHeaders)) + || !SetOption(CURLOPT_WRITEDATA, static_cast(&respBody))) + { + DispatchEvent(OnSendFailed); + goto cleanup; } // TODO: only two methods supported for now - POST and GET if (m_method.compare("POST") == 0) { // POST - curl_easy_setopt(curl, CURLOPT_POST, true); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, static_cast(request)); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, reqSize); + if (!SetOption(CURLOPT_POST, 1L) + || !SetOption(CURLOPT_POSTFIELDS, static_cast(request)) + || !SetOption(CURLOPT_POSTFIELDSIZE_LARGE, static_cast(reqSize))) + { + DispatchEvent(OnSendFailed); + goto cleanup; + } } else if (m_method.compare("GET") == 0) { @@ -280,16 +348,23 @@ class CurlHttpOperation { goto cleanup; } - curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 30L); - curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 4096); - DispatchEvent(OnSending); - res = curl_easy_perform(curl); - if(CURLE_OK != res) + if (!SetOption(CURLOPT_LOW_SPEED_TIME, 30L) + || !SetOption(CURLOPT_LOW_SPEED_LIMIT, 4096L)) { DispatchEvent(OnSendFailed); - TRACE("Error: %s\n", curl_easy_strerror(res)); goto cleanup; } + DispatchEvent(OnSending); + { + const CURLcode curlResult = curl_easy_perform(curl); + res = static_cast(curlResult); + if(CURLE_OK != curlResult) + { + DispatchEvent(OnSendFailed); + TRACE("Error: %s\n", curl_easy_strerror(curlResult)); + goto cleanup; + } + } /* Code snippet to parse raw HTTP response. This might come in handy * if we ever consider to handle the raw upload instead of curl_easy_perform @@ -303,7 +378,17 @@ class CurlHttpOperation { */ /* libcurl is nice enough to parse the response code itself: */ - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res); + { + long responseCode = 0; + const CURLcode infoResult = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode); + if (infoResult != CURLE_OK) + { + res = static_cast(infoResult); + DispatchEvent(OnSendFailed); + goto cleanup; + } + res = responseCode; + } // We got some response from server. Dump the contents. TRACE("HTTP response code %d\n", res); DispatchEvent(OnResponse); @@ -436,7 +521,7 @@ class CurlHttpOperation { const size_t httpConnTimeout; // Timeout for connect. Default: 5s CURL *curl; // Local curl instance - CURLcode res = CURLE_OK; // Curl result OR HTTP status code if successful + long res = CURLE_OK; // Curl result OR HTTP status code if successful IHttpResponseCallback* m_callback = nullptr; @@ -444,6 +529,7 @@ class CurlHttpOperation { std::string m_method; std::string m_url; std::string m_sslCaInfo; + bool m_isConfigured = false; // The SDK upload path keeps the owning IHttpRequest alive through the // callback context until Send() completes; copying this body would duplicate // every upload payload. Unlike CURLOPT_CAINFO, the body pointer is set and @@ -458,7 +544,7 @@ class CurlHttpOperation { // Socket parameters curl_socket_t sockfd = 0; - long sockextr = 0; + curl_socket_t sockextr = CURL_SOCKET_BAD; curl_off_t nread = 0; size_t sendlen = 0; // # bytes sent by client @@ -466,6 +552,20 @@ class CurlHttpOperation { std::future result; + template + bool SetOption(CURLoption option, TValue value) + { + const CURLcode optionResult = curl_easy_setopt(curl, option, value); + if (optionResult != CURLE_OK) + { + res = static_cast(optionResult); + TRACE("curl_easy_setopt(%d) failed: %s\n", + static_cast(option), curl_easy_strerror(optionResult)); + return false; + } + return true; + } + /** * Helper routine to wait for data on socket * @@ -507,7 +607,7 @@ class CurlHttpOperation { * @param userp * @return */ - static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) + static size_t WriteMemoryCallback(char *contents, size_t size, size_t nmemb, void *userp) { // Guard the size * nmemb product against size_t overflow before using it. if (nmemb != 0 && size > static_cast(-1) / nmemb) { @@ -551,14 +651,15 @@ class CurlHttpOperation { * @param data * @return */ - static size_t WriteVectorCallback(void *ptr, size_t size, size_t nmemb, std::vector* data) + static size_t WriteVectorCallback(char *ptr, size_t size, size_t nmemb, void* userp) { // Guard the size * nmemb product against size_t overflow before using it. if (nmemb != 0 && size > static_cast(-1) / nmemb) { return 0; } + size_t realsize = size * nmemb; + auto* data = static_cast*>(userp); if (data != nullptr) { - size_t realsize = size * nmemb; // SECURITY: bound the buffered response (see kMaxResponseBytes). Compare // overflow-safely (data->size() is always <= kMaxResponseBytes here). // Returning a short count aborts the transfer with CURLE_WRITE_ERROR. @@ -566,11 +667,11 @@ class CurlHttpOperation { TRACE("Response exceeds max buffered size (%zu bytes); aborting transfer\n", kMaxResponseBytes); return 0; } - const auto* begin = static_cast(ptr); + const auto* begin = reinterpret_cast(ptr); const auto* end = begin + realsize; data->insert( data->end(), begin, end); } - return size * nmemb; + return realsize; } }; diff --git a/lib/include/CMakeLists.txt b/lib/include/CMakeLists.txt index c5a0eb9f6..029c3ded0 100644 --- a/lib/include/CMakeLists.txt +++ b/lib/include/CMakeLists.txt @@ -11,22 +11,24 @@ set(MATSDK_PUBLIC_HEADER_INSTALL_EXCLUDES PATTERN "*.template" EXCLUDE ) -if(MATSDK_USE_VCPKG_DEPS) - # GitHub source archives used by the public vcpkg port do not include the - # private lib/modules submodule, so do not install public headers whose - # exported factories/functions are implemented only by those modules. +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/filter") + list(APPEND MATSDK_PUBLIC_HEADER_INSTALL_EXCLUDES + PATTERN "CompliantByDefaultFilterApi.hpp" EXCLUDE) +endif() +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/exp") list(APPEND MATSDK_PUBLIC_HEADER_INSTALL_EXCLUDES - PATTERN "CompliantByDefaultFilterApi.hpp" EXCLUDE PATTERN "IAFDClient.hpp" EXCLUDE - PATTERN "ICdsFactory.hpp" EXCLUDE - PATTERN "IECSClient.hpp" EXCLUDE - ) + PATTERN "IECSClient.hpp" EXCLUDE) +endif() +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/cds") + list(APPEND MATSDK_PUBLIC_HEADER_INSTALL_EXCLUDES + PATTERN "ICdsFactory.hpp" EXCLUDE) endif() install( DIRECTORY public/ DESTINATION - include/mat + ${CMAKE_INSTALL_INCLUDEDIR}/mat ${MATSDK_PUBLIC_HEADER_INSTALL_EXCLUDES} ) diff --git a/lib/pal/posix/NetworkInformationImpl_Android.cpp b/lib/pal/posix/NetworkInformationImpl_Android.cpp index 04f1960f5..15e2d646d 100644 --- a/lib/pal/posix/NetworkInformationImpl_Android.cpp +++ b/lib/pal/posix/NetworkInformationImpl_Android.cpp @@ -46,7 +46,7 @@ namespace PAL_NS_BEGIN { m_cost(NetworkCost_Unknown), m_info_helper(), m_registeredCount(0), - m_isNetDetectEnabled(configuration[CFG_BOOL_ENABLE_NET_DETECT]){}; + m_isNetDetectEnabled(configuration[CFG_BOOL_ENABLE_NET_DETECT]){} NetworkInformationImpl::~NetworkInformationImpl() {}; diff --git a/lib/system/EventProperties.cpp b/lib/system/EventProperties.cpp index 2ade77741..71d5f4c5b 100644 --- a/lib/system/EventProperties.cpp +++ b/lib/system/EventProperties.cpp @@ -474,7 +474,7 @@ namespace MAT_NS_BEGIN { evt_prop* EventProperties::pack() { size_t size = m_storage->properties.size() + m_storage->propertiesPartB.size() + 1; - evt_prop * result = static_cast(calloc(sizeof(evt_prop), size)); + evt_prop * result = static_cast(calloc(size, sizeof(evt_prop))); if (result==nullptr) { LOG_ERROR("Unable to allocate memory to pack EventProperties"); @@ -620,4 +620,3 @@ namespace MAT_NS_BEGIN { } MAT_NS_END - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 785372186..216590ebd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,25 +1,24 @@ -include_directories(. ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/public ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/../lib/decoder ${CMAKE_CURRENT_SOURCE_DIR}/../sqlite ) - -set(MATSDK_GTEST_INCLUDE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googletest/include) -set(MATSDK_GMOCK_INCLUDE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googlemock/include) -if(NOT EXISTS "${MATSDK_GTEST_INCLUDE_DIR}/gtest/gtest.h") - message(FATAL_ERROR - "Tests require the third_party/googletest submodule at " - "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest.") -endif() - -add_library(matsdk_test_includes INTERFACE) -target_include_directories(matsdk_test_includes INTERFACE +add_library(matsdk_test_config INTERFACE) +target_link_libraries(matsdk_test_config INTERFACE matsdk_internal_config) +target_include_directories(matsdk_test_config INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../lib + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/public ${CMAKE_CURRENT_SOURCE_DIR}/../lib/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/../lib/decoder + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/pal + ${CMAKE_CURRENT_SOURCE_DIR}/../lib/utils ${CMAKE_CURRENT_SOURCE_DIR}/../sqlite - ${MATSDK_GTEST_INCLUDE_DIR} - ${MATSDK_GMOCK_INCLUDE_DIR}) + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googletest/include + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/googletest/googlemock/include) +if(NOT MATSDK_USES_NLOHMANN_TARGET) + target_include_directories(matsdk_test_config INTERFACE ${PROJECT_SOURCE_DIR}) +endif() + +if(NOT TARGET gtest OR NOT TARGET gmock) + message(FATAL_ERROR "gtest/gmock targets were not configured.") +endif() set(TESTS_COMMON_SRCS ../common/Common.cpp @@ -28,11 +27,12 @@ set(TESTS_COMMON_SRCS ../../lib/decoder/PayloadDecoder.cpp ) -if(BUILD_FUNC_TESTS) +if(MATSDK_BUILD_FUNC_TESTS) add_subdirectory(functests) endif() -if(BUILD_UNIT_TESTS) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/unittests) +if(MATSDK_BUILD_UNIT_TESTS) + target_include_directories(matsdk_test_config INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/unittests) add_subdirectory(unittests) endif() diff --git a/tests/embedding/CMakeLists.txt b/tests/embedding/CMakeLists.txt new file mode 100644 index 000000000..d1bde3138 --- /dev/null +++ b/tests/embedding/CMakeLists.txt @@ -0,0 +1,80 @@ +cmake_minimum_required(VERSION 3.15...3.31) +project(cpp-client-telemetry_embedding_test LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(BUILD_SHARED_LIBS OFF CACHE BOOL "") +set(MATSDK_BUILD_HEADERS ON CACHE BOOL "" FORCE) +set(MATSDK_BUILD_LIBRARY ON CACHE BOOL "" FORCE) +set(MATSDK_BUILD_TEST_TOOL OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_FUNC_TESTS OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_PACKAGE OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_OBJC_WRAPPER OFF CACHE BOOL "" FORCE) +set(MATSDK_BUILD_SWIFT_WRAPPER OFF CACHE BOOL "" FORCE) +set(MATSDK_WARNINGS_AS_ERRORS ON CACHE BOOL "" FORCE) + +option(MATSDK_EMBEDDING_PRELOAD_CURL + "Pre-create CURL::libcurl and disable subsequent package discovery" OFF) +if(MATSDK_EMBEDDING_PRELOAD_CURL) + find_path(MATSDK_TEST_CURL_INCLUDE_DIR curl/curl.h) + find_library(MATSDK_TEST_CURL_LIBRARY NAMES curl) + if(NOT MATSDK_TEST_CURL_INCLUDE_DIR OR NOT MATSDK_TEST_CURL_LIBRARY) + message(FATAL_ERROR "System curl headers/library were not found.") + endif() + add_library(matsdk_test_curl INTERFACE) + target_include_directories(matsdk_test_curl INTERFACE + "${MATSDK_TEST_CURL_INCLUDE_DIR}") + target_link_libraries(matsdk_test_curl INTERFACE + "${MATSDK_TEST_CURL_LIBRARY}") + add_library(CURL::libcurl ALIAS matsdk_test_curl) + set(CMAKE_DISABLE_FIND_PACKAGE_CURL ON CACHE BOOL "" FORCE) +endif() + +option(MATSDK_EMBEDDING_PRELOAD_STORAGE_DEPS + "Pre-create SQLite::SQLite3/ZLIB::ZLIB and disable later discovery" OFF) +if(MATSDK_EMBEDDING_PRELOAD_STORAGE_DEPS) + find_path(MATSDK_TEST_SQLITE_INCLUDE_DIR sqlite3.h) + find_library(MATSDK_TEST_SQLITE_LIBRARY NAMES sqlite3) + find_path(MATSDK_TEST_ZLIB_INCLUDE_DIR zlib.h) + find_library(MATSDK_TEST_ZLIB_LIBRARY NAMES z zlib) + if(NOT MATSDK_TEST_SQLITE_INCLUDE_DIR OR NOT MATSDK_TEST_SQLITE_LIBRARY + OR NOT MATSDK_TEST_ZLIB_INCLUDE_DIR OR NOT MATSDK_TEST_ZLIB_LIBRARY) + message(FATAL_ERROR "System SQLite/zlib headers or libraries were not found.") + endif() + add_library(matsdk_test_sqlite INTERFACE) + target_include_directories(matsdk_test_sqlite INTERFACE + "${MATSDK_TEST_SQLITE_INCLUDE_DIR}") + target_link_libraries(matsdk_test_sqlite INTERFACE + "${MATSDK_TEST_SQLITE_LIBRARY}") + add_library(SQLite::SQLite3 ALIAS matsdk_test_sqlite) + add_library(matsdk_test_zlib INTERFACE) + target_include_directories(matsdk_test_zlib INTERFACE + "${MATSDK_TEST_ZLIB_INCLUDE_DIR}") + target_link_libraries(matsdk_test_zlib INTERFACE + "${MATSDK_TEST_ZLIB_LIBRARY}") + add_library(ZLIB::ZLIB ALIAS matsdk_test_zlib) + set(CMAKE_DISABLE_FIND_PACKAGE_SQLite3 ON CACHE BOOL "" FORCE) + set(CMAKE_DISABLE_FIND_PACKAGE_ZLIB ON CACHE BOOL "" FORCE) +endif() + +option(MATSDK_EMBEDDING_USE_FETCHCONTENT + "Exercise local-source FetchContent instead of add_subdirectory" OFF) +if(MATSDK_EMBEDDING_USE_FETCHCONTENT) + include(FetchContent) + FetchContent_Declare(cpp_client_telemetry + SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + FetchContent_MakeAvailable(cpp_client_telemetry) +else() + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../.." cpp_client_telemetry) +endif() + +add_executable(embedding_test "${CMAKE_CURRENT_LIST_DIR}/../vcpkg/main.cpp") +target_link_libraries(embedding_test PRIVATE MSTelemetry::mat) +if(MSVC) + target_compile_options(embedding_test PRIVATE /W4 /WX) +else() + target_compile_options(embedding_test PRIVATE -Wall -Wextra -Werror) +endif() diff --git a/tests/functests/CMakeLists.txt b/tests/functests/CMakeLists.txt index 796623789..9c2a47919 100644 --- a/tests/functests/CMakeLists.txt +++ b/tests/functests/CMakeLists.txt @@ -7,15 +7,16 @@ set(SRCS Main.cpp MultipleLogManagersTests.cpp ) +set(MATSDK_FUNC_TEST_DEFINITIONS) -if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/" AND BUILD_PRIVACYGUARD) - add_definitions(-DHAVE_MAT_PRIVACYGUARD) +if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/" AND MATSDK_BUILD_PRIVACYGUARD) + list(APPEND MATSDK_FUNC_TEST_DEFINITIONS HAVE_MAT_PRIVACYGUARD) list(APPEND SRCS "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/tests/functests/PrivacyGuardFuncTests.cpp" ) endif() -if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/sanitizer/" AND BUILD_SANITIZER) +if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/sanitizer/" AND MATSDK_BUILD_SANITIZER) list(APPEND SRCS "${PROJECT_SOURCE_DIR}/lib/modules/sanitizer/tests/functests/SanitizerFuncTests.cpp" ) @@ -27,8 +28,8 @@ if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/dataviewer/") ) endif() -if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/liveeventinspector/" AND BUILD_LIVEEVENTINSPECTOR) - add_definitions(-DHAVE_MAT_LIVEEVENTINSPECTOR) +if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/liveeventinspector/" AND MATSDK_BUILD_LIVEEVENTINSPECTOR) + list(APPEND MATSDK_FUNC_TEST_DEFINITIONS HAVE_MAT_LIVEEVENTINSPECTOR) list(APPEND SRCS "${PROJECT_SOURCE_DIR}/lib/modules/liveeventinspector/tests/functests/LiveEventInspectorFuncTests.cpp" ) @@ -48,115 +49,26 @@ endif() source_group(" " REGULAR_EXPRESSION "") source_group("common" REGULAR_EXPRESSION "/tests/common/") -if(BUILD_IOS) +if(MATSDK_PLATFORM_IOS) add_library(FuncTests ${SRCS} ${TESTS_COMMON_SRCS}) else() add_executable(FuncTests ${SRCS} ${TESTS_COMMON_SRCS}) endif() - -if(PAL_IMPLEMENTATION STREQUAL "WIN32") - # Link against prebuilt libraries on Windows - message(STATUS "WIN32: Linking against prebuilt libraries") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/gtest") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/gmock") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/zlib") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/sqlite") - # link_directories(${PROJECT_BINARY_DIR}/gtest/ ${PROJECT_BINARY_DIR}/gmock/ ${PROJECT_BINARY_DIR}/zlib/ ${PROJECT_BINARY_DIR}/sqlite/) - include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../zlib ) - target_link_libraries(FuncTests - mat - wininet.lib - ${PROJECT_BINARY_DIR}/gtest/gtest.lib - ${PROJECT_BINARY_DIR}/gmock/gmock.lib - ${PROJECT_BINARY_DIR}/zlib/zlib.lib - ${PROJECT_BINARY_DIR}/sqlite/sqlite.lib - ) -else() - - # Prefer the SDK's bundled sqlite3 when present (e.g. the Android legacy - # build, where the NDK has no system sqlite3), then a more recent local - # sqlite3, otherwise the system library. - if(TARGET sqlite3_bundled) - set (SQLITE3_LIB sqlite3_bundled) - elseif(EXISTS "/usr/local/lib/libsqlite3.a") - set (SQLITE3_LIB "/usr/local/lib/libsqlite3.a") - elseif(EXISTS "/usr/local/opt/sqlite/lib/libsqlite3.a") - set (SQLITE3_LIB "/usr/local/opt/sqlite/lib/libsqlite3.a") - else() - find_package(SQLite3 REQUIRED) - if(NOT TARGET SQLite3::SQLite3) - add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) - endif() - set (SQLITE3_LIB SQLite3::SQLite3) - endif() - - if(TARGET zlib_bundled) - set(MATSDK_TEST_ZLIB zlib_bundled) - else() - find_package( ZLIB REQUIRED ) - set(MATSDK_TEST_ZLIB ZLIB::ZLIB) - include_directories( ${ZLIB_INCLUDE_DIRS} ) - endif() - - set (PLATFORM_LIBS "") - # Add flags for obtaining system UUID via IOKit - if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set (PLATFORM_LIBS "-framework CoreFoundation -framework Foundation") - if(BUILD_IOS) - set (PLATFORM_LIBS "${PLATFORM_LIBS} -framework UIKit -framework Network -framework SystemConfiguration") - else() - set (PLATFORM_LIBS "${PLATFORM_LIBS} -framework IOKit -framework Network -framework SystemConfiguration") - endif() - endif() - - # Raspberry Pi 4 with gcc-8 on ARMv7l requires -latomic - if (CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7l") - set (PLATFORM_LIBS "atomic") - endif() - - message(STATUS "Linking libraries") - message(STATUS "Current Dir: ${CMAKE_CURRENT_SOURCE_DIR}") - message(STATUS "Binary Dir: ${PROJECT_BINARY_DIR}") - - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) - - find_file(LIBGTEST - NAMES libgtest.a - PATHS - ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/googletest/build/lib/ - ) - - find_file(LIBGMOCK - NAMES libgmock.a - PATHS - ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/googletest/build/lib/ - ) - - target_link_libraries(FuncTests - ${LIBGTEST} - ${LIBGMOCK} - mat - ${MATSDK_TEST_ZLIB} - ${SQLITE3_LIB} - ${PLATFORM_LIBS} - dl) - - # Link curl only when the SDK actually uses the curl HTTP client (Linux, and - # macOS without Apple HTTP). The tests don't use curl directly, and on the - # Android legacy path mat uses HttpClient_Android (no system curl in the NDK). - # MATSDK_NEEDS_CURL already excludes iOS/Apple-HTTP. Prefer the CURL::libcurl - # imported target (correct under vcpkg, matches ZLIB::ZLIB above) and fall back - # to the find-module variables on CMake < 3.12, which does not define it. - if(MATSDK_NEEDS_CURL) - if(TARGET CURL::libcurl) - target_link_libraries(FuncTests CURL::libcurl) - else() - target_link_libraries(FuncTests ${CURL_LIBRARIES}) - endif() - endif() - +target_link_libraries(FuncTests PRIVATE matsdk_test_config) +if(MATSDK_FUNC_TEST_DEFINITIONS) + target_compile_definitions(FuncTests PRIVATE ${MATSDK_FUNC_TEST_DEFINITIONS}) endif() -target_link_libraries(FuncTests matsdk_test_includes) +target_link_libraries(FuncTests PRIVATE + mat + ZLIB::ZLIB + gtest + gmock) +if(NOT MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "NONE") + target_link_libraries(FuncTests PRIVATE SQLite::SQLite3) +endif() +if(TARGET nlohmann_json::nlohmann_json) + target_link_libraries(FuncTests PRIVATE nlohmann_json::nlohmann_json) +endif() add_test(FuncTests FuncTests "--gtest_output=xml:${PROJECT_BINARY_DIR}/test-reports/FuncTests.xml") diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index a7efe90ae..05932e7b8 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -51,17 +51,24 @@ set(SRCS UtilsTests.cpp ZlibUtilsTests.cpp ) +if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "NONE") + list(REMOVE_ITEM SRCS OfflineStorageTests_SQLite.cpp) +endif() +set(MATSDK_UNIT_TEST_DEFINITIONS) -set_source_files_properties(${SRCS} PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations) +if(NOT MSVC) + set_source_files_properties(${SRCS} + PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations) +endif() # Enable Azure Monitor unit tests when the module is present. # The AIJsonSerializer test sources are guarded by HAVE_MAT_AI. if (EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/azmon/AIJsonSerializer.hpp") - add_definitions(-DHAVE_MAT_AI) + list(APPEND MATSDK_UNIT_TEST_DEFINITIONS HAVE_MAT_AI) endif() if (APPLE) - if (BUILD_IOS) + if (MATSDK_PLATFORM_IOS) list(APPEND SRCS SysInfoUtilsTests_iOS.cpp) else() list(APPEND SRCS SysInfoUtilsTests_Mac.cpp) @@ -76,8 +83,8 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/exp/tests") ) endif() -if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/" AND BUILD_PRIVACYGUARD) - add_definitions(-DHAVE_MAT_PRIVACYGUARD) +if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/" AND MATSDK_BUILD_PRIVACYGUARD) + list(APPEND MATSDK_UNIT_TEST_DEFINITIONS HAVE_MAT_PRIVACYGUARD) list(APPEND SRCS "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/tests/unittests/InitializationConfigurationTests.cpp" "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/tests/unittests/PrivacyConcernEventTests.cpp" @@ -87,7 +94,7 @@ if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/privacyguard/" AND BUILD_PRIVACYGUA ) endif() -if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/sanitizer/" AND BUILD_SANITIZER) +if(EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/sanitizer/" AND MATSDK_BUILD_SANITIZER) list(APPEND SRCS "${PROJECT_SOURCE_DIR}/lib/modules/sanitizer/tests/unittests/SanitizerJwtTests.cpp" "${PROJECT_SOURCE_DIR}/lib/modules/sanitizer/tests/unittests/SanitizerProviderTests.cpp" @@ -109,121 +116,26 @@ endif() source_group(" " REGULAR_EXPRESSION "") source_group("common" REGULAR_EXPRESSION "/tests/common/") -if(BUILD_IOS) +if(MATSDK_PLATFORM_IOS) add_library(UnitTests STATIC ${SRCS} ${TESTS_COMMON_SRCS}) else() add_executable(UnitTests ${SRCS} ${TESTS_COMMON_SRCS}) endif() - -if(PAL_IMPLEMENTATION STREQUAL "WIN32") - # Link against prebuilt libraries on Windows - message(STATUS "WIN32: Linking against prebuilt libraries") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/gtest") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/gmock") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/zlib") - message(STATUS "WIN32: ... ${PROJECT_BINARY_DIR}/sqlite") - # link_directories(${PROJECT_BINARY_DIR}/gtest/ ${PROJECT_BINARY_DIR}/gmock/ ${PROJECT_BINARY_DIR}/zlib/ ${PROJECT_BINARY_DIR}/sqlite/) - include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../zlib ) - target_link_libraries(UnitTests - mat - wininet.lib - ${PROJECT_BINARY_DIR}/gtest/gtest.lib - ${PROJECT_BINARY_DIR}/gmock/gmock.lib - ${PROJECT_BINARY_DIR}/zlib/zlib.lib - ${PROJECT_BINARY_DIR}/sqlite/sqlite.lib - ) -else() - - # Prefer the SDK's bundled sqlite3 when present (e.g. the Android legacy - # build, where the NDK has no system sqlite3), then a more recent local - # sqlite3, otherwise the system library. - if(TARGET sqlite3_bundled) - set (SQLITE3_LIB sqlite3_bundled) - elseif(EXISTS "/usr/local/lib/libsqlite3.a") - set (SQLITE3_LIB "/usr/local/lib/libsqlite3.a") - elseif(EXISTS "/usr/local/opt/sqlite/lib/libsqlite3.a") - set (SQLITE3_LIB "/usr/local/opt/sqlite/lib/libsqlite3.a") - elseif(EXISTS "/opt/homebrew/opt/sqlite/lib/libsqlite3.a") - # Apple Silicon homebrew installs to /opt/homebrew instead of /usr/local - set (SQLITE3_LIB "/opt/homebrew/opt/sqlite/lib/libsqlite3.a") - else() - find_package(SQLite3 REQUIRED) - if(NOT TARGET SQLite3::SQLite3) - add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3) - endif() - set (SQLITE3_LIB SQLite3::SQLite3) - endif() - - if(TARGET zlib_bundled) - set(MATSDK_TEST_ZLIB zlib_bundled) - else() - find_package( ZLIB REQUIRED ) - set(MATSDK_TEST_ZLIB ZLIB::ZLIB) - include_directories( ${ZLIB_INCLUDE_DIRS} ) - endif() - - set (PLATFORM_LIBS "") - # Add flags for obtaining system UUID via IOKit - if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set (PLATFORM_LIBS "-framework CoreFoundation -framework IOKit -framework SystemConfiguration -framework Foundation -framework Network") - if(BUILD_IOS) - set (PLATFORM_LIBS "${PLATFORM_LIBS} -framework UIKit") - endif() - endif() - - # Raspberry Pi 4 with gcc-8 on ARMv7l requires -latomic - if (CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7l") - set (PLATFORM_LIBS "atomic") - endif() - - message(STATUS "Linking libraries") - message(STATUS "Current Dir: ${CMAKE_CURRENT_SOURCE_DIR}") - message(STATUS "Binary Dir: ${PROJECT_BINARY_DIR}") - - include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../lib/ ) - - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) - - find_file(LIBGTEST - NAMES libgtest.a - PATHS - ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/googletest/build/lib/ - ) - - find_file(LIBGMOCK - NAMES libgmock.a - PATHS - ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party/googletest/build/lib/ - ) - - message(STATUS "GTEST: ${LIBGTEST}") - message(STATUS "GMOCK: ${LIBGMOCK}") - - target_link_libraries(UnitTests - ${LIBGTEST} - ${LIBGMOCK} - mat - ${MATSDK_TEST_ZLIB} - ${SQLITE3_LIB} - ${PLATFORM_LIBS} - dl) - - # Link curl only when the SDK actually uses the curl HTTP client (Linux, and - # macOS without Apple HTTP). The tests don't use curl directly, and on the - # Android legacy path mat uses HttpClient_Android (no system curl in the NDK). - # MATSDK_NEEDS_CURL already excludes iOS/Apple-HTTP. Prefer the CURL::libcurl - # imported target (correct under vcpkg, matches ZLIB::ZLIB above) and fall back - # to the find-module variables on CMake < 3.12, which does not define it. - if(MATSDK_NEEDS_CURL) - if(TARGET CURL::libcurl) - target_link_libraries(UnitTests CURL::libcurl) - else() - target_link_libraries(UnitTests ${CURL_LIBRARIES}) - endif() - endif() - +target_link_libraries(UnitTests PRIVATE matsdk_test_config) +if(MATSDK_UNIT_TEST_DEFINITIONS) + target_compile_definitions(UnitTests PRIVATE ${MATSDK_UNIT_TEST_DEFINITIONS}) endif() -target_link_libraries(UnitTests matsdk_test_includes) +target_link_libraries(UnitTests PRIVATE + mat + ZLIB::ZLIB + gtest + gmock) +if(NOT MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "NONE") + target_link_libraries(UnitTests PRIVATE SQLite::SQLite3) +endif() +if(TARGET nlohmann_json::nlohmann_json) + target_link_libraries(UnitTests PRIVATE nlohmann_json::nlohmann_json) +endif() add_test(UnitTests UnitTests "--gtest_output=xml:${PROJECT_BINARY_DIR}/test-reports/UnitTests.xml") diff --git a/tests/unittests/HttpClientCurlTests.cpp b/tests/unittests/HttpClientCurlTests.cpp index d494ba2fc..50a82a874 100644 --- a/tests/unittests/HttpClientCurlTests.cpp +++ b/tests/unittests/HttpClientCurlTests.cpp @@ -57,6 +57,63 @@ TEST_F(HttpClientCurlTests, CurlHttpOperation_ConstructsWithCaInfo) ASSERT_NE(op.GetHandle(), nullptr); } +TEST(HttpClientCurlOperationTests, SelectsHttp2OnlyWhenRuntimeSupportsIt) +{ + const curl_version_info_data* versionInfo = curl_version_info(CURLVERSION_NOW); + const long expected = (versionInfo != nullptr && (versionInfo->features & CURL_VERSION_HTTP2) != 0) + ? CURL_HTTP_VERSION_2_0 + : CURL_HTTP_VERSION_1_1; + EXPECT_EQ(CurlHttpOperation::GetPreferredHttpVersion(), expected); +} + +class HttpClientCurlHeaderTests : public ::testing::Test, + public HttpServer::Callback +{ +protected: + HttpServer m_server; + std::string m_url; + + void SetUp() override + { + const int port = m_server.addListeningPort(0); + std::ostringstream address; + address << "127.0.0.1:" << port; + m_url = "http://" + address.str() + "/headers/"; + m_server.setServerName(address.str()); + m_server.addHandler("/headers/", *this); + m_server.start(); + } + + void TearDown() override + { + m_server.stop(); + } + + int onHttpRequest(HttpServer::Request const&, HttpServer::Response& response) override + { + response.headers["X-MAT-Test"] = "header-value"; + response.content = "body-value"; + return 200; + } +}; + +TEST_F(HttpClientCurlHeaderTests, CapturesResponseHeadersAndBody) +{ + const std::map requestHeaders; + const std::vector requestBody; + const HttpClient_Curl client; + (void)client; // Initialize curl globally before constructing the operation. + CurlHttpOperation operation("GET", m_url, nullptr, requestHeaders, requestBody); + + ASSERT_EQ(operation.Send(), 200L); + const auto responseHeaders = operation.GetResponseHeaders(); + const auto responseBody = operation.GetResponseBody(); + + ASSERT_EQ(responseHeaders.count("X-MAT-Test"), 1u); + EXPECT_EQ(responseHeaders.at("X-MAT-Test"), "header-value"); + EXPECT_EQ(std::string(responseBody.begin(), responseBody.end()), "body-value"); +} + // --- ILogConfiguration integration --- TEST(HttpClientCurlConfigTests, LogConfiguration_SslVerify_DefaultIsTrue) diff --git a/tests/vcpkg/test-vcpkg-ios.sh b/tests/vcpkg/test-vcpkg-ios.sh index c1097c3bd..df78234ea 100755 --- a/tests/vcpkg/test-vcpkg-ios.sh +++ b/tests/vcpkg/test-vcpkg-ios.sh @@ -96,7 +96,7 @@ cmake -S "${SCRIPT_DIR}" -B "${BUILD_DIR}/consumer" \ -DCMAKE_SYSTEM_NAME=iOS \ -DCMAKE_OSX_SYSROOT="${APPLE_SDK}" \ -DCMAKE_OSX_ARCHITECTURES=arm64 \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 echo "" echo "--- Step 2: Build test consumer for iOS ---" diff --git a/tests/vcpkg/test-vcpkg-windows.ps1 b/tests/vcpkg/test-vcpkg-windows.ps1 index b1390425a..5073daa56 100644 --- a/tests/vcpkg/test-vcpkg-windows.ps1 +++ b/tests/vcpkg/test-vcpkg-windows.ps1 @@ -11,7 +11,6 @@ $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $RepoRoot = (Resolve-Path (Join-Path $ScriptDir "..\..")).Path -$BuildDir = Join-Path $ScriptDir "build-windows" $OverlayPorts = Join-Path $RepoRoot "tools\ports" # Build the working tree under review (not a pinned release) so this test @@ -56,6 +55,7 @@ if ([string]::IsNullOrEmpty($Triplet)) { $Triplet = "x64-windows-static" } } +$BuildDir = Join-Path $ScriptDir "build-windows-$Triplet" # Map triplet to vcvarsall architecture $VcvarsArch = switch -Regex ($Triplet) { diff --git a/tools/build-common.sh b/tools/build-common.sh new file mode 100644 index 000000000..1ba0ecb56 --- /dev/null +++ b/tools/build-common.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +matsdk_clean_build_outputs() { + local script_name="$1" + + echo "$script_name: cleaning previous build artifacts" + rm -f CMakeCache.txt *.cmake + rm -rf out + rm -rf .buildtools +} + +matsdk_mark_buildtools_checked() { + local marker_file="$1" + + echo > "$marker_file" +} + +matsdk_install_buildtools_once() { + local marker_file="$1" + shift + + if [ ! -f "$marker_file" ]; then + if [ $# -gt 0 ]; then + "$@" + fi + matsdk_mark_buildtools_checked "$marker_file" + fi +} + +matsdk_try_buildtools_once() { + local marker_file="$1" + local failure_message="$2" + shift 2 + + if [ ! -f "$marker_file" ]; then + if [ $# -gt 0 ]; then + "$@" || echo "$failure_message" + fi + matsdk_mark_buildtools_checked "$marker_file" + fi +} + +matsdk_print_compiler_versions() { + if [ -f /usr/bin/gcc ]; then + echo "gcc version: `gcc --version`" + fi + + if [ -f /usr/bin/clang ]; then + echo "clang version: `clang --version`" + fi +} + +matsdk_require_cmake_preset_support() { + cmake -P "$DIR/cmake/MatsdkRequirePresetSupport.cmake" +} + +matsdk_append_cmake_opts_to_cmake_args() { + local input="${CMAKE_OPTS:-}" + local token="" + local char="" + local escaped=false + local in_single_quote=false + local in_double_quote=false + local token_started=false + local index + local length=${#input} + local -a parsed_args=() + + # Parse the existing shell-like CMAKE_OPTS format without evaluating it. + for ((index = 0; index < length; index++)); do + char="${input:index:1}" + if [[ "$escaped" == true ]]; then + token+="$char" + escaped=false + token_started=true + elif [[ "$char" == "\\" && "$in_single_quote" == false ]]; then + escaped=true + token_started=true + elif [[ "$char" == "'" && "$in_double_quote" == false ]]; then + if [[ "$in_single_quote" == true ]]; then + in_single_quote=false + else + in_single_quote=true + fi + token_started=true + elif [[ "$char" == '"' && "$in_single_quote" == false ]]; then + if [[ "$in_double_quote" == true ]]; then + in_double_quote=false + else + in_double_quote=true + fi + token_started=true + elif [[ "$char" =~ [[:space:]] && "$in_single_quote" == false && "$in_double_quote" == false ]]; then + if [[ "$token_started" == true ]]; then + parsed_args+=("$token") + token="" + token_started=false + fi + else + token+="$char" + token_started=true + fi + done + + if [[ "$escaped" == true || "$in_single_quote" == true || "$in_double_quote" == true ]]; then + echo "Error: CMAKE_OPTS contains an unterminated escape or quote." >&2 + return 1 + fi + if [[ "$token_started" == true ]]; then + parsed_args+=("$token") + fi + + cmake_args+=("${parsed_args[@]}") +} + +matsdk_run_logged_command() { + printf ' %q' "$@" + printf '\n' + "$@" +} + +matsdk_build_and_package_preset() { + local preset="$1" + + cmake --build --preset "$preset" + cmake --build --preset "$preset" --target package +} diff --git a/tools/ports/cpp-client-telemetry/portfile.cmake b/tools/ports/cpp-client-telemetry/portfile.cmake index b2fdab830..5bc5fddf4 100644 --- a/tools/ports/cpp-client-telemetry/portfile.cmake +++ b/tools/ports/cpp-client-telemetry/portfile.cmake @@ -33,7 +33,7 @@ if(NOT DEFINED SOURCE_PATH) endif() # Determine if Apple HTTP should be used (no curl needed). -# Note: BUILD_APPLE_HTTP must remain ON for macOS/iOS because the vcpkg.json +# Note: MATSDK_BUILD_APPLE_HTTP must remain ON for macOS/iOS because the vcpkg.json # curl dependency is excluded on these platforms. set(MATSDK_BUILD_APPLE_HTTP OFF) if(VCPKG_TARGET_IS_OSX OR VCPKG_TARGET_IS_IOS) @@ -41,15 +41,28 @@ if(VCPKG_TARGET_IS_OSX OR VCPKG_TARGET_IS_IOS) endif() # iOS build options -set(MATSDK_BUILD_IOS OFF) +set(MATSDK_BUILD_IOS_LEGACY OFF) if(VCPKG_TARGET_IS_IOS) - set(MATSDK_BUILD_IOS ON) + set(MATSDK_BUILD_IOS_LEGACY ON) +endif() + +set(MATSDK_APPLE_DEPLOYMENT_OPTIONS) +if(VCPKG_TARGET_IS_IOS) + list(APPEND MATSDK_APPLE_DEPLOYMENT_OPTIONS + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0) endif() set(MATSDK_ANDROID_HTTP_CLIENT AUTO) if(VCPKG_TARGET_IS_ANDROID) file(READ "${SOURCE_PATH}/CMakeLists.txt" _matsdk_root_cmake) - if(NOT _matsdk_root_cmake MATCHES "MATSDK_ANDROID_HTTP_CLIENT") + set(_matsdk_android_option_source "${_matsdk_root_cmake}") + if(EXISTS "${SOURCE_PATH}/cmake/MatsdkOptions.cmake") + file(READ "${SOURCE_PATH}/cmake/MatsdkOptions.cmake" + _matsdk_options_cmake) + string(APPEND _matsdk_android_option_source + "\n${_matsdk_options_cmake}") + endif() + if(NOT _matsdk_android_option_source MATCHES "MATSDK_ANDROID_HTTP_CLIENT") message(FATAL_ERROR "Android vcpkg builds require a cpp-client-telemetry source revision that " "supports MATSDK_ANDROID_HTTP_CLIENT. Update this port's REF/SHA512 to a " @@ -106,19 +119,47 @@ if(VCPKG_TARGET_IS_LINUX OR MATSDK_ANDROID_HTTP_CLIENT STREQUAL "CURL") endif() endif() -# minimal-sqlite -> -DMATSDK_MINIMAL_SQLITE=ON (private feature-stripped SQLite). -vcpkg_check_features( - OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - minimal-sqlite MATSDK_MINIMAL_SQLITE -) +set(MATSDK_VCPKG_SQLITE_PROVIDER SYSTEM) +if("minimal-sqlite" IN_LIST FEATURES) + set(MATSDK_VCPKG_SQLITE_PROVIDER MINIMAL) +endif() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") + set(MATSDK_VCPKG_BUILD_SHARED_LIBS ON) +else() + set(MATSDK_VCPKG_BUILD_SHARED_LIBS OFF) +endif() + +file(READ "${SOURCE_PATH}/CMakeLists.txt" MATSDK_ROOT_CMAKE) +set(MATSDK_PINNED_SOURCE_OPTIONS) +if(MATSDK_ROOT_CMAKE MATCHES "MATSDK_USE_VCPKG_DEPS") + list(APPEND MATSDK_PINNED_SOURCE_OPTIONS -DMATSDK_USE_VCPKG_DEPS=ON) +endif() +if(MATSDK_ROOT_CMAKE MATCHES "MATSDK_MINIMAL_SQLITE" + AND "minimal-sqlite" IN_LIST FEATURES) + list(APPEND MATSDK_PINNED_SOURCE_OPTIONS -DMATSDK_MINIMAL_SQLITE=ON) +endif() vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS - ${FEATURE_OPTIONS} - -DMATSDK_USE_VCPKG_DEPS=ON + ${MATSDK_PINNED_SOURCE_OPTIONS} + -DMATSDK_SQLITE_PROVIDER=${MATSDK_VCPKG_SQLITE_PROVIDER} + -DBUILD_SHARED_LIBS=${MATSDK_VCPKG_BUILD_SHARED_LIBS} -DMATSDK_ANDROID_HTTP_CLIENT=${MATSDK_ANDROID_HTTP_CLIENT} + -DMATSDK_BUILD_HEADERS=ON + -DMATSDK_BUILD_LIBRARY=ON + -DMATSDK_BUILD_TEST_TOOL=OFF + -DMATSDK_BUILD_UNIT_TESTS=OFF + -DMATSDK_BUILD_FUNC_TESTS=OFF + -DMATSDK_BUILD_JNI_WRAPPER=OFF + -DMATSDK_BUILD_OBJC_WRAPPER=OFF + -DMATSDK_BUILD_SWIFT_WRAPPER=OFF + -DMATSDK_BUILD_PACKAGE=OFF + -DBUILD_VERSION=${VERSION} + -DMATSDK_BUILD_APPLE_HTTP=${MATSDK_BUILD_APPLE_HTTP} + # Legacy aliases keep the pinned release fallback buildable until the + # next release contains the canonical MATSDK_* options. -DBUILD_HEADERS=ON -DBUILD_LIBRARY=ON -DBUILD_TEST_TOOL=OFF @@ -128,9 +169,9 @@ vcpkg_cmake_configure( -DBUILD_OBJC_WRAPPER=OFF -DBUILD_SWIFT_WRAPPER=OFF -DBUILD_PACKAGE=OFF - -DBUILD_VERSION=${VERSION} -DBUILD_APPLE_HTTP=${MATSDK_BUILD_APPLE_HTTP} - -DBUILD_IOS=${MATSDK_BUILD_IOS} + -DBUILD_IOS=${MATSDK_BUILD_IOS_LEGACY} + ${MATSDK_APPLE_DEPLOYMENT_OPTIONS} ) vcpkg_cmake_install() diff --git a/tools/setup-buildtools-apple.sh b/tools/setup-buildtools-apple.sh index 83b75f091..489cf28da 100755 --- a/tools/setup-buildtools-apple.sh +++ b/tools/setup-buildtools-apple.sh @@ -57,7 +57,4 @@ cd $SQLITE_PKG ./configure && make && make install cd .. -## Build Google Test framework -./build-gtest.sh $1 - ## Install dotnet for test server diff --git a/tools/setup-buildtools.sh b/tools/setup-buildtools.sh index a5f001664..fa023f6c4 100755 --- a/tools/setup-buildtools.sh +++ b/tools/setup-buildtools.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash if [ -f /bin/yum ]; then if [ `cat /etc/redhat-release | tr -dc '0-9.'|cut -d \. -f1` == "7" ]; then @@ -30,11 +30,12 @@ echo "*********************************************************" exit 3 fi -if [ `cmake --version | grep 3` == "" ]; then +if ! command -v cmake >/dev/null 2>&1 || \ + [ "$(printf '%s\n' 3.21.7 "$(cmake --version | head -1 | awk '{print $3}')" | sort -V | head -1)" != "3.21.7" ]; then yum -y remove cmake -wget https://cmake.org/files/v3.6/cmake-3.6.2.tar.gz -tar -zxvf cmake-3.6.2.tar.gz -cd cmake-3.6.2 +wget https://cmake.org/files/v3.21/cmake-3.21.7.tar.gz +tar -zxvf cmake-3.21.7.tar.gz +cd cmake-3.21.7 ./bootstrap --prefix=/usr/local make make install @@ -72,8 +73,5 @@ cd $SQLITE_PKG ./configure && make && make install cd .. -## Build Google Test framework -./build-gtest.sh - ## Change owner from root to current dir owner chown -R `stat . -c %u:%g` * From acda890292578a3ea660cfb464573391322a6b30 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 8 Aug 2026 11:20:48 -0500 Subject: [PATCH 37/40] Update SPM packaging for canonical CMake options Use the MATSDK build options introduced by upstream main and skip package creation for xcframework slices. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 029e472b-bbe8-468f-81ed-3f860e4ff85b --- build-ios.sh | 5 ++++- tools/apple/build-xcframework.sh | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/build-ios.sh b/build-ios.sh index 42a8818a0..a70e74e19 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -116,4 +116,7 @@ fi matsdk_append_cmake_opts_to_cmake_args matsdk_run_logged_command "${cmake_args[@]}" -matsdk_build_and_package_preset "$PRESET" +cmake --build --preset "$PRESET" +if [ "${MATTELEMETRY_SKIP_PACKAGE:-}" != "1" ]; then + cmake --build --preset "$PRESET" --target package +fi diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 7fed4ccf9..712066f42 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -40,12 +40,12 @@ esac # need the repo's test, Swift wrapper, or package targets. CMAKE_OPTS="${CMAKE_OPTS:-}" CMAKE_OPTS="$CMAKE_OPTS -DBUILD_SHARED_LIBS=OFF" -CMAKE_OPTS="$CMAKE_OPTS -DBUILD_OBJC_WRAPPER=YES" -CMAKE_OPTS="$CMAKE_OPTS -DBUILD_TEST_TOOL=OFF" -CMAKE_OPTS="$CMAKE_OPTS -DBUILD_UNIT_TESTS=OFF" -CMAKE_OPTS="$CMAKE_OPTS -DBUILD_FUNC_TESTS=OFF" -CMAKE_OPTS="$CMAKE_OPTS -DBUILD_SWIFT_WRAPPER=OFF" -CMAKE_OPTS="$CMAKE_OPTS -DBUILD_PACKAGE=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DMATSDK_BUILD_OBJC_WRAPPER=ON" +CMAKE_OPTS="$CMAKE_OPTS -DMATSDK_BUILD_TEST_TOOL=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DMATSDK_BUILD_UNIT_TESTS=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DMATSDK_BUILD_FUNC_TESTS=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DMATSDK_BUILD_SWIFT_WRAPPER=OFF" +CMAKE_OPTS="$CMAKE_OPTS -DMATSDK_BUILD_PACKAGE=OFF" export CMAKE_OPTS rm -rf "$OUT" @@ -79,10 +79,10 @@ cmake_option_enabled() { # option-name default-value } [[ -d "$ROOT/lib/modules/dataviewer" ]] && has_dataviewer=true -if [[ -d "$ROOT/lib/modules/privacyguard" ]] && cmake_option_enabled BUILD_PRIVACYGUARD ON; then +if [[ -d "$ROOT/lib/modules/privacyguard" ]] && cmake_option_enabled MATSDK_BUILD_PRIVACYGUARD ON; then has_privacyguard=true fi -if [[ -d "$ROOT/lib/modules/sanitizer" ]] && cmake_option_enabled BUILD_SANITIZER ON; then +if [[ -d "$ROOT/lib/modules/sanitizer" ]] && cmake_option_enabled MATSDK_BUILD_SANITIZER ON; then has_sanitizer=true fi @@ -173,10 +173,10 @@ cmake -S "$ROOT" -B "$MACOS_BUILD" \ -DCMAKE_OSX_DEPLOYMENT_TARGET="$MACOS_DEPLOYMENT_TARGET" \ -DCMAKE_BUILD_TYPE="$CMAKE_BUILD_TYPE" \ -DCMAKE_PACKAGE_TYPE=tgz \ - -DBUILD_TEST_TOOL=OFF \ - -DBUILD_UNIT_TESTS=OFF \ - -DBUILD_FUNC_TESTS=OFF \ - -DBUILD_SWIFT_WRAPPER=OFF \ + -DMATSDK_BUILD_TEST_TOOL=OFF \ + -DMATSDK_BUILD_UNIT_TESTS=OFF \ + -DMATSDK_BUILD_FUNC_TESTS=OFF \ + -DMATSDK_BUILD_SWIFT_WRAPPER=OFF \ $CMAKE_OPTS cmake --build "$MACOS_BUILD" --target mat mkdir -p "$OUT/macos-universal" From 42cc8e550c58d574b47e44214507464ee667e4d7 Mon Sep 17 00:00:00 2001 From: bmehta001 Date: Sat, 8 Aug 2026 12:06:41 -0500 Subject: [PATCH 38/40] Fix JNI string lifetime/null-safety, Windows cancel-drain race, and UWP version parse (#1494) * Fix four latent safety bugs (JNI lifetime/null-safety, cancel race, UWP parse) Bundled small fixes from a repo-wide review. None are recent regressions (introduced 2018-2023). 1) Signals_jni.cpp (UAF): sendSignal called ReleaseStringUTFChars on the UTF buffer *before* passing it to Signals::CreateEventProperties (which copies from it by value), reading freed/unpinned memory. Move the release to after the buffer is consumed. 2) HttpClient_WinInet.cpp / HttpClient_WinRt.cpp (data race): the CancelAllRequests() drain loop read `m_requests.empty()` with no lock held, while erase() mutates the map on the HTTP callback / PPL continuation thread under m_requestsMutex -- a data race / UB on std::map. Read empty() under the lock each iteration, mirroring the already-correct WinRt destructor drain. (The recent #1460 fixed the destructor's drain but not these methods.) 3) JniConvertors.cpp / OfflineStorage_Room.cpp (null deref): the result of GetStringUTFChars (which returns null on allocation failure) was fed straight into a std::string ctor. Null-check before constructing. 4) WindowsRuntimeSystemInformationImpl.cpp (UWP): std::stoull on the DeviceFamilyVersion string was unguarded; guard it with try/catch defaulting to 0 (the code already has a versionDec==0 -> "10.0" fallback). Validation: JniConvertors.cpp and OfflineStorage_Room.cpp pass NDK aarch64 -fsyntax-only. Signals_jni (needs the private signals module), the Windows HTTP clients, and the UWP path can't be built on this host and rely on the Android/Windows CI; the WinInet/WinRt fix mirrors the existing correct destructor pattern in the same files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review comments: harden two JNI string reads lib/jni/Signals_jni.cpp (sendSignal): guard the jstring argument and the GetStringUTFChars() result for null before use. A null return (e.g. OOM, with a pending exception) previously flowed into CreateEventProperties()/Release as a null pointer. Now returns false instead. lib/offline/OfflineStorage_Room.cpp (ReleaseRecords): a failed tenant-token string read was turned into an empty std::string and reported as dropped[""] = count, silently misattributing dropped records to an empty tenant token. Now follows the file's established pattern (GetStringUTFChars + ThrowRuntime) and skips the entry when the read fails instead of fabricating an empty token. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard all remaining GetStringUTFChars reads against null Addresses @lalitb's review: Signals_jni nativeInitialize called strlen(convertedValue) (and ReleaseStringUTFChars) on the result of GetStringUTFChars(base_url, ...) with no null check, so a failed conversion (e.g. pending OOM) would dereference null. Guard it: only read/release when non-null; an empty/failed base_url keeps the existing default (BaseUrl left unset). While here, make the JNI string null-safety consistent across the file this PR already hardens. ThrowRuntime/ThrowLogic only throw when s_throwExceptions is true; otherwise they ExceptionClear() and execution continues, so the existing checks are not sufficient on their own. The other GetStringUTFChars sites in OfflineStorage_Room.cpp still used the pointer unguarded: - GetReservedRecords: token_utf passed straight into StorageRecord and ReleaseStringUTFChars (null -> std::string(nullptr) UB / release of null). - GetSetting: result = utf with no null check. - GetRecords: tenant_utf passed into emplace_back / released unguarded. Each now mirrors the guard already added for the dropped-records path: substitute an empty token (matching JniConvertors' canonical return "" on null) and only release when non-null. This avoids null deref and release-of-null without silently changing behavior on success. Validated with NDK r29 clang -fsyntax-only (aarch64-linux-android23, -std=c++14, JNI build defines) on both files: clean. Files changed: - lib/jni/Signals_jni.cpp: null-guard base_url GetStringUTFChars - lib/offline/OfflineStorage_Room.cpp: null-guard token_utf, result, tenant_utf Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard tenant_j for null before GetStringUTFChars in GetRecords (Copilot review) Unlike the other JNI string reads in this file, the GetRecords() path called env->GetStringUTFChars(tenant_j, ...) with neither a pre-call null check on the jstring nor a following ThrowRuntime() exception check. Passing a null jstring to GetStringUTFChars is undefined per the JNI spec (and can leave a pending NullPointerException in flight). Only call GetStringUTFChars when tenant_j is non-null; the downstream already tolerates a null tenant_utf (defaults to "" and skips the guarded ReleaseStringUTFChars). Validated: NDK 27 clang++ --target=aarch64-linux-android24 -fsyntax-only compiles OfflineStorage_Room.cpp (USE_ROOM) cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard remaining jstring inputs before GetStringUTFChars (Copilot review) Copilot's re-review flagged two more JNI string reads where the result was null-checked but the jstring input was passed to GetStringUTFChars unguarded, which crashes (or leaves a pending exception) before the result check when the jstring is null: - Signals_jni.cpp nativeInitialize: only read base_url when non-null; if the read itself fails (e.g. OOM) return immediately rather than continuing JNI calls with an exception pending, matching the nativeLog(signal_item_json) site. - OfflineStorage_Room.cpp ByTenant releaseRecords (token) and GetRecords-by-tenant (tenantToken_java): only call GetStringUTFChars when the jstring is non-null; the downstream already tolerates a null result (skips/defaults). The getSetting site is already guarded by an enclosing if (java_value). Validated: NDK 27 clang++ --target=aarch64-linux-android24 -fsyntax-only compiles both files cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * GetRecords: clear pending JNI exception after tenant-token read (Copilot review) If GetStringUTFChars(tenant_j) fails (e.g. OOM) it returns null and leaves a pending JNI exception. GetRecords() previously continued issuing Get*Field calls with that exception in flight, which can make them return defaults. Call ThrowRuntime after the read (as GetAndReserveRecords and the rest of this file already do) to describe+clear the exception, notify the observer, and unwind the batch via the surrounding try/catch. Validated: NDK 27 clang++ -fsyntax-only compiles cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * JStringToStdString: clear pending JNI exception on failed read (Copilot review) When GetStringUTFChars returns null (e.g. OOM) it leaves a pending Java exception. JStringToStdString returned "" without clearing it, so callers kept making JNI calls with an exception in flight. Clear the pending exception before returning, consistent with the ExceptionClear handling elsewhere in the JNI layer. Keeps the existing string-returning contract rather than redesigning the helper's signature, so no call sites change. Validated: NDK 27 clang++ -fsyntax-only compiles cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * UWP: include directly for std::exception catch (Copilot review) The DeviceFamilyVersion parse added a catch(const std::exception&) but the file only pulled in transitively. Include it explicitly so the build does not depend on transitive include order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bound the HTTP cancel drains so they cannot spin or hang (issue #1437) Both HttpClientManager::cancelAllRequests() and HttpClient_WinInet::CancelAllRequests() waited for their in-flight request lists to drain with an unbounded poll loop. A previously-merged fix stopped the 100% CPU burn (locked the empty() check + sleep) but left the loop unbounded: if the drainer never runs -- the SDK task dispatcher stalls/stops for the manager, or a WinInet callback stalls -- the loop still blocks forever, and cancelAllRequests holds the LogManager lock the whole time, freezing LogEvent (issue #1437, a macOS spindump). Replace both poll loops with a condition variable signaled from the drain site (HttpClientManager::onHttpResponse / HttpClient_WinInet::erase), bounded by a timeout. The CV makes the common case drain in microseconds; the timeout is a last-resort safety valve so the drain can never spin or block indefinitely (and so the LogManager lock is held for at most the timeout, not forever). Callbacks/requests are deliberately NOT force-cleared on timeout: the HTTP client still owns them and invokes them later, so deleting them here would use-after-free (the destructor is not a drain barrier). The timeout is therefore a bounded best-effort valve, not the primary drain. Adds HttpClientManagerTests.CancelAllRequests_TimesOutInsteadOfHanging, which holds a request open and asserts cancelAllRequests returns within the (shortened) timeout instead of hanging, then completes the outstanding callback to confirm it is still safe after the drain was abandoned. Verified: full FuncTests suite (39 tests) passes on Linux (CV drain, no regression); the new unit test passes; HttpClient_WinInet.cpp compiles under MSVC. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Distinguish best-effort (pause) from full-drain (teardown) HTTP cancel Address Copilot review on cancelAllRequests. The previous change bounded every cancel drain with a timeout, which -- as Copilot noted -- can return with callbacks still in flight on the shutdown/destructor paths, where the caller then destroys state a late callback references (use-after-free). Split the two intents: - HttpClientManager::cancelAllRequests(bool bestEffort). bestEffort=true (pause, which runs under the LogManager lock) caps the wait so it cannot block indefinitely (issue #1437); the manager is not destroyed, so outstanding callbacks stay valid and drain later. bestEffort=false (default; shutdown/cleanup) drains fully -- the lifetime barrier before the referenced state is destroyed. TelemetrySystem::onPause now passes bestEffort=true; onStop/onCleanup keep the full drain. - HttpClient_WinInet::CancelAllRequests() now always drains fully. Its destructor calls it, so a bounded return would let a late WinInet callback touch a destroyed client. WinInet delivers cancellations on its own threads, so this does not depend on the SDK task dispatcher. Both paths use a condition variable signaled from the drain site instead of a poll loop, so neither spins at 100% CPU -- the reported #1437 failure -- whether bounded or full. Verified: FuncTests (39 tests) pass on Linux, including teardown which now takes the full-drain path; HttpClientManagerTests.CancelAllRequests_TimesOutInsteadOfHanging covers the best-effort timeout; HttpClient_WinInet.cpp compiles under MSVC /permissive- /W4. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clear pending JNI exception before returning false in Signals_jni Addresses two Copilot review comments (Signals_jni.cpp:36 and :71). When GetStringUTFChars returns null it typically leaves a pending Java exception (e.g. OutOfMemoryError). Returning false without clearing it means the JVM throws that exception at the Java call site, so the caller sees the exception rather than the intended graceful alse -- contradicting the comment. Call env->ExceptionClear() before returning false at both sites so the caller observes a clean false (telemetry should degrade gracefully rather than propagate an OOM into the host app). ExceptionClear is one of the few JNI functions safe to call with an exception in flight, and no other JNI calls are made before return. Validated: the exact JNI usage (GetStringUTFChars + ExceptionClear + ReleaseStringUTFChars) compiles clean under the NDK aarch64-linux-android24 clang with -Wall -Werror. (A full Gradle build of this TU isn't possible in this worktree -- the private lib/modules submodule providing Signals.hpp is absent.) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop issue-number references from code comments Reword the HTTP cancel-drain comments to describe the behavior without citing tracking numbers; no code changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop issue-number reference from JniConvertors comment Reword the event-type comment to describe the behavior without citing a tracking number; no code change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Only add for the new member in HttpClient_WinInet This change adds a std::condition_variable_any member; is the only include it needs. Drop the include: the pre-existing recursive_mutex member already resolves via the transitively-included pal/PAL.hpp, so adding addressed a pre-existing concern outside this change's scope. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Re-add to HttpClient_WinInet for a self-contained header The header uses std::recursive_mutex directly, so it should include rather than rely on it being pulled in transitively via pal/PAL.hpp. This keeps the header self-contained (include-what-you-use) alongside for the condition_variable_any member. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarify that best-effort pause is only bounded on async-handler platforms The bestEffort drain in cancelAllRequests caps the wait on m_httpCallbacks, but on Windows (USE_SYNC_HTTPRESPONSE_HANDLER) onHttpResponse drains m_httpCallbacks synchronously inside m_httpClient.CancelAllRequests(), so that wait is usually already satisfied and the real blocking is the transport-level wait there (WinInet condition-variable wait, WinRt poll), which is not bounded. Correct the comment so it no longer implies pause is fully bounded on Windows; fully bounding it requires plumbing the deadline into IHttpClient::CancelAllRequests (follow-up). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bound best-effort pause on Windows by plumbing a deadline into CancelAllRequests PauseTransmission could block indefinitely on Windows even with the manager-level drain cap: with USE_SYNC_HTTPRESPONSE_HANDLER the callbacks drain synchronously inside IHttpClient::CancelAllRequests, so the real blocking region is the transport wait (WinInet condition-variable wait, WinRt poll), which was unbounded. Add a bestEffortTimeout parameter to IHttpClient::CancelAllRequests (default zero = full drain for shutdown/destructor; positive = best-effort cap). WinInet and WinRt now bound their drain wait by it; HttpClientManager passes m_cancelDrainTimeout on pause and zero on teardown. Fire-and-forget impls (Apple, CAPI, Android) accept and ignore the parameter. Files: lib/include/public/IHttpClient.hpp, lib/http/HttpClient_WinInet.{hpp,cpp}, HttpClient_WinRt.{hpp,cpp}, HttpClient_Apple.{hpp,mm}, HttpClient_CAPI.{hpp,cpp}, HttpClient_Android.{hpp,cpp}, HttpClientManager.{hpp,cpp}, tests/unittests/HttpClientCAPITests.cpp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: preserve CancelAllRequests() override compat and tighten the pause bound - Keep the legacy no-arg CancelAllRequests() virtual and add the timed overload as a separate method whose default forwards to it, so existing IHttpClient consumers that override CancelAllRequests() keep working (source-compatible) while built-in clients override the timed variant. - Treat m_cancelDrainTimeout as the total pause budget: subtract time already spent in the transport-level cancel before waiting on the manager callback drain, so the pause path holds the LogManager lock for at most ~one timeout, not up to 2x. - WinRt bounded poll now sleeps at most the remaining budget (min(100ms, remaining)) instead of a full 100ms, so it does not overshoot bestEffortTimeout by a poll interval. - Relax the manager timeout test's lower bound (Ge(100) for a 150ms timeout) so it still catches immediate-return regressions without being flaky under CI timer jitter. Files: lib/include/public/IHttpClient.hpp, lib/http/HttpClientManager.cpp, HttpClient_WinRt.cpp, tests/unittests/HttpClientManagerTests.cpp Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarify CancelAllRequests compatibility: source-compatible, not ABI-stable The overload preserves source compatibility for existing IHttpClient overrides, but adding a virtual changes the vtable, so binary implementations must be recompiled -- consistent with the SDK's C++ classes not being ABI-stable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Keep the no-arg CancelAllRequests() working on every built-in client With the legacy no-arg virtual restored on IHttpClient, an impl that overrode only the timed overload would let a CancelAllRequests() call through an IHttpClient reference hit the base no-op instead of cancelling. Each built-in client (WinInet, WinRt, Apple, CAPI, Android) now also overrides CancelAllRequests() to forward to the timed overload with a zero (full-drain) timeout, so both signatures cancel. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Avoid public timed HTTP cancel virtual Move bounded cancel support behind an internal optional capability and use per-request cancellation as the manager fallback for old-style IHttpClient implementations. Keep shutdown on the existing full-drain CancelAllRequests path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard Signals JNI logger pointer and drop unused cancel return Fail Signals.sendSignal cleanly when Java passes a null native logger pointer, matching the existing JNI hardening in this file and avoiding a null ILogger dereference before LogEvent(). Also make HttpClientManager::cancelAllRequestsAsync() return void since every caller ignored the bool and the helper has no meaningful failure signal. Deliberately defer the WinRt cancel-drain CV conversion: the current best-effort path is already bounded, and changing the UWP/PPL completion-drain coordination without a transport-specific validation loop is riskier than this follow-up warrants. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 * Align vcpkg iOS deployment target Ensure vcpkg-built Apple libraries match the consumer deployment target and avoid linker warnings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4259-b03b-8eeb87c06837 * Harden Apple packaging integration Propagate the resolved iOS sysroot to embedding builds and keep Apple vendored targets compatible with strict warning settings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 * Migrate Apple builds to canonical CMake variables Remove legacy Apple architecture, platform, and deployment-target inputs so standalone scripts and embedding consumers share CMAKE_OSX_* configuration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 * Harden nullable JNI and WinRT version inputs Prevent null and empty retry status arrays from reaching JNI APIs, and handle WinRT exceptions while reading the device family version. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Simplify cancellation documentation and clock setup Keep the behavior unchanged while reducing repeated rationale and centralizing the default kill-switch clock. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Stop test runner looping after failures Return the failing build or test status instead of jumping to a self-looping end label, so CI reports failures rather than appearing hung. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Avoid RTTI dependency and report test failures Use the tracked-request cancellation fallback for no-RTTI builds, and make the Windows test runner wait for concurrent tests and propagate every nonzero exit code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Preserve JNI allocation failures Keep pending Java exceptions from GetStringUTFChars failures so OOM and related conversion errors are not silently converted into empty values or false results. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b12c5862-01e3-45e4-bf91-6389c20cae41 Copilot-Session: 5f341bc5-f8ae-4259-b03b-8eeb87c06837 Copilot-Session: 5f341bc5-f8ae-4257-b03b-8eeb87c06837 --- build-tests.cmd | 12 ++- lib/http/HttpClientManager.cpp | 84 ++++++++++++++++--- lib/http/HttpClientManager.hpp | 18 +++- lib/http/HttpClient_WinInet.cpp | 30 ++++++- lib/http/HttpClient_WinInet.hpp | 11 ++- lib/http/HttpClient_WinRt.cpp | 40 ++++++++- lib/http/HttpClient_WinRt.hpp | 5 +- lib/http/IBoundedHttpClientCancel.hpp | 24 ++++++ lib/include/public/IHttpClient.hpp | 5 +- lib/jni/JniConvertors.cpp | 8 +- lib/jni/Signals_jni.cpp | 45 +++++++--- lib/offline/KillSwitchManager.hpp | 9 +- lib/offline/OfflineStorage_Room.cpp | 48 ++++++++--- .../WindowsRuntimeSystemInformationImpl.cpp | 16 +++- lib/system/TelemetrySystem.cpp | 5 +- tests/unittests/HttpClientManagerTests.cpp | 82 ++++++++++++++++++ 16 files changed, 378 insertions(+), 64 deletions(-) create mode 100644 lib/http/IBoundedHttpClientCancel.hpp diff --git a/build-tests.cmd b/build-tests.cmd index e6dc4bf6a..7f3d0a0ba 100644 --- a/build-tests.cmd +++ b/build-tests.cmd @@ -53,12 +53,10 @@ set MAXCPUCOUNT=%NUMBER_OF_PROCESSORS% set SOLUTION=Solutions\MSTelemetrySDK.sln msbuild %SOLUTION% /target:sqlite:Rebuild,zlib:Rebuild,Tests\gmock:Rebuild,Tests\gtest:Rebuild,Tests\UnitTests:Rebuild,Tests\FuncTests:Rebuild /p:BuildProjectReferences=true /maxcpucount:%MAXCPUCOUNT% /detailedsummary /p:Configuration=%CONFIGURATION% /p:Platform=%PLAT% %CUSTOM_PROPS% -if errorLevel 1 goto end +if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL% Solutions\out\%CONFIGURATION%\%PLAT%\UnitTests\UnitTests.exe -if errorLevel 1 goto end +if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL% Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe -:end -if errorLevel 1 goto end -start "" Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe --gtest_filter=MultipleLogManagersTests.MultiProcessesLogManager -start "" Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe --gtest_filter=MultipleLogManagersTests.MultiProcessesLogManager -:end +if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL% +powershell -NoProfile -ExecutionPolicy Bypass -Command "$path = Join-Path (Get-Location) 'Solutions\out\%CONFIGURATION%\%PLAT%\FuncTests\FuncTests.exe'; $args = '--gtest_filter=MultipleLogManagersTests.MultiProcessesLogManager'; $p1 = Start-Process -FilePath $path -ArgumentList $args -PassThru; $p2 = Start-Process -FilePath $path -ArgumentList $args -PassThru; $p1.WaitForExit(); $p2.WaitForExit(); if ($p1.ExitCode -ne 0 -or $p2.ExitCode -ne 0) { exit 1 }" +if not "%ERRORLEVEL%"=="0" exit /b %ERRORLEVEL% diff --git a/lib/http/HttpClientManager.cpp b/lib/http/HttpClientManager.cpp index 0de14e085..3c7d1f809 100644 --- a/lib/http/HttpClientManager.cpp +++ b/lib/http/HttpClientManager.cpp @@ -4,6 +4,7 @@ // #include "HttpClientManager.hpp" +#include "IBoundedHttpClientCancel.hpp" #include "utils/StringUtils.hpp" #include "pal/TaskDispatcher.hpp" @@ -11,6 +12,7 @@ #include #include #include +#include #ifdef linux #include @@ -137,34 +139,90 @@ namespace MAT_NS_BEGIN { LOG_TRACE("HTTP remove callback=%p", callback); m_httpCallbacks.remove(callback); + // Wake cancelAllRequests() waiting for the list to drain. + m_httpCallbacksCV.notify_all(); } delete callback; } - bool HttpClientManager::cancelAllRequestsAsync() + void HttpClientManager::cancelAllRequestsAsync(std::chrono::milliseconds bestEffortTimeout) { + if (bestEffortTimeout > std::chrono::milliseconds::zero()) + { +#if defined(_CPPRTTI) || defined(__GXX_RTTI) + auto boundedCancel = dynamic_cast(&m_httpClient); + if (boundedCancel != nullptr) + { + boundedCancel->CancelAllRequests(bestEffortTimeout); + return; + } +#endif + + cancelTrackedRequestsAsync(); + return; + } + m_httpClient.CancelAllRequests(); - return true; } - void HttpClientManager::cancelAllRequests() + void HttpClientManager::cancelTrackedRequestsAsync() { - cancelAllRequestsAsync(); - - // Wait for callbacks to drain before shutdown can destroy state that - // those callbacks still use. Keep the list check synchronized and sleep - // between polls so a slow adapter does not burn CPU while draining. - for (;;) + std::vector requestIds; { + LOCKGUARD(m_httpCallbacksMtx); + for (const auto& callback : m_httpCallbacks) { - LOCKGUARD(m_httpCallbacksMtx); - if (m_httpCallbacks.empty()) + if (callback == nullptr || callback->m_ctx == nullptr) { - return; + continue; + } + + std::string id = callback->m_ctx->httpRequestId; + if (id.empty() && callback->m_ctx->httpRequest != nullptr) + { + id = callback->m_ctx->httpRequest->GetId(); + } + if (!id.empty()) + { + requestIds.push_back(id); } } - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + for (const auto& id : requestIds) + { + m_httpClient.CancelRequestAsync(id); + } + } + + void HttpClientManager::cancelAllRequests(bool bestEffort) + { + // Use the transport-specific bounded path when available; older clients + // fall back to cancelling tracked requests individually. + const auto cancelStart = std::chrono::steady_clock::now(); + cancelAllRequestsAsync(bestEffort ? m_cancelDrainTimeout : std::chrono::milliseconds::zero()); + + // Drain callbacks through the condition variable signaled by onHttpResponse. + std::unique_lock lock(m_httpCallbacksMtx); + if (bestEffort) + { + // Keep pause bounded, including time spent in the transport cancel. + const auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - cancelStart); + const auto remaining = (elapsed < m_cancelDrainTimeout) + ? (m_cancelDrainTimeout - elapsed) : std::chrono::milliseconds::zero(); + if (!m_httpCallbacksCV.wait_for(lock, remaining, + [this] { return m_httpCallbacks.empty(); })) + { + LOG_WARN("cancelAllRequests: %zu callback(s) still draining after %lld ms (best-effort)", + m_httpCallbacks.size(), static_cast(m_cancelDrainTimeout.count())); + } + } + else + { + // Shutdown/cleanup is the lifetime barrier for callback state, so drain fully. + m_httpCallbacksCV.wait(lock, [this] { return m_httpCallbacks.empty(); }); } } diff --git a/lib/http/HttpClientManager.hpp b/lib/http/HttpClientManager.hpp index e8214d631..4f350e37f 100644 --- a/lib/http/HttpClientManager.hpp +++ b/lib/http/HttpClientManager.hpp @@ -12,6 +12,8 @@ #include #include +#include +#include namespace MAT_NS_BEGIN { @@ -28,7 +30,9 @@ class HttpClientManager virtual ~HttpClientManager() noexcept; - void cancelAllRequests(); + // Cancel in-flight requests. Shutdown drains fully; pause uses a bounded, + // best-effort drain because it may run under the LogManager lock. + void cancelAllRequests(bool bestEffort = false); size_t requestCount() const { @@ -55,14 +59,22 @@ class HttpClientManager void handleSendRequest(EventsUploadContextPtr const& ctx); virtual void scheduleOnHttpResponse(HttpCallback* callback); void onHttpResponse(HttpCallback* callback); - bool cancelAllRequestsAsync(); + void cancelAllRequestsAsync(std::chrono::milliseconds bestEffortTimeout = std::chrono::milliseconds::zero()); + void cancelTrackedRequestsAsync(); ILogManager& m_logManager; IHttpClient& m_httpClient; ITaskDispatcher& m_taskDispatcher; mutable std::recursive_mutex m_httpCallbacksMtx; std::list m_httpCallbacks; + // Signaled from onHttpResponse when a callback is removed, so cancelAllRequests + // can drain via a condition variable instead of a poll loop. + std::condition_variable_any m_httpCallbacksCV; + // Upper bound on how long cancelAllRequests waits for callbacks to drain. A + // last-resort safety valve so a stalled dispatcher/HTTP stack can never make + // the drain spin or block forever. Adjustable so tests can + // exercise the timeout path without a long wait. + std::chrono::milliseconds m_cancelDrainTimeout{std::chrono::seconds(30)}; }; } MAT_NS_END - diff --git a/lib/http/HttpClient_WinInet.cpp b/lib/http/HttpClient_WinInet.cpp index b1d3b4013..2ec8be9b0 100644 --- a/lib/http/HttpClient_WinInet.cpp +++ b/lib/http/HttpClient_WinInet.cpp @@ -503,6 +503,8 @@ void HttpClient_WinInet::erase(std::string const& id) if (it != m_requests.end()) { auto req = it->second; m_requests.erase(it); + // Wake CancelAllRequests() waiting for the map to drain. + m_requestsCV.notify_all(); // delete WinInetRequestWrapper delete req; } @@ -535,6 +537,11 @@ void HttpClient_WinInet::CancelRequestAsync(std::string const& id) void HttpClient_WinInet::CancelAllRequests() +{ + CancelAllRequests(std::chrono::milliseconds::zero()); +} + +void HttpClient_WinInet::CancelAllRequests(std::chrono::milliseconds bestEffortTimeout) { // vector of all request IDs std::vector ids; @@ -548,11 +555,26 @@ void HttpClient_WinInet::CancelAllRequests() for (const auto &id : ids) CancelRequestAsync(id); - // wait for all destructors to run - while (!m_requests.empty()) + // Wait for all request destructors to run (erase() removes them on the WinInet + // callback thread). Use a condition variable signaled from erase() rather than a + // poll loop so this never spins at 100% CPU while draining. WinInet delivers the + // cancellation callbacks on its own threads, so the wait completes without + // depending on the SDK task dispatcher. + std::unique_lock lock(m_requestsMutex); + if (bestEffortTimeout > std::chrono::milliseconds::zero()) + { + // Best-effort (e.g. pause): the caller must not block indefinitely. The client + // is NOT being destroyed here, so a late callback that arrives after this + // returns still runs erase() on a live client -- returning early is safe. + m_requestsCV.wait_for(lock, bestEffortTimeout, [this] { return m_requests.empty(); }); + } + else { - PAL::sleep(100); - std::this_thread::yield(); + // Full drain barrier (the destructor calls this): returning early with + // requests still in flight would let a late WinInet callback invoke + // WinInetRequestWrapper::OnHttpResponse -> m_parent.erase() on a destroyed + // client, so wait for every request to drain. + m_requestsCV.wait(lock, [this] { return m_requests.empty(); }); } } diff --git a/lib/http/HttpClient_WinInet.hpp b/lib/http/HttpClient_WinInet.hpp index 7e9379ded..42b256157 100644 --- a/lib/http/HttpClient_WinInet.hpp +++ b/lib/http/HttpClient_WinInet.hpp @@ -8,10 +8,14 @@ #ifdef HAVE_MAT_DEFAULT_HTTP_CLIENT #include "IHttpClient.hpp" +#include "IBoundedHttpClientCancel.hpp" #include "pal/PAL.hpp" #include "ILogManager.hpp" +#include +#include + namespace MAT_NS_BEGIN { #ifndef _WININET_ @@ -20,7 +24,7 @@ typedef void* HINTERNET; class WinInetRequestWrapper; -class HttpClient_WinInet : public IHttpClient { +class HttpClient_WinInet : public IHttpClient, public IBoundedHttpClientCancel { public: // Common IHttpClient methods HttpClient_WinInet(); @@ -29,6 +33,7 @@ class HttpClient_WinInet : public IHttpClient { virtual void SendRequestAsync(IHttpRequest* request, IHttpResponseCallback* callback) final; virtual void CancelRequestAsync(std::string const& id) final; virtual void CancelAllRequests() final; + virtual void CancelAllRequests(std::chrono::milliseconds bestEffortTimeout) final; virtual void ApplySettings(ILogConfiguration& config) override; @@ -43,6 +48,9 @@ class HttpClient_WinInet : public IHttpClient { HINTERNET m_hInternet; std::recursive_mutex m_requestsMutex; std::map m_requests; + // Signaled from erase() when a request is removed, so CancelAllRequests can drain + // via a condition variable instead of a poll loop (no 100% CPU spin). + std::condition_variable_any m_requestsCV; static unsigned s_nextRequestId; bool m_msRootCheck; friend class WinInetRequestWrapper; @@ -53,4 +61,3 @@ class HttpClient_WinInet : public IHttpClient { #endif // HAVE_MAT_DEFAULT_HTTP_CLIENT #endif // HTTPCLIENT_WININET_HPP - diff --git a/lib/http/HttpClient_WinRt.cpp b/lib/http/HttpClient_WinRt.cpp index 1efc1bb22..12ac6aa00 100644 --- a/lib/http/HttpClient_WinRt.cpp +++ b/lib/http/HttpClient_WinRt.cpp @@ -399,6 +399,11 @@ namespace MAT_NS_BEGIN { } void HttpClient_WinRt::CancelAllRequests() + { + CancelAllRequests(std::chrono::milliseconds::zero()); + } + + void HttpClient_WinRt::CancelAllRequests(std::chrono::milliseconds bestEffortTimeout) { // vector of all request IDs std::vector ids; @@ -412,11 +417,40 @@ namespace MAT_NS_BEGIN { for (const auto &id : ids) CancelRequestAsync(id); - // wait for all destructors to run - while (!m_requests.empty()) + // wait for all destructors to run. Read m_requests under the lock each + // iteration; erase() runs on the PPL continuation thread under the same lock. + // A zero timeout drains fully (shutdown); a positive timeout is a best-effort + // cap so callers such as pause do not block indefinitely. + const bool bounded = bestEffortTimeout > std::chrono::milliseconds::zero(); + const auto deadline = std::chrono::steady_clock::now() + bestEffortTimeout; + bool done; { - PAL::sleep(100); + std::lock_guard lock(m_requestsMutex); + done = m_requests.empty(); + } + while (!done) + { + if (bounded) + { + const auto now = std::chrono::steady_clock::now(); + if (now >= deadline) + break; + // Sleep no longer than the remaining budget so the bounded wait does not + // overshoot bestEffortTimeout by up to a full poll interval. + long long remainingMs = std::chrono::duration_cast(deadline - now).count(); + if (remainingMs < 1) remainingMs = 1; + if (remainingMs > 100) remainingMs = 100; + PAL::sleep(static_cast(remainingMs)); + } + else + { + PAL::sleep(100); + } std::this_thread::yield(); + { + std::lock_guard lock(m_requestsMutex); + done = m_requests.empty(); + } } }; diff --git a/lib/http/HttpClient_WinRt.hpp b/lib/http/HttpClient_WinRt.hpp index e6352a45b..0e10857f1 100644 --- a/lib/http/HttpClient_WinRt.hpp +++ b/lib/http/HttpClient_WinRt.hpp @@ -13,6 +13,7 @@ #include #include "IHttpClient.hpp" +#include "IBoundedHttpClientCancel.hpp" #include "pal/PAL.hpp" #include @@ -28,7 +29,7 @@ namespace MAT_NS_BEGIN { class WinRtRequestWrapper; -class HttpClient_WinRt : public IHttpClient { +class HttpClient_WinRt : public IHttpClient, public IBoundedHttpClientCancel { public: HttpClient_WinRt(); virtual ~HttpClient_WinRt(); @@ -36,6 +37,7 @@ class HttpClient_WinRt : public IHttpClient { virtual void SendRequestAsync(IHttpRequest* request, IHttpResponseCallback* callback) override; virtual void CancelRequestAsync(std::string const& id) override; virtual void CancelAllRequests() override; + virtual void CancelAllRequests(std::chrono::milliseconds bestEffortTimeout) override; HttpClient^ getHttpClient() { return m_httpClient; } protected: @@ -55,4 +57,3 @@ class HttpClient_WinRt : public IHttpClient { #endif // HAVE_MAT_DEFAULT_HTTP_CLIENT #endif // HTTPCLIENT_WINRT_HPP - diff --git a/lib/http/IBoundedHttpClientCancel.hpp b/lib/http/IBoundedHttpClientCancel.hpp new file mode 100644 index 000000000..f832e4678 --- /dev/null +++ b/lib/http/IBoundedHttpClientCancel.hpp @@ -0,0 +1,24 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "ctmacros.hpp" + +#include + +namespace MAT_NS_BEGIN { + +class IBoundedHttpClientCancel +{ +public: + virtual ~IBoundedHttpClientCancel() noexcept = default; + + // Positive timeout is a best-effort cap. Zero means the caller requires a + // full drain, matching IHttpClient::CancelAllRequests(). + virtual void CancelAllRequests(std::chrono::milliseconds bestEffortTimeout) = 0; +}; + +} MAT_NS_END diff --git a/lib/include/public/IHttpClient.hpp b/lib/include/public/IHttpClient.hpp index 7a8678ceb..0b2727803 100644 --- a/lib/include/public/IHttpClient.hpp +++ b/lib/include/public/IHttpClient.hpp @@ -556,6 +556,10 @@ namespace MAT_NS_BEGIN /// A string that contains the ID of the request to cancel. virtual void CancelRequestAsync(std::string const& id) = 0; + /// + /// Cancels all pending requests, draining fully before returning when the + /// implementation owns a synchronous drain. + /// virtual void CancelAllRequests() {} /// @@ -572,4 +576,3 @@ namespace MAT_NS_BEGIN } MAT_NS_END #endif - diff --git a/lib/jni/JniConvertors.cpp b/lib/jni/JniConvertors.cpp index d944ddff8..dc61a4304 100644 --- a/lib/jni/JniConvertors.cpp +++ b/lib/jni/JniConvertors.cpp @@ -13,6 +13,11 @@ std::string JStringToStdString(JNIEnv* env, const jstring& jstr) { size_t jstr_length = env->GetStringUTFLength(jstr); auto jstr_utf = env->GetStringUTFChars(jstr, nullptr); + if (jstr_utf == nullptr) { + // Preserve the pending Java exception (typically an allocation failure) + // so the JNI caller observes the real failure instead of an empty value. + return ""; + } std::string str(jstr_utf, jstr_utf + jstr_length); env->ReleaseStringUTFChars(jstr, jstr_utf); return str; @@ -160,7 +165,7 @@ EventProperties GetEventProperties(JNIEnv* env, const jstring& jstrEventName, co EventProperties eventProperties; eventProperties.SetName(JStringToStdString(env, jstrEventName)); if (jstrEventType != NULL) { - // An empty type means "unset" (the native default). Before #1329 the + // An empty type means "unset" (the native default). Previously the // Java getType() returned null for a default EventProperties, so this // branch was skipped. getType() now returns "" to fix a Java-side NPE; // forwarding SetType("") here would fail native event-name validation @@ -208,4 +213,3 @@ std::vector ConvertJObjectArrayToStdStringVector(JNIEnv* env, const } } MAT_NS_END - diff --git a/lib/jni/Signals_jni.cpp b/lib/jni/Signals_jni.cpp index 59ca6f32d..b16a48c8d 100644 --- a/lib/jni/Signals_jni.cpp +++ b/lib/jni/Signals_jni.cpp @@ -25,11 +25,22 @@ Java_com_microsoft_applications_events_Signals_sendSignal(JNIEnv *env, jlong nativeLoggerPtr, jstring signal_item_json) { jboolean isCopy = true; + auto logger = reinterpret_cast(nativeLoggerPtr); + if (logger == nullptr) { + return false; + } + if (signal_item_json == nullptr) { + return false; + } const char *signalItemJson = (env)->GetStringUTFChars(signal_item_json, &isCopy); - env->ReleaseStringUTFChars(signal_item_json, signalItemJson); + if (signalItemJson == nullptr) { + // Preserve the pending Java exception (typically an allocation failure) + // rather than masking it as a clean false result. + return false; + } - auto logger = reinterpret_cast(nativeLoggerPtr); EventProperties eventProperties = Signals::CreateEventProperties(signalItemJson); + env->ReleaseStringUTFChars(signal_item_json, signalItemJson); logger->LogEvent(eventProperties); return true; } @@ -54,20 +65,34 @@ Java_com_microsoft_applications_events_Signals_nativeInitialize(JNIEnv *env, jcl SubstrateSignalsConfiguration config; jboolean isCopy = true; - const char *convertedValue = (env)->GetStringUTFChars(base_url, &isCopy); - if (strlen(convertedValue) > 0) { - config.ServiceRequestConfig.BaseUrl = convertedValue; + if (base_url != nullptr) { + const char *convertedValue = (env)->GetStringUTFChars(base_url, &isCopy); + if (convertedValue == nullptr) { + // Preserve the pending Java exception (typically an allocation failure) + // rather than masking it as a clean false result. + return false; + } + if (strlen(convertedValue) > 0) { + config.ServiceRequestConfig.BaseUrl = convertedValue; + } + env->ReleaseStringUTFChars(base_url, convertedValue); } - env->ReleaseStringUTFChars(base_url, convertedValue); config.ServiceRequestConfig.TimeoutMs = reinterpret_cast(timeout_ms); config.ServiceRequestConfig.RetryTimes = reinterpret_cast(retry_times); config.ServiceRequestConfig.RetryTimesToWait = reinterpret_cast(retry_time_to_wait); - jsize size = env->GetArrayLength(retry_status_codes); - std::vector retryStatusCodes(size); - env->GetIntArrayRegion(retry_status_codes, jsize{0}, size, &retryStatusCodes[0] ); - config.ServiceRequestConfig.RetryStatusCodes = std::vector(retryStatusCodes.begin(), retryStatusCodes.end()); + if (retry_status_codes != nullptr) + { + jsize size = env->GetArrayLength(retry_status_codes); + std::vector retryStatusCodes(size); + if (size > 0) + { + env->GetIntArrayRegion(retry_status_codes, jsize{0}, size, retryStatusCodes.data()); + } + config.ServiceRequestConfig.RetryStatusCodes = + std::vector(retryStatusCodes.begin(), retryStatusCodes.end()); + } spDataInspector = Signals::CreateSignalsEventInspector(nullptr, config); return true; diff --git a/lib/offline/KillSwitchManager.hpp b/lib/offline/KillSwitchManager.hpp index d5f5a1211..244edc537 100644 --- a/lib/offline/KillSwitchManager.hpp +++ b/lib/offline/KillSwitchManager.hpp @@ -32,14 +32,14 @@ namespace MAT_NS_BEGIN { } KillSwitchManager() - : KillSwitchManager([]() { return static_cast(PAL::getMonotonicTimeMs()); }) + : KillSwitchManager(defaultClock()) { } explicit KillSwitchManager(Clock clock) : m_clock(clock ? std::move(clock) - : Clock([]() { return static_cast(PAL::getMonotonicTimeMs()); })), + : defaultClock()), m_isRetryAfterActive(false), m_retryAfterExpiryTime(0) { @@ -186,6 +186,11 @@ namespace MAT_NS_BEGIN { } private: + static Clock defaultClock() + { + return []() { return static_cast(PAL::getMonotonicTimeMs()); }; + } + // Precondition: seconds > 0. All call sites enforce this (handleResponse // and addToken both guard with `timeinSecs > 0` / `timeInSeconds > 0`). // Passing a non-positive value is UB: a negative durationMs makes the diff --git a/lib/offline/OfflineStorage_Room.cpp b/lib/offline/OfflineStorage_Room.cpp index d052e7a9d..5ea0611e0 100644 --- a/lib/offline/OfflineStorage_Room.cpp +++ b/lib/offline/OfflineStorage_Room.cpp @@ -495,7 +495,9 @@ namespace MAT_NS_BEGIN auto tenantToken_java = static_cast(env->GetObjectField(record, tenantToken_id)); ThrowRuntime(env, "get tenant"); - auto token_utf = env->GetStringUTFChars(tenantToken_java, nullptr); + auto token_utf = (tenantToken_java != nullptr) + ? env->GetStringUTFChars(tenantToken_java, nullptr) + : nullptr; ThrowRuntime(env, "string tenant"); auto latency = static_cast(std::max(latency_lb, std::min( @@ -529,14 +531,17 @@ namespace MAT_NS_BEGIN uint8_t* end = start + env->GetArrayLength(blob_java); StorageRecord dest( std::to_string(id_java), - token_utf, + token_utf != nullptr ? token_utf : "", latency, persistence, timestamp, StorageBlob(start, end), retryCount, reservedUntil); - env->ReleaseStringUTFChars(tenantToken_java, token_utf); + if (token_utf != nullptr) + { + env->ReleaseStringUTFChars(tenantToken_java, token_utf); + } env->ReleaseByteArrayElements(blob_java, reinterpret_cast(start), 0); env.popLocalFrame(); @@ -769,10 +774,17 @@ namespace MAT_NS_BEGIN ThrowLogic(env, "Exception fetching token"); auto count = env->GetLongField(byTenant, count_id); ThrowLogic(env, "Exception fetching count"); - auto utf = env->GetStringUTFChars(token, nullptr); - std::string key(utf); - env->ReleaseStringUTFChars(token, utf); - dropped[key] = static_cast(count); + auto utf = (token != nullptr) ? env->GetStringUTFChars(token, nullptr) + : nullptr; + ThrowRuntime(env, "Exception fetching token string"); + // Skip rather than misattribute dropped records to an empty + // tenant token when the string read fails. + if (utf != nullptr) + { + std::string key(utf); + env->ReleaseStringUTFChars(token, utf); + dropped[key] = static_cast(count); + } env.popLocalFrame(); } m_observer->OnStorageRecordsDropped(dropped); @@ -1098,8 +1110,11 @@ namespace MAT_NS_BEGIN { auto utf = env->GetStringUTFChars(java_value, nullptr); ThrowRuntime(env, "copy setting value"); - result = utf; - env->ReleaseStringUTFChars(java_value, utf); + if (utf != nullptr) + { + result = utf; + env->ReleaseStringUTFChars(java_value, utf); + } } return result; } @@ -1343,7 +1358,13 @@ namespace MAT_NS_BEGIN auto id_j = env->GetLongField(record, id_id); auto tenant_j = static_cast(env->GetObjectField(record, tenantToken_id)); - auto tenant_utf = env->GetStringUTFChars(tenant_j, nullptr); + const char* tenant_utf = (tenant_j != nullptr) + ? env->GetStringUTFChars(tenant_j, nullptr) + : nullptr; + // Clear/handle any pending exception from a failed string read + // (e.g. OOM) before making further JNI calls, consistent with the + // other read paths in this file. + ThrowRuntime(env, "string tenant"); auto latency = static_cast(env->GetIntField(record, latency_id)); auto persistence = static_cast(env->GetIntField(record, @@ -1359,14 +1380,17 @@ namespace MAT_NS_BEGIN auto blob_end = blob_store + blob_length; records.emplace_back( std::to_string(id_j), - tenant_utf, + tenant_utf != nullptr ? tenant_utf : "", latency, persistence, timestamp, StorageBlob(blob_store, blob_end), retryCount, reservedUntil); - env->ReleaseStringUTFChars(tenant_j, tenant_utf); + if (tenant_utf != nullptr) + { + env->ReleaseStringUTFChars(tenant_j, tenant_utf); + } env->ReleaseByteArrayElements(blob_j, elements, 0); env.popLocalFrame(); } diff --git a/lib/pal/universal/WindowsRuntimeSystemInformationImpl.cpp b/lib/pal/universal/WindowsRuntimeSystemInformationImpl.cpp index 2ae7e9af4..fa7ac6575 100644 --- a/lib/pal/universal/WindowsRuntimeSystemInformationImpl.cpp +++ b/lib/pal/universal/WindowsRuntimeSystemInformationImpl.cpp @@ -5,6 +5,7 @@ #include "pal/PAL.hpp" #include +#include #include "ISystemInformation.hpp" #include "pal/SystemInformationImpl.hpp" @@ -80,7 +81,19 @@ namespace PAL_NS_BEGIN { // The DeviceFamilyVersion is a decimalized form of the ULONGLONG hex form. For example: // 2814750430068736 = 000A000027840000 = 10.0.10116.0 - auto versionDec = std::stoull(AnalyticsInfo::VersionInfo->DeviceFamilyVersion->Data()); + unsigned long long versionDec = 0ull; + try + { + versionDec = std::stoull(AnalyticsInfo::VersionInfo->DeviceFamilyVersion->Data()); + } + catch (const std::exception&) + { + versionDec = 0ull; + } + catch (Platform::Exception^) + { + versionDec = 0ull; + } if (versionDec != 0ull) { m_os_major_version = std::to_string(versionDec >> 16 * 3) + "." + std::to_string(versionDec >> 16 * 2 & 0xFFFF); @@ -129,4 +142,3 @@ namespace PAL_NS_BEGIN { } } PAL_NS_END - diff --git a/lib/system/TelemetrySystem.cpp b/lib/system/TelemetrySystem.cpp index 24ad34ba9..2e5059b47 100644 --- a/lib/system/TelemetrySystem.cpp +++ b/lib/system/TelemetrySystem.cpp @@ -141,7 +141,10 @@ namespace MAT_NS_BEGIN { { bool result = true; result &= tpm.pause(); - hcm.cancelAllRequests(); + // Best-effort: pause runs under the LogManager lock and must not block + // indefinitely if a callback is slow to drain. The system + // is not being torn down, so outstanding callbacks stay valid. + hcm.cancelAllRequests(/* bestEffort */ true); return result; }; diff --git a/tests/unittests/HttpClientManagerTests.cpp b/tests/unittests/HttpClientManagerTests.cpp index b2e34a99e..287e420ed 100644 --- a/tests/unittests/HttpClientManagerTests.cpp +++ b/tests/unittests/HttpClientManagerTests.cpp @@ -2,6 +2,7 @@ #include "common/Common.hpp" #include "common/MockIHttpClient.hpp" +#include "http/IBoundedHttpClientCancel.hpp" #include "http/HttpClientManager.hpp" #include "NullObjects.hpp" @@ -23,6 +24,11 @@ class HttpClientManager4Test : public HttpClientManager { { onHttpResponse(callback); } + + void setCancelDrainTimeout(std::chrono::milliseconds t) + { + m_cancelDrainTimeout = t; + } }; class HttpClientManagerTests : public StrictMock { @@ -42,6 +48,12 @@ class HttpClientManagerTests : public StrictMock { MOCK_METHOD1(resultRequestDone, void(EventsUploadContextPtr const &)); }; +class MockBoundedIHttpClient : public MockIHttpClient, public IBoundedHttpClientCancel { + public: + using MockIHttpClient::CancelAllRequests; + MOCK_METHOD1(CancelAllRequests, void(std::chrono::milliseconds)); +}; + TEST_F(HttpClientManagerTests, HandlesRequestFlow) { @@ -74,3 +86,73 @@ TEST_F(HttpClientManagerTests, HandlesRequestFlow) EXPECT_THAT(ctx->httpResponse, rspRef); EXPECT_THAT(ctx->durationMs, Gt(199)); } + +// Regression test: cancelAllRequests() must not spin/hang forever +// when an in-flight callback never drains (e.g. the dispatcher or HTTP stack is +// stalled). It waits for the drain via a condition variable, bounded by a timeout. +TEST_F(HttpClientManagerTests, CancelAllRequests_TimesOutInsteadOfHanging) +{ + hcm.setCancelDrainTimeout(std::chrono::milliseconds(150)); + + SimpleHttpRequest* req = new SimpleHttpRequest("stall"); + auto ctx = std::make_shared(); + ctx->httpRequestId = req->GetId(); + ctx->httpRequest = req; + ctx->recordIdsAndTenantIds["r1"] = "t1"; + ctx->latency = EventLatency_Normal; + ctx->packageIds["tenant1-token"] = 0; + + IHttpResponseCallback* callback = nullptr; + EXPECT_CALL(httpClientMock, SendRequestAsync(ctx->httpRequest, _)) + .WillOnce(SaveArg<1>(&callback)); + hcm.sendRequest(ctx); + ASSERT_THAT(callback, NotNull()); + + // The response never arrives, so the callback never drains from m_httpCallbacks. + // The best-effort (pause) drain must still return, bounded by the drain timeout, + // rather than block forever. MockIHttpClient does not implement the bounded + // cancel capability, so HttpClientManager falls back to per-request async cancel + // and then abandons the drain when the callback remains outstanding. + EXPECT_CALL(httpClientMock, CancelRequestAsync(ctx->httpRequestId)).WillOnce(Return()); + auto start = std::chrono::steady_clock::now(); + hcm.cancelAllRequests(/* bestEffort */ true); + auto elapsedMs = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count(); + EXPECT_THAT(elapsedMs, Ge(100)); // waited a meaningful fraction of the 150ms timeout, not an immediate return + EXPECT_THAT(elapsedMs, Lt(5000)); // but did not hang + + // Drain the still-outstanding callback so nothing leaks, and confirm it is still + // safe to complete after cancelAllRequests abandoned the drain. + EXPECT_CALL(*this, resultRequestDone(ctx)).WillOnce(Return()); + callback->OnHttpResponse(new SimpleHttpResponse("stall")); +} + +TEST_F(HttpClientManagerTests, CancelAllRequests_UsesBoundedCancelCapability) +{ + MockBoundedIHttpClient boundedClient; + HttpClientManager4Test boundedHcm(boundedClient); + boundedHcm.setCancelDrainTimeout(std::chrono::milliseconds(150)); + boundedHcm.requestDone >> requestDone; + + SimpleHttpRequest* req = new SimpleHttpRequest("bounded"); + auto ctx = std::make_shared(); + ctx->httpRequestId = req->GetId(); + ctx->httpRequest = req; + ctx->recordIdsAndTenantIds["r1"] = "t1"; + ctx->latency = EventLatency_Normal; + ctx->packageIds["tenant1-token"] = 0; + + IHttpResponseCallback* callback = nullptr; + EXPECT_CALL(boundedClient, SendRequestAsync(ctx->httpRequest, _)) + .WillOnce(SaveArg<1>(&callback)); + boundedHcm.sendRequest(ctx); + ASSERT_THAT(callback, NotNull()); + + EXPECT_CALL(boundedClient, CancelAllRequests(std::chrono::milliseconds(150))).WillOnce(Return()); + EXPECT_CALL(boundedClient, CancelRequestAsync(_)).Times(0); + + boundedHcm.cancelAllRequests(/* bestEffort */ true); + + EXPECT_CALL(*this, resultRequestDone(ctx)).WillOnce(Return()); + callback->OnHttpResponse(new SimpleHttpResponse("bounded")); +} From 29839d266c463a9599c9a81b21b5d8e2197cb2e6 Mon Sep 17 00:00:00 2001 From: bmehta001 Date: Sat, 8 Aug 2026 21:52:48 -0500 Subject: [PATCH 39/40] Rm unnecessary comment --- tools/apple/build-xcframework.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/apple/build-xcframework.sh b/tools/apple/build-xcframework.sh index 712066f42..9fcb45f62 100755 --- a/tools/apple/build-xcframework.sh +++ b/tools/apple/build-xcframework.sh @@ -16,9 +16,6 @@ # Slices built here: iOS device (arm64), iOS simulator (arm64 + x86_64 fat), # Mac Catalyst (arm64 + x86_64 fat), visionOS device/simulator (arm64), and # macOS (arm64 + x86_64 universal). -# -# NOTE: this is a first-pass scaffold. It has been validated on macOS for iOS -# device, simulator, Mac Catalyst, visionOS, and macOS slices. set -euo pipefail From 9c207bde70780f03c7036b264713066647b42f4b Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 8 Aug 2026 21:55:18 -0500 Subject: [PATCH 40/40] Declare Swift bridge common context dependency Import ODWCommonDataContext directly so the Swift bridge does not depend on Privacy Guard headers exposing it transitively. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dac7318d-1c0e-48c5-ac24-21a82ed62710 --- wrappers/swift/Headers/MATTelemetryObjC-Bridging-Header.h | 1 + 1 file changed, 1 insertion(+) diff --git a/wrappers/swift/Headers/MATTelemetryObjC-Bridging-Header.h b/wrappers/swift/Headers/MATTelemetryObjC-Bridging-Header.h index 2de4f6a56..4853889f9 100644 --- a/wrappers/swift/Headers/MATTelemetryObjC-Bridging-Header.h +++ b/wrappers/swift/Headers/MATTelemetryObjC-Bridging-Header.h @@ -7,6 +7,7 @@ #import +#import "../../obj-c/ODWCommonDataContext.h" #import "../../obj-c/ODWDiagnosticDataViewer.h" #import "../../obj-c/ODWEventProperties.h" #import "../../obj-c/ODWLogConfiguration.h"