diff --git a/.github/scripts/prepare-vcpkg-release.py b/.github/scripts/prepare-vcpkg-release.py index 529eda2b6..30c1beaac 100644 --- a/.github/scripts/prepare-vcpkg-release.py +++ b/.github/scripts/prepare-vcpkg-release.py @@ -1,4 +1,4 @@ -"""Copy the release's complete overlay port into a vcpkg checkout.""" +"""Copy the release's registry recipe into a vcpkg checkout.""" import argparse import json @@ -34,14 +34,27 @@ def prepare_port(source_port, destination_port, version, sha512): 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") + raise ValueError("Release recipe is not the cpp-client-telemetry port") + required_features = {"device-id", "android-capi-http-client"} + actual_features = set(manifest.get("features", {})) + unsupported_features = actual_features - required_features + if unsupported_features: + raise ValueError( + "Release recipe has unsupported features: " + + ", ".join(sorted(unsupported_features)) + ) + missing_features = required_features - actual_features + if missing_features: + raise ValueError( + "Release recipe is missing required features: " + + ", ".join(sorted(missing_features)) + ) manifest["version"] = version manifest.pop("port-version", None) portfile = (source_port / "portfile.cmake").read_text(encoding="utf-8") + if "MATSDK_VCPKG_SOURCE_DIR" in portfile: + raise ValueError("Registry recipes must not allow development source overrides") for field, value in (("REF", f"v{version}"), ("SHA512", sha512)): portfile, count = re.subn( rf"(?m)^([ \t]*{field}[ \t]+)[^\r\n]+", diff --git a/.github/workflows/test-embedding.yml b/.github/workflows/test-embedding.yml index 54980ed04..2a0795866 100644 --- a/.github/workflows/test-embedding.yml +++ b/.github/workflows/test-embedding.yml @@ -83,6 +83,16 @@ jobs: run: | cmake --build build-embedding --target embedding_test --parallel 4 ctest --test-dir build-embedding --output-on-failure + - name: Consume installed caller-provided package + if: matrix.preload-storage == 'ON' + run: | + cmake --install build-embedding --prefix "${RUNNER_TEMP}/matsdk" + cmake -G Ninja -S tests/vcpkg -B build-provided-consumer \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/matsdk" \ + -DMATSDK_TEST_PRELOAD_DEPENDENCIES=ON + cmake --build build-provided-consumer --parallel 4 + ctest --test-dir build-provided-consumer --output-on-failure windows: runs-on: windows-2022 @@ -100,6 +110,39 @@ jobs: cmake --build build-embedding --config Release --target embedding_test -- /m ctest --test-dir build-embedding -C Release --output-on-failure + sqlite-coexistence: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-2022, macos-latest] + shared: ["OFF", "ON"] + sqlite: [MINIMAL, VENDORED] + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - name: Install Linux HTTP dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y libcurl4-openssl-dev + - name: Configure source consumer + run: > + cmake -S tests/package-coexistence -B build-coexistence + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHARED_LIBS=${{ matrix.shared }} + -DMATSDK_SQLITE_PROVIDER=${{ matrix.sqlite }} + -DMATSDK_ZLIB_PROVIDER=VENDORED + - name: Build, install, and test both consumers + shell: bash + run: | + cmake --build build-coexistence --config Release --parallel 4 + ctest --test-dir build-coexistence -C Release --output-on-failure + cmake --install build-coexistence --config Release --prefix "${RUNNER_TEMP}/matsdk" + cmake -S tests/package-coexistence -B build-installed-coexistence \ + -DCMAKE_BUILD_TYPE=Release \ + -DMATSDK_SQLITE_PROVIDER=${{ matrix.sqlite }} \ + -DMATSDK_INSTALLED_PREFIX="${RUNNER_TEMP}/matsdk" + cmake --build build-installed-coexistence --config Release --parallel 4 + ctest --test-dir build-installed-coexistence -C Release --output-on-failure + installed-package-linux: runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/test-vcpkg.yml b/.github/workflows/test-vcpkg.yml index 48ff3d718..a93abedac 100644 --- a/.github/workflows/test-vcpkg.yml +++ b/.github/workflows/test-vcpkg.yml @@ -35,6 +35,10 @@ jobs: run: python tests/vcpkg/test-release-port.py - name: Verify native feature mappings run: cmake -P tests/vcpkg/native-feature-tests.cmake + - name: Verify registry additive features and platform transports + run: cmake -DREGISTRY_MODE=ON -P tests/vcpkg/native-feature-tests.cmake + - name: Verify installed package dependency selection + run: cmake -P tests/vcpkg/package-dependency-tests.cmake windows: runs-on: windows-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index d47c0167a..f56eb8f7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,15 @@ cmake_minimum_required(VERSION 3.15...3.31) project(MSTelemetry LANGUAGES C CXX) -if(APPLE) - set(MATSDK_BUILD_PLATFORM_APPLE TRUE) -else() - set(MATSDK_BUILD_PLATFORM_APPLE FALSE) + +# Snapshot caller targets before curl discovery can import storage dependencies. +set(MATSDK_SQLITE_DEPENDENCY_KIND PACKAGE) +if(TARGET SQLite3::SQLite3 OR TARGET SQLite::SQLite3) + set(MATSDK_SQLITE_DEPENDENCY_KIND PROVIDED) +endif() +set(MATSDK_ZLIB_DEPENDENCY_KIND PACKAGE) +if(TARGET ZLIB::ZLIB) + set(MATSDK_ZLIB_DEPENDENCY_KIND PROVIDED) endif() ################################################################################################ @@ -178,8 +183,8 @@ endif() # Only use custom curl if compiling with CPP11 PAL set(MATSDK_NEEDS_CURL OFF) -set(MATSDK_CURL_FETCHED OFF) set(MATSDK_CURL_LINK_TARGET "") +set(MATSDK_CURL_DEPENDENCY_KIND NONE) if(PAL_IMPLEMENTATION STREQUAL "CPP11" AND NOT MATSDK_PLATFORM_IOS AND (NOT CMAKE_SYSTEM_NAME STREQUAL "Android" OR MATSDK_ANDROID_USES_CURL) @@ -189,11 +194,12 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11" HAVE_MAT_CURL_HTTP_CLIENT) if(TARGET CURL::libcurl) set(MATSDK_CURL_LINK_TARGET CURL::libcurl) + set(MATSDK_CURL_DEPENDENCY_KIND PROVIDED) elseif(MATSDK_CURL_PROVIDER_UPPER STREQUAL "FETCH") include(cmake/MatsdkFetchCurl.cmake) matsdk_fetch_curl(_matsdk_curl_target) set(MATSDK_CURL_LINK_TARGET "${_matsdk_curl_target}") - set(MATSDK_CURL_FETCHED ON) + set(MATSDK_CURL_DEPENDENCY_KIND BUNDLED) else() find_package(CURL REQUIRED) if(NOT TARGET CURL::libcurl) @@ -201,19 +207,32 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11" "find_package(CURL) did not create the required CURL::libcurl target.") endif() set(MATSDK_CURL_LINK_TARGET CURL::libcurl) + set(MATSDK_CURL_DEPENDENCY_KIND PACKAGE) endif() endif() ################################################################################################ # Canonical dependency targets ################################################################################################ +set(MATSDK_SQLITE_PACKAGE_NAME SQLite3) +set(MATSDK_SQLITE_PACKAGE_TARGET SQLite::SQLite3) +if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "NONE") + set(MATSDK_SQLITE_DEPENDENCY_KIND NONE) +elseif(NOT MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "SYSTEM") + set(MATSDK_SQLITE_DEPENDENCY_KIND BUNDLED) +endif() +if(NOT MATSDK_ZLIB_PROVIDER_RESOLVED STREQUAL "SYSTEM") + set(MATSDK_ZLIB_DEPENDENCY_KIND BUNDLED) +endif() if(TARGET SQLite::SQLite3 AND NOT TARGET SQLite3::SQLite3) matsdk_add_interface_dependency(SQLite3::SQLite3 SQLite::SQLite3) endif() if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "SYSTEM" AND NOT TARGET SQLite3::SQLite3) if(APPLE AND NOT MATSDK_USING_VCPKG) matsdk_add_apple_system_library(SQLite3::SQLite3 sqlite3) + set(MATSDK_SQLITE_DEPENDENCY_KIND APPLE_SYSTEM) else() + set(MATSDK_SQLITE_DEPENDENCY_KIND PACKAGE) find_package(SQLite3 QUIET) if(TARGET SQLite::SQLite3 AND NOT TARGET SQLite3::SQLite3) matsdk_add_interface_dependency(SQLite3::SQLite3 SQLite::SQLite3) @@ -222,6 +241,8 @@ if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "SYSTEM" AND NOT TARGET SQLite3::SQL find_package(unofficial-sqlite3 CONFIG REQUIRED) matsdk_add_interface_dependency( SQLite3::SQLite3 unofficial::sqlite3::sqlite3) + set(MATSDK_SQLITE_PACKAGE_NAME unofficial-sqlite3) + set(MATSDK_SQLITE_PACKAGE_TARGET unofficial::sqlite3::sqlite3) endif() if(NOT TARGET SQLite3::SQLite3) message(FATAL_ERROR @@ -234,8 +255,10 @@ endif() if(MATSDK_ZLIB_PROVIDER_RESOLVED STREQUAL "SYSTEM" AND NOT TARGET ZLIB::ZLIB) if(APPLE AND NOT MATSDK_USING_VCPKG) matsdk_add_apple_system_library(ZLIB::ZLIB z) + set(MATSDK_ZLIB_DEPENDENCY_KIND APPLE_SYSTEM) else() find_package(ZLIB REQUIRED) + set(MATSDK_ZLIB_DEPENDENCY_KIND PACKAGE) endif() endif() diff --git a/cmake/MSTelemetryConfig.cmake.in b/cmake/MSTelemetryConfig.cmake.in index 60a3579c2..56dec0c0d 100644 --- a/cmake/MSTelemetryConfig.cmake.in +++ b/cmake/MSTelemetryConfig.cmake.in @@ -5,26 +5,28 @@ include("${CMAKE_CURRENT_LIST_DIR}/MatsdkDependencyTargets.cmake") # Recreate dependencies only when a static package needs them at the final link. if(@MATSDK_CONFIG_STATIC_PACKAGE@) - if(@MATSDK_BUILD_PLATFORM_APPLE@) - set(_matsdk_package_sqlite_args APPLE_SYSTEM APPLE_LIBRARY sqlite3) - set(_matsdk_package_zlib_args APPLE_SYSTEM APPLE_LIBRARY z) - else() - set(_matsdk_package_sqlite_args) - set(_matsdk_package_zlib_args) + # find_dependency must run at config scope so failure stops package loading. + if("@MATSDK_SQLITE_DEPENDENCY_KIND@" STREQUAL "PACKAGE" + AND NOT TARGET SQLite3::SQLite3 + AND NOT TARGET "@MATSDK_SQLITE_PACKAGE_TARGET@") + find_dependency(@MATSDK_SQLITE_PACKAGE_NAME@) + endif() + if("@MATSDK_ZLIB_DEPENDENCY_KIND@" STREQUAL "PACKAGE" AND NOT TARGET ZLIB::ZLIB) + find_dependency(ZLIB) endif() - matsdk_add_package_system_dependency( + matsdk_add_package_dependency( MSTelemetry::sqlite_dependency SQLite3::SQLite3 - "@MATSDK_SQLITE_PROVIDER_RESOLVED@" - SQLite3 - LEGACY_TARGET SQLite::SQLite3 - ${_matsdk_package_sqlite_args}) - matsdk_add_package_system_dependency( + "@MATSDK_SQLITE_PACKAGE_NAME@" + KIND "@MATSDK_SQLITE_DEPENDENCY_KIND@" + LEGACY_TARGET "@MATSDK_SQLITE_PACKAGE_TARGET@" + APPLE_LIBRARY sqlite3) + matsdk_add_package_dependency( MSTelemetry::zlib_dependency ZLIB::ZLIB - "@MATSDK_ZLIB_PROVIDER_RESOLVED@" ZLIB - ${_matsdk_package_zlib_args}) + KIND "@MATSDK_ZLIB_DEPENDENCY_KIND@" + APPLE_LIBRARY z) endif() if("@MATSDK_ANDROID_HTTP_CLIENT_RESOLVED@" STREQUAL "") @@ -45,7 +47,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") endif() if(@MATSDK_CONFIG_STATIC_PACKAGE@ AND @MATSDK_NEEDS_CURL@) - if(@MATSDK_CURL_FETCHED@) + if("@MATSDK_CURL_DEPENDENCY_KIND@" STREQUAL "BUNDLED") function(_matsdk_import_static target_name archive_name) if(NOT TARGET "${target_name}") add_library("${target_name}" STATIC IMPORTED GLOBAL) @@ -87,18 +89,16 @@ if(@MATSDK_CONFIG_STATIC_PACKAGE@ AND @MATSDK_NEEDS_CURL@) _matsdk_import_static(MSTelemetry::curl_archive libcurl.a) set_property(TARGET MSTelemetry::curl_archive PROPERTY INTERFACE_LINK_LIBRARIES "${_matsdk_curl_tls_targets}") + matsdk_add_interface_dependency( + MSTelemetry::curl_dependency MSTelemetry::curl_archive) else() - if(NOT TARGET CURL::libcurl) + if("@MATSDK_CURL_DEPENDENCY_KIND@" STREQUAL "PACKAGE" AND NOT TARGET CURL::libcurl) find_dependency(CURL) endif() + matsdk_add_package_dependency( + MSTelemetry::curl_dependency CURL::libcurl CURL + KIND "@MATSDK_CURL_DEPENDENCY_KIND@") endif() - if(@MATSDK_CURL_FETCHED@) - set(_matsdk_curl_dependency_target MSTelemetry::curl_archive) - else() - set(_matsdk_curl_dependency_target CURL::libcurl) - endif() - matsdk_add_interface_dependency( - MSTelemetry::curl_dependency "${_matsdk_curl_dependency_target}") endif() include("${CMAKE_CURRENT_LIST_DIR}/MSTelemetryTargets.cmake") diff --git a/cmake/MatsdkDependencyTargets.cmake b/cmake/MatsdkDependencyTargets.cmake index 47ddf21a4..535462c6b 100644 --- a/cmake/MatsdkDependencyTargets.cmake +++ b/cmake/MatsdkDependencyTargets.cmake @@ -14,26 +14,14 @@ function(matsdk_add_interface_dependency target_name) INTERFACE_LINK_LIBRARIES "${ARGN}") endfunction() -function(matsdk_add_package_system_dependency dependency_target canonical_target provider_value package_name) - if(NOT "${provider_value}" STREQUAL "SYSTEM") - return() +function(matsdk_add_package_dependency dependency_target canonical_target package_name) + set(one_value_args APPLE_LIBRARY LEGACY_TARGET KIND) + cmake_parse_arguments(MATSDK_PACKAGE_DEP "" "${one_value_args}" "" ${ARGN}) + if(NOT MATSDK_PACKAGE_DEP_KIND MATCHES "^(PACKAGE|PROVIDED|APPLE_SYSTEM|BUNDLED|NONE)$") + message(FATAL_ERROR "Invalid dependency kind: ${MATSDK_PACKAGE_DEP_KIND}") endif() - - set(options APPLE_SYSTEM) - set(one_value_args APPLE_LIBRARY LEGACY_TARGET) - cmake_parse_arguments(MATSDK_PACKAGE_DEP "${options}" "${one_value_args}" "" ${ARGN}) - - if(MATSDK_PACKAGE_DEP_APPLE_SYSTEM) - if(NOT DEFINED MATSDK_PACKAGE_DEP_APPLE_LIBRARY - OR MATSDK_PACKAGE_DEP_APPLE_LIBRARY STREQUAL "") - message(FATAL_ERROR - "APPLE_LIBRARY is required for Apple system dependencies.") - endif() - include("${_MATSDK_DEPENDENCY_TARGETS_DIR}/MatsdkAppleSystemDeps.cmake") - matsdk_add_apple_system_library( - "${canonical_target}" "${MATSDK_PACKAGE_DEP_APPLE_LIBRARY}") - elseif(NOT TARGET "${canonical_target}") - find_dependency(${package_name}) + if(MATSDK_PACKAGE_DEP_KIND MATCHES "^(BUNDLED|NONE)$") + return() endif() if(NOT TARGET "${canonical_target}" AND DEFINED MATSDK_PACKAGE_DEP_LEGACY_TARGET @@ -41,6 +29,22 @@ function(matsdk_add_package_system_dependency dependency_target canonical_target matsdk_add_interface_dependency( "${canonical_target}" "${MATSDK_PACKAGE_DEP_LEGACY_TARGET}") endif() + if(NOT TARGET "${canonical_target}") + if(MATSDK_PACKAGE_DEP_KIND STREQUAL "APPLE_SYSTEM") + if(NOT DEFINED MATSDK_PACKAGE_DEP_APPLE_LIBRARY + OR MATSDK_PACKAGE_DEP_APPLE_LIBRARY STREQUAL "") + message(FATAL_ERROR + "APPLE_LIBRARY is required for Apple system dependencies.") + endif() + include("${_MATSDK_DEPENDENCY_TARGETS_DIR}/MatsdkAppleSystemDeps.cmake") + matsdk_add_apple_system_library( + "${canonical_target}" "${MATSDK_PACKAGE_DEP_APPLE_LIBRARY}") + elseif(MATSDK_PACKAGE_DEP_KIND STREQUAL "PROVIDED") + message(FATAL_ERROR + "MSTelemetry was built with a caller-provided ${canonical_target}. " + "Define that target before find_package(MSTelemetry).") + endif() + endif() if(NOT TARGET "${canonical_target}") message(FATAL_ERROR "${package_name} did not create the required ${canonical_target} target.") diff --git a/cmake/MatsdkSqliteNamespace.cmake b/cmake/MatsdkSqliteNamespace.cmake new file mode 100644 index 000000000..fd25a5621 --- /dev/null +++ b/cmake/MatsdkSqliteNamespace.cmake @@ -0,0 +1,33 @@ +include_guard() + +function(matsdk_namespace_sqlite target header output) + set(symbols) + # The amalgamations also declare platform APIs and test globals absent from sqlite3.h. + foreach(source IN ITEMS "${header}" ${ARGN}) + file(STRINGS "${source}" declarations REGEX "^SQLITE_API ") + foreach(declaration IN LISTS declarations) + string(REGEX MATCH "sqlite3[A-Za-z0-9_]+[ \t]*[()\\[;=]" symbol "${declaration}") + if(NOT symbol) + message(FATAL_ERROR "Cannot namespace SQLite API declaration: ${declaration}") + endif() + string(REGEX REPLACE "[ \t]*[()\\[;=]$" "" symbol "${symbol}") + list(APPEND symbols "${symbol}") + endforeach() + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source}") + endforeach() + if(NOT symbols) + message(FATAL_ERROR "No SQLite API declarations found in ${header}") + endif() + list(REMOVE_DUPLICATES symbols) + list(SORT symbols) + set(contents "#pragma once\n") + foreach(symbol IN LISTS symbols) + string(APPEND contents "#define ${symbol} matsdk_${symbol}\n") + endforeach() + file(GENERATE OUTPUT "${output}" CONTENT "${contents}") + if(MSVC) + target_compile_options(${target} INTERFACE "/FI${output}") + else() + target_compile_options(${target} INTERFACE "SHELL:-include \"${output}\"") + endif() +endfunction() diff --git a/docs/building-with-vcpkg.md b/docs/building-with-vcpkg.md index 53531cc03..92e945d73 100644 --- a/docs/building-with-vcpkg.md +++ b/docs/building-with-vcpkg.md @@ -37,6 +37,23 @@ cd cpp_client_telemetry vcpkg install --overlay-ports=tools/ports cpp-client-telemetry ``` +The two recipes deliberately have different dependency policies: + +| Recipe | Source and dependency policy | +| --- | --- | +| `tools/ports` development overlay | Local checkout support and private `minimal-sqlite`; Linux TLS selection requires exactly one backend. | +| `tools/registry-ports` release recipe | Pinned archive only; external SQLite/zlib in core on every platform and Linux curl/OpenSSL in core. Fixed native platform transports; only additive `device-id` and Android C API callback features. | + +The feature examples below describe the development overlay unless explicitly +marked otherwise. In the release recipe, `[core]` still includes the required +external dependencies. + +The registry selects WinHTTP on Windows, Java/JNI HTTP on Android, Apple-native +HTTP on macOS/iOS, and curl with OpenSSL on Linux. The optional Android C API +callback feature uses the caller's send/cancel callbacks when supplied and +retains Java/JNI HTTP otherwise. Configure exception policy in the toolchain or +custom triplet. + ### Using in your CMake project After installing, add the SDK to your CMake project: @@ -501,60 +518,64 @@ autovacuum, `VACUUM`, PRAGMAs, the custom UTF-8 function, blobs, 64-bit integers transactions) are retained, and the SDK's offline-storage unit tests pass unchanged against the minimal build. -> **Caveat — symbol visibility when linking statically.** The private SQLite keeps -> SQLite's default `sqlite3_*` symbol names. For a **shared** `mat` -> (`mat.dll` / `libmat.so` / `libmat.dylib`), those symbols are hidden by the -> SDK's `-fvisibility=hidden`, so there is no conflict. For a **static** `mat`, -> the minimal SQLite is installed and exported as a separate -> `MSTelemetry::sqlite3_bundled` archive that links into your binary; if **any** -> part of the final static link — your own code *or another dependency* — also -> pulls in SQLite, the duplicate `sqlite3_*` symbols will collide at link time. In -> that case, prefer the default `system-sqlite` feature so the whole graph shares a -> single SQLite. +Both `MINIMAL` and `VENDORED` SQLite use SDK-private `matsdk_sqlite3*` names +for functions and data, including platform-specific APIs. Static and shared SDKs +can therefore coexist with an application's full SQLite without duplicate +symbols or cross-instance interposition. The renaming header is private to +SDK-owned targets; consumers retain the normal SQLite API. Static packages +install the private archive as `MSTelemetry::sqlite3_bundled`. +Do not pass database handles, callbacks, or allocated buffers between the two +SQLite instances. Use `SYSTEM` when the whole graph should share one SQLite. ## 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. +registry port update. 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. +**complete registry recipe from `tools/registry-ports` in the release tag**, +validates its two additive features and pinned-source policy, and replaces the +archive `REF`, `SHA512`, and manifest version while removing any old +`port-version`. Run it with Python 3.10 or newer 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. +including existing patches; retain or restore registry patches still required +by the released source. 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: +published GitHub source `.tar.gz` archive: ```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 +python .github/scripts/prepare-vcpkg-release.py --source-port tools/registry-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, +submitting the registry PR. Cover core-only, default, and advertised opt-in +graphs and static/shared external consumers. Verify that enabling either +advertised feature retains the port's fixed platform transport. +The `tests/vcpkg` scripts cover local-source builds; release validation must use +the published archive. 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 +### Dependency 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 -separate: `MATSDK_ANDROID_HTTP_CLIENT=AUTO` resolves to the Java/JNI transport, -while the explicit Android curl features select the native curl transport. +provides those targets through normal `find_package()` discovery. + +Installed static packages reconstruct their recorded dependency providers: +discovered packages, Apple system libraries, or bundled archives. Shared +packages keep SQLite, zlib, and curl private. If a source +embedding build supplied canonical dependency targets itself, its installed +static package requires the consuming project to define those same targets +(`SQLite3::SQLite3`, `ZLIB::ZLIB`, or `CURL::libcurl`, as applicable) before +`find_package(MSTelemetry)`. Missing caller-provided targets produce an explicit +configuration error. +If a discovered dependency package is unavailable, an optional +`find_package(MSTelemetry)` reports the SDK as not found; a required lookup fails +before importing the SDK target. ## Migrating from the older overlay port diff --git a/docs/maintainer-onboarding.md b/docs/maintainer-onboarding.md index 47e9111e0..fa7254b9b 100644 --- a/docs/maintainer-onboarding.md +++ b/docs/maintainer-onboarding.md @@ -368,13 +368,14 @@ The vcpkg port update is manual: 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. + to copy `tools/registry-ports/cpp-client-telemetry` and set the new release + version and source archive SHA512. Promote the complete recipe and preserve any + registry-specific patches still needed by the released source. 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. + Verify core-only, default, and advertised opt-in feature graphs, including + the union of retained additive features and external consumers of static and + shared packages. Confirm that feature unions retain the native transport. 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. diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b77acdf41..d44de8ab6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -532,7 +532,17 @@ set(MATSDK_SQLITE_MINIMAL_DEFS ) if(MATSDK_BUNDLE_SQLITE AND NOT TARGET sqlite3_bundled) + include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/MatsdkSqliteNamespace.cmake") + add_library(matsdk_sqlite_namespace INTERFACE) + matsdk_namespace_sqlite(matsdk_sqlite_namespace + "${CMAKE_CURRENT_SOURCE_DIR}/../sqlite/sqlite3.h" + "${CMAKE_CURRENT_BINARY_DIR}/matsdk-sqlite-symbols.h" + "${CMAKE_CURRENT_SOURCE_DIR}/../sqlite/sqlite3_retail.c" + "${CMAKE_CURRENT_SOURCE_DIR}/../sqlite/sqlite3_debug.c") + target_link_libraries(matsdk_internal_config INTERFACE matsdk_sqlite_namespace) add_library(sqlite3_bundled STATIC "${CMAKE_CURRENT_SOURCE_DIR}/../sqlite/sqlite3.c") + target_link_libraries(sqlite3_bundled PRIVATE + "$") # Consumers of MSTelemetry::mat never include sqlite3.h (it is an internal # implementation detail), so the header path is only needed while building the # SDK itself -- wrap it in BUILD_INTERFACE so install(EXPORT) stays valid. diff --git a/lib/include/public/Version.hpp b/lib/include/public/Version.hpp index 3e9c6a7c3..662e103f3 100644 --- a/lib/include/public/Version.hpp +++ b/lib/include/public/Version.hpp @@ -6,8 +6,8 @@ #define MAT_VERSION_HPP // WARNING: DO NOT MODIFY THIS FILE! // This file has been automatically generated, manual changes will be lost. -#define BUILD_VERSION_STR "3.10.279.1" -#define BUILD_VERSION 3,10,279,1 +#define BUILD_VERSION_STR "3.10.281.1" +#define BUILD_VERSION 3,10,281,1 #ifndef RESOURCE_COMPILER_INVOKED #include "ctmacros.hpp" @@ -18,7 +18,7 @@ namespace MAT_NS_BEGIN { uint64_t const Version = ((uint64_t)3 << 48) | ((uint64_t)10 << 32) | - ((uint64_t)279 << 16) | + ((uint64_t)281 << 16) | ((uint64_t)1); } MAT_NS_END diff --git a/tests/embedding/CMakeLists.txt b/tests/embedding/CMakeLists.txt index 49b11b481..fb7a01ffa 100644 --- a/tests/embedding/CMakeLists.txt +++ b/tests/embedding/CMakeLists.txt @@ -71,6 +71,39 @@ else() add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../.." cpp_client_telemetry) endif() +get_filename_component(sdk_source "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +foreach(dependency SQLITE ZLIB CURL) + get_directory_property(kind DIRECTORY "${sdk_source}" + DEFINITION MATSDK_${dependency}_DEPENDENCY_KIND) + get_directory_property(provider DIRECTORY "${sdk_source}" + DEFINITION MATSDK_${dependency}_PROVIDER_RESOLVED) + if(dependency STREQUAL "CURL") + get_directory_property(needs_curl DIRECTORY "${sdk_source}" DEFINITION MATSDK_NEEDS_CURL) + if(NOT needs_curl) + set(expected NONE) + elseif(MATSDK_EMBEDDING_PRELOAD_CURL) + set(expected PROVIDED) + elseif(MATSDK_CURL_PROVIDER STREQUAL "FETCH") + set(expected BUNDLED) + else() + set(expected PACKAGE) + endif() + elseif(provider STREQUAL "NONE") + set(expected NONE) + elseif(NOT provider STREQUAL "SYSTEM") + set(expected BUNDLED) + elseif(MATSDK_EMBEDDING_PRELOAD_STORAGE_DEPS) + set(expected PROVIDED) + elseif(NOT APPLE) + set(expected PACKAGE) + else() + set(expected APPLE_SYSTEM) + endif() + if(NOT kind STREQUAL expected) + message(FATAL_ERROR "${dependency} recorded ${kind}, expected ${expected}") + endif() +endforeach() + add_executable(embedding_test "${CMAKE_CURRENT_LIST_DIR}/../vcpkg/main.cpp") target_link_libraries(embedding_test PRIVATE MSTelemetry::mat) if(MSVC) diff --git a/tests/package-coexistence/CMakeLists.txt b/tests/package-coexistence/CMakeLists.txt new file mode 100644 index 000000000..bd5dda1a8 --- /dev/null +++ b/tests/package-coexistence/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.19) +project(sqlite_coexistence_test LANGUAGES C CXX) + +get_filename_component(SDK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(MATSDK_SQLITE_PROVIDER MINIMAL CACHE STRING "") +set(MATSDK_ZLIB_PROVIDER VENDORED CACHE STRING "") +add_library(external_sqlite STATIC "${SDK_ROOT}/sqlite/sqlite3.c") +target_include_directories(external_sqlite PUBLIC "${SDK_ROOT}/sqlite") +if(NOT WIN32) + find_package(Threads REQUIRED) + target_link_libraries(external_sqlite PUBLIC Threads::Threads "${CMAKE_DL_LIBS}" m) +endif() + +if(MATSDK_INSTALLED_PREFIX) + find_package(MSTelemetry CONFIG REQUIRED PATHS "${MATSDK_INSTALLED_PREFIX}" + NO_DEFAULT_PATH) +else() + foreach(option HEADERS LIBRARY) + set(MATSDK_BUILD_${option} ON CACHE BOOL "" FORCE) + endforeach() + foreach(option UNIT_TESTS FUNC_TESTS TEST_TOOL PACKAGE JNI_WRAPPER OBJC_WRAPPER SWIFT_WRAPPER) + set(MATSDK_BUILD_${option} OFF CACHE BOOL "" FORCE) + endforeach() + add_subdirectory("${SDK_ROOT}" sdk) +endif() + +add_executable(sqlite_coexistence main.cpp) +target_link_libraries(sqlite_coexistence PRIVATE MSTelemetry::mat external_sqlite) +get_target_property(library_type MSTelemetry::mat TYPE) +if(library_type STREQUAL "STATIC_LIBRARY") + target_compile_definitions(sqlite_coexistence PRIVATE MATSDK_TEST_PRIVATE_SQLITE) +endif() +if(MATSDK_SQLITE_PROVIDER STREQUAL "MINIMAL") + target_compile_definitions(sqlite_coexistence PRIVATE MATSDK_TEST_MINIMAL_SQLITE) +endif() +if(WIN32 AND library_type STREQUAL "SHARED_LIBRARY") + add_custom_command(TARGET sqlite_coexistence POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" "$") +endif() +if(MSVC) + target_compile_options(sqlite_coexistence PRIVATE /W4 /WX) +else() + target_compile_options(sqlite_coexistence PRIVATE -Wall -Wextra -Werror) +endif() +enable_testing() +add_test(NAME sqlite_coexistence COMMAND sqlite_coexistence) diff --git a/tests/package-coexistence/main.cpp b/tests/package-coexistence/main.cpp new file mode 100644 index 000000000..d75f01ca0 --- /dev/null +++ b/tests/package-coexistence/main.cpp @@ -0,0 +1,81 @@ +#include "LogManager.hpp" +#include "sqlite3.h" +#include + +#if defined(sqlite3_open) || defined(Z_PREFIX) || defined(MATSDK_DISABLE_EXCEPTIONS) +#error SDK-private compile policy leaked to a consumer +#endif + +LOGMANAGER_INSTANCE + +#ifdef MATSDK_TEST_PRIVATE_SQLITE +extern "C" +{ + int matsdk_sqlite3_open(const char*, sqlite3**); + int matsdk_sqlite3_close(sqlite3*); + int matsdk_sqlite3_exec(sqlite3*, const char*, int (*)(void*, int, char**, char**), void*, char**); + int matsdk_sqlite3_compileoption_used(const char*); +} +#endif + +int main() +{ + sqlite3* external = nullptr; + if (sqlite3_open(":memory:", &external) != SQLITE_OK) + { + std::fprintf(stderr, "External SQLite failed to open\n"); + return 1; + } + if (sqlite3_compileoption_used("OMIT_JSON") || + sqlite3_exec(external, "CREATE TABLE external_data(value); INSERT INTO external_data VALUES(42);", + nullptr, nullptr, nullptr) != SQLITE_OK) + { + std::fprintf(stderr, "External SQLite was replaced by the SDK's stripped copy\n"); + sqlite3_close(external); + return 1; + } +#ifdef MATSDK_TEST_PRIVATE_SQLITE + sqlite3* internal = nullptr; + if (matsdk_sqlite3_open(":memory:", &internal) != SQLITE_OK || + matsdk_sqlite3_exec(internal, "CREATE TABLE sdk_data(value); INSERT INTO sdk_data VALUES(7);", + nullptr, nullptr, nullptr) != SQLITE_OK) + { + std::fprintf(stderr, "SDK-private SQLite failed\n"); + if (internal) + { + matsdk_sqlite3_close(internal); + } + sqlite3_close(external); + return 1; + } +#ifdef MATSDK_TEST_MINIMAL_SQLITE + if (!matsdk_sqlite3_compileoption_used("OMIT_JSON")) + { + std::fprintf(stderr, "SDK-private SQLite was replaced by the external copy\n"); + matsdk_sqlite3_close(internal); + sqlite3_close(external); + return 1; + } +#endif + if (matsdk_sqlite3_close(internal) != SQLITE_OK) + { + std::fprintf(stderr, "SDK-private SQLite failed to close\n"); + sqlite3_close(external); + return 1; + } +#endif + MAT::ILogger* logger = MAT::LogManager::Initialize("sqlite-coexistence-test"); + if (!logger) + { + std::fprintf(stderr, "SDK initialization failed with external SQLite linked\n"); + sqlite3_close(external); + return 1; + } + MAT::LogManager::FlushAndTeardown(); + if (sqlite3_close(external) != SQLITE_OK) + { + std::fprintf(stderr, "External SQLite failed to close\n"); + return 1; + } + return 0; +} diff --git a/tests/package-dependencies/CMakeLists.txt b/tests/package-dependencies/CMakeLists.txt new file mode 100644 index 000000000..05d0ef434 --- /dev/null +++ b/tests/package-dependencies/CMakeLists.txt @@ -0,0 +1,137 @@ +cmake_minimum_required(VERSION 3.19) +project(package_dependency_test LANGUAGES NONE) + +set(CMAKE_SYSTEM_NAME Darwin) +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) +set(CMAKE_PREFIX_PATH "${CMAKE_CURRENT_BINARY_DIR}/packages") +file(MAKE_DIRECTORY "${CMAKE_PREFIX_PATH}") +foreach(package_target IN ITEMS + "SQLite3|SQLite::SQLite3" "unofficial-sqlite3|unofficial::sqlite3::sqlite3" + "ZLIB|ZLIB::ZLIB" "CURL|CURL::libcurl") + string(REPLACE "|" ";" pair "${package_target}") + list(GET pair 0 package) + list(GET pair 1 target) + file(WRITE "${CMAKE_PREFIX_PATH}/${package}Config.cmake" + "add_library(${target} INTERFACE IMPORTED GLOBAL)\n" + "set_property(TARGET ${target} PROPERTY INTERFACE_LINK_LIBRARIES package-${package})\n") +endforeach() +file(WRITE "${CMAKE_PREFIX_PATH}/OpenSSLConfig.cmake" + "add_library(OpenSSL::SSL INTERFACE IMPORTED GLOBAL)\n" + "add_library(OpenSSL::Crypto INTERFACE IMPORTED GLOBAL)\n") + +set(MATSDK_CONFIG_STATIC_PACKAGE ON) +if(SHARED) + set(MATSDK_CONFIG_STATIC_PACKAGE OFF) +endif() +set(MATSDK_SQLITE_DEPENDENCY_KIND "${SQLITE_KIND}") +set(MATSDK_ZLIB_DEPENDENCY_KIND "${ZLIB_KIND}") +set(MATSDK_SQLITE_PACKAGE_NAME SQLite3) +set(MATSDK_SQLITE_PACKAGE_TARGET SQLite::SQLite3) +if(UNOFFICIAL_SQLITE) + set(MATSDK_SQLITE_PACKAGE_NAME unofficial-sqlite3) + set(MATSDK_SQLITE_PACKAGE_TARGET unofficial::sqlite3::sqlite3) +endif() +if(NOT DEFINED CURL_KIND) + set(CURL_KIND PACKAGE) +endif() +if(NOT SHARED) + foreach(pair IN ITEMS "SQLite3::SQLite3|${SQLITE_KIND}" "ZLIB::ZLIB|${ZLIB_KIND}" + "CURL::libcurl|${CURL_KIND}") + string(REPLACE "|" ";" pair "${pair}") + list(GET pair 0 target) + list(GET pair 1 kind) + if(kind STREQUAL "PROVIDED" AND NOT target STREQUAL MISSING_TARGET) + if(target STREQUAL "SQLite3::SQLite3" AND LEGACY_SQLITE) + set(target SQLite::SQLite3) + endif() + add_library("${target}" INTERFACE IMPORTED GLOBAL) + set_property(TARGET "${target}" PROPERTY INTERFACE_LINK_LIBRARIES provided) + endif() + endforeach() +endif() +set(MATSDK_NEEDS_CURL ON) +set(MATSDK_CURL_DEPENDENCY_KIND "${CURL_KIND}") +set(MATSDK_CURL_TLS_BACKEND_UPPER MBEDTLS) +if(OPENSSL) + set(MATSDK_CURL_TLS_BACKEND_UPPER OPENSSL) +endif() +set(CMAKE_INSTALL_LIBDIR lib) +set(CMAKE_INSTALL_DATADIR share) +set(MATSDK_ANDROID_HTTP_CLIENT_RESOLVED NONE) +set(PACKAGE_INIT + "set(PACKAGE_PREFIX_DIR \"${CMAKE_CURRENT_BINARY_DIR}\")\nmacro(check_required_components name)\nendmacro()") +file(COPY "${SDK_ROOT}/cmake/MatsdkDependencyTargets.cmake" + "${SDK_ROOT}/cmake/MatsdkAppleSystemDeps.cmake" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryTargets.cmake" + "add_library(MSTelemetry::mat INTERFACE IMPORTED)\n") +configure_file("${SDK_ROOT}/cmake/MSTelemetryConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/MSTelemetryConfig.cmake" @ONLY) +if(MISSING_PACKAGE) + set(CMAKE_DISABLE_FIND_PACKAGE_${MISSING_PACKAGE} ON) +endif() +set(lookup_options CONFIG) +if(LOOKUP_MODE STREQUAL "REQUIRED") + list(APPEND lookup_options REQUIRED) +elseif(LOOKUP_MODE STREQUAL "QUIET") + list(APPEND lookup_options QUIET) +endif() +find_package(MSTelemetry ${lookup_options} + PATHS "${CMAKE_CURRENT_BINARY_DIR}" NO_DEFAULT_PATH) +if(MISSING_PACKAGE AND NOT SHARED) + if(MSTelemetry_FOUND OR TARGET MSTelemetry::mat) + message(FATAL_ERROR "SDK package was imported despite missing ${MISSING_PACKAGE}") + endif() + if(NOT MSTelemetry_NOT_FOUND_MESSAGE MATCHES "${MISSING_PACKAGE}") + message(FATAL_ERROR "Missing dependency diagnostic lost: ${MSTelemetry_NOT_FOUND_MESSAGE}") + endif() + return() +endif() +if(NOT MSTelemetry_FOUND OR NOT TARGET MSTelemetry::mat) + message(FATAL_ERROR "SDK package was not imported") +endif() + +foreach(pair IN ITEMS + "SQLite3::SQLite3|${SQLITE_KIND}|MSTelemetry::sqlite_dependency" + "ZLIB::ZLIB|${ZLIB_KIND}|MSTelemetry::zlib_dependency" + "CURL::libcurl|${CURL_KIND}|MSTelemetry::curl_dependency") + string(REPLACE "|" ";" pair "${pair}") + list(GET pair 0 target) + list(GET pair 1 kind) + list(GET pair 2 wrapper) + if(NOT SHARED AND kind STREQUAL "BUNDLED" AND target STREQUAL "CURL::libcurl") + get_target_property(links "${wrapper}" INTERFACE_LINK_LIBRARIES) + if(NOT links STREQUAL "MSTelemetry::curl_archive") + message(FATAL_ERROR "Bundled curl was replaced by ${links}") + endif() + get_target_property(tls_links MSTelemetry::curl_archive INTERFACE_LINK_LIBRARIES) + if(OPENSSL) + set(expected_tls "OpenSSL::SSL;OpenSSL::Crypto") + else() + set(expected_tls "MSTelemetry::mbedtls;MSTelemetry::mbedx509;MSTelemetry::mbedcrypto") + endif() + if(NOT tls_links STREQUAL expected_tls) + message(FATAL_ERROR "Bundled curl TLS was replaced by ${tls_links}") + endif() + elseif(SHARED OR kind MATCHES "^(BUNDLED|NONE)$") + if(TARGET "${wrapper}") + message(FATAL_ERROR "Unexpected private dependency ${wrapper}") + endif() + else() + if(NOT TARGET "${wrapper}" OR NOT TARGET "${target}") + message(FATAL_ERROR "Missing ${kind} dependency ${target}") + endif() + get_target_property(links "${target}" INTERFACE_LINK_LIBRARIES) + if(kind STREQUAL "APPLE_SYSTEM") + if(NOT links MATCHES "^(sqlite3|z)$") + message(FATAL_ERROR "Apple dependency was replaced by ${links}") + endif() + elseif(kind STREQUAL "PROVIDED") + if(NOT links MATCHES "^(provided|SQLite::SQLite3)$") + message(FATAL_ERROR "Caller dependency was replaced by ${links}") + endif() + elseif(NOT links MATCHES "^(package-|SQLite::SQLite3|unofficial::sqlite3::sqlite3)") + message(FATAL_ERROR "Package dependency was replaced by ${links}") + endif() + endif() +endforeach() diff --git a/tests/vcpkg/CMakeLists.txt b/tests/vcpkg/CMakeLists.txt index b197a0920..a85ece89c 100644 --- a/tests/vcpkg/CMakeLists.txt +++ b/tests/vcpkg/CMakeLists.txt @@ -10,6 +10,18 @@ if(MSVC AND VCPKG_TARGET_TRIPLET MATCHES "-static$") set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() +option(MATSDK_TEST_PRELOAD_DEPENDENCIES + "Supply canonical targets required by a caller-provided static SDK package" OFF) +if(MATSDK_TEST_PRELOAD_DEPENDENCIES) + find_package(CURL REQUIRED) + find_package(SQLite3 REQUIRED) + if(NOT TARGET SQLite3::SQLite3) + add_library(SQLite3::SQLite3 INTERFACE IMPORTED) + set_property(TARGET SQLite3::SQLite3 PROPERTY INTERFACE_LINK_LIBRARIES SQLite::SQLite3) + endif() + find_package(ZLIB REQUIRED) +endif() + find_package(MSTelemetry CONFIG REQUIRED) get_target_property(SDK_COMPILE_OPTIONS MSTelemetry::mat INTERFACE_COMPILE_OPTIONS) diff --git a/tests/vcpkg/native-feature-tests.cmake b/tests/vcpkg/native-feature-tests.cmake index 85892ac2c..b113dcdd5 100644 --- a/tests/vcpkg/native-feature-tests.cmake +++ b/tests/vcpkg/native-feature-tests.cmake @@ -1,7 +1,37 @@ cmake_minimum_required(VERSION 3.19) get_filename_component(REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) -set(ENV{MATSDK_VCPKG_SOURCE_DIR} "${REPO_ROOT}") +if(REGISTRY_MODE) + set(ENV{MATSDK_VCPKG_SOURCE_DIR} "${REPO_ROOT}/does-not-exist") +else() + set(ENV{MATSDK_VCPKG_SOURCE_DIR} "${REPO_ROOT}") +endif() + +function(vcpkg_from_github) + cmake_parse_arguments(FETCH "" "OUT_SOURCE_PATH;REPO;REF;SHA512;HEAD_REF" "" ${ARGN}) + string(LENGTH "${FETCH_SHA512}" hash_length) + if(NOT FETCH_REPO STREQUAL "microsoft/cpp_client_telemetry" + OR NOT FETCH_REF MATCHES "^v[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$" + OR NOT FETCH_SHA512 MATCHES "^[0-9a-f]+$" OR NOT hash_length EQUAL 128) + message(FATAL_ERROR "Registry source must be a hash-verified release archive.") + endif() + set("${FETCH_OUT_SOURCE_PATH}" "${REPO_ROOT}" PARENT_SCOPE) + set(FETCHED ON PARENT_SCOPE) +endfunction() + +function(vcpkg_check_features) + cmake_parse_arguments(CHECK "" "OUT_FEATURE_OPTIONS" "FEATURES" ${ARGN}) + set(options) + while(CHECK_FEATURES) + list(POP_FRONT CHECK_FEATURES feature option) + set(value OFF) + if(feature IN_LIST FEATURES) + set(value ON) + endif() + list(APPEND options "-D${option}=${value}") + endwhile() + set("${CHECK_OUT_FEATURE_OPTIONS}" "${options}" PARENT_SCOPE) +endfunction() function(vcpkg_cmake_configure) cmake_parse_arguments(CONFIG "" "SOURCE_PATH" "OPTIONS" ${ARGN}) @@ -12,6 +42,9 @@ function(vcpkg_cmake_configure) endforeach() set(LEGACY_OPTIONS) foreach(option IN LISTS CONFIG_OPTIONS) + if(REGISTRY_MODE AND option MATCHES "^-DMATSDK_DISABLE_(EXCEPTIONS|LOGGING)=") + message(FATAL_ERROR "Registry features must not override compile policy: ${option}") + endif() if(option MATCHES "^-D(MATSDK_USE_VCPKG_DEPS|MATSDK_MINIMAL_SQLITE)=") message(FATAL_ERROR "Obsolete dependency option was forwarded: ${option}") endif() @@ -34,7 +67,11 @@ endfunction() function(check_feature_mapping platform linkage) set(FEATURES ${ARGN}) + set(VERSION 3.10.999.1) set(CURRENT_PORT_DIR "${REPO_ROOT}/tools/ports/cpp-client-telemetry") + if(REGISTRY_MODE) + set(CURRENT_PORT_DIR "${REPO_ROOT}/tools/registry-ports/cpp-client-telemetry") + endif() set(CURRENT_PACKAGES_DIR "${REPO_ROOT}/out/native-feature-tests") set(VCPKG_LIBRARY_LINKAGE "${linkage}") foreach(target LINUX WINDOWS OSX IOS ANDROID) @@ -56,6 +93,9 @@ function(check_feature_mapping platform linkage) string(REPLACE "|" ";" pair "${pair}") list(GET pair 0 feature) list(GET pair 1 option) + if(REGISTRY_MODE AND feature MATCHES "^(no-exceptions|no-logging|wininet)$") + continue() + endif() set(enabled OFF) if(feature IN_LIST FEATURES) set(enabled ON) @@ -68,28 +108,59 @@ function(check_feature_mapping platform linkage) endif() list(APPEND EXPECTED_OPTIONS "-DBUILD_SHARED_LIBS=${shared}") set(sqlite SYSTEM) - if("minimal-sqlite" IN_LIST FEATURES) + if(NOT REGISTRY_MODE AND "minimal-sqlite" IN_LIST FEATURES) set(sqlite MINIMAL) endif() list(APPEND EXPECTED_OPTIONS "-DMATSDK_SQLITE_PROVIDER=${sqlite}") + if(REGISTRY_MODE) + list(APPEND EXPECTED_OPTIONS + -DMATSDK_ZLIB_PROVIDER=SYSTEM -DMATSDK_CURL_PROVIDER=SYSTEM + -DMATSDK_USE_WININET=OFF + -DBUILD_VERSION=3.10.999.1) + endif() set(apple_http OFF) if(platform STREQUAL "OSX" OR platform STREQUAL "IOS") set(apple_http ON) endif() list(APPEND EXPECTED_OPTIONS "-DMATSDK_BUILD_APPLE_HTTP=${apple_http}") set(android_http AUTO) - if(platform STREQUAL "ANDROID" + if(REGISTRY_MODE AND platform STREQUAL "ANDROID") + set(android_http JAVA) + elseif(platform STREQUAL "ANDROID" AND ("android-curl-openssl" IN_LIST FEATURES OR "android-curl-mbedtls" IN_LIST FEATURES)) set(android_http CURL) endif() list(APPEND EXPECTED_OPTIONS "-DMATSDK_ANDROID_HTTP_CLIENT=${android_http}") set(CONFIGURED OFF) + set(FETCHED OFF) include("${CURRENT_PORT_DIR}/portfile.cmake") if(NOT CONFIGURED) message(FATAL_ERROR "The feature mapping did not configure the SDK.") endif() + if(REGISTRY_MODE AND NOT FETCHED) + message(FATAL_ERROR "Registry port bypassed the pinned archive.") + endif() endfunction() +if(REGISTRY_MODE) + foreach(platform LINUX WINDOWS OSX IOS ANDROID) + foreach(linkage static dynamic) + foreach(features IN ITEMS "" device-id) + check_feature_mapping("${platform}" "${linkage}" ${features}) + endforeach() + if(platform STREQUAL "ANDROID") + foreach(features IN ITEMS android-capi-http-client + "device-id;android-capi-http-client") + check_feature_mapping("${platform}" "${linkage}" + ${features}) + endforeach() + endif() + endforeach() + endforeach() + message(STATUS "Registry additive features, core graphs, and fixed platform transports passed") + return() +endif() + if(DEFINED UNSUPPORTED_FEATURE) set(ENV{MATSDK_VCPKG_SOURCE_DIR} "${OLD_SOURCE}") if(UNSUPPORTED_FEATURE STREQUAL "minimal-sqlite") diff --git a/tests/vcpkg/package-dependency-tests.cmake b/tests/vcpkg/package-dependency-tests.cmake new file mode 100644 index 000000000..52ca7de30 --- /dev/null +++ b/tests/vcpkg/package-dependency-tests.cmake @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.19) + +get_filename_component(REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +if(NOT DEFINED OUTPUT_DIR) + set(OUTPUT_DIR "${REPO_ROOT}/out/package-dependency-tests") +endif() + +function(check_dependency_config sqlite_kind zlib_kind shared) + string(MAKE_C_IDENTIFIER "${sqlite_kind}-${zlib_kind}-${shared}-${ARGN}" name) + execute_process( + COMMAND "${CMAKE_COMMAND}" + -S "${REPO_ROOT}/tests/package-dependencies" + -B "${OUTPUT_DIR}/${name}" + "-DSDK_ROOT=${REPO_ROOT}" + "-DSQLITE_KIND=${sqlite_kind}" + "-DZLIB_KIND=${zlib_kind}" + "-DSHARED=${shared}" ${ARGN} + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error) + if(EXPECTED_MISSING_TARGET) + if(result EQUAL 0 OR NOT error MATCHES "caller-provided ${EXPECTED_MISSING_TARGET}") + message(FATAL_ERROR "${name} did not reject the missing provided target:\n${output}\n${error}") + endif() + elseif(EXPECTED_MISSING_PACKAGE) + if(result EQUAL 0 OR NOT error MATCHES "${EXPECTED_MISSING_PACKAGE}") + message(FATAL_ERROR "${name} did not reject the missing required package:\n${output}\n${error}") + endif() + elseif(NOT result EQUAL 0) + message(FATAL_ERROR "${name} failed:\n${output}\n${error}") + endif() +endfunction() + +foreach(sqlite_kind PACKAGE PROVIDED APPLE_SYSTEM BUNDLED NONE) + foreach(zlib_kind PACKAGE PROVIDED APPLE_SYSTEM BUNDLED) + foreach(shared ON OFF) + check_dependency_config("${sqlite_kind}" "${zlib_kind}" "${shared}") + endforeach() + endforeach() +endforeach() +check_dependency_config(PACKAGE PACKAGE OFF -DUNOFFICIAL_SQLITE=ON) +check_dependency_config(PROVIDED PROVIDED OFF -DLEGACY_SQLITE=ON) +foreach(shared ON OFF) + check_dependency_config(PACKAGE PACKAGE "${shared}" -DCURL_KIND=PROVIDED) +endforeach() +foreach(shared ON OFF) + foreach(openssl ON OFF) + check_dependency_config(PACKAGE PACKAGE "${shared}" + -DCURL_KIND=BUNDLED "-DOPENSSL=${openssl}") + endforeach() +endforeach() +foreach(target SQLite3::SQLite3 ZLIB::ZLIB CURL::libcurl) + set(EXPECTED_MISSING_TARGET "${target}") + check_dependency_config(PROVIDED PROVIDED OFF + -DCURL_KIND=PROVIDED "-DMISSING_TARGET=${target}") +endforeach() +unset(EXPECTED_MISSING_TARGET) + +foreach(package SQLite3 unofficial-sqlite3 ZLIB CURL OpenSSL) + set(options "-DMISSING_PACKAGE=${package}") + if(package STREQUAL "unofficial-sqlite3") + list(APPEND options -DUNOFFICIAL_SQLITE=ON) + elseif(package STREQUAL "OpenSSL") + list(APPEND options -DCURL_KIND=BUNDLED -DOPENSSL=ON) + endif() + foreach(mode OPTIONAL QUIET) + foreach(shared ON OFF) + check_dependency_config(PACKAGE PACKAGE "${shared}" + ${options} "-DLOOKUP_MODE=${mode}") + endforeach() + endforeach() + check_dependency_config(PACKAGE PACKAGE ON ${options} -DLOOKUP_MODE=REQUIRED) + set(EXPECTED_MISSING_PACKAGE "${package}") + check_dependency_config(PACKAGE PACKAGE OFF ${options} -DLOOKUP_MODE=REQUIRED) + unset(EXPECTED_MISSING_PACKAGE) +endforeach() + +message(STATUS "All 48 package dependency configurations, 3 missing-target checks, and 30 missing-package checks passed") diff --git a/tests/vcpkg/test-release-port.py b/tests/vcpkg/test-release-port.py index c75cceb34..e04883009 100644 --- a/tests/vcpkg/test-release-port.py +++ b/tests/vcpkg/test-release-port.py @@ -1,4 +1,4 @@ -"""Regression tests for promoting the release overlay into the registry.""" +"""Regression tests for promoting the release registry recipe.""" import importlib.util import json @@ -21,7 +21,7 @@ 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 = root / "release" / "tools" / "registry-ports" / "cpp-client-telemetry" self.source.mkdir(parents=True) self.destination = root / "vcpkg" / "ports" / "cpp-client-telemetry" self.destination.mkdir(parents=True) @@ -35,14 +35,14 @@ def setUp(self): 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( + (REPO_ROOT / "tools" / "registry-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" + REPO_ROOT / "tools" / "registry-ports" / "cpp-client-telemetry" / "portfile.cmake" ).read_text(encoding="utf-8") (self.source / "portfile.cmake").write_text(self.portfile, encoding="utf-8") @@ -62,16 +62,21 @@ def test_promotes_complete_release_port(self): 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_SQLITE_PROVIDER=SYSTEM", portfile) + self.assertIn("-DMATSDK_ZLIB_PROVIDER=SYSTEM", portfile) self.assertIn("-DMATSDK_ANDROID_HTTP_CLIENT=${MATSDK_ANDROID_HTTP_CLIENT}", portfile) - self.assertIn("${MATSDK_NATIVE_FEATURE_OPTIONS}", portfile) + self.assertIn("set(MATSDK_ANDROID_HTTP_CLIENT JAVA)", portfile) + self.assertIn("-DMATSDK_USE_WININET=OFF", portfile) + self.assertNotIn("MATSDK_DISABLE_EXCEPTIONS", portfile) + self.assertNotIn("MATSDK_DISABLE_LOGGING", portfile) + self.assertNotIn("MATSDK_CURL_TLS_BACKEND", portfile) + self.assertIn("${FEATURE_OPTIONS}", portfile) for feature, option in ( - ("no-exceptions", "MATSDK_DISABLE_EXCEPTIONS"), - ("no-logging", "MATSDK_DISABLE_LOGGING"), + ("device-id", "MATSDK_ENABLE_DEVICE_ID"), ("android-capi-http-client", "MATSDK_ENABLE_CAPI_HTTP_CLIENT"), ): self.assertIn(feature, actual["features"]) - self.assertIn(f"{feature}|{option}", portfile) + self.assertIn(f"{feature} {option}", portfile) self.assertEqual((self.destination / "release.patch").read_text(), "new patch") self.assertFalse((self.destination / "obsolete.patch").exists()) self.assertEqual( @@ -81,7 +86,7 @@ def test_promotes_complete_release_port(self): 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"): + for feature in ("device-id", "android-capi-http-client"): with self.subTest(feature=feature): definition = self.manifest["features"].pop(feature) self.write_manifest() @@ -90,6 +95,49 @@ def test_rejects_release_missing_required_features_before_replacing_port(self): self.assertTrue((self.destination / "obsolete.patch").exists()) self.manifest["features"][feature] = definition + def test_rejects_unsupported_features_without_replacing_port(self): + for feature in ( + "minimal-sqlite", "no-exceptions", "no-logging", "system-sqlite", + "curl-openssl", "curl-mbedtls", "android-curl-openssl", + "android-curl-mbedtls", "wininet", + "unknown-feature", + ): + with self.subTest(feature=feature): + self.manifest["features"][feature] = {"description": "Unsupported feature"} + self.write_manifest() + with self.assertRaisesRegex(ValueError, feature): + self.prepare() + self.assertTrue((self.destination / "obsolete.patch").exists()) + del self.manifest["features"][feature] + + def test_rejects_development_source_override_without_replacing_port(self): + (self.source / "portfile.cmake").write_text( + self.portfile + "\nset(SOURCE_PATH \"$ENV{MATSDK_VCPKG_SOURCE_DIR}\")\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "source overrides"): + self.prepare() + self.assertTrue((self.destination / "obsolete.patch").exists()) + + def test_registry_core_contains_required_dependencies(self): + dependencies = { + item if isinstance(item, str) else item["name"]: item + for item in self.manifest["dependencies"] + } + for package in ("sqlite3", "zlib"): + self.assertIn(package, dependencies) + if isinstance(dependencies[package], dict): + self.assertNotIn("platform", dependencies[package]) + self.assertEqual(dependencies["curl"]["platform"], "linux") + self.assertIn("openssl", dependencies["curl"]["features"]) + self.assertEqual( + set(self.manifest["features"]), {"device-id", "android-capi-http-client"} + ) + self.assertEqual(self.manifest["default-features"], ["device-id"]) + self.assertEqual( + self.manifest["features"]["android-capi-http-client"]["supports"], "android" + ) + def test_rejects_missing_or_duplicate_archive_fields(self): for field in ("REF", "SHA512"): for replacement in ("", f" {field} duplicate\n {field} duplicate\n"): @@ -124,7 +172,7 @@ def test_describes_invalid_sha512_as_hexadecimal_characters(self): def test_rejects_overlapping_ports(self): with self.assertRaisesRegex(ValueError, "overlap"): - PREPARE.prepare_port(self.source, self.source, "3.10.999.1", "a" * 128) + PREPARE.prepare_port(self.destination, self.destination, "3.10.999.1", "a" * 128) self.assertTrue((self.source / "portfile.cmake").exists()) def test_rejects_lookalike_non_vcpkg_destination_without_deleting_files(self): diff --git a/tools/registry-ports/cpp-client-telemetry/portfile.cmake b/tools/registry-ports/cpp-client-telemetry/portfile.cmake new file mode 100644 index 000000000..f804b5b46 --- /dev/null +++ b/tools/registry-ports/cpp-client-telemetry/portfile.cmake @@ -0,0 +1,62 @@ +# Release-promotion template: prepare-vcpkg-release.py replaces the version, +# REF, and SHA512 with the release containing these packaging fixes. +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO microsoft/cpp_client_telemetry + REF v3.10.279.1 + SHA512 fc31686bfbe50c3a7f2a287671354579c77de05adab74b350238e02441ede29fff8a5de0621742015547cc0571bb606683b343cd1b85b45fcaac85d31642f430 + HEAD_REF main +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + device-id MATSDK_ENABLE_DEVICE_ID + android-capi-http-client MATSDK_ENABLE_CAPI_HTTP_CLIENT +) + +set(MATSDK_BUILD_APPLE_HTTP OFF) +if(VCPKG_TARGET_IS_OSX OR VCPKG_TARGET_IS_IOS) + set(MATSDK_BUILD_APPLE_HTTP ON) +endif() +set(MATSDK_ANDROID_HTTP_CLIENT AUTO) +if(VCPKG_TARGET_IS_ANDROID) + set(MATSDK_ANDROID_HTTP_CLIENT JAVA) +endif() +set(MATSDK_BUILD_SHARED_LIBS OFF) +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") + set(MATSDK_BUILD_SHARED_LIBS ON) +endif() +set(MATSDK_PLATFORM_OPTIONS) +if(VCPKG_TARGET_IS_IOS) + list(APPEND MATSDK_PLATFORM_OPTIONS -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + ${MATSDK_PLATFORM_OPTIONS} + -DBUILD_SHARED_LIBS=${MATSDK_BUILD_SHARED_LIBS} + -DBUILD_VERSION=${VERSION} + -DMATSDK_BUILD_HEADERS=ON + -DMATSDK_BUILD_LIBRARY=ON + -DMATSDK_BUILD_TEST_TOOL=OFF + -DMATSDK_BUILD_UNIT_TESTS=OFF + -DMATSDK_BUILD_FUNC_TESTS=OFF + -DMATSDK_BUILD_PACKAGE=OFF + -DMATSDK_BUILD_JNI_WRAPPER=OFF + -DMATSDK_BUILD_OBJC_WRAPPER=OFF + -DMATSDK_BUILD_SWIFT_WRAPPER=OFF + -DMATSDK_BUILD_APPLE_HTTP=${MATSDK_BUILD_APPLE_HTTP} + -DMATSDK_USE_WININET=OFF + -DMATSDK_ANDROID_HTTP_CLIENT=${MATSDK_ANDROID_HTTP_CLIENT} + -DMATSDK_SQLITE_PROVIDER=SYSTEM + -DMATSDK_ZLIB_PROVIDER=SYSTEM + -DMATSDK_CURL_PROVIDER=SYSTEM +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(PACKAGE_NAME MSTelemetry CONFIG_PATH lib/cmake/MSTelemetry) +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") diff --git a/tools/registry-ports/cpp-client-telemetry/vcpkg.json b/tools/registry-ports/cpp-client-telemetry/vcpkg.json new file mode 100644 index 000000000..7727eeb32 --- /dev/null +++ b/tools/registry-ports/cpp-client-telemetry/vcpkg.json @@ -0,0 +1,44 @@ +{ + "name": "cpp-client-telemetry", + "version": "3.10.279.1", + "description": "Microsoft 1DS C/C++ Client Telemetry Library", + "homepage": "https://github.com/microsoft/cpp_client_telemetry", + "license": "Apache-2.0", + "supports": "((windows & !mingw) | linux | osx | ios | android) & !uwp", + "dependencies": [ + { + "name": "curl", + "default-features": false, + "features": [ + "openssl" + ], + "platform": "linux" + }, + "nlohmann-json", + { + "name": "sqlite3", + "default-features": false + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + }, + "zlib" + ], + "default-features": [ + "device-id" + ], + "features": { + "android-capi-http-client": { + "description": "Allow Android C API consumers to provide custom HTTP send/cancel callbacks.", + "supports": "android" + }, + "device-id": { + "description": "Collect a native device ID (default). Omit with default-features=false to provide your own DeviceInfo.Id." + } + } +}