From 9e8b49a0a5031c072d1fabc4bac4836d4fd58fe7 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 19:24:28 -0500 Subject: [PATCH 01/26] Add vcpkg-release-bump workflow to automate port version bumps On a published version release, open a PR to microsoft/vcpkg bumping the cpp-client-telemetry port (REF -> tag, recomputed SHA512, version, then x-add-version). Runs only on published, non-prerelease version tags (vX.Y.Z.W) or manual dispatch, and opens no PR when the port already matches the release. Requires repo variable VCPKG_FORK_REPO and secret VCPKG_BUMP_TOKEN. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/vcpkg-release-bump.yml | 162 +++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 .github/workflows/vcpkg-release-bump.yml diff --git a/.github/workflows/vcpkg-release-bump.yml b/.github/workflows/vcpkg-release-bump.yml new file mode 100644 index 000000000..e29cec448 --- /dev/null +++ b/.github/workflows/vcpkg-release-bump.yml @@ -0,0 +1,162 @@ +name: Vcpkg release bump + +# Opens a version-bump pull request against microsoft/vcpkg for the +# `cpp-client-telemetry` port whenever a new SDK release is published. +# +# It runs ONLY when a new version is cut: +# * automatically on a published, non-draft, non-prerelease GitHub Release +# whose tag looks like a version (vMAJOR.MINOR.PATCH.BUILD), or +# * manually via workflow_dispatch for a specific tag (recovery / re-run). +# It never runs on ordinary pushes, and it opens no PR if the port already +# matches the release (no version change). +# +# One-time setup required in this repository: +# * Variable VCPKG_FORK_REPO -> the vcpkg fork to push branches to, +# e.g. "your-org/vcpkg". +# * Secret VCPKG_BUMP_TOKEN -> a PAT (classic: repo+workflow, or +# fine-grained: Contents+Pull requests RW on +# the fork) able to push to VCPKG_FORK_REPO and +# open pull requests on microsoft/vcpkg. + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: "Release tag to bump the vcpkg port to (e.g. v3.10.161.1)" + required: true + type: string + +permissions: + contents: read + +concurrency: + group: vcpkg-release-bump-${{ github.event.release.tag_name || inputs.tag }} + cancel-in-progress: false + +jobs: + bump: + name: Bump cpp-client-telemetry port + # Skip drafts and pre-releases; always allow manual dispatch. + if: >- + ${{ github.event_name == 'workflow_dispatch' || + (github.event.release.draft == false && github.event.release.prerelease == false) }} + runs-on: ubuntu-latest + env: + UPSTREAM_REPO: ${{ github.repository }} # microsoft/cpp_client_telemetry + VCPKG_UPSTREAM: microsoft/vcpkg + VCPKG_FORK_REPO: ${{ vars.VCPKG_FORK_REPO }} + PORT: cpp-client-telemetry + steps: + - name: Validate configuration + env: + VCPKG_BUMP_TOKEN: ${{ secrets.VCPKG_BUMP_TOKEN }} + run: | + set -euo pipefail + if [ -z "${VCPKG_FORK_REPO}" ]; then + echo "::error::Repository variable VCPKG_FORK_REPO is not set (e.g. 'your-org/vcpkg')." + exit 1 + fi + if [ -z "${VCPKG_BUMP_TOKEN}" ]; then + echo "::error::Secret VCPKG_BUMP_TOKEN is not set. Provide a token that can push to ${VCPKG_FORK_REPO} and open PRs on ${VCPKG_UPSTREAM}." + exit 1 + fi + + - name: Resolve tag and version + id: ver + run: | + set -euo pipefail + TAG="${{ github.event.release.tag_name }}" + if [ -z "${TAG}" ]; then TAG="${{ inputs.tag }}"; fi + if [ -z "${TAG}" ]; then echo "::error::No release tag could be resolved."; exit 1; fi + # Only act on version tags: vMAJOR.MINOR.PATCH.BUILD + if ! printf '%s' "${TAG}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Tag '${TAG}' is not a version tag (expected vX.Y.Z.W); skipping." + exit 1 + fi + VERSION="${TAG#v}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "branch=port/${PORT}-${VERSION}" >> "$GITHUB_OUTPUT" + echo "Bumping ${PORT} -> tag=${TAG} version=${VERSION}" + + - name: Compute source archive SHA512 + id: sha + run: | + set -euo pipefail + URL="https://github.com/${UPSTREAM_REPO}/archive/${{ steps.ver.outputs.tag }}.tar.gz" + echo "Downloading ${URL}" + curl -fsSL --retry 3 "${URL}" -o source.tar.gz + SHA512="$(sha512sum source.tar.gz | cut -d' ' -f1)" + echo "sha512=${SHA512}" >> "$GITHUB_OUTPUT" + echo "SHA512=${SHA512}" + + - name: Clone vcpkg fork and branch off upstream master + env: + VCPKG_BUMP_TOKEN: ${{ secrets.VCPKG_BUMP_TOKEN }} + run: | + set -euo pipefail + git clone --depth 1 "https://x-access-token:${VCPKG_BUMP_TOKEN}@github.com/${VCPKG_FORK_REPO}.git" vcpkg + cd vcpkg + git remote add upstream "https://github.com/${VCPKG_UPSTREAM}.git" + git fetch --depth 1 upstream master + git checkout -B "${{ steps.ver.outputs.branch }}" upstream/master + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Bootstrap vcpkg + run: cd vcpkg && ./bootstrap-vcpkg.sh -disableMetrics + + - name: Update port REF, SHA512 and version + run: | + set -euo pipefail + cd vcpkg + PORTFILE="ports/${PORT}/portfile.cmake" + MANIFEST="ports/${PORT}/vcpkg.json" + if [ ! -f "${PORTFILE}" ] || [ ! -f "${MANIFEST}" ]; then + echo "::error::${PORT} port not found in ${VCPKG_UPSTREAM}. The port must already be in the registry before it can be bumped." + exit 1 + fi + sed -i -E "s|^([[:space:]]*REF[[:space:]]+).*$|\1${{ steps.ver.outputs.tag }}|" "${PORTFILE}" + sed -i -E "s|^([[:space:]]*SHA512[[:space:]]+).*$|\1${{ steps.sha.outputs.sha512 }}|" "${PORTFILE}" + jq --arg v "${{ steps.ver.outputs.version }}" '.version = $v | del(."port-version")' "${MANIFEST}" > "${MANIFEST}.tmp" + mv "${MANIFEST}.tmp" "${MANIFEST}" + ./vcpkg format-manifest "${MANIFEST}" + + - name: Detect change + id: diff + run: | + set -euo pipefail + cd vcpkg + if git diff --quiet -- "ports/${PORT}"; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "No change: ${PORT} is already at ${{ steps.ver.outputs.version }} with this REF/SHA512. Nothing to do." + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Commit, update version DB, push and open PR + if: ${{ steps.diff.outputs.changed == 'true' }} + env: + GH_TOKEN: ${{ secrets.VCPKG_BUMP_TOKEN }} + run: | + set -euo pipefail + cd vcpkg + BR="${{ steps.ver.outputs.branch }}" + git add "ports/${PORT}" + git commit -m "[${PORT}] Update to ${{ steps.ver.outputs.version }}" + ./vcpkg x-add-version "${PORT}" --overwrite-version + git add versions + git commit -m "[${PORT}] Update version database" + git push --force-with-lease origin "${BR}" + if [ -n "$(gh pr list --repo "${VCPKG_UPSTREAM}" --head "$(printf '%s' "${VCPKG_FORK_REPO}" | cut -d/ -f1):${BR}" --state open --json number --jq '.[0].number' 2>/dev/null)" ]; then + echo "An open PR already exists for ${BR}; the force-pushed branch refreshes it." + else + gh pr create \ + --repo "${VCPKG_UPSTREAM}" \ + --base master \ + --head "$(printf '%s' "${VCPKG_FORK_REPO}" | cut -d/ -f1):${BR}" \ + --title "[${PORT}] Update to ${{ steps.ver.outputs.version }}" \ + --body "Automated port bump to [\`${UPSTREAM_REPO}@${{ steps.ver.outputs.tag }}\`](https://github.com/${UPSTREAM_REPO}/releases/tag/${{ steps.ver.outputs.tag }}). Generated by the \`vcpkg-release-bump\` workflow." + fi From 1662a46f73e9ca9d8d06ed2bf30f461dc9c9f2e4 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 10 Jun 2026 19:27:27 -0500 Subject: [PATCH 02/26] Bump in-repo overlay port to v3.10.161.1 tag Repoint tools/ports/cpp-client-telemetry REF from the pre-release commit to the published v3.10.161.1 tag (SHA512 updated) for exact parity with the official microsoft/vcpkg port. Version was already 3.10.161.1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/ports/cpp-client-telemetry/portfile.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ports/cpp-client-telemetry/portfile.cmake b/tools/ports/cpp-client-telemetry/portfile.cmake index b0ce77107..9beb44448 100644 --- a/tools/ports/cpp-client-telemetry/portfile.cmake +++ b/tools/ports/cpp-client-telemetry/portfile.cmake @@ -1,8 +1,8 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO microsoft/cpp_client_telemetry - REF 4485b82005abf1d24336ace99b11df88dd578eb0 - SHA512 1f3ee1c26f1ae9e7323262c9b4c8796efba2c6addcde432d6c6c77b8c1c2f254cb8ff334b1dd0a72dc8ecfbfbae04ab374ec5ac7e5d286d6042953d53e50fd5b + REF v3.10.161.1 + SHA512 4664b34ddce601d6a95669df4a59d11a6cc67de1f23de132192f791a275edc6a10b8498d340e6cf7d120d9e7a22c494d7517b24fc0954bf9e236e84a8800589a HEAD_REF main ) From 136e0100a033e3bb0a6e13be952590e67a4ea445 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 11:24:10 -0500 Subject: [PATCH 03/26] docs(vcpkg): add manifest-mode overlay fallback for pre-registry installs building-with-vcpkg.md only told manifest-mode users to add `cpp-client-telemetry` to vcpkg.json, which fails with an unknown-port error until the port is accepted into the official vcpkg registry. Document the `vcpkg-configuration.json` `overlay-ports` fallback that points manifest mode at the in-repo overlay port, giving parity with the classic-mode `--overlay-ports` instructions already in the doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-with-vcpkg.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index b736ba3c5..f52c0382d 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -53,6 +53,24 @@ project's `vcpkg.json`: } ``` +> **If `cpp-client-telemetry` is not in the official vcpkg registry yet:** +> manifest mode only installs packages it can resolve from a registry, so +> `vcpkg install` fails with an *unknown port* error until the port is +> published. Until then, point vcpkg at the overlay port shipped in this repo by +> adding a `vcpkg-configuration.json` next to your `vcpkg.json`: +> +> ```json +> { +> "overlay-ports": ["/path/to/cpp_client_telemetry/tools/ports"] +> } +> ``` +> +> Use the path to your `cpp_client_telemetry` checkout (the directory that holds +> the `cpp-client-telemetry/` port folder). Classic-mode users can pass the same +> directory on the command line instead — see *Installing from the overlay port* +> above. Remove the `overlay-ports` entry once the port is available in the +> registry. + ## Platform-Specific Instructions ### Windows From 5c345c81a02b5bcead1892ee56ff0c6283d5e54c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 11 Jun 2026 11:44:36 -0500 Subject: [PATCH 04/26] vcpkg-bump: address Copilot force-with-lease comment + drop docs note .github/workflows/vcpkg-release-bump.yml (Copilot): `git push --force-with-lease` could fail on reruns because a fresh clone has no remote-tracking ref for an already-existing bump branch, so the workflow couldn't refresh an open bump PR (contradicting the "force-pushed branch refreshes it" intent). Fetch the branch into refs/remotes/origin/${BR} (|| true on the first run, when it doesn't exist yet) before the force-with-lease push so the lease has a ref to compare against. Verified at .github/workflows/vcpkg-release-bump.yml:146-152. docs/building-with-vcpkg.md: remove the manifest-mode overlay-ports note added in 136e0100, per maintainer request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/vcpkg-release-bump.yml | 5 +++++ docs/building-with-vcpkg.md | 18 ------------------ 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/.github/workflows/vcpkg-release-bump.yml b/.github/workflows/vcpkg-release-bump.yml index e29cec448..336abaead 100644 --- a/.github/workflows/vcpkg-release-bump.yml +++ b/.github/workflows/vcpkg-release-bump.yml @@ -149,6 +149,11 @@ jobs: ./vcpkg x-add-version "${PORT}" --overwrite-version git add versions git commit -m "[${PORT}] Update version database" + # Ensure a remote-tracking ref exists so --force-with-lease has a lease + # to compare against on reruns: the bump branch may already exist on the + # fork but be absent from this fresh clone. Ignore failure on the first + # run, when the branch does not exist remotely yet. + git fetch origin "+refs/heads/${BR}:refs/remotes/origin/${BR}" || true git push --force-with-lease origin "${BR}" if [ -n "$(gh pr list --repo "${VCPKG_UPSTREAM}" --head "$(printf '%s' "${VCPKG_FORK_REPO}" | cut -d/ -f1):${BR}" --state open --json number --jq '.[0].number' 2>/dev/null)" ]; then echo "An open PR already exists for ${BR}; the force-pushed branch refreshes it." diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index f52c0382d..b736ba3c5 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -53,24 +53,6 @@ project's `vcpkg.json`: } ``` -> **If `cpp-client-telemetry` is not in the official vcpkg registry yet:** -> manifest mode only installs packages it can resolve from a registry, so -> `vcpkg install` fails with an *unknown port* error until the port is -> published. Until then, point vcpkg at the overlay port shipped in this repo by -> adding a `vcpkg-configuration.json` next to your `vcpkg.json`: -> -> ```json -> { -> "overlay-ports": ["/path/to/cpp_client_telemetry/tools/ports"] -> } -> ``` -> -> Use the path to your `cpp_client_telemetry` checkout (the directory that holds -> the `cpp-client-telemetry/` port folder). Classic-mode users can pass the same -> directory on the command line instead — see *Installing from the overlay port* -> above. Remove the `overlay-ports` entry once the port is available in the -> registry. - ## Platform-Specific Instructions ### Windows From f28bddda3f908ce17b088e06945c4c9caffdef92 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 12 Jun 2026 21:08:46 -0500 Subject: [PATCH 05/26] Update vcpkg docs: port is now live in the official registry The cpp-client-telemetry port was merged into the upstream vcpkg registry (microsoft/vcpkg#52316, version 3.10.161.1), so docs/building-with-vcpkg.md no longer needs the conditional "once the port is accepted" phrasing. - Intro: state the port is published in the official registry and consumable directly; drop the stale "build recipe / CONTROL file" wording (vcpkg uses vcpkg.json, and the port is registry-resolved now). - "Installing from the vcpkg registry": present tense, link to the upstream ports/cpp-client-telemetry directory. - "Installing from the overlay port": reframe as development-only (test local port changes or a newer SDK revision before they reach the registry). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-with-vcpkg.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index b736ba3c5..797a1e20e 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -1,6 +1,6 @@ # Building 1DS C++ SDK with vcpkg -[vcpkg](https://vcpkg.io/) is a Microsoft cross-platform open source C++ package manager. Onboarding instructions for Windows, Linux and Mac OS X [available here](https://docs.microsoft.com/en-us/cpp/build/vcpkg). This document assumes that the customer build system is already configured to use vcpkg ([getting started guide](https://learn.microsoft.com/en-us/vcpkg/get_started/overview)). 1DS C++ SDK maintainers provide a build recipe, `cpp-client-telemetry` port or CONTROL file for vcpkg. The mainline vcpkg repo is refreshed to point to latest stable open source release of 1DS C++ SDK. +[vcpkg](https://vcpkg.io/) is a Microsoft cross-platform open source C++ package manager. Onboarding instructions for Windows, Linux and Mac OS X [available here](https://docs.microsoft.com/en-us/cpp/build/vcpkg). This document assumes that the customer build system is already configured to use vcpkg ([getting started guide](https://learn.microsoft.com/en-us/vcpkg/get_started/overview)). The `cpp-client-telemetry` port is published in the official vcpkg registry, so it can be consumed directly with no overlay or extra configuration. Maintainers refresh the registry to point to the latest stable open source release of the 1DS C++ SDK on each release. The port provides the core SDK — the `MSTelemetry::mat` target and its public C++ headers. The optional Microsoft-proprietary modules (Privacy Guard, @@ -16,7 +16,8 @@ git clone --recurse-submodules https://github.com/microsoft/cpp_client_telemetry ### Installing from the vcpkg registry -Once a new port has been accepted into the official vcpkg registry, install with: +The `cpp-client-telemetry` port is available in the [official vcpkg registry](https://github.com/microsoft/vcpkg/tree/master/ports/cpp-client-telemetry), +so you can install it directly — no overlay or extra configuration required: ```console vcpkg install cpp-client-telemetry @@ -26,8 +27,9 @@ That's it! The package should be compiled for the current OS. ### Installing from the overlay port (development / pre-release) -Before the port is published, or to test local changes, use the overlay port -shipped in this repository: +The overlay port shipped in this repository is for **development only** — use it +to test local changes to the port, or a newer SDK revision, before they are +published to the registry: ```console git clone https://github.com/microsoft/cpp_client_telemetry From 049c6e5a746afeb72da8d7a7cf5aa86dc212ac1a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 13 Jun 2026 02:06:48 -0500 Subject: [PATCH 06/26] Align overlay port with the merged upstream vcpkg port The cpp-client-telemetry port merged into microsoft/vcpkg (ports/cpp-client-telemetry) ships only portfile.cmake + vcpkg.json. Bring the in-repo overlay back in sync so testing the overlay validates exactly what is published. - Drop the custom 'usage' file and its install step in portfile.cmake. The two lines it printed (find_package(MSTelemetry CONFIG REQUIRED) + target_link_libraries ... MSTelemetry::mat) duplicate vcpkg's auto-generated heuristic usage, and the upstream port carries no usage file. - Reorder vcpkg.json dependencies to vcpkg format-manifest canonical (alphabetical) order; same dependency set, no resolution change. After this, the overlay portfile.cmake and vcpkg.json are byte-identical to the upstream port blobs (cfdab236 / a74cc08b). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/ports/cpp-client-telemetry/portfile.cmake | 3 --- tools/ports/cpp-client-telemetry/usage | 4 ---- tools/ports/cpp-client-telemetry/vcpkg.json | 8 ++++---- 3 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 tools/ports/cpp-client-telemetry/usage diff --git a/tools/ports/cpp-client-telemetry/portfile.cmake b/tools/ports/cpp-client-telemetry/portfile.cmake index 9beb44448..cfdab2368 100644 --- a/tools/ports/cpp-client-telemetry/portfile.cmake +++ b/tools/ports/cpp-client-telemetry/portfile.cmake @@ -46,8 +46,5 @@ vcpkg_cmake_config_fixup(PACKAGE_NAME MSTelemetry CONFIG_PATH lib/cmake/MSTeleme file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") -# Install usage instructions -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") - # Install license vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") diff --git a/tools/ports/cpp-client-telemetry/usage b/tools/ports/cpp-client-telemetry/usage deleted file mode 100644 index 736d289f6..000000000 --- a/tools/ports/cpp-client-telemetry/usage +++ /dev/null @@ -1,4 +0,0 @@ -cpp-client-telemetry provides CMake targets: - - find_package(MSTelemetry CONFIG REQUIRED) - target_link_libraries(main PRIVATE MSTelemetry::mat) diff --git a/tools/ports/cpp-client-telemetry/vcpkg.json b/tools/ports/cpp-client-telemetry/vcpkg.json index d721df65a..a74cc08b3 100644 --- a/tools/ports/cpp-client-telemetry/vcpkg.json +++ b/tools/ports/cpp-client-telemetry/vcpkg.json @@ -6,9 +6,6 @@ "license": "Apache-2.0", "supports": "((windows & !mingw) | linux | osx | ios | android) & !uwp", "dependencies": [ - "nlohmann-json", - "sqlite3", - "zlib", { "name": "curl", "default-features": false, @@ -17,6 +14,8 @@ ], "platform": "linux | android" }, + "nlohmann-json", + "sqlite3", { "name": "vcpkg-cmake", "host": true @@ -24,6 +23,7 @@ { "name": "vcpkg-cmake-config", "host": true - } + }, + "zlib" ] } From efcd72bfa4708c3dc386f2fb4cee052cbb29908a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 13 Jun 2026 02:16:41 -0500 Subject: [PATCH 07/26] Fix vcpkg-bump PR existence check: jq null skipped PR creation Copilot review (round): the open-PR existence guard used --jq '.[0].number'. On the first run, when no PR exists yet, gh pr list returns [] and .[0].number evaluates to null, which gh prints as the literal string "null". [ -n "null" ] is true, so the workflow wrongly logged "An open PR already exists" and skipped 'gh pr create' -- the release-bump PR would never be opened on a clean run. Fix: --jq '.[0].number // empty' yields empty output when no PR exists (guard false -> PR created) and the PR number when one does (guard true -> skipped). Verified jq semantics (jq 1.x): '[] | .[0].number' -> null (prints "null"); '[] | .[0].number // empty' -> no output; '[{number:42}] | .[0].number // empty' -> 42. Confirmed at .github/workflows/vcpkg-release-bump.yml:158. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/vcpkg-release-bump.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vcpkg-release-bump.yml b/.github/workflows/vcpkg-release-bump.yml index 336abaead..c5dc37e81 100644 --- a/.github/workflows/vcpkg-release-bump.yml +++ b/.github/workflows/vcpkg-release-bump.yml @@ -155,7 +155,7 @@ jobs: # run, when the branch does not exist remotely yet. git fetch origin "+refs/heads/${BR}:refs/remotes/origin/${BR}" || true git push --force-with-lease origin "${BR}" - if [ -n "$(gh pr list --repo "${VCPKG_UPSTREAM}" --head "$(printf '%s' "${VCPKG_FORK_REPO}" | cut -d/ -f1):${BR}" --state open --json number --jq '.[0].number' 2>/dev/null)" ]; then + if [ -n "$(gh pr list --repo "${VCPKG_UPSTREAM}" --head "$(printf '%s' "${VCPKG_FORK_REPO}" | cut -d/ -f1):${BR}" --state open --json number --jq '.[0].number // empty' 2>/dev/null)" ]; then echo "An open PR already exists for ${BR}; the force-pushed branch refreshes it." else gh pr create \ From 10b620e6e8c634f80ca62d9ec1af03206b742ca2 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 13 Jun 2026 02:32:14 -0500 Subject: [PATCH 08/26] Harden vcpkg-bump workflow: graceful no-op on non-version tags, no token in URL Address Copilot round comments on the release-bump workflow. vcpkg-release-bump.yml:77 - non-version tag handling was contradictory: the message said "skipping" but the step ran exit 1, failing the workflow. A release published with a non-4-part tag (the SDK has historical 3-part tags like v3.3.8) would mark the automatic run red for what should be a no-op. Now: manual workflow_dispatch with a bad tag still fails loudly (user error), but the automatic release trigger emits a notice, sets a 'skip' output, and exits 0. All downstream steps are gated on steps.ver.outputs.skip != 'true'. vcpkg-release-bump.yml:100 - the PAT was embedded in the clone URL, which persists it in .git/config and risks leaking if git echoes the remote. Switch to 'gh auth setup-git' (writes a credential helper to the global gitconfig) plus a tokenless https clone; the later push step reuses that helper via its GH_TOKEN env. No token appears in any URL or on disk. Validated: workflow YAML parses (PyYAML) and every embedded run block passes 'bash -n'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/vcpkg-release-bump.yml | 32 +++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vcpkg-release-bump.yml b/.github/workflows/vcpkg-release-bump.yml index c5dc37e81..af9ae2676 100644 --- a/.github/workflows/vcpkg-release-bump.yml +++ b/.github/workflows/vcpkg-release-bump.yml @@ -70,10 +70,18 @@ jobs: TAG="${{ github.event.release.tag_name }}" if [ -z "${TAG}" ]; then TAG="${{ inputs.tag }}"; fi if [ -z "${TAG}" ]; then echo "::error::No release tag could be resolved."; exit 1; fi - # Only act on version tags: vMAJOR.MINOR.PATCH.BUILD + # Only act on version tags: vMAJOR.MINOR.PATCH.BUILD. A non-matching + # tag from the automatic release trigger is a clean no-op (the SDK also + # has historical 3-part tags such as v3.3.8); a non-matching tag from a + # manual workflow_dispatch is user error and fails loudly. if ! printf '%s' "${TAG}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "::error::Tag '${TAG}' is not a version tag (expected vX.Y.Z.W); skipping." - exit 1 + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "::error::Tag '${TAG}' is not a version tag (expected vX.Y.Z.W)." + exit 1 + fi + echo "::notice::Tag '${TAG}' is not a version tag (expected vX.Y.Z.W); nothing to bump." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 fi VERSION="${TAG#v}" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" @@ -83,6 +91,7 @@ jobs: - name: Compute source archive SHA512 id: sha + if: ${{ steps.ver.outputs.skip != 'true' }} run: | set -euo pipefail URL="https://github.com/${UPSTREAM_REPO}/archive/${{ steps.ver.outputs.tag }}.tar.gz" @@ -93,11 +102,17 @@ jobs: echo "SHA512=${SHA512}" - name: Clone vcpkg fork and branch off upstream master + if: ${{ steps.ver.outputs.skip != 'true' }} env: - VCPKG_BUMP_TOKEN: ${{ secrets.VCPKG_BUMP_TOKEN }} + GH_TOKEN: ${{ secrets.VCPKG_BUMP_TOKEN }} run: | set -euo pipefail - git clone --depth 1 "https://x-access-token:${VCPKG_BUMP_TOKEN}@github.com/${VCPKG_FORK_REPO}.git" vcpkg + # Authenticate git via gh's credential helper instead of embedding the + # token in the clone URL (which would persist it in .git/config and + # risk leaking it if git echoes the remote). The helper is written to + # the global gitconfig and reused by the later push step. + gh auth setup-git + git clone --depth 1 "https://github.com/${VCPKG_FORK_REPO}.git" vcpkg cd vcpkg git remote add upstream "https://github.com/${VCPKG_UPSTREAM}.git" git fetch --depth 1 upstream master @@ -106,9 +121,11 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - name: Bootstrap vcpkg + if: ${{ steps.ver.outputs.skip != 'true' }} run: cd vcpkg && ./bootstrap-vcpkg.sh -disableMetrics - name: Update port REF, SHA512 and version + if: ${{ steps.ver.outputs.skip != 'true' }} run: | set -euo pipefail cd vcpkg @@ -126,6 +143,7 @@ jobs: - name: Detect change id: diff + if: ${{ steps.ver.outputs.skip != 'true' }} run: | set -euo pipefail cd vcpkg @@ -137,12 +155,14 @@ jobs: fi - name: Commit, update version DB, push and open PR - if: ${{ steps.diff.outputs.changed == 'true' }} + if: ${{ steps.ver.outputs.skip != 'true' && steps.diff.outputs.changed == 'true' }} env: GH_TOKEN: ${{ secrets.VCPKG_BUMP_TOKEN }} run: | set -euo pipefail cd vcpkg + # gh auth setup-git ran in the clone step; reuse that credential helper + # so 'git push' authenticates without a token in the remote URL. BR="${{ steps.ver.outputs.branch }}" git add "ports/${PORT}" git commit -m "[${PORT}] Update to ${{ steps.ver.outputs.version }}" From 4323df2bab5ea2170b9093ff56a81288f0f18308 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 13 Jun 2026 16:55:42 -0500 Subject: [PATCH 09/26] vcpkg-bump workflow: pass release tag via env to prevent shell injection Code review flagged a GitHub Actions script-injection vector: the "Resolve tag and version" step interpolated the untrusted release tag directly into the run: shell, before any validation. A tag containing shell metacharacters (e.g. v1.0.0.0";id;") would execute at assignment time, before the version regex runs. Injected code could write to GITHUB_ENV/GITHUB_PATH, which persist into the later Clone and push/PR steps that carry the VCPKG_BUMP_TOKEN PAT, enabling token exfiltration. Fix: pass the tag values through env: (RELEASE_TAG/INPUT_TAG) and reference them as quoted shell variables (TAG="${RELEASE_TAG:-$INPUT_TAG}"). Env values are not parsed as shell, so metacharacters can no longer inject. All downstream steps already use the regex-validated steps.ver.outputs.* values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/vcpkg-release-bump.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/vcpkg-release-bump.yml b/.github/workflows/vcpkg-release-bump.yml index af9ae2676..820e19b33 100644 --- a/.github/workflows/vcpkg-release-bump.yml +++ b/.github/workflows/vcpkg-release-bump.yml @@ -65,10 +65,16 @@ jobs: - name: Resolve tag and version id: ver + env: + # Pass untrusted tag values through the environment instead of + # interpolating ${{ ... }} directly into the script body, so a tag + # containing shell metacharacters cannot inject commands into this + # step (which shares a runner with later PAT-bearing steps). + RELEASE_TAG: ${{ github.event.release.tag_name }} + INPUT_TAG: ${{ inputs.tag }} run: | set -euo pipefail - TAG="${{ github.event.release.tag_name }}" - if [ -z "${TAG}" ]; then TAG="${{ inputs.tag }}"; fi + TAG="${RELEASE_TAG:-$INPUT_TAG}" if [ -z "${TAG}" ]; then echo "::error::No release tag could be resolved."; exit 1; fi # Only act on version tags: vMAJOR.MINOR.PATCH.BUILD. A non-matching # tag from the automatic release trigger is a clean no-op (the SDK also From 74d5af891886e37f7553486381975e1dda766ea5 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 15 Jun 2026 12:31:21 -0500 Subject: [PATCH 10/26] Enable function-level linking in the CMake build so consumers can dead-strip The compiler-flags block in CMakeLists.txt is wrapped entirely in `if(NOT MATSDK_USE_VCPKG_DEPS)`, and its MSVC branch only sets warning flags -- never /Gy. So the CMake/vcpkg build (the one packaged for downstream consumers) compiles every TU without function-level COMDATs, and referencing one symbol pulls the whole .obj into the consumer image. Add a block, applied in BOTH vendored and vcpkg modes, that splits functions/data into COMDATs/sections (MSVC /Gy /Gw; GCC/Clang -ffunction-sections -fdata-sections; AppleClang -ffunction-sections). This lets a consumer's linker dead-strip unreferenced SDK code (/OPT:REF + /OPT:ICF, --gc-sections, -dead_strip) and matches the MSBuild Release projects, which already enable FunctionLevelLinking + OptimizeReferences + EnableCOMDATFolding. No source or ABI change. Bundled into the vcpkg PR since it directly improves the footprint of the vcpkg-packaged library. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index dea843b23..71b2b70d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,26 @@ endif() endif() # NOT MATSDK_USE_VCPKG_DEPS (compiler flags) +# --- Dead-strip enablement (applies in BOTH vendored and vcpkg modes) --------- +# Split functions/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 vendored-deps flag block above sets these only when +# NOT MATSDK_USE_VCPKG_DEPS, and never sets /Gy at all on MSVC -- so the vcpkg +# build (the one packaged for downstream consumers) and every MSVC build link +# whole .obj files instead of individual functions. This block closes that gap +# and matches the MSBuild Release projects, which already enable +# FunctionLevelLinking + OptimizeReferences + EnableCOMDATFolding. +if(MSVC) + add_compile_options(/Gy /Gw) +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + # -fdata-sections historically conflicted with bitcode on AppleClang, and the + # mach-o linker already dead-strips at symbol granularity with -dead_strip. + add_compile_options(-ffunction-sections) +else() + # GCC / Clang (Linux, Android, MinGW) + add_compile_options(-ffunction-sections -fdata-sections) +endif() + include(tools/Utils.cmake) include(GNUInstallDirs) include(CMakePackageConfigHelpers) From 54a0b0607b2d10bff2ed643a1cd009c6dd607cb2 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 15 Jun 2026 16:39:27 -0500 Subject: [PATCH 11/26] Reword function-level-linking comment to justify the vcpkg-mode exception Copilot review: the new comment read as conflicting with the earlier "let the toolchain manage compiler flags" note. Clarify that these section/COMDAT flags are a deliberate exception -- they are not optimization/dependency choices the toolchain owns; the toolchain doesn't set them, and without them the vcpkg-packaged library links whole .obj files. No code change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71b2b70d1..eba019d10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,14 +207,16 @@ endif() endif() # NOT MATSDK_USE_VCPKG_DEPS (compiler flags) # --- Dead-strip enablement (applies in BOTH vendored and vcpkg modes) --------- -# Split functions/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 vendored-deps flag block above sets these only when -# NOT MATSDK_USE_VCPKG_DEPS, and never sets /Gy at all on MSVC -- so the vcpkg -# build (the one packaged for downstream consumers) and every MSVC build link -# whole .obj files instead of individual functions. This block closes that gap -# and matches the MSBuild Release projects, which already enable -# FunctionLevelLinking + OptimizeReferences + EnableCOMDATFolding. +# 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. if(MSVC) add_compile_options(/Gy /Gw) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") From cd64ce2943da24aecb263931e166ec016e555193 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 15 Jun 2026 16:59:16 -0500 Subject: [PATCH 12/26] Consolidate section-splitting flags into the global block (drop REL_FLAGS dupes) Copilot review: the non-vcpkg REL_FLAGS already injected -ffunction-sections (and -fdata-sections for GNU), duplicating the new global block on Release builds. Remove them from REL_FLAGS so the global add_compile_options block is the single source of truth for section splitting across both dependency modes (it now also covers MSVC /Gy /Gw and AppleClang, which CI confirms build clean). No behavior change; eliminates redundant flags and future drift. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eba019d10..b317a4796 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,16 +149,17 @@ else() endif() if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # Using GCC with -s and -Wl linker flags - set(REL_FLAGS "-s -Wl,--gc-sections -Os ${WARN_FLAGS} -ffunction-sections -fdata-sections -fmerge-all-constants") + # 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") - # AppleClang does not support -ffunction-sections and -fdata-sections with the -fembed-bitcode and -fembed-bitcode-marker set(REL_FLAGS "-Os ${WARN_FLAGS} -fmerge-all-constants") else() - # Using clang - strip unsupported GCC options - set(REL_FLAGS "-Os ${WARN_FLAGS} -ffunction-sections -fmerge-all-constants") + # 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 From 17473f5af94aba932f8722796ffcebeacb40057d Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 16 Jun 2026 15:19:38 -0500 Subject: [PATCH 13/26] vcpkg: request sqlite3 without default features (drop json1) + docs The SDK uses SQLite only for offline event storage (plain tables/indexes, no JSON/FTS/RTREE/vtab), so the port now declares sqlite3 with default-features:false instead of pulling json1. This is the necessary floor for footprint-conscious consumers: vcpkg unions features across the dependency graph and ignores default-features:false on transitive deps, so without this the SDK's own edge forces json1 on and no consumer can opt out. Measured: a consumer that also sets {"name":"sqlite3","default-features":false} in its root manifest links ~52 KB smaller (SQLITE_OMIT_JSON) on x64-windows-static Release; the vcpkg integration test stays 10/10. - tools/ports/cpp-client-telemetry/vcpkg.json: sqlite3 default-features:false; port-version 1 (port-only change over the published 3.10.161.1#0) - docs/building-with-vcpkg.md: document the required root-manifest opt-out Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-with-vcpkg.md | 27 +++++++++++++++++++++ tools/ports/cpp-client-telemetry/vcpkg.json | 6 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index 797a1e20e..be3dcf11f 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -192,6 +192,33 @@ will automatically use the optimized zlib-ng build. > zlib. When using `ZLIB_COMPAT=ON`, ensure all dependencies resolve to > zlib-ng rather than mixing stock zlib and zlib-ng. +## Optional: Reducing footprint (SQLite features) + +The SDK uses SQLite only for offline event storage — simple tables and indexes, +with no JSON, FTS, R*Tree, or virtual-table features. The port therefore +requests `sqlite3` **without default features**, so it never pulls SQLite's +`json1` extension on the SDK's behalf. + +vcpkg resolves a dependency's features as the *union* across the whole graph and +**ignores `default-features: false` on transitive dependencies** — only the +top-level (root) manifest can drop a default feature. So to actually omit +`json1` (which compiles SQLite with `SQLITE_OMIT_JSON`, ~50 KB smaller in a +static `x64-windows-static` Release build), a footprint-conscious consumer must +also declare it in their own manifest: + +```json +{ + "dependencies": [ + "cpp-client-telemetry", + { "name": "sqlite3", "default-features": false } + ] +} +``` + +If any package in your build (including your own code) needs SQLite's JSON +functions, request `sqlite3[json1]` instead and the extension is restored for +the whole graph. + ## How It Works: MATSDK_USE_VCPKG_DEPS When the SDK detects it is being built via vcpkg (by checking for diff --git a/tools/ports/cpp-client-telemetry/vcpkg.json b/tools/ports/cpp-client-telemetry/vcpkg.json index a74cc08b3..a63483590 100644 --- a/tools/ports/cpp-client-telemetry/vcpkg.json +++ b/tools/ports/cpp-client-telemetry/vcpkg.json @@ -1,6 +1,7 @@ { "name": "cpp-client-telemetry", "version": "3.10.161.1", + "port-version": 1, "description": "Microsoft 1DS C/C++ Client Telemetry Library", "homepage": "https://github.com/microsoft/cpp_client_telemetry", "license": "Apache-2.0", @@ -15,7 +16,10 @@ "platform": "linux | android" }, "nlohmann-json", - "sqlite3", + { + "name": "sqlite3", + "default-features": false + }, { "name": "vcpkg-cmake", "host": true From c0a148ef33d573485b6979cc30a9104ebd249a49 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 16 Jun 2026 18:29:28 -0500 Subject: [PATCH 14/26] docs(vcpkg): add consumer linker dead-strip guidance; clarify json1 opt-out Address Copilot round comments on docs/building-with-vcpkg.md and document the footprint lever consumers actually need (validated end-to-end on a real downstream DLL, which shrank substantially once /OPT:REF,ICF were on). - Add "Enable linker dead-stripping" section: the SDK's /Gy /Gw only enable stripping; the consumer's link must set /OPT:REF + /OPT:ICF (with the /DEBUG gotcha that silently disables them) + /INCREMENTAL:NO, or --gc-sections / -dead_strip on GNU/Clang/Apple. Note static-link vs DLL-reexport caveat. - json1 section: clarify this is the in-repo OVERLAY port (registry port to follow upstream), addressing the "registry still pulls defaults" comment. - Reword the resolution explanation around vcpkg's union model instead of "ignores transitive default-features:false". Verified by dry-run: SDK edge opt-out alone keeps sqlite3[core,json1]; adding the same at the root yields sqlite3 (no json1); any edge requesting defaults restores json1 -- so the consumer must opt out at the root too. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-with-vcpkg.md | 59 ++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index be3dcf11f..7ce82e7de 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -192,19 +192,50 @@ will automatically use the optimized zlib-ng build. > zlib. When using `ZLIB_COMPAT=ON`, ensure all dependencies resolve to > zlib-ng rather than mixing stock zlib and zlib-ng. -## Optional: Reducing footprint (SQLite features) +## Reducing binary footprint -The SDK uses SQLite only for offline event storage — simple tables and indexes, -with no JSON, FTS, R*Tree, or virtual-table features. The port therefore -requests `sqlite3` **without default features**, so it never pulls SQLite's -`json1` extension on the SDK's behalf. +The SDK links statically into your binary, so most footprint control lives on +*your* side of the link. -vcpkg resolves a dependency's features as the *union* across the whole graph and -**ignores `default-features: false` on transitive dependencies** — only the -top-level (root) manifest can drop a default feature. So to actually omit -`json1` (which compiles SQLite with `SQLITE_OMIT_JSON`, ~50 KB smaller in a -static `x64-windows-static` Release build), a footprint-conscious consumer must -also declare it in their own manifest: +### Enable linker dead-stripping (largest lever) + +The SDK is compiled with function-level linking (`/Gy /Gw` on MSVC, +`-ffunction-sections -fdata-sections` on GCC/Clang) so that **your** linker can +discard SDK code you never reference. Make sure your final link enables it: + +- **MSVC:** `/OPT:REF` (drop unreferenced functions/data) and `/OPT:ICF` (fold + identical COMDATs). These are on by default for Release, **but `/DEBUG` + silently disables them** — if you ship PDBs, re-enable them explicitly. Also + use `/INCREMENTAL:NO` (incremental linking disables `/OPT:REF`): + + ```cmake + target_link_options(your_target PRIVATE + $<$:/OPT:REF> + $<$:/OPT:ICF> + $<$:/INCREMENTAL:NO>) + ``` + +- **GCC / Clang:** link with `-Wl,--gc-sections`. +- **Apple (clang):** link with `-Wl,-dead_strip`. + +This is by far the largest lever — on a static `x64-windows-static` Release link +it can roughly halve the binary. The SDK's `/Gy /Gw` flags only *enable* this; +the stripping happens at your link. Keep the SDK a static dependency linked +*into* your binary: if you re-export its API across your own DLL boundary, the +export table pins its symbols and defeats `/OPT:REF`. + +### Drop unused SQLite features (json1) + +The SDK uses SQLite only for offline event storage — plain tables and indexes, +with no JSON, FTS, R*Tree, or virtual-table features. This in-repo overlay port +already requests `sqlite3` with `default-features: false` on its dependency edge +(the published registry port will follow once this change is upstreamed). + +vcpkg unions feature requests across the whole dependency graph, and a +transitive opt-out alone is **not** enough: you must **also** request `sqlite3` +with `default-features: false` in your own top-level manifest to actually omit +`json1` (which compiles SQLite with `SQLITE_OMIT_JSON`, ~50 KB smaller on a +static `x64-windows-static` Release build): ```json { @@ -215,9 +246,9 @@ also declare it in their own manifest: } ``` -If any package in your build (including your own code) needs SQLite's JSON -functions, request `sqlite3[json1]` instead and the extension is restored for -the whole graph. +If any package in your build (or your own code) needs SQLite's JSON functions, +request `sqlite3[json1]` instead and the extension is restored for the whole +graph. ## How It Works: MATSDK_USE_VCPKG_DEPS From 9f461aeb18315508637af1faf5fa81a5a99e4469 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 16 Jun 2026 18:33:19 -0500 Subject: [PATCH 15/26] vcpkg(overlay): drop port-version The in-repo overlay is only used for local testing (overlays ignore the version database), and the vcpkg-release-bump workflow deletes port-version on every bump, so it served no purpose here. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/ports/cpp-client-telemetry/vcpkg.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/ports/cpp-client-telemetry/vcpkg.json b/tools/ports/cpp-client-telemetry/vcpkg.json index a63483590..2ee6d9bc3 100644 --- a/tools/ports/cpp-client-telemetry/vcpkg.json +++ b/tools/ports/cpp-client-telemetry/vcpkg.json @@ -1,7 +1,6 @@ { "name": "cpp-client-telemetry", "version": "3.10.161.1", - "port-version": 1, "description": "Microsoft 1DS C/C++ Client Telemetry Library", "homepage": "https://github.com/microsoft/cpp_client_telemetry", "license": "Apache-2.0", From eddcb1fc953e27b7add030ffcea8851650c7c008 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 01:24:26 -0500 Subject: [PATCH 16/26] docs(vcpkg): precise /DEBUG wording for /OPT:REF,ICF Address Copilot comment on the linker-stripping guidance. Per the MSVC /OPT docs, /DEBUG changes the /OPT default from REF/ICF to NOREF/NOICF (it does disable them by default, contrary to the comment's premise). Reword to the exact behavior ("flips their default to off, /OPT:NOREF,NOICF") and note that /OPT:REF is also incompatible with incremental linking (hence /INCREMENTAL:NO). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-with-vcpkg.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index 7ce82e7de..47878ba56 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -204,9 +204,10 @@ The SDK is compiled with function-level linking (`/Gy /Gw` on MSVC, discard SDK code you never reference. Make sure your final link enables it: - **MSVC:** `/OPT:REF` (drop unreferenced functions/data) and `/OPT:ICF` (fold - identical COMDATs). These are on by default for Release, **but `/DEBUG` - silently disables them** — if you ship PDBs, re-enable them explicitly. Also - use `/INCREMENTAL:NO` (incremental linking disables `/OPT:REF`): + identical COMDATs). These are on by default for Release, **but `/DEBUG` flips + their default to off** (`/OPT:NOREF,NOICF`, per the MSVC `/OPT` docs) — so if + you ship PDBs, re-enable them explicitly. `/OPT:REF` is also incompatible with + incremental linking, so set `/INCREMENTAL:NO`: ```cmake target_link_options(your_target PRIVATE From 7571daf00ddd977d5aeb1b0e5e42f1406af0660c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 02:04:26 -0500 Subject: [PATCH 17/26] cmake: clarify AppleClang dead-strip comment (ld64 atomizes per symbol) Address Copilot comment: reword the AppleClang branch to state the actual mechanism -- clang emits .subsections_via_symbols on Mach-O, so ld64's -dead_strip removes unreferenced code at per-symbol (function) granularity without -ffunction-sections (which we add only for cross-toolchain consistency). -fdata-sections stays omitted due to the historical bitcode conflict. No behavior change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b317a4796..06bcc2a78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,8 +221,11 @@ endif() # NOT MATSDK_USE_VCPKG_DEPS (compiler flags) if(MSVC) add_compile_options(/Gy /Gw) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - # -fdata-sections historically conflicted with bitcode on AppleClang, and the - # mach-o linker already dead-strips at symbol granularity with -dead_strip. + # 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) else() # GCC / Clang (Linux, Android, MinGW) From fd3f9582b29ca3add212ff82c2ed4a91a22e171b Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 17:48:03 -0500 Subject: [PATCH 18/26] build: hide non-public symbols on non-Windows (-fvisibility=hidden) Add -fvisibility=hidden -fvisibility-inlines-hidden for non-MSVC builds and make MATSDK_LIBABI = __attribute__((visibility("default"))) on GCC/Clang, so only the MATSDK_LIBABI-decorated public API (the 56 public classes + the EVTSDK_LIBABI C API in mat.h) is exported; SDK internals and the bundled sqlite3/zlib are hidden. Non-Windows analog of the __declspec(dllexport)-gated export on Windows and of /Gy + the consumer's /OPT:REF: a much smaller dynamic symbol table -> faster dynamic linking/loading, smaller shared binaries, and more inlining/dead-code elimination. No behavior change for static consumers (hidden symbols remain usable within the same link); for shared-lib builds it restricts exports to the public API. Validated (NDK aarch64): compiling EventProperties.cpp with the flag yields the decorated public methods (EventProperties::SetType/GetType) as GLOBAL DEFAULT while internals (EventPropertiesStorage) are WEAK HIDDEN -- 64 exported vs 300 hidden in that one TU. Full cross-platform validation (build a shared lib and link a separate consumer on Linux/macOS/iOS/Android to confirm no public symbol is missing) should run in CI before merge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 11 +++++++++++ lib/include/public/ctmacros.hpp | 11 +++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06bcc2a78..9bf2f53dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,6 +232,17 @@ else() 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. Windows already restricts exports via __declspec(dllexport) +# on MATSDK_LIBABI (lib/include/public/ctmacros.hpp). +if(NOT MSVC) + add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden) +endif() + include(tools/Utils.cmake) include(GNUInstallDirs) include(CMakePackageConfigHelpers) diff --git a/lib/include/public/ctmacros.hpp b/lib/include/public/ctmacros.hpp index 42547e41d..eb106b6fa 100644 --- a/lib/include/public/ctmacros.hpp +++ b/lib/include/public/ctmacros.hpp @@ -47,8 +47,15 @@ #define MATSDK_LIBABI_CDECL #endif -#ifndef MATSDK_LIBABI -#define MATSDK_LIBABI +#ifndef MATSDK_LIBABI +// Mark the public API as default-visibility so it stays exported when the SDK is +// built with -fvisibility=hidden (see CMakeLists.txt). This is the non-Windows +// analog of the __declspec(dllexport) gating above. Harmless without the flag. +# if defined(__GNUC__) || defined(__clang__) +# define MATSDK_LIBABI __attribute__((visibility("default"))) +# else +# define MATSDK_LIBABI +# endif #endif // TODO: [MG] - ideally we'd like to use __attribute__((unused)) with gcc/clang From 5b3429345172e821e6ff70d371562ec350979f7a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 18 Jun 2026 18:12:20 -0500 Subject: [PATCH 19/26] address Copilot round on #1475: clang-cl /Gw gating + workflow_dispatch input - CMakeLists.txt: if(MSVC) is also true for the ClangCL toolset, which supports /Gy but not /Gw. Apply /Gy for all MSVC-like compilers and gate /Gw to real cl.exe (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") so ClangCL builds don't choke on /Gw. - vcpkg-release-bump.yml: read the workflow_dispatch tag via github.event.inputs.tag (concurrency key + resolve step) so manual dispatches resolve the tag unambiguously. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/vcpkg-release-bump.yml | 4 ++-- CMakeLists.txt | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/vcpkg-release-bump.yml b/.github/workflows/vcpkg-release-bump.yml index 820e19b33..d09706242 100644 --- a/.github/workflows/vcpkg-release-bump.yml +++ b/.github/workflows/vcpkg-release-bump.yml @@ -32,7 +32,7 @@ permissions: contents: read concurrency: - group: vcpkg-release-bump-${{ github.event.release.tag_name || inputs.tag }} + group: vcpkg-release-bump-${{ github.event.release.tag_name || github.event.inputs.tag }} cancel-in-progress: false jobs: @@ -71,7 +71,7 @@ jobs: # containing shell metacharacters cannot inject commands into this # step (which shares a runner with later PAT-bearing steps). RELEASE_TAG: ${{ github.event.release.tag_name }} - INPUT_TAG: ${{ inputs.tag }} + INPUT_TAG: ${{ github.event.inputs.tag }} run: | set -euo pipefail TAG="${RELEASE_TAG:-$INPUT_TAG}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bf2f53dd..43168ca99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,7 +219,13 @@ endif() # NOT MATSDK_USE_VCPKG_DEPS (compiler flags) # here in both modes closes that gap and matches the MSBuild Release projects, # which already enable FunctionLevelLinking + OptimizeReferences + COMDATFolding. if(MSVC) - add_compile_options(/Gy /Gw) + # /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 From 63b954e7046bba7c5284873a3db1b1f622288ff7 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 23 Jun 2026 13:07:12 -0500 Subject: [PATCH 20/26] Windows shared-lib: add a dllimport path and tie export decoration to linkage The SDK has no .def file, so __declspec(dllexport)/(dllimport) via MATSDK_LIBABI is the sole Windows export mechanism, but it only had export and static states -- no dllimport path for a consumer of a shared mat.dll. And the cmake build hard-defined MATSDK_SHARED_LIB=1 for every Win32 build regardless of BUILD_SHARED_LIBS, so a static build still decorated the public API with dllexport (which also re-exports SDK symbols from any consumer DLL that absorbs the static lib). - ctmacros.hpp: add a MATSDK_IMPORT_LIB branch -> __declspec(dllimport). - lib/CMakeLists.txt: drop the hard-coded MATSDK_SHARED_LIB=1; instead set it on the mat target by linkage. Shared: PRIVATE MATSDK_SHARED_LIB (export from the SDK) + INTERFACE MATSDK_IMPORT_LIB (carried by the installed MSTelemetry::mat target, so find_package() consumers get dllimport automatically). Static: MATSDK_STATIC_LIB so nothing is decorated. This makes the C++ public API safe to consume from a single shared mat.dll (and fixes the missing-dllimport gap). The MSBuild/.vcxproj projects are unaffected (they define MATSDK_SHARED_LIB themselves). Add docs/sharing-a-single-sdk-runtime.md: how to ship one shared mat runtime that multiple modules in a process import (stable C ABI recommended; C++ shared-DLL path with its ABI-matching requirements), per-port vcpkg linkage, single LogManager lifetime ownership, one-copy-on-the-loader-path, and validation. Validated: MSVC preprocessor expands MATSDK_LIBABI to dllexport / dllimport / empty for the shared / import / static cases; cmake configures and the static Linux build is unaffected (change is Windows-guarded). Windows shared export/import is exercised by CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/sharing-a-single-sdk-runtime.md | 161 +++++++++++++++++++++++++++ lib/CMakeLists.txt | 19 +++- lib/include/public/ctmacros.hpp | 7 +- 3 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 docs/sharing-a-single-sdk-runtime.md diff --git a/docs/sharing-a-single-sdk-runtime.md b/docs/sharing-a-single-sdk-runtime.md new file mode 100644 index 000000000..feeb8c296 --- /dev/null +++ b/docs/sharing-a-single-sdk-runtime.md @@ -0,0 +1,161 @@ +# Sharing one SDK runtime across several modules in a process + +When more than one module in a single process links this SDK — for example an +application that loads several plug-ins or libraries, each of which uses 1DS — +the easy default (every module statically embeds the SDK) has two costs: + +1. **Size.** The SDK (plus its bundled SQLite/zlib) is duplicated once per module. +2. **Duplicated global state.** Each static copy has its *own* default + `LogManager`, HTTP transport, offline SQLite cache, and upload threads. They do + not share a pipeline, and multiple writers to the same offline-cache path will + corrupt it. + +This document describes how to ship **one** shared SDK runtime (`mat.dll` / +`libmat.so` / `libmat.dylib`) that every module imports, so there is a single +copy on disk and a single set of process-global state. + +There are two ways to consume the shared runtime. **The C API is strongly +recommended** because it removes the fragile C++/CRT ABI coupling between modules. + +--- + +## Option 1 (recommended): consume the stable C API + +The SDK ships a flat **C ABI** in [`mat.h`](../lib/include/public/mat.h). Every +`evt_*` entry point (`evt_open`, `evt_log`, `evt_flush`, `evt_upload`, +`evt_pause`, `evt_resume`, `evt_close`, `evt_configure`, …) is a `static inline` +wrapper that marshals its arguments into a POD struct and calls through a single +exported `__cdecl` symbol, `evt_api_call_default`. + +Consequences that make this the robust choice: + +* **Only one symbol crosses the module boundary, and no C++/STL type does.** The + request is a plain C struct, so there is *no* requirement that the modules and + the shared runtime agree on the C++ standard library ABI (`/MD`, + `_ITERATOR_DEBUG_LEVEL`, MSVC toolset/STL version, libstdc++ vs libc++, + `_GLIBCXX_USE_CXX11_ABI`, …). A 1DS version bump does not force every module to + rebuild in lockstep against an identical toolchain. +* **It links without `__declspec(dllimport)`.** A plain C function resolves + through the shared library's import lib, so the C API works across the boundary + today. + +Each module includes `mat.h`, links the one shared runtime, and uses its own +tenant/source. You still pin the **same SDK version** in every module (so the +request/struct layout matches), but you avoid the C++ ABI lockstep entirely. + +## Option 2: consume the C++ API from a shared library + +All modules `find_package(MSTelemetry CONFIG REQUIRED)` and link +`MSTelemetry::mat` (resolving to the import lib); none statically embed the SDK. + +The C++ public API passes C++ standard-library types (`std::string`, `std::map`, +…) across the module boundary, so **every module and the shared runtime must +share one C++ ABI**. If they do not, you get heap corruption / undefined +behavior. Pin all of the following identically: + +| Axis | Requirement | +|------|-------------| +| **CRT linkage (Windows)** | Dynamic CRT (`/MD`, `/MDd` for Debug) everywhere — never `/MT`, and never mix Debug/Release CRT across the boundary. (vcpkg: `VCPKG_CRT_LINKAGE dynamic`.) | +| **STL / iterator debug** | One compiler + STL, one build config. `_ITERATOR_DEBUG_LEVEL` must match (Release `0` vs Debug `2`) — a Release consumer + Debug runtime is a silent layout mismatch. | +| **Toolset** | One MSVC toolset across all binaries (the v14x toolsets share an STL ABI, but don't mix major versions); or one libstdc++/libc++ with the same `_GLIBCXX_USE_CXX11_ABI`. | +| **Language / model** | Same `/std:c++NN`, same `/EHsc` exception model, same architecture, no overridden struct packing. | +| **SDK build options** | Same SDK feature/version selection in every module's manifest — different features mean different headers, hence a different ABI even at the same version. | + +Because the C++ ABI must match exactly across separately built and separately +versioned modules, this option is materially more brittle than the C API. Prefer +Option 1 unless you specifically need the C++ surface and control all modules' +toolchains. + +--- + +## Building the shared runtime with vcpkg (per-port linkage) + +A common requirement is "share *this* SDK, but keep everything else statically +linked (no DLL forest)". Override the library linkage **per port** in your +triplet so only this SDK goes dynamic: + +```cmake +set(VCPKG_CRT_LINKAGE dynamic) +if(PORT STREQUAL "cpp-client-telemetry") + set(VCPKG_LIBRARY_LINKAGE dynamic) # mat.dll / libmat.so / libmat.dylib + import lib +else() + set(VCPKG_LIBRARY_LINKAGE static) +endif() +``` + +The port honors `VCPKG_LIBRARY_LINKAGE` / `BUILD_SHARED_LIBS` and emits the +shared `mat` plus its import lib and the `MSTelemetry` CMake config package. + +### Pin one version across all modules + +All modules must compile against identical SDK headers. Pin the same +`cpp-client-telemetry` version and `builtin-baseline` (or a shared version +override) in every module's manifest, and ideally build all artifacts in the same +CI job/container with the same toolchain image. Most ABI drift comes from +separate modules quietly building on different agents. + +--- + +## How the SDK decorates its public symbols + +The SDK has no `.def` file; on Windows, exporting/importing is driven entirely by +`MATSDK_LIBABI` in [`ctmacros.hpp`](../lib/include/public/ctmacros.hpp), which the +build ties to the actual linkage: + +* **Shared build:** the SDK is compiled with `MATSDK_SHARED_LIB` + (`__declspec(dllexport)`), and the installed `MSTelemetry::mat` target carries + an `INTERFACE` definition of `MATSDK_IMPORT_LIB`, so consumers that + `find_package` + link automatically get `__declspec(dllimport)` — no + consumer-side configuration required. +* **Static build:** nothing is decorated, so the SDK's public symbols are not + re-exported by a consumer DLL that absorbs the static lib. + +On non-Windows platforms the SDK is built with `-fvisibility=hidden` and the +public API is marked `__attribute__((visibility("default")))`, so only the public +API (including the C API) is exported from the shared object. + +--- + +## Coordinate the single runtime's lifetime + +One shared runtime means **one** set of process-global state. Decide ownership: + +* **Recommended — single owner.** The top-level module initializes and tears down + the SDK (`LogManager::Initialize` / `FlushAndTeardown`, or `evt_open` / + `evt_close`). Other modules obtain loggers (their own tenant/source) but never + initialize or tear down. This avoids teardown-ordering crashes. +* **Alternative — named instances.** `LogManagerProvider::CreateLogManager(id)` + gives each module its own instance/tenant/config sharing the one transport; then + you need a last-one-out teardown refcount and **distinct offline-cache paths** + (one shared path with multiple writers corrupts it). +* Teardown must happen exactly once, **last**, after every module has stopped + logging. + +--- + +## Ship exactly one copy on the loader path + +Place a single runtime where every module finds it: + +* **Windows:** the same directory as the consumers (or side-by-side assembly). +* **Linux:** `RPATH=$ORIGIN` so every module resolves the one copy. +* **macOS:** a stable install name, `@rpath/libmat.dylib`. + +Make exactly one package own and ship the SDK runtime; the others declare a +dependency rather than bundling their own. If several packages each ship their +own copy, which one loads is path-order luck — and if their versions differ, you +are back to an ABI mismatch even with "one" DLL. + +--- + +## Validate + +* **Dependency present, definitions absent.** `dumpbin /dependents` (Windows), + `ldd` (Linux), `otool -L` (macOS) on each consumer should show a dependency on + the one `mat` module; `dumpbin /exports` (or `nm -D`) on a consumer should show + it imports — not defines — the SDK symbols. +* **One copy at runtime.** Process Explorer / `/proc//maps` / `vmmap` should + map the `mat` module exactly once; there should be one offline-cache file. +* **(C++ option) CRT/STL smoke test.** Have a consumer pass a `std::string` event + property into the SDK and read it back. A `/MD` vs `/MT` or `_ITERATOR_DEBUG_LEVEL` + mismatch typically crashes immediately (especially in Debug). diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 533144c06..460e16a6a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -271,7 +271,7 @@ elseif(PAL_IMPLEMENTATION STREQUAL "WIN32") 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 -DMATSDK_SHARED_LIB=1 -D_UTC_SDK -DUSE_BOND -D_WINDOWS -D_USRDLL -DWINVER=_WIN32_WINNT_WIN7) +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) list(APPEND SRCS http/HttpClient_WinInet.cpp @@ -323,6 +323,23 @@ else() add_library(mat STATIC ${SRCS}) endif() +# Windows export/import decoration (MATSDK_LIBABI in lib/include/public/ctmacros.hpp). +# The SDK has no .def file, so __declspec(dllexport)/(dllimport) is the sole export +# mechanism, and it must follow the actual linkage: +# * shared: export the public API from the SDK build (PRIVATE), and make +# consumers import it. The INTERFACE define is carried by the installed +# MSTelemetry::mat target, so find_package() + link gives consumers +# dllimport automatically -- no consumer-side configuration. +# * static: decorate nothing (define on both sides), so the SDK's public symbols +# are NOT re-exported by a consumer DLL that absorbs this static lib. +if(WIN32) + if(BUILD_SHARED_LIBS) + target_compile_definitions(mat PRIVATE MATSDK_SHARED_LIB=1 INTERFACE MATSDK_IMPORT_LIB=1) + else() + target_compile_definitions(mat PUBLIC MATSDK_STATIC_LIB=1) + endif() +endif() + # Target-based include paths for vcpkg / install workflow. # PUBLIC propagates to consumers; PRIVATE is SDK-internal only. # BUILD_INTERFACE is used during the SDK build; INSTALL_INTERFACE is used diff --git a/lib/include/public/ctmacros.hpp b/lib/include/public/ctmacros.hpp index eb106b6fa..b3d5500d5 100644 --- a/lib/include/public/ctmacros.hpp +++ b/lib/include/public/ctmacros.hpp @@ -28,9 +28,14 @@ #define MATSDK_LIBABI_CDECL __cdecl # if defined(MATSDK_SHARED_LIB) # define MATSDK_LIBABI __declspec(dllexport) +# elif defined(MATSDK_IMPORT_LIB) +// Consumer importing the public API from a shared mat.dll. The installed +// MSTelemetry::mat CMake target propagates this automatically when the SDK was +// built shared (see lib/CMakeLists.txt). +# define MATSDK_LIBABI __declspec(dllimport) # elif defined(MATSDK_STATIC_LIB) # define MATSDK_LIBABI -# else // Header file included by client +# else // Header file included by client; linkage unspecified # ifndef MATSDK_LIBABI # define MATSDK_LIBABI # endif From 2867d2c7669ae5f00ae8b62de483373c155b74f8 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 23 Jun 2026 13:35:44 -0500 Subject: [PATCH 21/26] Scope -fvisibility-inlines-hidden to C++ to avoid -Werror C failures -fvisibility-inlines-hidden is a C++-only option. Applying it to all languages via add_compile_options meant the legacy Android path's bundled C sources (sqlite3_bundled, zlib_bundled) received it too; under Clang this emits an unused-argument warning that becomes an error with the project's -Werror. Scope it to CXX via a COMPILE_LANGUAGE generator expression while keeping -fvisibility=hidden for both C and C++. Files changed: - CMakeLists.txt: -fvisibility-inlines-hidden gated to \$ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5db922ab8..4b612e747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,7 +246,11 @@ endif() # achieve on MSVC. Windows already restricts exports via __declspec(dllexport) # on MATSDK_LIBABI (lib/include/public/ctmacros.hpp). if(NOT MSVC) - add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden) + # -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>) endif() include(tools/Utils.cmake) From 41fb5178b55e4b6dd798708f8e5aacc0e1918205 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 23 Jun 2026 14:00:00 -0500 Subject: [PATCH 22/26] Re-export ObjC wrapper classes in shared Apple builds The global -fvisibility=hidden (root CMakeLists.txt) hides Objective-C class symbols (_OBJC_CLASS_\*) as well as C++ internals. With the default BUILD_OBJC_WRAPPER=YES, a shared libmat.dylib on Apple would therefore export none of the public ODW* wrapper classes, and consumers linking against them would fail with undefined _OBJC_CLASS_\... symbols. Collect the ObjC wrapper translation units into OBJC_WRAPPER_SRCS and, for shared Apple builds, compile just those units with -fvisibility=default so the public Objective-C API is re-exported while the C++ core stays hidden. Files changed: - lib/CMakeLists.txt: OBJC_WRAPPER_SRCS variable + per-source -fvisibility=default for shared Apple builds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/CMakeLists.txt | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 10d850458..b7a388d8b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -216,7 +216,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") endif() if(APPLE AND BUILD_OBJC_WRAPPER) message(STATUS "Include ObjC Wrappers") - list(APPEND SRCS + set(OBJC_WRAPPER_SRCS ../wrappers/obj-c/ODWLogger.mm ../wrappers/obj-c/ODWLogManager.mm ../wrappers/obj-c/ODWEventProperties.mm @@ -227,22 +227,23 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") ../wrappers/obj-c/ODWSanitizerInitConfig.mm ) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer/") - list(APPEND SRCS + list(APPEND OBJC_WRAPPER_SRCS ../wrappers/obj-c/ODWDiagnosticDataViewer.mm ) endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard/" AND BUILD_PRIVACYGUARD) set(MATSDK_OBJC_PRIVACYGUARD_AVAILABLE ON) - list(APPEND SRCS + list(APPEND OBJC_WRAPPER_SRCS ../wrappers/obj-c/ODWPrivacyGuard.mm ) endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer/" AND BUILD_SANITIZER) set(MATSDK_OBJC_SANITIZER_AVAILABLE ON) - list(APPEND SRCS + list(APPEND OBJC_WRAPPER_SRCS ../wrappers/obj-c/ODWSanitizer.mm ) endif() + list(APPEND SRCS ${OBJC_WRAPPER_SRCS}) endif() if(APPLE AND BUILD_SWIFT_WRAPPER) @@ -356,6 +357,17 @@ target_include_directories(mat ) if(APPLE AND 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 + # the wrapper class symbols (_OBJC_CLASS_$_ODW*), which are public API on + # Apple: a shared libmat.dylib would export no ODW* classes and consumers + # would fail to link (undefined _OBJC_CLASS_$_...). Re-export just the wrapper + # translation units with default visibility; the C++ core stays hidden. + set_source_files_properties(${OBJC_WRAPPER_SRCS} + PROPERTIES COMPILE_FLAGS "-fvisibility=default") + endif() + if(MATSDK_OBJC_PRIVACYGUARD_AVAILABLE) target_compile_definitions(mat PRIVATE MATSDK_OBJC_PRIVACYGUARD_AVAILABLE=1) else() From e6e34373a057a0e9e1cfd54ef75ffb71f6a3cef7 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 23 Jun 2026 14:26:53 -0500 Subject: [PATCH 23/26] Use portable OR generator expression in footprint docs snippet The multi-config form \$ only matches on CMake >= 3.19; on older CMake it compares the literal string and never matches, so a consumer copy-pasting the snippet would silently fail to enable /OPT:REF, /OPT:ICF, and /INCREMENTAL:NO. Switch to \$,\$>, which is valid across all supported CMake versions. Files changed: - docs/building-with-vcpkg.md: OR-based CONFIG generator expression in the dead-strip snippet Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-with-vcpkg.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index 47878ba56..0eb8ee7bd 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -211,9 +211,9 @@ discard SDK code you never reference. Make sure your final link enables it: ```cmake target_link_options(your_target PRIVATE - $<$:/OPT:REF> - $<$:/OPT:ICF> - $<$:/INCREMENTAL:NO>) + $<$,$>:/OPT:REF> + $<$,$>:/OPT:ICF> + $<$,$>:/INCREMENTAL:NO>) ``` - **GCC / Clang:** link with `-Wl,--gc-sections`. From d1adf07c58ba801123aa55d07b765638bc96783f Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 23 Jun 2026 14:40:30 -0500 Subject: [PATCH 24/26] Gate visibility on NOT WIN32; clarify C-API dllimport doc wording Two review fixes: - CMakeLists.txt: the hidden-visibility block is described as non-Windows but was gated on NOT MSVC, which also matches MinGW/Clang-GNU Windows and would apply ELF-style -fvisibility=hidden to a PE/COFF target. Gate it on NOT WIN32 so all Windows toolchains rely on __declspec(dllexport) as intended. - docs/sharing-a-single-sdk-runtime.md: the C-API bullet said it 'links without __declspec(dllimport)', which contradicted the new MATSDK_IMPORT_LIB interface define. Reworded to: dllimport is not required (a C function resolves via the import-lib thunk) but is applied automatically to shared consumers as a harmless optimization. Files changed: - CMakeLists.txt: NOT MSVC -> NOT WIN32 for the visibility block - docs/sharing-a-single-sdk-runtime.md: C-API dllimport wording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 9 ++++++--- docs/sharing-a-single-sdk-runtime.md | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b612e747..360bf7436 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,9 +243,12 @@ endif() # 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. Windows already restricts exports via __declspec(dllexport) -# on MATSDK_LIBABI (lib/include/public/ctmacros.hpp). -if(NOT MSVC) +# 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 diff --git a/docs/sharing-a-single-sdk-runtime.md b/docs/sharing-a-single-sdk-runtime.md index feeb8c296..10e69746d 100644 --- a/docs/sharing-a-single-sdk-runtime.md +++ b/docs/sharing-a-single-sdk-runtime.md @@ -35,9 +35,12 @@ Consequences that make this the robust choice: `_ITERATOR_DEBUG_LEVEL`, MSVC toolset/STL version, libstdc++ vs libc++, `_GLIBCXX_USE_CXX11_ABI`, …). A 1DS version bump does not force every module to rebuild in lockstep against an identical toolchain. -* **It links without `__declspec(dllimport)`.** A plain C function resolves - through the shared library's import lib, so the C API works across the boundary - today. +* **It does not *require* `__declspec(dllimport)` to link.** A plain C function + resolves through the shared library's import lib even without `dllimport`, so + the C API works across the boundary regardless. Consumers that link the shared + `MSTelemetry::mat` target do get `dllimport` applied automatically (via the + `MATSDK_IMPORT_LIB` interface define this PR adds); for a C function that is a + harmless calling-convention optimization, not a requirement. Each module includes `mat.h`, links the one shared runtime, and uses its own tenant/source. You still pin the **same SDK version** in every module (so the From 7e652a9813f9a55e3498b461fd8ae1eed0eed10e Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 23 Jun 2026 15:24:15 -0500 Subject: [PATCH 25/26] Scope footprint guidance to static-link scenarios The 'Reducing binary footprint' section assumed the SDK is always linked statically, but vcpkg's default triplets (e.g. x64-windows) build dynamic libraries and the port also supports BUILD_SHARED_LIBS=ON. Clarify that the consumer-side dead-stripping guidance applies to static linkage, and note that a dynamic mat ships its own runtime whose export table is already trimmed by the SDK's -fvisibility=hidden and /Gy /Gw. Files changed: - docs/building-with-vcpkg.md: scope footprint section to static-link case Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/building-with-vcpkg.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index 0eb8ee7bd..7e5cff7d0 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -194,8 +194,13 @@ will automatically use the optimized zlib-ng build. ## Reducing binary footprint -The SDK links statically into your binary, so most footprint control lives on -*your* side of the link. +This section applies when the SDK is linked **statically** into your binary +(the default for the `*-static` vcpkg triplets) — most footprint control then +lives on *your* side of the link. If you instead consume a **dynamic** `mat` +(e.g. the default `x64-windows` triplet, or `BUILD_SHARED_LIBS=ON`), the runtime +ships as its own `mat.dll` / `libmat.so` / `libmat.dylib`; the SDK's own +`-fvisibility=hidden` and `/Gy /Gw` already trim its exported symbol table, and +the consumer-side linker options below are specific to the static-link case. ### Enable linker dead-stripping (largest lever) From 94434f7b117c1a7721e40fe5818cb0137661b6b5 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 23 Jun 2026 15:53:04 -0500 Subject: [PATCH 26/26] Gate non-Windows default-visibility on shared builds (mirror Windows) On non-Windows, MATSDK_LIBABI unconditionally expanded to __attribute__((visibility("default"))), so even in a static build the public API kept default visibility despite -fvisibility=hidden. A consumer that statically absorbs libmat.a into its own .so/.dylib would then unintentionally re-export the SDK's public API (larger dynamic symbol table, leaked SDK surface) -- the non-Windows analog of the Windows re-export that MATSDK_STATIC_LIB already prevents. Gate the visibility attribute on MATSDK_SHARED_LIB so it mirrors the __declspec(dllexport) gating: shared builds export the API; static builds omit the attribute, letting the public symbols inherit -fvisibility=hidden. Define MATSDK_SHARED_LIB PRIVATE for all shared builds in lib/CMakeLists.txt (keeping the Windows-only INTERFACE MATSDK_IMPORT_LIB and MATSDK_STATIC_LIB). Verified on Linux (readelf): static build -> evt_api_call_default is GLOBAL HIDDEN (still resolvable by static linking -- a consumer links+runs against libmat.a -- but not re-exported); shared build -> GLOBAL DEFAULT (exported from libmat.so). Files changed: - lib/include/public/ctmacros.hpp: non-Windows MATSDK_LIBABI gated on MATSDK_SHARED_LIB - lib/CMakeLists.txt: define MATSDK_SHARED_LIB for all shared builds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/CMakeLists.txt | 34 +++++++++++++++++++-------------- lib/include/public/ctmacros.hpp | 12 ++++++++---- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b7a388d8b..0d4161811 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -324,21 +324,27 @@ else() add_library(mat STATIC ${SRCS}) endif() -# Windows export/import decoration (MATSDK_LIBABI in lib/include/public/ctmacros.hpp). -# The SDK has no .def file, so __declspec(dllexport)/(dllimport) is the sole export -# mechanism, and it must follow the actual linkage: -# * shared: export the public API from the SDK build (PRIVATE), and make -# consumers import it. The INTERFACE define is carried by the installed -# MSTelemetry::mat target, so find_package() + link gives consumers -# dllimport automatically -- no consumer-side configuration. -# * static: decorate nothing (define on both sides), so the SDK's public symbols -# are NOT re-exported by a consumer DLL that absorbs this static lib. -if(WIN32) - if(BUILD_SHARED_LIBS) - target_compile_definitions(mat PRIVATE MATSDK_SHARED_LIB=1 INTERFACE MATSDK_IMPORT_LIB=1) - else() - target_compile_definitions(mat PUBLIC MATSDK_STATIC_LIB=1) +# 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 +# __attribute__((visibility("default"))) elsewhere are the sole export mechanisms, +# and the decoration must follow the actual linkage: +# * shared: the SDK's own translation units export the public API +# (MATSDK_SHARED_LIB, PRIVATE). On Windows, consumers must additionally import +# it -- the INTERFACE MATSDK_IMPORT_LIB is carried by the installed +# MSTelemetry::mat target, so find_package() + link gives consumers dllimport +# automatically with no consumer-side configuration. Non-Windows consumers need +# nothing: they call symbols exported by libmat.so/.dylib. +# * static: decorate nothing, so the SDK's public symbols are NOT re-exported by +# a consumer DLL/.so that statically absorbs this library. Windows needs an +# explicit (empty) MATSDK_STATIC_LIB; elsewhere the empty MATSDK_LIBABI default +# plus -fvisibility=hidden (root CMakeLists.txt) already hides them. +if(BUILD_SHARED_LIBS) + target_compile_definitions(mat PRIVATE MATSDK_SHARED_LIB=1) + if(WIN32) + target_compile_definitions(mat INTERFACE MATSDK_IMPORT_LIB=1) endif() +elseif(WIN32) + target_compile_definitions(mat PUBLIC MATSDK_STATIC_LIB=1) endif() # Target-based include paths for vcpkg / install workflow. diff --git a/lib/include/public/ctmacros.hpp b/lib/include/public/ctmacros.hpp index b3d5500d5..cabd36f5f 100644 --- a/lib/include/public/ctmacros.hpp +++ b/lib/include/public/ctmacros.hpp @@ -53,10 +53,14 @@ #endif #ifndef MATSDK_LIBABI -// Mark the public API as default-visibility so it stays exported when the SDK is -// built with -fvisibility=hidden (see CMakeLists.txt). This is the non-Windows -// analog of the __declspec(dllexport) gating above. Harmless without the flag. -# if defined(__GNUC__) || defined(__clang__) +// Mark the public API as default-visibility ONLY in shared builds, so it stays +// exported when the SDK is compiled with -fvisibility=hidden (see CMakeLists.txt). +// This mirrors the __declspec(dllexport) gating above: in a static build the +// attribute is omitted, so the public symbols inherit -fvisibility=hidden and are +// NOT re-exported when a consumer .so/.dylib statically absorbs libmat. (When a +// consumer includes this header, MATSDK_SHARED_LIB is not defined either, which +// is fine: the symbols are exported by the shared libmat they link against.) +# if (defined(__GNUC__) || defined(__clang__)) && defined(MATSDK_SHARED_LIB) # define MATSDK_LIBABI __attribute__((visibility("default"))) # else # define MATSDK_LIBABI