From f6f451cdcaa2254d106c8d1d7dabc5b3d5056c72 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Thu, 10 Sep 2026 00:54:11 -0700 Subject: [PATCH 1/3] Complete Conan, Make and BCR release preparation for kdtreepp 2 --- .bcr/metadata.template.json | 7 +++ .bcr/presubmit.yml | 11 +++++ .github/workflows/ci.yml | 19 ++++++++ .gitignore | 6 +++ .travis.yml | 72 ---------------------------- CHANGELOG.md | 12 +++++ CMakeLists.txt | 3 +- MODULE.bazel | 2 +- Makefile | 46 +++++++----------- README.md | 8 +++- RELEASING.md | 29 +++++++++++ bench/run.cpp | 13 ++--- conanfile.py | 43 ++++++++--------- examples/bazel-consumer/MODULE.bazel | 2 +- include/kdtreepp/version.hpp | 4 +- scripts/clang-tidy.sh | 26 +++------- scripts/coverage.sh | 41 +++++++--------- test/test.cpp | 2 +- test_package/CMakeLists.txt | 6 +++ test_package/conanfile.py | 24 ++++++++++ test_package/main.cpp | 12 +++++ tools/prepare_bcr.py | 55 +++++++++++++++++++++ tools/test_bcr.py | 34 +++++++++++++ 23 files changed, 296 insertions(+), 181 deletions(-) create mode 100644 .bcr/metadata.template.json create mode 100644 .bcr/presubmit.yml delete mode 100644 .travis.yml create mode 100644 RELEASING.md create mode 100644 test_package/CMakeLists.txt create mode 100644 test_package/conanfile.py create mode 100644 test_package/main.cpp create mode 100644 tools/prepare_bcr.py create mode 100644 tools/test_bcr.py diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json new file mode 100644 index 0000000..7e5dd90 --- /dev/null +++ b/.bcr/metadata.template.json @@ -0,0 +1,7 @@ +{ + "homepage": "https://github.com/jhurliman/kdtreepp", + "maintainers": [{"github": "jhurliman", "github_user_id": 195374}], + "repository": ["github:jhurliman/kdtreepp"], + "versions": [], + "yanked_versions": {} +} diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml new file mode 100644 index 0000000..f94f98c --- /dev/null +++ b/.bcr/presubmit.yml @@ -0,0 +1,11 @@ +matrix: + platform: [ubuntu2404, macos_arm64] + bazel: ["9.*"] +tasks: + verify_targets: + name: Verify library and regression tests + platform: ${{ platform }} + bazel: ${{ bazel }} + build_flags: ["--cxxopt=-std=c++17"] + build_targets: ["@kdtreepp//:kdtreepp"] + test_targets: ["@kdtreepp//:regression_test"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7c5383..709b322 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ jobs: node-version: 24 - run: npm install --global @bazel/bazelisk@1.28.1 - run: bazelisk test //:regression_test + - run: BAZEL=bazelisk python3 tools/test_bcr.py - run: bazelisk test //:consumer --cxxopt=-std=c++17 working-directory: examples/bazel-consumer cmake: @@ -22,3 +23,21 @@ jobs: - run: cmake -S . -B build -DKDTREEPP_BUILD_TESTS=ON -DCMAKE_CXX_FLAGS=-fsanitize=address,undefined -DCMAKE_INSTALL_PREFIX="$PWD/install" - run: cmake --build build && ctest --test-dir build --output-on-failure && cmake --install build - run: cmake -S examples/cmake-consumer -B consumer-build -DCMAKE_PREFIX_PATH="$PWD/install" && cmake --build consumer-build && ./consumer-build/consumer + + conan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install conan==2.32.0 + - run: conan profile detect && conan create . --build=missing -s compiler.cppstd=17 + make: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: sudo apt-get update && sudo apt-get install -y libeigen3-dev libbenchmark-dev clang llvm + - run: make test + - run: make bench BENCHMARK_FILTER='BM_Points3dConstruction/100$' + - run: CXX=clang++ make coverage diff --git a/.gitignore b/.gitignore index 8b5a18c..4367d17 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,9 @@ bazel-testlogs /bazel-kdtreepp /examples/bazel-consumer/bazel-bazel-consumer /build/ + +/test_package/build/ +/test_package/CMakeUserPresets.json + +/build-coverage/ +__pycache__/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ce441a3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,72 +0,0 @@ -language: python -python: "3.7" - -matrix: - include: - # clang-format/tidy specific job - - os: linux - sudo: false - env: CXX=clang++ CLANG_FORMAT CLANG_TIDY - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'clang', 'clang-format', 'clang-tidy', 'libstdc++-8-dev' ] - script: - - make tidy - # FIXME: Re-enable this when we are using a pinned clang-format instead - # of the system version - # - make format - # build with gcc - - os: linux - sudo: false - env: CXX=g++-9 - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'g++-9' ] - # build with clang - - os: linux - sudo: false - env: CXX=clang++ - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'clang', 'libstdc++-8-dev' ] - # build with sanitizer checks under clang - - os: linux - sudo: required # workaround https://github.com/mapbox/node-cpp-skel/issues/93 - env: CXX=clang++ CXXFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'clang', 'libstdc++-8-dev' ] - # coverage build - - os: linux - sudo: false - env: CXX=clang++ CXXFLAGS="--coverage" LDFLAGS="--coverage" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'clang', 'llvm', 'libstdc++-8-dev' ] - script: - - make debug - - make test - - curl -S -f https://codecov.io/bash -o codecov - - chmod +x codecov - - ./codecov -x "llvm-cov gcov -l" -Z - -env: - global: - - CMAKE_VERSION="3.15.2" - -install: - - pip install conan - - which cmake - -script: - - make release - - make test - - make clean - - make debug - - make test - - make clean diff --git a/CHANGELOG.md b/CHANGELOG.md index c4d17bb..894176d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# Changelog + +## 2.0.0 (release candidate) + +- Provide a Bzlmod library target with transitive Eigen and independent consumer checks. +- Export CMake package targets without automatically invoking Conan or setting global compiler flags. +- Replace the nonfunctional Conan 1 recipe with a tested Conan 2 header-only package and consumer. +- Restore Make test, benchmark and coverage commands; update Google Benchmark APIs and remove obsolete Travis configuration. +- Prepare checksum-based BCR metadata from the actual release archive and test consumption through a generated registry without local overrides. +- Keep version metadata synchronized across the C++ header, CMake, Conan and Bazel modules. + +The C++ API is unchanged; the build and package-manager migration motivates the major version. Publication/tagging and registry submission follow review and merge. # 01/27/2021 * Initial release diff --git a/CMakeLists.txt b/CMakeLists.txt index 2577238..6232968 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project(kdtreepp VERSION 1.0.0 LANGUAGES CXX) +project(kdtreepp VERSION 2.0.0 LANGUAGES CXX) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(GNUInstallDirs) include(CMakePackageConfigHelpers) find_package(Eigen3 REQUIRED NO_MODULE) diff --git a/MODULE.bazel b/MODULE.bazel index 64d4861..73f5942 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,3 +1,3 @@ -module(name = "kdtreepp", version = "1.0.0") +module(name = "kdtreepp", version = "2.0.0") bazel_dep(name = "rules_cc", version = "0.2.22") bazel_dep(name = "eigen", version = "3.4.1.bcr.1") diff --git a/Makefile b/Makefile index 520dc23..f4e0b5e 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,25 @@ - -# Whether to turn compiler warnings into errors -export WERROR ?= true -export BUILD_DIR ?= build - -default: release - +BUILD_DIR ?= build +BENCHMARK_FILTER ?= . +.PHONY: release debug test bench clean format coverage tidy release: - mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Release -DWERROR=$(WERROR) && VERBOSE=1 cmake --build . - + cmake -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=Release -DKDTREEPP_BUILD_TESTS=ON + cmake --build $(BUILD_DIR) debug: - mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Debug -DWERROR=$(WERROR) && VERBOSE=1 cmake --build . - -test: - @if [ -f ./$(BUILD_DIR)/bin/unit-tests ]; then ./$(BUILD_DIR)/bin/unit-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi - + cmake -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=Debug -DKDTREEPP_BUILD_TESTS=ON + cmake --build $(BUILD_DIR) +test: release + ctest --test-dir $(BUILD_DIR) --output-on-failure bench: - @if [ -f ./$(BUILD_DIR)/bin/bench-tests ]; then ./$(BUILD_DIR)/bin/bench-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi - -tidy: - ./scripts/clang-tidy.sh - -coverage: - ./scripts/coverage.sh - + cmake -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=Release -DKDTREEPP_BUILD_BENCHMARKS=ON + cmake --build $(BUILD_DIR) --target bench-tests + $(BUILD_DIR)/bench-tests --benchmark_filter="$(BENCHMARK_FILTER)" --benchmark_min_time=0.01s clean: - rm -rf ./$(BUILD_DIR) - # remove remains from running 'make coverage' - rm -f *.profraw - rm -f *.profdata - + cmake -E rm -rf $(BUILD_DIR) format: ./scripts/format.sh -.PHONY: test bench +coverage: + BUILD_DIR="$(BUILD_DIR)-coverage" ./scripts/coverage.sh + +tidy: debug + BUILD_DIR="$(BUILD_DIR)" ./scripts/clang-tidy.sh diff --git a/README.md b/README.md index afc67ce..c28fd31 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Requires C++17 and Eigen. The library is header-only; tests and benchmarks are o Until a release is registered in the Bazel Central Registry, use a checkout override: ```starlark -bazel_dep(name = "kdtreepp", version = "1.0.0") +bazel_dep(name = "kdtreepp", version = "2.0.0") local_path_override(module_name = "kdtreepp", path = "third_party/kdtreepp") ``` @@ -31,7 +31,7 @@ find_package(kdtreepp CONFIG REQUIRED) target_link_libraries(my_application PRIVATE kdtreepp::kdtreepp) ``` -CMake no longer invokes Conan automatically or changes global compiler flags. Existing Conan 1 recipes remain legacy and have not been validated with this integration; supply Eigen through a package manager or installed CMake package. +CMake no longer invokes Conan automatically or changes global compiler flags. The Conan 2 recipe declares Eigen transitively and is validated with `conan create . --build=missing -s compiler.cppstd=17`. Conan 1 is no longer supported. ## Usage @@ -104,3 +104,7 @@ For sanitizer checks, configure with `-DCMAKE_CXX_FLAGS=-fsanitize=address,undef kdtreepp is licensed under [MIT](https://opensource.org/licenses/MIT). Made with [hpp-skel](https://github.com/mapbox/hpp-skel). + +## Release preparation + +See [RELEASING.md](RELEASING.md) for the tested Conan consumer, restored Make commands and BCR archive/registry validation and submission procedure. Version 2.0.0 is prepared in this branch; it is not yet published or registered. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..d8e4649 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,29 @@ +# Release 2.0.0 + +This branch prepares a major release because integration now uses Bzlmod, exported CMake targets and Conan 2 instead of automatically invoking Conan 1. The C++17 library API is unchanged. + +## Verify before tagging + +- `bazel test //:regression_test`, then run the renamed-repository consumer in `examples/bazel-consumer`. +- `make test`; `make bench` with Google Benchmark installed; `make coverage` with Clang and matching LLVM tools. +- `conan profile detect` if no profile exists, then `conan create . --build=missing -s compiler.cppstd=17`. +- `BAZEL=bazel python3 tools/test_bcr.py`. This archives the committed HEAD and tests an independent Bazel consumer through a temporary registry and checksum-verified archive, without a source override. Commit all intended release changes first. + +CI performs these checks on the reviewed commit. Merging the stacked PRs and publishing/tagging are separate maintainer actions; test/pack commands do not publish anything. + +## Bazel Central Registry submission + +After the reviewed commit is merged and tagged `v2.0.0`, download the actual GitHub tag archive: + +```sh +curl -fL https://github.com/jhurliman/kdtreepp/archive/refs/tags/v2.0.0.tar.gz -o kdtreepp-2.0.0.tar.gz +python3 tools/prepare_bcr.py kdtreepp-2.0.0.tar.gz --output /path/to/bazel-central-registry +``` + +The generator copies the archive's MODULE.bazel, computes its SHA-256 integrity, and prepares `modules/kdtreepp/metadata.json`, `2.0.0/source.json` and `presubmit.yml`. Use the downloaded release archive, not a locally recompressed copy: integrity must match the bytes served at the public URL. Review the files and run the BCR validation tools from that checkout before opening the registry PR, following [BCR contribution guidance](https://github.com/bazelbuild/bazel-central-registry/blob/main/docs/README.md). + +There is no pre-existing registry entry in this release preparation. Until the BCR PR is merged, use the documented source override. After registration, a monorepo can use `bazel_dep(name = "kdtreepp", version = "2.0.0")` and depend on `@kdtreepp//:kdtreepp`. + +## Conan 2 + +The recipe packages headers and the license without cloning another revision or downloading build-time test tools. Eigen is a declared transitive header dependency, and `test_package` compiles/runs a real consumer. ConanCenter submission or upload to a remote is an explicit release action; `conan create` only populates the local cache. diff --git a/bench/run.cpp b/bench/run.cpp index 9ddfb84..f1c97f5 100644 --- a/bench/run.cpp +++ b/bench/run.cpp @@ -9,7 +9,7 @@ using AlignedBox3 = Eigen::AlignedBox3d; static void BM_Points3dConstruction(benchmark::State& state) { std::vector> points; - std::mt19937_64 randGen{size_t(state.thread_index)}; + std::mt19937_64 randGen{size_t(state.thread_index())}; std::uniform_real_distribution dist{-1000.0, 1000.0}; // Make random points @@ -20,7 +20,7 @@ static void BM_Points3dConstruction(benchmark::State& state) { } while (state.KeepRunning()) { - const auto node = kdtreepp::MakeEigenKdTreeNode( + auto node = kdtreepp::MakeEigenKdTreeNode( points.begin(), points.end(), [](const Vector3& p) { return p; }, [](const Vector3& p) { return p; }); benchmark::DoNotOptimize(node); @@ -30,7 +30,7 @@ static void BM_Points3dConstruction(benchmark::State& state) { static void BM_Points3dClosest(benchmark::State& state) { std::vector> points; - std::mt19937_64 randGen{size_t(state.thread_index)}; + std::mt19937_64 randGen{size_t(state.thread_index())}; std::uniform_real_distribution dist{-1000.0, 1000.0}; // Make random points @@ -80,7 +80,8 @@ int main(int argc, char* argv[]) { ->Arg(1000000); benchmark::RegisterBenchmark("BM_Points3dClosest", BM_Points3dClosest)->Args({100000, 10000}); benchmark::Initialize(&argc, argv); - benchmark::RunSpecifiedBenchmarks(); - - return 0; + if (benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; + const auto matched = benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return matched == 0 ? 1 : 0; } diff --git a/conanfile.py b/conanfile.py index 490c4e5..b727bdc 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,32 +1,29 @@ -from conans import ConanFile, CMake - +import os +from conan import ConanFile +from conan.tools.files import copy class KdTreePpConan(ConanFile): name = "kdtreepp" - version = "1.0.0" + version = "2.0.0" + package_type = "header-library" + license = "MIT" url = "https://github.com/jhurliman/kdtreepp" - homepage = "https://github.com/jhurliman/kdtreepp" - description = "A C++ k-d tree implementation" - license = ("MIT") - topics = ("data-structures", "spatial", "eigen") - settings = "os", "compiler", "build_type", "arch" - requires = "eigen/3.3.9" - build_requires = "catch2/2.13.4", "benchmark/1.5.2" - generators = "cmake" - - def source(self): - self.run("git clone https://github.com/jhurliman/kdtreepp.git") + homepage = url + description = "A header-only C++17 k-d tree for Eigen" + exports_sources = "include/*", "LICENSE" + no_copy_source = True - def build(self): - cmake = CMake(self) - cmake.configure(source_folder="kdtreepp") - cmake.build() + def requirements(self): + self.requires("eigen/3.4.0", transitive_headers=True) def package(self): - cmake = CMake(self) - cmake.configure(source_folder=self._source_subfolder) - cmake.install() + copy(self, "*.hpp", src=os.path.join(self.source_folder, "include"), + dst=os.path.join(self.package_folder, "include")) + copy(self, "LICENSE", src=self.source_folder, + dst=os.path.join(self.package_folder, "licenses")) def package_info(self): - self.cpp_info.names["cmake_find_package"] = "kdtreepp" - self.cpp_info.names["pkg_config"] = "kdtreepp" + self.cpp_info.bindirs = [] + self.cpp_info.libdirs = [] + self.cpp_info.set_property("cmake_file_name", "kdtreepp") + self.cpp_info.set_property("cmake_target_name", "kdtreepp::kdtreepp") diff --git a/examples/bazel-consumer/MODULE.bazel b/examples/bazel-consumer/MODULE.bazel index 6fd5899..52fa0e2 100644 --- a/examples/bazel-consumer/MODULE.bazel +++ b/examples/bazel-consumer/MODULE.bazel @@ -1,4 +1,4 @@ module(name = "consumer") bazel_dep(name = "rules_cc", version = "0.2.22") -bazel_dep(name = "kdtreepp", version = "1.0.0", repo_name = "spatial") +bazel_dep(name = "kdtreepp", version = "2.0.0", repo_name = "spatial") local_path_override(module_name = "kdtreepp", path = "../..") diff --git a/include/kdtreepp/version.hpp b/include/kdtreepp/version.hpp index 7171ad5..6a409ee 100644 --- a/include/kdtreepp/version.hpp +++ b/include/kdtreepp/version.hpp @@ -1,7 +1,7 @@ #pragma once /// The major version number -#define KDTREEPP_VERSION_MAJOR 1 +#define KDTREEPP_VERSION_MAJOR 2 /// The minor version number #define KDTREEPP_VERSION_MINOR 0 @@ -14,4 +14,4 @@ (KDTREEPP_VERSION_MAJOR * 10000 + KDTREEPP_VERSION_MINOR * 100 + KDTREEPP_VERSION_PATCH) /// Version number as string -#define KDTREEPP_VERSION_STRING "1.0.0" +#define KDTREEPP_VERSION_STRING "2.0.0" diff --git a/scripts/clang-tidy.sh b/scripts/clang-tidy.sh index 26189a9..5defaf6 100755 --- a/scripts/clang-tidy.sh +++ b/scripts/clang-tidy.sh @@ -1,22 +1,8 @@ #!/usr/bin/env bash - -set -eu -set -o pipefail - -# https://clang.llvm.org/extra/clang-tidy/ - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -cd ${DIR}/.. - -CLANG_TIDY_SCRIPT="${DIR}/run-clang-tidy.py" - -# build the compile_commands.json file if it does not exist -if [[ ! -f build/compile_commands.json ]]; then - # the build automatically puts the compile commands in the ./build directory - make +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BUILD="${BUILD_DIR:-${ROOT}/build}" +if [[ ! -f "${BUILD}/compile_commands.json" ]]; then + make -C "${ROOT}" debug BUILD_DIR="${BUILD}" fi - -# change into the build directory so that clang-tidy can find the files -# at the right paths (since this is where the actual build happens) -cd build -${CLANG_TIDY_SCRIPT} +python3 "${ROOT}/scripts/run-clang-tidy.py" -p "${BUILD}" diff --git a/scripts/coverage.sh b/scripts/coverage.sh index c616df3..8858d57 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -1,25 +1,18 @@ #!/usr/bin/env bash - -set -eu -set -o pipefail - -# http://clang.llvm.org/docs/UsersManual.html#profiling-with-instrumentation -# https://www.bignerdranch.com/blog/weve-got-you-covered/ - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -cd ${DIR}/.. - -make clean -export CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping" -export LDFLAGS="-fprofile-instr-generate" -make debug -rm -f *profraw -rm -f *gcov -rm -f *profdata -LLVM_PROFILE_FILE="code-%p.profraw" make test -CXX_MODULE="./build/unit-tests" -llvm-profdata merge -output=code.profdata code-*.profraw -llvm-cov report ${CXX_MODULE} -instr-profile=code.profdata -use-color -llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -path-equivalence -use-color -llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -path-equivalence -use-color --format html > /tmp/coverage.html -echo "open /tmp/coverage.html for HTML version of this report" +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BUILD="${BUILD_DIR:-${ROOT}/build-coverage}" +mkdir -p "${BUILD}" +BUILD="$(cd "${BUILD}" && pwd)" +cmake -S "${ROOT}" -B "${BUILD}" -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_COMPILER="${CXX:-clang++}" -DKDTREEPP_BUILD_TESTS=ON \ + -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" \ + -DCMAKE_EXE_LINKER_FLAGS="-fprofile-instr-generate" +cmake --build "${BUILD}" +rm -f "${BUILD}"/coverage-*.profraw +LLVM_PROFILE_FILE="${BUILD}/coverage-%p.profraw" ctest --test-dir "${BUILD}" --output-on-failure +llvm-profdata merge -sparse "${BUILD}"/coverage-*.profraw -o "${BUILD}/coverage.profdata" +llvm-cov report "${BUILD}/kdtreepp-regression" -instr-profile="${BUILD}/coverage.profdata" +llvm-cov show "${BUILD}/kdtreepp-regression" -instr-profile="${BUILD}/coverage.profdata" \ + --format=html -output-dir="${BUILD}/coverage" +echo "HTML report: ${BUILD}/coverage/index.html" diff --git a/test/test.cpp b/test/test.cpp index 0c87f67..dba8700 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -14,7 +14,7 @@ static void CHECK_VEC3_EQ(const Vector3& a, const Vector3& b) { CHECK(a[2] == Approx(b[2])); } -TEST_CASE("VERSION_STRING") { REQUIRE(KDTREEPP_VERSION_STRING == std::string("1.0.0")); } +TEST_CASE("VERSION_STRING") { REQUIRE(KDTREEPP_VERSION_STRING == std::string("2.0.0")); } TEST_CASE("PointTreeTest") { // Build a k-d tree from a list of points diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..57a00fa --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.16) +project(consumer LANGUAGES CXX) +find_package(kdtreepp CONFIG REQUIRED) +add_executable(consumer main.cpp) +target_compile_features(consumer PRIVATE cxx_std_17) +target_link_libraries(consumer PRIVATE kdtreepp::kdtreepp) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..99ac5f4 --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,24 @@ +import os +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout +from conan.tools.build import can_run + +class ConsumerTest(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + test_type = "explicit" + + def requirements(self): + self.requires(self.tested_reference_str) + + def layout(self): + cmake_layout(self) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + if can_run(self): + self.run(os.path.join(self.cpp.build.bindir, "consumer"), env="conanrun") diff --git a/test_package/main.cpp b/test_package/main.cpp new file mode 100644 index 0000000..c9af47a --- /dev/null +++ b/test_package/main.cpp @@ -0,0 +1,12 @@ +#include +#include +int main() { + using Point = Eigen::Vector3d; + std::vector points{Point::Zero(), Point::Ones()}; + const auto tree = kdtreepp::MakeEigenKdTreeNode( + points.begin(), points.end(), [](const Point& p) { return p; }, + [](const Point& p) { return p; }); + int count = 0; + tree.visit([](const auto&) { return true; }, [&](const Point&) { ++count; }); + return count == 2 ? 0 : 1; +} diff --git a/tools/prepare_bcr.py b/tools/prepare_bcr.py new file mode 100644 index 0000000..a8b280f --- /dev/null +++ b/tools/prepare_bcr.py @@ -0,0 +1,55 @@ +"""Generate a registry entry from the exact source archive being released.""" +import argparse +import base64 +import hashlib +import json +from pathlib import Path +import re +import tarfile + + +def prepare(archive, output, url=None): + root = Path(__file__).resolve().parents[1] + with tarfile.open(archive, 'r:gz') as tar: + modules = [m for m in tar.getmembers() if m.name.count('/') == 1 and m.name.endswith('/MODULE.bazel')] + if len(modules) != 1 or not modules[0].isfile() or modules[0].size > 65536: + raise ValueError('archive must contain one top-level MODULE.bazel') + module = tar.extractfile(modules[0]).read() + text = module.decode('utf-8') + name = re.search(r'\bname\s*=\s*"([a-z0-9_]+)"', text).group(1) + version = re.search(r'\bversion\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)"', text).group(1) + if name != 'kdtreepp': + raise ValueError('unexpected module name') + prefix = modules[0].name.split('/')[0] + if prefix != 'kdtreepp-' + version: + raise ValueError('archive prefix must match the module version') + for required in ['BUILD.bazel', 'LICENSE', 'include/kdtreepp.hpp']: + if not tar.getmember(prefix + '/' + required).isfile(): + raise ValueError('missing release file: ' + required) + directory = Path(output) / 'modules' / name + target = directory / version + target.mkdir(parents=True, exist_ok=True) + metadata_path = directory / 'metadata.json' + metadata = json.loads(metadata_path.read_text()) if metadata_path.exists() else json.loads((root / '.bcr/metadata.template.json').read_text()) + if version not in metadata['versions']: + metadata['versions'].append(version) + metadata_path.write_text(json.dumps(metadata, indent=2) + '\n') + source = { + 'url': url or f'https://github.com/jhurliman/kdtreepp/archive/refs/tags/v{version}.tar.gz', + 'integrity': 'sha256-' + base64.b64encode(hashlib.sha256(Path(archive).read_bytes()).digest()).decode(), + 'strip_prefix': prefix, + } + (target / 'source.json').write_text(json.dumps(source, indent=2) + '\n') + (target / 'MODULE.bazel').write_bytes(module) + (target / 'presubmit.yml').write_bytes((root / '.bcr/presubmit.yml').read_bytes()) + (Path(output) / 'bazel_registry.json').write_text('{}\n') + return version + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('archive', type=Path) + parser.add_argument('--output', type=Path, required=True) + parser.add_argument('--url') + args = parser.parse_args() + print(prepare(args.archive, args.output, args.url)) diff --git a/tools/test_bcr.py b/tools/test_bcr.py new file mode 100644 index 0000000..52cb6f8 --- /dev/null +++ b/tools/test_bcr.py @@ -0,0 +1,34 @@ +"""Test consumption through a generated registry, with no local_path_override.""" +from functools import partial +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +import os +from pathlib import Path +import re +import subprocess +import tempfile +from threading import Thread +from prepare_bcr import prepare + +root = Path(__file__).resolve().parents[1] +version = re.search(r'version\s*=\s*"([^"]+)"', (root / 'MODULE.bazel').read_text()).group(1) +with tempfile.TemporaryDirectory(prefix='kdtree-bcr-') as temp: + base = Path(temp) + archive = base / 'source.tar.gz' + subprocess.run(['git','archive','--format=tar.gz',f'--prefix=kdtreepp-{version}/',f'--output={archive}','HEAD'],cwd=root,check=True) + server = ThreadingHTTPServer(('127.0.0.1',0),partial(SimpleHTTPRequestHandler,directory=temp)) + thread = Thread(target=server.serve_forever,daemon=True) + thread.start() + try: + registry = base / 'registry' + actual = prepare(archive,registry,f'http://127.0.0.1:{server.server_port}/source.tar.gz') + assert actual == version + consumer = base / 'consumer' + consumer.mkdir() + (consumer/'MODULE.bazel').write_text(f'module(name="registry_consumer")\nbazel_dep(name="kdtreepp",version="{version}",repo_name="spatial")\nbazel_dep(name="rules_cc",version="0.2.22")\n') + (consumer/'BUILD.bazel').write_text('load("@rules_cc//cc:cc_test.bzl","cc_test")\ncc_test(name="consumer",srcs=["main.cpp"],deps=["@spatial//:kdtreepp"])\n') + (consumer/'main.cpp').write_bytes((root/'examples/cmake-consumer/main.cpp').read_bytes()) + (consumer/'.bazelversion').write_bytes((root/'.bazelversion').read_bytes()) + subprocess.run([os.environ.get('BAZEL','bazel'),'--batch','test','//:consumer','--cxxopt=-std=c++17',f'--registry={registry.as_uri()}','--registry=https://bcr.bazel.build'],cwd=consumer,check=True) + finally: + server.shutdown() + server.server_close() From 511003471d16942748206ba4bd0e227db379910a Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Thu, 10 Sep 2026 01:03:23 -0700 Subject: [PATCH 2/3] Preserve registry configuration and use Conan bindirs API --- test_package/conanfile.py | 2 +- tools/prepare_bcr.py | 4 +++- tools/test_bcr.py | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/test_package/conanfile.py b/test_package/conanfile.py index 99ac5f4..aeb3465 100644 --- a/test_package/conanfile.py +++ b/test_package/conanfile.py @@ -21,4 +21,4 @@ def build(self): def test(self): if can_run(self): - self.run(os.path.join(self.cpp.build.bindir, "consumer"), env="conanrun") + self.run(os.path.join(self.cpp.build.bindirs[0], "consumer"), env="conanrun") diff --git a/tools/prepare_bcr.py b/tools/prepare_bcr.py index a8b280f..b7a5dba 100644 --- a/tools/prepare_bcr.py +++ b/tools/prepare_bcr.py @@ -42,7 +42,9 @@ def prepare(archive, output, url=None): (target / 'source.json').write_text(json.dumps(source, indent=2) + '\n') (target / 'MODULE.bazel').write_bytes(module) (target / 'presubmit.yml').write_bytes((root / '.bcr/presubmit.yml').read_bytes()) - (Path(output) / 'bazel_registry.json').write_text('{}\n') + descriptor = Path(output) / 'bazel_registry.json' + if not descriptor.exists(): + descriptor.write_text('{}\n') return version diff --git a/tools/test_bcr.py b/tools/test_bcr.py index 52cb6f8..1dc57ad 100644 --- a/tools/test_bcr.py +++ b/tools/test_bcr.py @@ -22,6 +22,13 @@ registry = base / 'registry' actual = prepare(archive,registry,f'http://127.0.0.1:{server.server_port}/source.tar.gz') assert actual == version + descriptor = registry / 'bazel_registry.json' + assert descriptor.read_text() == '{}\n' + existing = '{"mirrors": []}\n' + descriptor.write_text(existing) + prepare(archive, registry, f'http://127.0.0.1:{server.server_port}/source.tar.gz') + assert descriptor.read_text() == existing, 'existing registry configuration must survive' + consumer = base / 'consumer' consumer.mkdir() (consumer/'MODULE.bazel').write_text(f'module(name="registry_consumer")\nbazel_dep(name="kdtreepp",version="{version}",repo_name="spatial")\nbazel_dep(name="rules_cc",version="0.2.22")\n') From 50930155e0b86029c0673fc7f482b8a6f54b0823 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Thu, 10 Sep 2026 09:48:18 -0700 Subject: [PATCH 3/3] Accept header-only package from either pointer-size architecture --- CMakeLists.txt | 6 +++++- test/architecture-version.cmake | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 test/architecture-version.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 6232968..fa7ad64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,11 @@ configure_package_config_file(cmake/kdtreeppConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/kdtreeppConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/kdtreepp) write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/kdtreeppConfigVersion.cmake - VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) + VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kdtreeppConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdtreeppConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/kdtreepp) + +if(KDTREEPP_BUILD_TESTS) + add_test(NAME architecture-independent-version COMMAND ${CMAKE_COMMAND} -DVERSION_FILE=${CMAKE_CURRENT_BINARY_DIR}/kdtreeppConfigVersion.cmake -P ${CMAKE_CURRENT_SOURCE_DIR}/test/architecture-version.cmake) +endif() diff --git a/test/architecture-version.cmake b/test/architecture-version.cmake new file mode 100644 index 0000000..652cf66 --- /dev/null +++ b/test/architecture-version.cmake @@ -0,0 +1,13 @@ +# A header-only package must accept either consumer pointer size. This checks +# generated version selection; it does not pretend to compile a 32-bit binary. +foreach(pointer_size 4 8) + set(CMAKE_SIZEOF_VOID_P ${pointer_size}) + set(PACKAGE_FIND_VERSION "2.0.0") + set(PACKAGE_FIND_VERSION_MAJOR 2) + unset(PACKAGE_VERSION_UNSUITABLE) + unset(PACKAGE_VERSION_COMPATIBLE) + include("${VERSION_FILE}") + if(PACKAGE_VERSION_UNSUITABLE OR NOT PACKAGE_VERSION_COMPATIBLE) + message(FATAL_ERROR "Header package rejected pointer size ${pointer_size}") + endif() +endforeach()