diff --git a/.github/scripts/prepare-vcpkg-release.py b/.github/scripts/prepare-vcpkg-release.py new file mode 100644 index 000000000..529eda2b6 --- /dev/null +++ b/.github/scripts/prepare-vcpkg-release.py @@ -0,0 +1,76 @@ +"""Copy the release's complete overlay port into a vcpkg checkout.""" + +import argparse +import json +from pathlib import Path +import re +import shutil + + +def prepare_port(source_port, destination_port, version, sha512): + if not re.fullmatch(r"\d+\.\d+\.\d+\.\d+", version): + raise ValueError("Expected a four-component SDK release version") + if not re.fullmatch(r"[0-9a-fA-F]{128}", sha512): + raise ValueError("Expected a 128-character hexadecimal SHA512") + source_port = source_port.resolve() + destination_port = destination_port.resolve() + if destination_port.name != "cpp-client-telemetry" or destination_port.parent.name != "ports": + raise ValueError("Destination must be a ports/cpp-client-telemetry directory") + if ( + source_port == destination_port + or source_port in destination_port.parents + or destination_port in source_port.parents + ): + raise ValueError("Source and destination ports must not overlap") + vcpkg_root = destination_port.parent.parent + if ( + not (vcpkg_root / ".vcpkg-root").is_file() + or not (vcpkg_root / "scripts" / "buildsystems" / "vcpkg.cmake").is_file() + ): + raise ValueError( + "Destination must belong to a vcpkg checkout containing " + ".vcpkg-root and scripts/buildsystems/vcpkg.cmake" + ) + + manifest = json.loads((source_port / "vcpkg.json").read_text(encoding="utf-8")) + if manifest["name"] != "cpp-client-telemetry": + raise ValueError("Release overlay is not the cpp-client-telemetry port") + for feature in ("minimal-sqlite", "android-curl-openssl"): + if feature not in manifest.get("features", {}): + raise ValueError(f"Release overlay is missing the {feature} feature") + manifest["version"] = version + manifest.pop("port-version", None) + + portfile = (source_port / "portfile.cmake").read_text(encoding="utf-8") + for field, value in (("REF", f"v{version}"), ("SHA512", sha512)): + portfile, count = re.subn( + rf"(?m)^([ \t]*{field}[ \t]+)[^\r\n]+", + lambda match: match[1] + value, + portfile, + ) + if count != 1: + raise ValueError(f"Release portfile must contain exactly one {field}") + + # Replace the complete port so obsolete patches cannot survive a release. + if destination_port.exists(): + shutil.rmtree(destination_port) + shutil.copytree(source_port, destination_port) + (destination_port / "portfile.cmake").write_text(portfile, encoding="utf-8", newline="\n") + (destination_port / "vcpkg.json").write_text( + json.dumps(manifest, indent=2) + "\n", encoding="utf-8", newline="\n" + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-port", type=Path, required=True) + parser.add_argument("--destination-port", type=Path, required=True) + parser.add_argument("--version", required=True) + parser.add_argument("--sha512", required=True) + arguments = parser.parse_args() + prepare_port( + arguments.source_port, + arguments.destination_port, + arguments.version, + arguments.sha512, + ) diff --git a/.github/workflows/test-vcpkg.yml b/.github/workflows/test-vcpkg.yml index 98ef86429..7cae03115 100644 --- a/.github/workflows/test-vcpkg.yml +++ b/.github/workflows/test-vcpkg.yml @@ -22,6 +22,18 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + release-packaging: + runs-on: ${{ matrix.os }} + name: Release packaging (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - name: Test release port promotion + run: python tests/vcpkg/test-release-port.py + windows: runs-on: windows-latest name: Windows (x64-windows-static, ${{ matrix.transport }}) @@ -49,7 +61,11 @@ jobs: linux: runs-on: ubuntu-latest - name: Linux (x64-linux) + name: Linux (x64-linux, ${{ matrix.features }}) + strategy: + fail-fast: false + matrix: + features: [default, minimal-sqlite] steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 @@ -63,7 +79,7 @@ jobs: VCPKG_ROOT: ${{ runner.temp }}/vcpkg run: | chmod +x tests/vcpkg/test-vcpkg-linux.sh - ./tests/vcpkg/test-vcpkg-linux.sh + ./tests/vcpkg/test-vcpkg-linux.sh "${{ matrix.features }}" macos: runs-on: macos-latest @@ -103,7 +119,11 @@ jobs: android: runs-on: ubuntu-latest - name: Android (arm64-v8a API 23 cross-compile) + name: Android (arm64-v8a API 23, ${{ matrix.features }}) + strategy: + fail-fast: false + matrix: + features: [default, minimal-sqlite, "minimal-sqlite;android-curl-openssl", "minimal-sqlite;android-curl-mbedtls"] steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 @@ -120,4 +140,4 @@ jobs: VCPKG_ROOT: ${{ runner.temp }}/vcpkg run: | chmod +x tests/vcpkg/test-vcpkg-android.sh - ./tests/vcpkg/test-vcpkg-android.sh arm64-v8a 23 + ./tests/vcpkg/test-vcpkg-android.sh arm64-v8a 23 "${{ matrix.features }}" diff --git a/.github/workflows/vcpkg-release-bump.yml b/.github/workflows/vcpkg-release-bump.yml deleted file mode 100644 index 77ed47444..000000000 --- a/.github/workflows/vcpkg-release-bump.yml +++ /dev/null @@ -1,218 +0,0 @@ -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 || github.event.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 - 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: ${{ github.event.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 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 - 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" - 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 - if: ${{ steps.ver.outputs.skip != 'true' }} - 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 - if: ${{ steps.ver.outputs.skip != 'true' }} - env: - GH_TOKEN: ${{ secrets.VCPKG_BUMP_TOKEN }} - run: | - set -euo pipefail - # 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 - 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 - 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 - 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: Validate updated production port - if: ${{ steps.ver.outputs.skip != 'true' }} - run: | - set -euo pipefail - cd vcpkg - MANIFEST="ports/${PORT}/vcpkg.json" - - # Exercise the real production path: MATSDK_VCPKG_SOURCE_DIR must be - # unset so the port downloads the just-updated REF/SHA512 instead of - # accidentally validating this workflow's working tree. This catches - # manifest/portfile changes that require source changes not present in - # the release tag. - unset MATSDK_VCPKG_SOURCE_DIR - - PORT_SPEC="${PORT}" - if jq -e '(.features["minimal-sqlite"] != null) and (.features["curl-openssl"] != null)' "${MANIFEST}" >/dev/null; then - # Use an opt-in feature set when available so release validation covers - # feature wiring as well as the default graph. The default graph is - # still covered by regular vcpkg CI and by consumers. - PORT_SPEC="${PORT}[core,minimal-sqlite,curl-openssl]" - fi - - echo "Validating production port: ${PORT_SPEC}" - ./vcpkg install "${PORT_SPEC}" --triplet x64-linux --clean-after-build - - - name: Detect change - id: diff - if: ${{ steps.ver.outputs.skip != 'true' }} - 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.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 }}" - ./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 // empty' 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 diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index a4aa85a3c..36feeea17 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -449,6 +449,42 @@ unchanged against the minimal build. ## How It Works +### Promoting features on release + +After cutting an SDK release, maintainers manually prepare and submit the +registry port update. There is no release-triggered workflow that creates a +vcpkg PR, and no repository token is needed for port preparation. + +The [preparation helper](../.github/scripts/prepare-vcpkg-release.py) copies the +**complete overlay port from the release tag**, not from the current development +branch. It then replaces the archive +`REF`, `SHA512`, and manifest version and removes any old `port-version`. This +carries feature declarations and their CMake wiring together, including +`minimal-sqlite` and the explicit Android curl backends. Run it from the released +SDK checkout against a separate vcpkg checkout outside the SDK source tree. +Start with a clean vcpkg working tree: the helper replaces the destination port, +including existing patches. Before replacing it, the helper resolves the +destination and requires both `.vcpkg-root` and +`scripts/buildsystems/vcpkg.cmake` in the inferred vcpkg checkout. +The helper requires Python 3.10 or newer and uses only the standard library. +Pass the four-component version **without** its `v` prefix and the SHA512 of the +published GitHub source `.tar.gz` archive, not an xcframework, AAR, or ZIP: + +```console +python .github/scripts/prepare-vcpkg-release.py --source-port tools/ports/cpp-client-telemetry --destination-port /ports/cpp-client-telemetry --version X.Y.Z.W --sha512 +``` + +Run `vcpkg format-manifest` on the resulting manifest and validate the downloaded +release with `MATSDK_VCPKG_SOURCE_DIR` unset and without the SDK overlay before +submitting the registry PR. Cover the default graph and the advertised opt-in +features. Do not use the `tests/vcpkg` scripts for this release validation: they +intentionally build the local SDK checkout instead of the pinned archive. +The helper does not build, commit, push, or open a PR. After validating the port, +commit its changes, update the vcpkg version database, and submit the registry PR +using your normal local GitHub authentication. + +### Dependency and transport selection + 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 diff --git a/docs/maintainer-onboarding.md b/docs/maintainer-onboarding.md index 25f423a51..02209f911 100644 --- a/docs/maintainer-onboarding.md +++ b/docs/maintainer-onboarding.md @@ -38,6 +38,19 @@ requires those modules and your GitHub account has access: git clone --recurse-submodules https://github.com/microsoft/cpp_client_telemetry.git ``` +The [modules repository](https://github.com/microsoft/cpp_client_telemetry_modules) +requires a GitHub account with access to that repository. See the +[Microsoft everyone team](https://github.com/orgs/microsoft/teams/everyone) for +Microsoft organization membership; confirm repository permissions before +cloning. + +Keep the SDK checkout ref in the modules repository's CI workflows aligned with +a tested commit on this SDK's `main` branch so module tests cover current SDK +changes. In the other direction, after module PRs merge, update the pinned +`lib/modules` submodule commit in this SDK through a reviewed PR and validate the +combined build. A recursive clone checks out that pinned commit; it does not +automatically follow the modules repository's latest HEAD. + Do not make core behavior accidentally depend on a private module. Builds that do not fetch `lib/modules` must continue to compile and link. @@ -78,6 +91,11 @@ Treat a warning suppression, skipped test, conditional source exclusion, or whether the changed code makes that claim invalid. Fix the underlying defect instead of broadening a suppression. +## OneCollector + +For questions about OneCollector, contact the +[Collector team](https://teams.microsoft.com/l/channel/19%3A3b4fbc0eaff54e2aa6e373acfc46fe1f%40thread.skype/AEF%2C%20Collector%2C%20and%20Interchange?groupId=5658f840-c680-4882-93be-7cc69578f94e&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47). + ## Building locally ### CMake presets @@ -149,8 +167,10 @@ C API or its implementation. ### vcpkg consumer tests -The scripts under `tests/vcpkg` configure, build, link, and run a separate -consumer against the package: +The scripts under `tests/vcpkg` configure, build, and link a separate consumer +against the working-tree overlay port. Host builds run the consumer; iOS +Simulator builds run it in the simulator, while Android and iOS device builds +are cross-compile checks that require separate device deployment for execution: ```powershell .\tests\vcpkg\test-vcpkg-windows.ps1 -VcpkgRoot C:\path\to\vcpkg @@ -328,33 +348,29 @@ must reach SPM, choose a new `X.Y.Z` in `Solutions/version.txt` and regenerate `Version.hpp` before tagging: changing only `W` produces the same SPM tag, so the SPM workflow skips it and leaves consumers on the earlier package. -### Verify downstream publication - -Publishing the GitHub Release triggers: - -- `.github/workflows/spm-release.yml`, which builds and validates - `MATTelemetry.xcframework`, uploads it to the release, and publishes the - parallel three-component Swift Package Manager tag. -- `.github/workflows/vcpkg-release-bump.yml`, which can prepare a - `microsoft/vcpkg` port update when its fork and credential settings are - configured. - -Check both workflows; do not assume that the release trigger completed all -distribution work. - -The vcpkg workflow requires repository configuration and a token capable of -pushing to a fork and opening the upstream PR. If those credentials are -intentionally unavailable, manually: - -1. Branch from current `microsoft/vcpkg` `master`. -2. Update `ports/cpp-client-telemetry/portfile.cmake` to the new tag and source - archive SHA512. -3. Update and format the port manifest. -4. Run `vcpkg x-add-version cpp-client-telemetry --overwrite-version`. -5. Build the production port from the published archive, not from a local SDK - source override. -6. Commit the port and version-database changes and open a - `microsoft/vcpkg` PR. +### Publish and verify downstream packages + +Publishing the GitHub Release triggers `.github/workflows/spm-release.yml`, +which builds and validates `MATTelemetry.xcframework`, uploads it to the release, +and publishes the parallel three-component Swift Package Manager tag. Check the +workflow output to ensure it has succeeded. + +The vcpkg port update is manual: + +1. Start with a clean vcpkg checkout outside the SDK source tree and branch from + current `microsoft/vcpkg` `master`. +2. From the released SDK checkout, use the + [port preparation helper](building-with-vcpkg.md#promoting-features-on-release) + to copy the complete overlay port and set the new release version and source + archive SHA512. Include the manifest and feature wiring, not just the tag. +3. Format the port manifest and build the production port from the published + archive with `MATSDK_VCPKG_SOURCE_DIR` unset and without the SDK overlay. + Verify both the default graph and the release's advertised opt-in features. + Do not use the `tests/vcpkg` scripts for this step: they deliberately select + local SDK source rather than the pinned release archive. +4. Commit the port changes. +5. Run `vcpkg x-add-version cpp-client-telemetry --overwrite-version`. +6. Commit the version-database changes and manually open a `microsoft/vcpkg` PR. Coordinate updates to other maintained distribution channels, including CocoaPods, when the release is expected to be available there. Validate each @@ -368,7 +384,8 @@ artifact exists. - Release notes describe compatibility or migration requirements. - SPM artifact, checksum, external consumer build, and three-component tag succeeded. -- The vcpkg port PR is open or the existing port already matches the release. +- The vcpkg port PR is open or the registry already packages the release and its + advertised features. - Other maintained package feeds have been updated or explicitly tracked. - A representative consumer can build against the released package. - Release-critical telemetry changes have an authorized end-to-end ingestion diff --git a/tests/vcpkg/test-release-port.py b/tests/vcpkg/test-release-port.py new file mode 100644 index 000000000..b637627ae --- /dev/null +++ b/tests/vcpkg/test-release-port.py @@ -0,0 +1,154 @@ +"""Regression tests for promoting the release overlay into the registry.""" + +import importlib.util +import json +from pathlib import Path +import re +import tempfile +import unittest + + +REPO_ROOT = Path(__file__).resolve().parents[2] +SPEC = importlib.util.spec_from_file_location( + "prepare_vcpkg_release", REPO_ROOT / ".github" / "scripts" / "prepare-vcpkg-release.py" +) +PREPARE = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(PREPARE) + + +class ReleasePortTests(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + root = Path(self.temporary.name) + self.source = root / "release" / "tools" / "ports" / "cpp-client-telemetry" + self.source.mkdir(parents=True) + self.destination = root / "vcpkg" / "ports" / "cpp-client-telemetry" + self.destination.mkdir(parents=True) + vcpkg_root = self.destination.parent.parent + self.checkout_markers = ( + vcpkg_root / ".vcpkg-root", + vcpkg_root / "scripts" / "buildsystems" / "vcpkg.cmake", + ) + for marker in self.checkout_markers: + marker.parent.mkdir(parents=True, exist_ok=True) + marker.touch() + (self.destination / "obsolete.patch").write_text("old patch", encoding="utf-8") + self.manifest = json.loads( + (REPO_ROOT / "tools" / "ports" / "cpp-client-telemetry" / "vcpkg.json").read_text( + encoding="utf-8" + ) + ) + self.manifest["port-version"] = 7 + self.write_manifest() + self.portfile = ( + REPO_ROOT / "tools" / "ports" / "cpp-client-telemetry" / "portfile.cmake" + ).read_text(encoding="utf-8") + (self.source / "portfile.cmake").write_text(self.portfile, encoding="utf-8") + + def write_manifest(self): + (self.source / "vcpkg.json").write_text(json.dumps(self.manifest), encoding="utf-8") + + def prepare(self): + PREPARE.prepare_port(self.source, self.destination, "3.10.999.1", "a" * 128) + + def test_promotes_complete_release_port(self): + (self.source / "release.patch").write_text("new patch", encoding="utf-8") + self.prepare() + actual = json.loads((self.destination / "vcpkg.json").read_text(encoding="utf-8")) + expected = dict(self.manifest, version="3.10.999.1") + del expected["port-version"] + self.assertEqual(actual, expected) + portfile = (self.destination / "portfile.cmake").read_text(encoding="utf-8") + self.assertIn("REF v3.10.999.1\n", portfile) + self.assertIn(f"SHA512 {'a' * 128}\n", portfile) + self.assertIn("-DMATSDK_SQLITE_PROVIDER=${MATSDK_VCPKG_SQLITE_PROVIDER}", portfile) + self.assertIn("-DMATSDK_ANDROID_HTTP_CLIENT=${MATSDK_ANDROID_HTTP_CLIENT}", portfile) + self.assertEqual((self.destination / "release.patch").read_text(), "new patch") + self.assertFalse((self.destination / "obsolete.patch").exists()) + self.assertEqual( + json.loads((self.source / "vcpkg.json").read_text(encoding="utf-8")), self.manifest + ) + self.prepare() + self.assertEqual((self.destination / "portfile.cmake").read_text(), portfile) + + def test_rejects_release_missing_required_features_before_replacing_port(self): + for feature in ("minimal-sqlite", "android-curl-openssl"): + with self.subTest(feature=feature): + definition = self.manifest["features"].pop(feature) + self.write_manifest() + with self.assertRaisesRegex(ValueError, feature): + self.prepare() + self.assertTrue((self.destination / "obsolete.patch").exists()) + self.manifest["features"][feature] = definition + + def test_rejects_missing_or_duplicate_archive_fields(self): + for field in ("REF", "SHA512"): + for replacement in ("", f" {field} duplicate\n {field} duplicate\n"): + with self.subTest(field=field, replacement=replacement): + malformed = re.sub( + rf"(?m)^[ \t]*{field}[ \t]+[^\n]*\n", replacement, self.portfile + ) + (self.source / "portfile.cmake").write_text(malformed, encoding="utf-8") + with self.assertRaisesRegex(ValueError, field): + self.prepare() + self.assertTrue((self.destination / "obsolete.patch").exists()) + + def test_rejects_invalid_release_metadata(self): + for version, sha512 in (("v3.10.999.1", "a" * 128), ("3.10.999.1", "invalid")): + with self.subTest(version=version, sha512=sha512): + with self.assertRaises(ValueError): + PREPARE.prepare_port(self.source, self.destination, version, sha512) + self.assertTrue((self.destination / "obsolete.patch").exists()) + + def test_rejects_destination_outside_the_port(self): + with self.assertRaisesRegex(ValueError, "Destination"): + PREPARE.prepare_port(self.source, self.destination.parent, "3.10.999.1", "a" * 128) + + def test_describes_invalid_sha512_as_hexadecimal_characters(self): + for sha512 in ("a" * 127, "a" * 129, "g" * 128): + with self.subTest(sha512=sha512): + with self.assertRaisesRegex( + ValueError, "^Expected a 128-character hexadecimal SHA512$" + ): + PREPARE.prepare_port(self.source, self.destination, "3.10.999.1", sha512) + self.assertTrue((self.destination / "obsolete.patch").exists()) + + def test_rejects_overlapping_ports(self): + with self.assertRaisesRegex(ValueError, "overlap"): + PREPARE.prepare_port(self.source, self.source, "3.10.999.1", "a" * 128) + self.assertTrue((self.source / "portfile.cmake").exists()) + + def test_rejects_lookalike_non_vcpkg_destination_without_deleting_files(self): + lookalike = Path(self.temporary.name) / "other-project" / "ports" / "cpp-client-telemetry" + lookalike.mkdir(parents=True) + sentinel = lookalike / "keep.txt" + sentinel.write_text("unrelated project", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "vcpkg checkout"): + PREPARE.prepare_port(self.source, lookalike, "3.10.999.1", "a" * 128) + self.assertEqual(sentinel.read_text(encoding="utf-8"), "unrelated project") + self.assertEqual(list(lookalike.iterdir()), [sentinel]) + + def test_requires_both_checkout_markers_before_replacing_port(self): + for marker in self.checkout_markers: + with self.subTest(marker=marker.name): + marker.unlink() + with self.assertRaisesRegex(ValueError, "vcpkg checkout"): + self.prepare() + self.assertEqual( + (self.destination / "obsolete.patch").read_text(encoding="utf-8"), "old patch" + ) + marker.touch() + + def test_validates_and_replaces_the_resolved_destination(self): + alias = self.destination / ".." / "cpp-client-telemetry" + PREPARE.prepare_port(self.source, alias, "3.10.999.1", "a" * 128) + self.assertFalse((self.destination / "obsolete.patch").exists()) + self.assertEqual( + json.loads((self.destination / "vcpkg.json").read_text(encoding="utf-8"))["version"], + "3.10.999.1", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/vcpkg/test-vcpkg-android.sh b/tests/vcpkg/test-vcpkg-android.sh index f49a24195..af9bc6a88 100755 --- a/tests/vcpkg/test-vcpkg-android.sh +++ b/tests/vcpkg/test-vcpkg-android.sh @@ -1,8 +1,9 @@ #!/bin/bash # Test script: Verify cpp-client-telemetry vcpkg port for Android (cross-compile only) -# Usage: ./tests/vcpkg/test-vcpkg-android.sh [ABI] [API_LEVEL] +# Usage: ./tests/vcpkg/test-vcpkg-android.sh [ABI] [API_LEVEL] [FEATURES] # ABI: arm64-v8a (default), armeabi-v7a, x86_64, x86 # API_LEVEL: 23 (default), 28, or another level with a matching overlay triplet +# FEATURES: default, minimal-sqlite, or minimal-sqlite;android-curl-{openssl,mbedtls} # Prerequisites: VCPKG_ROOT set, ANDROID_NDK_HOME set, cmake, ninja set -e @@ -17,6 +18,18 @@ export MATSDK_VCPKG_SOURCE_DIR="${REPO_ROOT}" # Android ABI/API (defaults match the repo's Android minSdk) ANDROID_ABI="${1:-arm64-v8a}" ANDROID_API="${2:-23}" +MANIFEST_ARGS=() +case "${3:-default}" in + default) ;; + minimal-sqlite|"minimal-sqlite;android-curl-openssl"|"minimal-sqlite;android-curl-mbedtls") + MANIFEST_ARGS=(-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON "-DVCPKG_MANIFEST_FEATURES=${3}") + ;; + *) + echo "ERROR: Unsupported feature set '${3}'. Use default, minimal-sqlite," + echo " minimal-sqlite;android-curl-openssl, or minimal-sqlite;android-curl-mbedtls." + exit 1 + ;; +esac # Map ABI to vcpkg triplet case "${ANDROID_ABI}" in @@ -97,6 +110,7 @@ cmake -G Ninja -S "${SCRIPT_DIR}" -B "${BUILD_DIR}/consumer" \ -DCMAKE_TOOLCHAIN_FILE="${VCPKG_TOOLCHAIN}" \ -DVCPKG_TARGET_TRIPLET="${TRIPLET}" \ -DVCPKG_OVERLAY_PORTS="${OVERLAY_PORTS}" \ + "${MANIFEST_ARGS[@]}" \ "${OVERLAY_TRIPLETS_ARGS[@]}" \ -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" \ -DANDROID_ABI="${ANDROID_ABI}" \ diff --git a/tests/vcpkg/test-vcpkg-linux.sh b/tests/vcpkg/test-vcpkg-linux.sh index d98757db8..06b03293d 100755 --- a/tests/vcpkg/test-vcpkg-linux.sh +++ b/tests/vcpkg/test-vcpkg-linux.sh @@ -1,6 +1,6 @@ #!/bin/bash # Test script: Verify cpp-client-telemetry vcpkg port on Linux -# Usage: ./tests/vcpkg/test-vcpkg-linux.sh +# Usage: ./tests/vcpkg/test-vcpkg-linux.sh [default|minimal-sqlite] # Prerequisites: VCPKG_ROOT set, gcc/g++, cmake set -e @@ -8,6 +8,17 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" BUILD_DIR="${SCRIPT_DIR}/build-linux" OVERLAY_PORTS="${REPO_ROOT}/tools/ports" +MANIFEST_ARGS=() +case "${1:-default}" in + default) ;; + minimal-sqlite) + MANIFEST_ARGS=(-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON -DVCPKG_MANIFEST_FEATURES=minimal-sqlite) + ;; + *) + echo "ERROR: Unsupported feature set '${1}'. Use default or minimal-sqlite." + exit 1 + ;; +esac # Build the working tree under review (not a pinned release) so this test # validates the actual SDK source together with the port manifest/portfile. @@ -48,6 +59,7 @@ cmake -S "${SCRIPT_DIR}" -B "${BUILD_DIR}/consumer" \ -DCMAKE_TOOLCHAIN_FILE="${VCPKG_TOOLCHAIN}" \ -DVCPKG_TARGET_TRIPLET="${TRIPLET}" \ -DVCPKG_OVERLAY_PORTS="${OVERLAY_PORTS}" \ + "${MANIFEST_ARGS[@]}" \ -DCMAKE_BUILD_TYPE=Release echo "" diff --git a/tests/vcpkg/vcpkg.json b/tests/vcpkg/vcpkg.json index dbcee9cfc..df03c0829 100644 --- a/tests/vcpkg/vcpkg.json +++ b/tests/vcpkg/vcpkg.json @@ -3,9 +3,60 @@ "version-string": "0.0.1", "description": "Integration test for cpp-client-telemetry vcpkg port", "dependencies": [ - "cpp-client-telemetry" + { + "name": "cpp-client-telemetry", + "default-features": false + } + ], + "default-features": [ + "sdk-defaults" ], "features": { + "android-curl-mbedtls": { + "description": "Exercise the native Android curl transport with mbedTLS.", + "supports": "android", + "dependencies": [ + { + "name": "cpp-client-telemetry", + "default-features": false, + "features": [ + "android-curl-mbedtls" + ] + } + ] + }, + "android-curl-openssl": { + "description": "Exercise the native Android curl transport with OpenSSL.", + "supports": "android", + "dependencies": [ + { + "name": "cpp-client-telemetry", + "default-features": false, + "features": [ + "android-curl-openssl" + ] + } + ] + }, + "minimal-sqlite": { + "description": "Exercise private SQLite without the external sqlite3 dependency.", + "dependencies": [ + { + "name": "cpp-client-telemetry", + "default-features": false, + "features": [ + "curl-openssl", + "minimal-sqlite" + ] + } + ] + }, + "sdk-defaults": { + "description": "Exercise the default SDK dependency graph.", + "dependencies": [ + "cpp-client-telemetry" + ] + }, "wininet": { "description": "Exercise the cpp-client-telemetry WinInet feature on Windows.", "supports": "windows & !mingw",