From e93b3ed85a56f86e8187637dc4cbd7a1bb1b1d4e Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:02:07 +0100 Subject: [PATCH 01/14] Add design spec for CMake packaging and CI/release pipeline Documents the chosen approach: pre-built release tarballs with CMake config-mode packages, matching OpenMS's existing find_package() pattern. Co-Authored-By: Claude Opus 4.6 --- ...03-11-cmake-packaging-ci-release-design.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md diff --git a/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md b/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md new file mode 100644 index 0000000..5699b8c --- /dev/null +++ b/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md @@ -0,0 +1,160 @@ +# Design: CMake Packaging & CI/Release Pipeline for timsrust_cpp_bridge + +## Goal + +Make timsrust_cpp_bridge consumable by OpenMS (and other CMake projects) via `find_package()`, with a CI pipeline that produces pre-built release artifacts for all platforms OpenMS supports. + +## Decision: Pre-built Release Tarballs + CMake Config Package + +Chosen over FetchContent-based download (adds a new pattern OpenMS doesn't use) and Corrosion/build-from-source (forces Rust toolchain on all consumers). Pre-built artifacts with a CMake config package match OpenMS's existing `find_package()` dependency pattern exactly. + +Static linking chosen because: +- Simplifies distribution (no runtime dependency to ship) +- C ABI is compiler-agnostic — Rust-built `.a`/`.lib` links with gcc, clang, and MSVC +- The CMake config file declares required system link deps so consumers don't need to know about them + +## Release Artifact Structure + +Each GitHub Release contains 4 archives, one per platform/arch: + +``` +timsrust_cpp_bridge-v-linux-x86_64.tar.gz +timsrust_cpp_bridge-v-linux-aarch64.tar.gz +timsrust_cpp_bridge-v-macos-arm64.tar.gz +timsrust_cpp_bridge-v-windows-x86_64.zip +``` + +Each archive layout: + +``` +timsrust_cpp_bridge/ +├── include/ +│ └── timsrust_cpp_bridge.h +├── lib/ +│ └── libtimsrust_cpp_bridge.a (.lib on Windows) +└── cmake/ + └── timsrust_cpp_bridge/ + ├── timsrust_cpp_bridgeConfig.cmake + └── timsrust_cpp_bridgeConfigVersion.cmake +``` + +### OpenMS consumption + +```cmake +find_package(timsrust_cpp_bridge REQUIRED) +target_link_libraries(OpenMS PRIVATE timsrust_cpp_bridge::timsrust_cpp_bridge) +``` + +OpenMS points `CMAKE_PREFIX_PATH` at the extracted archive (or installs it to a contrib-like location). + +## CI Pipeline + +Single workflow at `.github/workflows/release.yml`. + +### Triggers + +- Push/PR to `master`: build + smoke test on all platforms (validates compilation and linking) +- Tags matching `v*` (e.g. `v0.1.0`): build + package + create GitHub Release with artifacts + +### Build Matrix + +| Runner | Target | Rust target triple | Archive format | +|---|---|---|---| +| `ubuntu-24.04` | linux-x86_64 | `x86_64-unknown-linux-gnu` | `.tar.gz` | +| `ubuntu-24.04-arm` | linux-aarch64 | `aarch64-unknown-linux-gnu` | `.tar.gz` | +| `macos-14` | macos-arm64 | `aarch64-apple-darwin` | `.tar.gz` | +| `windows-2025` | windows-x86_64 | `x86_64-pc-windows-msvc` | `.zip` | + +### Job Steps (per matrix entry) + +1. Install Rust toolchain (`dtolnay/rust-toolchain`, stable) +2. `cargo build --features with_timsrust --release` +3. Smoke test: compile and link `examples/cpp_client.cpp` against the built static library, run with no arguments to verify clean exit +4. Package: run `scripts/package.sh` to assemble tarball (header + static lib + configured CMake files) +5. On tag builds: upload artifact via `actions/upload-artifact` + +### Release Job + +Runs after all matrix jobs succeed on a tag push: +- Collects all 4 platform artifacts +- Creates a GitHub Release named after the tag +- Attaches all tarballs/zips as release assets + +### Versioning + +Driven by git tags. The `timsrust_cpp_bridgeConfigVersion.cmake` encodes the version extracted from the tag. Uses `SameMajorVersion` compatibility (0.2.0 satisfies requests for 0.1.0; 1.0.0 does not). + +## CMake Config Files + +### `timsrust_cpp_bridgeConfig.cmake` + +Stored as a template at `cmake/timsrust_cpp_bridgeConfig.cmake.in`, configured at package time with the correct library filename. + +```cmake +get_filename_component(_TIMSRUST_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) + +if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) + add_library(timsrust_cpp_bridge::timsrust_cpp_bridge STATIC IMPORTED) + + set_target_properties(timsrust_cpp_bridge::timsrust_cpp_bridge PROPERTIES + IMPORTED_LOCATION "${_TIMSRUST_PREFIX}/lib/@TIMSRUST_LIB_FILENAME@" + INTERFACE_INCLUDE_DIRECTORIES "${_TIMSRUST_PREFIX}/include" + ) + + # Platform-specific system dependencies + if(UNIX AND NOT APPLE) + target_link_libraries(timsrust_cpp_bridge::timsrust_cpp_bridge + INTERFACE pthread dl m) + elseif(APPLE) + target_link_libraries(timsrust_cpp_bridge::timsrust_cpp_bridge + INTERFACE "-framework Security" "-framework SystemConfiguration" resolv) + elseif(WIN32) + target_link_libraries(timsrust_cpp_bridge::timsrust_cpp_bridge + INTERFACE ws2_32 userenv bcrypt ntdll) + endif() +endif() +``` + +Note: exact system link dependencies (especially macOS frameworks) will be validated empirically during CI. The smoke test catches any missing deps immediately. + +### `timsrust_cpp_bridgeConfigVersion.cmake` + +Stored as a template at `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in`, version injected from the git tag at package time. Standard CMake version compatibility file using `SameMajorVersion`. + +## Smoke Test + +Validates the build artifact is usable without requiring real `.d` datasets: + +1. Compile `examples/cpp_client.cpp` against the static library and header +2. Run the binary with no arguments — verifies clean exit (not crash/segfault) + +**Catches:** missing system link deps, ABI mismatches, platform-specific linking issues. + +**Does not catch:** data reading correctness (requires real `.d` files, remains manual). + +**Required change:** `examples/cpp_client.cpp` needs a clean early exit when invoked with no arguments (currently expects a dataset path). + +## Repository Structure Changes + +New files: + +``` +.github/ + workflows/ + release.yml # CI + release workflow +cmake/ + timsrust_cpp_bridgeConfig.cmake.in # CMake config template + timsrust_cpp_bridgeConfigVersion.cmake.in # Version config template +scripts/ + package.sh # Assembles release tarball +``` + +Modified files: + +``` +examples/cpp_client.cpp # Clean exit when no arguments +``` + +**Not added:** +- No top-level `CMakeLists.txt` — this is a Cargo project that produces CMake-consumable artifacts, not a CMake project itself +- No `build.rs` — cargo build is sufficient as-is From 653e9e0888902eaf317f94c246474c08a83784f5 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:07:37 +0100 Subject: [PATCH 02/14] Update design spec based on review feedback Fixes: SameMinorVersion versioning, lib/cmake/ conventional path, Windows lib filename docs, cargo test + caching steps, smoke test exit code semantics, cdylib exclusion note. Co-Authored-By: Claude Opus 4.6 --- ...03-11-cmake-packaging-ci-release-design.md | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md b/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md index 5699b8c..343de6c 100644 --- a/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md +++ b/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md @@ -30,14 +30,18 @@ Each archive layout: timsrust_cpp_bridge/ ├── include/ │ └── timsrust_cpp_bridge.h -├── lib/ -│ └── libtimsrust_cpp_bridge.a (.lib on Windows) -└── cmake/ - └── timsrust_cpp_bridge/ - ├── timsrust_cpp_bridgeConfig.cmake - └── timsrust_cpp_bridgeConfigVersion.cmake +└── lib/ + ├── libtimsrust_cpp_bridge.a (timsrust_cpp_bridge.lib on Windows — no lib prefix with MSVC) + └── cmake/ + └── timsrust_cpp_bridge/ + ├── timsrust_cpp_bridgeConfig.cmake + └── timsrust_cpp_bridgeConfigVersion.cmake ``` +The `lib/cmake//` path follows the conventional CMake installed package layout and is a default search path for `find_package()`. + +Only the static library (`.a`/`.lib`) is included. The shared library (`cdylib`) output from Cargo is intentionally excluded since OpenMS will link statically. + ### OpenMS consumption ```cmake @@ -68,10 +72,12 @@ Single workflow at `.github/workflows/release.yml`. ### Job Steps (per matrix entry) 1. Install Rust toolchain (`dtolnay/rust-toolchain`, stable) -2. `cargo build --features with_timsrust --release` -3. Smoke test: compile and link `examples/cpp_client.cpp` against the built static library, run with no arguments to verify clean exit -4. Package: run `scripts/package.sh` to assemble tarball (header + static lib + configured CMake files) -5. On tag builds: upload artifact via `actions/upload-artifact` +2. Cache Cargo registry and target directory (`Swatinem/rust-cache`) +3. `cargo test --features with_timsrust` (validates Rust code compiles and any future tests pass) +4. `cargo build --features with_timsrust --release` +5. Smoke test: compile and link `examples/cpp_client.cpp` against the built static library, run with no arguments to verify it does not crash/segfault (non-zero exit code is expected and allowed) +6. Package: run `scripts/package.sh` to assemble tarball (header + static lib + configured CMake files). Uses `shell: bash` on all platforms (including Windows, where GitHub Actions provides Git Bash) +7. On tag builds: upload artifact via `actions/upload-artifact` ### Release Job @@ -82,16 +88,19 @@ Runs after all matrix jobs succeed on a tag push: ### Versioning -Driven by git tags. The `timsrust_cpp_bridgeConfigVersion.cmake` encodes the version extracted from the tag. Uses `SameMajorVersion` compatibility (0.2.0 satisfies requests for 0.1.0; 1.0.0 does not). +Driven by git tags. The `timsrust_cpp_bridgeConfigVersion.cmake` encodes the version extracted from the tag. Uses `SameMinorVersion` compatibility (CMake 3.11+), so 0.1.1 satisfies a request for 0.1.0, but 0.2.0 does not. This respects semver conventions during the 0.x phase where minor versions may contain breaking changes. ## CMake Config Files ### `timsrust_cpp_bridgeConfig.cmake` -Stored as a template at `cmake/timsrust_cpp_bridgeConfig.cmake.in`, configured at package time with the correct library filename. +Stored as a template at `cmake/timsrust_cpp_bridgeConfig.cmake.in`, configured at package time with the correct library filename per platform: +- Linux/macOS: `libtimsrust_cpp_bridge.a` +- Windows (MSVC): `timsrust_cpp_bridge.lib` ```cmake -get_filename_component(_TIMSRUST_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +# Config file lives at /lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake +get_filename_component(_TIMSRUST_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) add_library(timsrust_cpp_bridge::timsrust_cpp_bridge STATIC IMPORTED) @@ -119,20 +128,20 @@ Note: exact system link dependencies (especially macOS frameworks) will be valid ### `timsrust_cpp_bridgeConfigVersion.cmake` -Stored as a template at `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in`, version injected from the git tag at package time. Standard CMake version compatibility file using `SameMajorVersion`. +Stored as a template at `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in`, version injected from the git tag at package time. Generated using `write_basic_package_version_file()` from CMakePackageConfigHelpers with `SameMinorVersion` compatibility. Alternatively, the packaging script can write this file directly using the standard CMake version-file boilerplate. ## Smoke Test Validates the build artifact is usable without requiring real `.d` datasets: 1. Compile `examples/cpp_client.cpp` against the static library and header -2. Run the binary with no arguments — verifies clean exit (not crash/segfault) +2. Run the binary with no arguments — verifies it does not crash or segfault. The binary already prints usage and returns exit code 1 when called with no arguments, which is correct CLI behavior. The CI step allows non-zero exit codes (e.g. `./cpp_client || true`) and only fails on signals (segfault, abort). **Catches:** missing system link deps, ABI mismatches, platform-specific linking issues. **Does not catch:** data reading correctness (requires real `.d` files, remains manual). -**Required change:** `examples/cpp_client.cpp` needs a clean early exit when invoked with no arguments (currently expects a dataset path). +No changes needed to `examples/cpp_client.cpp` — the existing early-exit-with-usage behavior is suitable for the smoke test. ## Repository Structure Changes @@ -149,12 +158,6 @@ scripts/ package.sh # Assembles release tarball ``` -Modified files: - -``` -examples/cpp_client.cpp # Clean exit when no arguments -``` - **Not added:** - No top-level `CMakeLists.txt` — this is a Cargo project that produces CMake-consumable artifacts, not a CMake project itself - No `build.rs` — cargo build is sufficient as-is From 192fd39849000d6f5dca6f8577a27ca23a7e53a4 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:15:43 +0100 Subject: [PATCH 03/14] Add implementation plan for CMake packaging and CI/release pipeline 4 tasks across 4 chunks: CMake config templates, packaging script, GitHub Actions workflow, and local validation. Also updates spec to add advapi32 to Windows system deps. Co-Authored-By: Claude Opus 4.6 --- .../2026-03-11-cmake-packaging-ci-release.md | 474 ++++++++++++++++++ ...03-11-cmake-packaging-ci-release-design.md | 2 +- 2 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md diff --git a/docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md b/docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md new file mode 100644 index 0000000..32554bb --- /dev/null +++ b/docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md @@ -0,0 +1,474 @@ +# CMake Packaging & CI/Release Pipeline Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add CI pipeline and CMake config packaging so OpenMS can consume timsrust_cpp_bridge via `find_package()` with pre-built static library releases. + +**Architecture:** GitHub Actions CI builds the Rust library on 4 platform/arch combos (linux-x86_64, linux-aarch64, macos-arm64, windows-x86_64). A packaging script assembles each build into a tarball containing the static library, C header, and CMake config files. Tag pushes trigger GitHub Releases with all 4 artifacts attached. + +**Tech Stack:** Rust/Cargo, GitHub Actions, CMake (config-mode packages), Bash + +**Spec:** `docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md` + +--- + +## File Map + +| File | Action | Responsibility | +|---|---|---| +| `cmake/timsrust_cpp_bridgeConfig.cmake.in` | Create | CMake config template with platform system deps | +| `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in` | Create | CMake version compatibility template | +| `scripts/package.sh` | Create | Assembles release tarball from cargo build output | +| `.github/workflows/release.yml` | Create | CI build + smoke test + release workflow | + +No existing files are modified. + +--- + +## Chunk 1: CMake Config Templates + +### Task 1: Create CMake Config Template + +**Files:** +- Create: `cmake/timsrust_cpp_bridgeConfig.cmake.in` + +- [ ] **Step 1: Create the CMake config template** + +```cmake +# timsrust_cpp_bridgeConfig.cmake +# Config-mode package file for timsrust_cpp_bridge +# +# Provides imported target: timsrust_cpp_bridge::timsrust_cpp_bridge +# +# This file lives at /lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake +# The prefix is resolved relative to this file's location. + +get_filename_component(_TIMSRUST_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) + +if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) + add_library(timsrust_cpp_bridge::timsrust_cpp_bridge STATIC IMPORTED) + + set_target_properties(timsrust_cpp_bridge::timsrust_cpp_bridge PROPERTIES + IMPORTED_LOCATION "${_TIMSRUST_PREFIX}/lib/@TIMSRUST_LIB_FILENAME@" + INTERFACE_INCLUDE_DIRECTORIES "${_TIMSRUST_PREFIX}/include" + ) + + # Platform-specific system link dependencies required by the Rust static library. + # These are validated empirically by CI smoke tests on each platform. + if(UNIX AND NOT APPLE) + set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY + INTERFACE_LINK_LIBRARIES pthread dl m) + elseif(APPLE) + set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY + INTERFACE_LINK_LIBRARIES "-framework Security" "-framework SystemConfiguration" resolv) + elseif(WIN32) + set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY + INTERFACE_LINK_LIBRARIES ws2_32 userenv bcrypt ntdll advapi32) + endif() +endif() + +unset(_TIMSRUST_PREFIX) +``` + +Write this to `cmake/timsrust_cpp_bridgeConfig.cmake.in`. + +- [ ] **Step 2: Verify the template placeholder** + +Run: `grep '@TIMSRUST_LIB_FILENAME@' cmake/timsrust_cpp_bridgeConfig.cmake.in` +Expected: One match on the `IMPORTED_LOCATION` line. + +- [ ] **Step 3: Commit** + +```bash +git add cmake/timsrust_cpp_bridgeConfig.cmake.in +git commit -m "feat: add CMake config template for find_package() support" +``` + +### Task 2: Create CMake Version Config Template + +**Files:** +- Create: `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in` + +- [ ] **Step 1: Create the version config template** + +This is the standard CMake version compatibility boilerplate using `SameMinorVersion` semantics. The `@TIMSRUST_VERSION_MAJOR@`, `@TIMSRUST_VERSION_MINOR@`, and `@TIMSRUST_VERSION_PATCH@` placeholders are replaced by `scripts/package.sh` at package time. + +```cmake +# timsrust_cpp_bridgeConfigVersion.cmake +# Auto-generated version compatibility file — SameMinorVersion semantics. + +set(PACKAGE_VERSION "@TIMSRUST_VERSION_MAJOR@.@TIMSRUST_VERSION_MINOR@.@TIMSRUST_VERSION_PATCH@") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + # SameMinorVersion: major and minor must match exactly + if("@TIMSRUST_VERSION_MAJOR@" EQUAL PACKAGE_FIND_VERSION_MAJOR + AND "@TIMSRUST_VERSION_MINOR@" EQUAL PACKAGE_FIND_VERSION_MINOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() +``` + +Write this to `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in`. + +- [ ] **Step 2: Verify placeholders** + +Run: `grep '@TIMSRUST_VERSION' cmake/timsrust_cpp_bridgeConfigVersion.cmake.in` +Expected: Multiple matches for MAJOR, MINOR, PATCH placeholders. + +- [ ] **Step 3: Commit** + +```bash +git add cmake/timsrust_cpp_bridgeConfigVersion.cmake.in +git commit -m "feat: add CMake version config template with SameMinorVersion semantics" +``` + +--- + +## Chunk 2: Packaging Script + +### Task 3: Create package.sh + +**Files:** +- Create: `scripts/package.sh` + +This script runs after `cargo build --release` and assembles the release tarball. It must work under both Linux/macOS bash and Windows Git Bash (GitHub Actions `shell: bash`). + +- [ ] **Step 1: Create the packaging script** + +```bash +#!/usr/bin/env bash +# +# Package timsrust_cpp_bridge release artifacts. +# +# Usage: scripts/package.sh +# e.g.: scripts/package.sh 0.1.0 x86_64-unknown-linux-gnu +# +# Expects cargo build --release to have been run already. +# Outputs: timsrust_cpp_bridge-v-.tar.gz (or .zip on Windows) + +set -euo pipefail + +VERSION="${1:?Usage: package.sh }" +TARGET="${2:?Usage: package.sh }" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Map Rust target triple to platform label and library filename +case "$TARGET" in + x86_64-unknown-linux-gnu) + PLATFORM="linux-x86_64" + LIB_FILENAME="libtimsrust_cpp_bridge.a" + ;; + aarch64-unknown-linux-gnu) + PLATFORM="linux-aarch64" + LIB_FILENAME="libtimsrust_cpp_bridge.a" + ;; + aarch64-apple-darwin) + PLATFORM="macos-arm64" + LIB_FILENAME="libtimsrust_cpp_bridge.a" + ;; + x86_64-pc-windows-msvc) + PLATFORM="windows-x86_64" + LIB_FILENAME="timsrust_cpp_bridge.lib" + ;; + *) + echo "Error: unknown target triple: $TARGET" >&2 + exit 1 + ;; +esac + +ARCHIVE_NAME="timsrust_cpp_bridge-v${VERSION}-${PLATFORM}" +STAGING_DIR="$PROJECT_ROOT/target/package/${ARCHIVE_NAME}/timsrust_cpp_bridge" + +# Clean and create staging directory +rm -rf "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}" +mkdir -p "$STAGING_DIR/include" +mkdir -p "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge" + +# Copy header +cp "$PROJECT_ROOT/include/timsrust_cpp_bridge.h" "$STAGING_DIR/include/" + +# Copy static library +cp "$PROJECT_ROOT/target/release/$LIB_FILENAME" "$STAGING_DIR/lib/" + +# Configure CMake config file (replace @TIMSRUST_LIB_FILENAME@ placeholder) +sed "s|@TIMSRUST_LIB_FILENAME@|${LIB_FILENAME}|g" \ + "$PROJECT_ROOT/cmake/timsrust_cpp_bridgeConfig.cmake.in" \ + > "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake" + +# Parse version components +IFS='.' read -r V_MAJOR V_MINOR V_PATCH <<< "$VERSION" + +# Configure version config file +sed -e "s|@TIMSRUST_VERSION_MAJOR@|${V_MAJOR}|g" \ + -e "s|@TIMSRUST_VERSION_MINOR@|${V_MINOR}|g" \ + -e "s|@TIMSRUST_VERSION_PATCH@|${V_PATCH}|g" \ + "$PROJECT_ROOT/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in" \ + > "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfigVersion.cmake" + +# Create archive +cd "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}" +if [[ "$PLATFORM" == windows-* ]]; then + 7z a -tzip "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.zip" timsrust_cpp_bridge/ + echo "Created: target/package/${ARCHIVE_NAME}.zip" +else + tar czf "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.tar.gz" timsrust_cpp_bridge/ + echo "Created: target/package/${ARCHIVE_NAME}.tar.gz" +fi +``` + +Write this to `scripts/package.sh`. + +- [ ] **Step 2: Make the script executable** + +Run: `chmod +x scripts/package.sh` + +- [ ] **Step 3: Verify the script parses without errors** + +Run: `bash -n scripts/package.sh` +Expected: No output (clean parse). + +- [ ] **Step 4: Commit** + +```bash +git add scripts/package.sh +git commit -m "feat: add packaging script for release artifact assembly" +``` + +--- + +## Chunk 3: GitHub Actions CI/Release Workflow + +### Task 4: Create the CI/Release Workflow + +**Files:** +- Create: `.github/workflows/release.yml` + +- [ ] **Step 1: Create the workflow file** + +```yaml +name: CI & Release + +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + branches: [master] + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + platform: linux-x86_64 + archive_ext: tar.gz + + - os: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + platform: linux-aarch64 + archive_ext: tar.gz + + - os: macos-14 + target: aarch64-apple-darwin + platform: macos-arm64 + archive_ext: tar.gz + + - os: windows-2025 + target: x86_64-pc-windows-msvc + platform: windows-x86_64 + archive_ext: zip + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + + - name: Run tests + run: cargo test --features with_timsrust + + - name: Build release + run: cargo build --features with_timsrust --release + + - name: Smoke test (Linux) + if: runner.os == 'Linux' + shell: bash + run: | + g++ -std=c++17 examples/cpp_client.cpp \ + -Iinclude \ + -Ltarget/release -ltimsrust_cpp_bridge \ + -lpthread -ldl -lm \ + -o target/smoke_test + # Run with no args — expect exit code 1 (usage), fail only on signal/crash + target/smoke_test || if [ $? -gt 128 ]; then exit 1; fi + + - name: Smoke test (macOS) + if: runner.os == 'macOS' + shell: bash + run: | + clang++ -std=c++17 examples/cpp_client.cpp \ + -Iinclude \ + -Ltarget/release -ltimsrust_cpp_bridge \ + -framework Security -framework SystemConfiguration \ + -lresolv -lpthread \ + -o target/smoke_test + # Run with no args — expect exit code 1 (usage), fail only on signal/crash + target/smoke_test || if [ $? -gt 128 ]; then exit 1; fi + + - name: Setup MSVC dev environment + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Smoke test (Windows) + if: runner.os == 'Windows' + shell: bash + run: | + cl.exe /std:c++17 /EHsc /Fe:target/smoke_test.exe \ + /I include examples/cpp_client.cpp \ + target/release/timsrust_cpp_bridge.lib \ + ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib + target/smoke_test.exe || if [ $? -gt 128 ]; then exit 1; fi + + - name: Extract version from tag + if: startsWith(github.ref, 'refs/tags/v') + id: version + shell: bash + run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + + - name: Package + if: startsWith(github.ref, 'refs/tags/v') + shell: bash + run: bash scripts/package.sh "${{ steps.version.outputs.version }}" "${{ matrix.target }}" + + - name: Upload artifact + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/upload-artifact@v4 + with: + name: timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }} + path: target/package/timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }} + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: artifacts/**/* + generate_release_notes: true +``` + +Write this to `.github/workflows/release.yml`. + +- [ ] **Step 2: Validate YAML syntax** + +Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))"` +If python3-yaml is not available, run: `python3 -c "import json, sys; print('YAML check skipped')"` + +- [ ] **Step 3: Commit** + +```bash +git add .github/workflows/release.yml +git commit -m "feat: add CI/release workflow for multi-platform builds" +``` + +--- + +## Chunk 4: Local Validation + +### Task 5: Validate the Full Pipeline Locally + +This task validates that all the pieces fit together by doing a dry run of the packaging script against a local build. + +- [ ] **Step 1: Build the library locally** + +Run: `cargo build --features with_timsrust --release` +Expected: Successful build, `target/release/libtimsrust_cpp_bridge.a` exists. + +- [ ] **Step 2: Run the packaging script locally** + +Run: `bash scripts/package.sh 0.1.0 x86_64-unknown-linux-gnu` +Expected: Output says `Created: target/package/timsrust_cpp_bridge-v0.1.0-linux-x86_64.tar.gz` + +- [ ] **Step 3: Verify tarball contents** + +Run: `tar tzf target/package/timsrust_cpp_bridge-v0.1.0-linux-x86_64.tar.gz | sort` +Expected: +``` +timsrust_cpp_bridge/ +timsrust_cpp_bridge/include/ +timsrust_cpp_bridge/include/timsrust_cpp_bridge.h +timsrust_cpp_bridge/lib/ +timsrust_cpp_bridge/lib/cmake/ +timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/ +timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake +timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfigVersion.cmake +timsrust_cpp_bridge/lib/libtimsrust_cpp_bridge.a +``` + +- [ ] **Step 4: Verify CMake config file has no remaining placeholders** + +Run: `tar xzf target/package/timsrust_cpp_bridge-v0.1.0-linux-x86_64.tar.gz -C /tmp && grep '@' /tmp/timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/*.cmake` +Expected: No output (all placeholders replaced). + +- [ ] **Step 5: Verify find_package works with a minimal CMake consumer** + +Create a temporary test directory and verify CMake can find and link the package: + +```bash +mkdir -p /tmp/timsrust_test && cat > /tmp/timsrust_test/CMakeLists.txt << 'CMAKEOF' +cmake_minimum_required(VERSION 3.11) +project(test_consumer C CXX) +set(CMAKE_CXX_STANDARD 17) +find_package(timsrust_cpp_bridge REQUIRED) +add_executable(test_consumer test.cpp) +target_link_libraries(test_consumer timsrust_cpp_bridge::timsrust_cpp_bridge) +CMAKEOF + +cat > /tmp/timsrust_test/test.cpp << 'CPPEOF' +#include "timsrust_cpp_bridge.h" +int main() { return 0; } +CPPEOF + +cmake -S /tmp/timsrust_test -B /tmp/timsrust_test/build \ + -DCMAKE_PREFIX_PATH=/tmp/timsrust_cpp_bridge +cmake --build /tmp/timsrust_test/build +/tmp/timsrust_test/build/test_consumer +``` + +Expected: Configures, builds, and runs without error. + +- [ ] **Step 6: Clean up and commit any fixes** + +If any issues were found, fix them and commit. Otherwise, no commit needed. + +```bash +rm -rf /tmp/timsrust_test /tmp/timsrust_cpp_bridge target/package +``` diff --git a/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md b/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md index 343de6c..25ce7e5 100644 --- a/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md +++ b/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md @@ -119,7 +119,7 @@ if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) INTERFACE "-framework Security" "-framework SystemConfiguration" resolv) elseif(WIN32) target_link_libraries(timsrust_cpp_bridge::timsrust_cpp_bridge - INTERFACE ws2_32 userenv bcrypt ntdll) + INTERFACE ws2_32 userenv bcrypt ntdll advapi32) endif() endif() ``` From 902f8293a54745bcb42153572fe908287f57fa08 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:17:55 +0100 Subject: [PATCH 04/14] feat: add CMake config templates for find_package() support Adds Config.cmake.in with imported static target and platform-specific system link deps, plus ConfigVersion.cmake.in with SameMinorVersion compatibility semantics. Co-Authored-By: Claude Opus 4.6 --- cmake/timsrust_cpp_bridgeConfig.cmake.in | 33 +++++++++++++++++++ .../timsrust_cpp_bridgeConfigVersion.cmake.in | 20 +++++++++++ 2 files changed, 53 insertions(+) create mode 100644 cmake/timsrust_cpp_bridgeConfig.cmake.in create mode 100644 cmake/timsrust_cpp_bridgeConfigVersion.cmake.in diff --git a/cmake/timsrust_cpp_bridgeConfig.cmake.in b/cmake/timsrust_cpp_bridgeConfig.cmake.in new file mode 100644 index 0000000..26c05d4 --- /dev/null +++ b/cmake/timsrust_cpp_bridgeConfig.cmake.in @@ -0,0 +1,33 @@ +# timsrust_cpp_bridgeConfig.cmake +# Config-mode package file for timsrust_cpp_bridge +# +# Provides imported target: timsrust_cpp_bridge::timsrust_cpp_bridge +# +# This file lives at /lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake +# The prefix is resolved relative to this file's location. + +get_filename_component(_TIMSRUST_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) + +if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) + add_library(timsrust_cpp_bridge::timsrust_cpp_bridge STATIC IMPORTED) + + set_target_properties(timsrust_cpp_bridge::timsrust_cpp_bridge PROPERTIES + IMPORTED_LOCATION "${_TIMSRUST_PREFIX}/lib/@TIMSRUST_LIB_FILENAME@" + INTERFACE_INCLUDE_DIRECTORIES "${_TIMSRUST_PREFIX}/include" + ) + + # Platform-specific system link dependencies required by the Rust static library. + # These are validated empirically by CI smoke tests on each platform. + if(UNIX AND NOT APPLE) + set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY + INTERFACE_LINK_LIBRARIES pthread dl m) + elseif(APPLE) + set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY + INTERFACE_LINK_LIBRARIES "-framework Security" "-framework SystemConfiguration" resolv) + elseif(WIN32) + set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY + INTERFACE_LINK_LIBRARIES ws2_32 userenv bcrypt ntdll advapi32) + endif() +endif() + +unset(_TIMSRUST_PREFIX) diff --git a/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in b/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in new file mode 100644 index 0000000..9ab47d7 --- /dev/null +++ b/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in @@ -0,0 +1,20 @@ +# timsrust_cpp_bridgeConfigVersion.cmake +# Auto-generated version compatibility file — SameMinorVersion semantics. + +set(PACKAGE_VERSION "@TIMSRUST_VERSION_MAJOR@.@TIMSRUST_VERSION_MINOR@.@TIMSRUST_VERSION_PATCH@") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + # SameMinorVersion: major and minor must match exactly + if("@TIMSRUST_VERSION_MAJOR@" EQUAL PACKAGE_FIND_VERSION_MAJOR + AND "@TIMSRUST_VERSION_MINOR@" EQUAL PACKAGE_FIND_VERSION_MINOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() From 6e537eb9f253eb1e83a22d79c271c602bcbea7bf Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:18:13 +0100 Subject: [PATCH 05/14] feat: add packaging script for release artifact assembly Assembles release tarballs from cargo build output with header, static library, and configured CMake config files. Handles all 4 target platform/arch combos. Co-Authored-By: Claude Opus 4.6 --- scripts/package.sh | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100755 scripts/package.sh diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 0000000..e88e4a6 --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# Package timsrust_cpp_bridge release artifacts. +# +# Usage: scripts/package.sh +# e.g.: scripts/package.sh 0.1.0 x86_64-unknown-linux-gnu +# +# Expects cargo build --release to have been run already. +# Outputs: timsrust_cpp_bridge-v-.tar.gz (or .zip on Windows) + +set -euo pipefail + +VERSION="${1:?Usage: package.sh }" +TARGET="${2:?Usage: package.sh }" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Map Rust target triple to platform label and library filename +case "$TARGET" in + x86_64-unknown-linux-gnu) + PLATFORM="linux-x86_64" + LIB_FILENAME="libtimsrust_cpp_bridge.a" + ;; + aarch64-unknown-linux-gnu) + PLATFORM="linux-aarch64" + LIB_FILENAME="libtimsrust_cpp_bridge.a" + ;; + aarch64-apple-darwin) + PLATFORM="macos-arm64" + LIB_FILENAME="libtimsrust_cpp_bridge.a" + ;; + x86_64-pc-windows-msvc) + PLATFORM="windows-x86_64" + LIB_FILENAME="timsrust_cpp_bridge.lib" + ;; + *) + echo "Error: unknown target triple: $TARGET" >&2 + exit 1 + ;; +esac + +ARCHIVE_NAME="timsrust_cpp_bridge-v${VERSION}-${PLATFORM}" +STAGING_DIR="$PROJECT_ROOT/target/package/${ARCHIVE_NAME}/timsrust_cpp_bridge" + +# Clean and create staging directory +rm -rf "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}" +mkdir -p "$STAGING_DIR/include" +mkdir -p "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge" + +# Copy header +cp "$PROJECT_ROOT/include/timsrust_cpp_bridge.h" "$STAGING_DIR/include/" + +# Copy static library +cp "$PROJECT_ROOT/target/release/$LIB_FILENAME" "$STAGING_DIR/lib/" + +# Configure CMake config file (replace @TIMSRUST_LIB_FILENAME@ placeholder) +sed "s|@TIMSRUST_LIB_FILENAME@|${LIB_FILENAME}|g" \ + "$PROJECT_ROOT/cmake/timsrust_cpp_bridgeConfig.cmake.in" \ + > "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake" + +# Parse version components +IFS='.' read -r V_MAJOR V_MINOR V_PATCH <<< "$VERSION" + +# Configure version config file +sed -e "s|@TIMSRUST_VERSION_MAJOR@|${V_MAJOR}|g" \ + -e "s|@TIMSRUST_VERSION_MINOR@|${V_MINOR}|g" \ + -e "s|@TIMSRUST_VERSION_PATCH@|${V_PATCH}|g" \ + "$PROJECT_ROOT/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in" \ + > "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfigVersion.cmake" + +# Create archive +cd "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}" +if [[ "$PLATFORM" == windows-* ]]; then + 7z a -tzip "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.zip" timsrust_cpp_bridge/ + echo "Created: target/package/${ARCHIVE_NAME}.zip" +else + tar czf "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.tar.gz" timsrust_cpp_bridge/ + echo "Created: target/package/${ARCHIVE_NAME}.tar.gz" +fi From fc06029dc75f33e7b771359fc5c0396e06125117 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:19:02 +0100 Subject: [PATCH 06/14] feat: add CI/release workflow for multi-platform builds Builds and tests on linux-x86_64, linux-aarch64, macos-arm64, and windows-x86_64. Smoke tests link the C++ example against the static library on each platform. Tag pushes create GitHub Releases with pre-built artifacts. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 127 ++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..73aeeaa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,127 @@ +name: CI & Release + +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + branches: [master] + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + platform: linux-x86_64 + archive_ext: tar.gz + + - os: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + platform: linux-aarch64 + archive_ext: tar.gz + + - os: macos-14 + target: aarch64-apple-darwin + platform: macos-arm64 + archive_ext: tar.gz + + - os: windows-2025 + target: x86_64-pc-windows-msvc + platform: windows-x86_64 + archive_ext: zip + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + + - name: Run tests + run: cargo test --features with_timsrust + + - name: Build release + run: cargo build --features with_timsrust --release + + - name: Smoke test (Linux) + if: runner.os == 'Linux' + shell: bash + run: | + g++ -std=c++17 examples/cpp_client.cpp \ + -Iinclude \ + -Ltarget/release -ltimsrust_cpp_bridge \ + -lpthread -ldl -lm \ + -o target/smoke_test + # Run with no args — expect exit code 1 (usage), fail only on signal/crash + target/smoke_test || if [ $? -gt 128 ]; then exit 1; fi + + - name: Smoke test (macOS) + if: runner.os == 'macOS' + shell: bash + run: | + clang++ -std=c++17 examples/cpp_client.cpp \ + -Iinclude \ + -Ltarget/release -ltimsrust_cpp_bridge \ + -framework Security -framework SystemConfiguration \ + -lresolv -lpthread \ + -o target/smoke_test + # Run with no args — expect exit code 1 (usage), fail only on signal/crash + target/smoke_test || if [ $? -gt 128 ]; then exit 1; fi + + - name: Setup MSVC dev environment + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Smoke test (Windows) + if: runner.os == 'Windows' + shell: bash + run: | + cl.exe /std:c++17 /EHsc /Fe:target/smoke_test.exe \ + /I include examples/cpp_client.cpp \ + target/release/timsrust_cpp_bridge.lib \ + ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib + target/smoke_test.exe || if [ $? -gt 128 ]; then exit 1; fi + + - name: Extract version from tag + if: startsWith(github.ref, 'refs/tags/v') + id: version + shell: bash + run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + + - name: Package + if: startsWith(github.ref, 'refs/tags/v') + shell: bash + run: bash scripts/package.sh "${{ steps.version.outputs.version }}" "${{ matrix.target }}" + + - name: Upload artifact + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/upload-artifact@v4 + with: + name: timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }} + path: target/package/timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }} + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: artifacts/**/* + generate_release_notes: true From dec8c18a4a4420a9c0c481b1dfc0107b99ce916a Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:34:24 +0100 Subject: [PATCH 07/14] fix: use cmd shell for Windows smoke test to avoid MSYS path mangling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git Bash's MSYS layer converts /EHsc to C:/Program Files/Git/EHsc and /I to I:/ — breaking cl.exe flag parsing. Using shell: cmd avoids this. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73aeeaa..4db3c77 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,13 +81,10 @@ jobs: - name: Smoke test (Windows) if: runner.os == 'Windows' - shell: bash + shell: cmd run: | - cl.exe /std:c++17 /EHsc /Fe:target/smoke_test.exe \ - /I include examples/cpp_client.cpp \ - target/release/timsrust_cpp_bridge.lib \ - ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib - target/smoke_test.exe || if [ $? -gt 128 ]; then exit 1; fi + cl.exe /std:c++17 /EHsc /Fe:target\smoke_test.exe /I include examples\cpp_client.cpp target\release\timsrust_cpp_bridge.lib ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib + target\smoke_test.exe || ver>nul - name: Extract version from tag if: startsWith(github.ref, 'refs/tags/v') From 485cf98993c6d3639970e3f6d02f8c826861579c Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 19:57:57 +0100 Subject: [PATCH 08/14] Address PR review feedback - Pin all GitHub Actions to commit SHAs for reproducibility - Link static archive explicitly in smoke tests (not -l which prefers .so) - Run packaging + CMake find_package() validation on every build, not just tags - Use VERSION_EQUAL instead of STREQUAL in ConfigVersion.cmake.in - Validate semver format and rm stale zip before 7z in package.sh - Remove design/plan docs from PR Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 60 ++- .../timsrust_cpp_bridgeConfigVersion.cmake.in | 2 +- .../2026-03-11-cmake-packaging-ci-release.md | 474 ------------------ ...03-11-cmake-packaging-ci-release-design.md | 163 ------ scripts/package.sh | 7 + 5 files changed, 55 insertions(+), 651 deletions(-) delete mode 100644 docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md delete mode 100644 docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4db3c77..a594ecd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,13 +36,18 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + # actions/checkout v4.3.1 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable + # dtolnay/rust-toolchain stable branch + uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 + with: + toolchain: stable - name: Cache Cargo - uses: Swatinem/rust-cache@v2 + # Swatinem/rust-cache v2.8.2 + uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 - name: Run tests run: cargo test --features with_timsrust @@ -56,7 +61,7 @@ jobs: run: | g++ -std=c++17 examples/cpp_client.cpp \ -Iinclude \ - -Ltarget/release -ltimsrust_cpp_bridge \ + target/release/libtimsrust_cpp_bridge.a \ -lpthread -ldl -lm \ -o target/smoke_test # Run with no args — expect exit code 1 (usage), fail only on signal/crash @@ -68,7 +73,7 @@ jobs: run: | clang++ -std=c++17 examples/cpp_client.cpp \ -Iinclude \ - -Ltarget/release -ltimsrust_cpp_bridge \ + target/release/libtimsrust_cpp_bridge.a \ -framework Security -framework SystemConfiguration \ -lresolv -lpthread \ -o target/smoke_test @@ -77,7 +82,8 @@ jobs: - name: Setup MSVC dev environment if: runner.os == 'Windows' - uses: ilammy/msvc-dev-cmd@v1 + # ilammy/msvc-dev-cmd v1.13.0 + uses: ilammy/msvc-dev-cmd@a102174a2b586eec2ea151a69e6fd14404a8ce7c - name: Smoke test (Windows) if: runner.os == 'Windows' @@ -86,20 +92,46 @@ jobs: cl.exe /std:c++17 /EHsc /Fe:target\smoke_test.exe /I include examples\cpp_client.cpp target\release\timsrust_cpp_bridge.lib ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib target\smoke_test.exe || ver>nul - - name: Extract version from tag - if: startsWith(github.ref, 'refs/tags/v') + - name: Set version id: version shell: bash - run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + run: | + if [[ "$GITHUB_REF" == refs/tags/v* ]]; then + echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + else + echo "version=0.0.0-dev" >> "$GITHUB_OUTPUT" + fi - name: Package - if: startsWith(github.ref, 'refs/tags/v') shell: bash run: bash scripts/package.sh "${{ steps.version.outputs.version }}" "${{ matrix.target }}" + - name: Validate CMake package + shell: bash + run: | + ARCHIVE_DIR="target/package/timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }}/timsrust_cpp_bridge" + mkdir -p /tmp/timsrust_cmake_test + cat > /tmp/timsrust_cmake_test/CMakeLists.txt << 'CMAKEOF' + cmake_minimum_required(VERSION 3.11) + project(test_consumer CXX) + set(CMAKE_CXX_STANDARD 17) + find_package(timsrust_cpp_bridge REQUIRED) + add_executable(test_consumer test.cpp) + target_link_libraries(test_consumer timsrust_cpp_bridge::timsrust_cpp_bridge) + CMAKEOF + cat > /tmp/timsrust_cmake_test/test.cpp << 'CPPEOF' + #include "timsrust_cpp_bridge.h" + int main() { return 0; } + CPPEOF + cmake -S /tmp/timsrust_cmake_test -B /tmp/timsrust_cmake_test/build \ + -DCMAKE_PREFIX_PATH="$(pwd)/${ARCHIVE_DIR}" + cmake --build /tmp/timsrust_cmake_test/build + rm -rf /tmp/timsrust_cmake_test + - name: Upload artifact if: startsWith(github.ref, 'refs/tags/v') - uses: actions/upload-artifact@v4 + # actions/upload-artifact v4.6.2 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: name: timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }} path: target/package/timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }} @@ -113,12 +145,14 @@ jobs: steps: - name: Download all artifacts - uses: actions/download-artifact@v4 + # actions/download-artifact v4.3.0 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: path: artifacts/ - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + # softprops/action-gh-release v2.5.0 + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b with: files: artifacts/**/* generate_release_notes: true diff --git a/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in b/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in index 9ab47d7..742a421 100644 --- a/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in +++ b/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in @@ -14,7 +14,7 @@ else() set(PACKAGE_VERSION_COMPATIBLE FALSE) endif() - if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) endif() endif() diff --git a/docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md b/docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md deleted file mode 100644 index 32554bb..0000000 --- a/docs/superpowers/plans/2026-03-11-cmake-packaging-ci-release.md +++ /dev/null @@ -1,474 +0,0 @@ -# CMake Packaging & CI/Release Pipeline Implementation Plan - -> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add CI pipeline and CMake config packaging so OpenMS can consume timsrust_cpp_bridge via `find_package()` with pre-built static library releases. - -**Architecture:** GitHub Actions CI builds the Rust library on 4 platform/arch combos (linux-x86_64, linux-aarch64, macos-arm64, windows-x86_64). A packaging script assembles each build into a tarball containing the static library, C header, and CMake config files. Tag pushes trigger GitHub Releases with all 4 artifacts attached. - -**Tech Stack:** Rust/Cargo, GitHub Actions, CMake (config-mode packages), Bash - -**Spec:** `docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md` - ---- - -## File Map - -| File | Action | Responsibility | -|---|---|---| -| `cmake/timsrust_cpp_bridgeConfig.cmake.in` | Create | CMake config template with platform system deps | -| `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in` | Create | CMake version compatibility template | -| `scripts/package.sh` | Create | Assembles release tarball from cargo build output | -| `.github/workflows/release.yml` | Create | CI build + smoke test + release workflow | - -No existing files are modified. - ---- - -## Chunk 1: CMake Config Templates - -### Task 1: Create CMake Config Template - -**Files:** -- Create: `cmake/timsrust_cpp_bridgeConfig.cmake.in` - -- [ ] **Step 1: Create the CMake config template** - -```cmake -# timsrust_cpp_bridgeConfig.cmake -# Config-mode package file for timsrust_cpp_bridge -# -# Provides imported target: timsrust_cpp_bridge::timsrust_cpp_bridge -# -# This file lives at /lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake -# The prefix is resolved relative to this file's location. - -get_filename_component(_TIMSRUST_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) - -if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) - add_library(timsrust_cpp_bridge::timsrust_cpp_bridge STATIC IMPORTED) - - set_target_properties(timsrust_cpp_bridge::timsrust_cpp_bridge PROPERTIES - IMPORTED_LOCATION "${_TIMSRUST_PREFIX}/lib/@TIMSRUST_LIB_FILENAME@" - INTERFACE_INCLUDE_DIRECTORIES "${_TIMSRUST_PREFIX}/include" - ) - - # Platform-specific system link dependencies required by the Rust static library. - # These are validated empirically by CI smoke tests on each platform. - if(UNIX AND NOT APPLE) - set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY - INTERFACE_LINK_LIBRARIES pthread dl m) - elseif(APPLE) - set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY - INTERFACE_LINK_LIBRARIES "-framework Security" "-framework SystemConfiguration" resolv) - elseif(WIN32) - set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY - INTERFACE_LINK_LIBRARIES ws2_32 userenv bcrypt ntdll advapi32) - endif() -endif() - -unset(_TIMSRUST_PREFIX) -``` - -Write this to `cmake/timsrust_cpp_bridgeConfig.cmake.in`. - -- [ ] **Step 2: Verify the template placeholder** - -Run: `grep '@TIMSRUST_LIB_FILENAME@' cmake/timsrust_cpp_bridgeConfig.cmake.in` -Expected: One match on the `IMPORTED_LOCATION` line. - -- [ ] **Step 3: Commit** - -```bash -git add cmake/timsrust_cpp_bridgeConfig.cmake.in -git commit -m "feat: add CMake config template for find_package() support" -``` - -### Task 2: Create CMake Version Config Template - -**Files:** -- Create: `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in` - -- [ ] **Step 1: Create the version config template** - -This is the standard CMake version compatibility boilerplate using `SameMinorVersion` semantics. The `@TIMSRUST_VERSION_MAJOR@`, `@TIMSRUST_VERSION_MINOR@`, and `@TIMSRUST_VERSION_PATCH@` placeholders are replaced by `scripts/package.sh` at package time. - -```cmake -# timsrust_cpp_bridgeConfigVersion.cmake -# Auto-generated version compatibility file — SameMinorVersion semantics. - -set(PACKAGE_VERSION "@TIMSRUST_VERSION_MAJOR@.@TIMSRUST_VERSION_MINOR@.@TIMSRUST_VERSION_PATCH@") - -if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) - set(PACKAGE_VERSION_COMPATIBLE FALSE) -else() - # SameMinorVersion: major and minor must match exactly - if("@TIMSRUST_VERSION_MAJOR@" EQUAL PACKAGE_FIND_VERSION_MAJOR - AND "@TIMSRUST_VERSION_MINOR@" EQUAL PACKAGE_FIND_VERSION_MINOR) - set(PACKAGE_VERSION_COMPATIBLE TRUE) - else() - set(PACKAGE_VERSION_COMPATIBLE FALSE) - endif() - - if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) - set(PACKAGE_VERSION_EXACT TRUE) - endif() -endif() -``` - -Write this to `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in`. - -- [ ] **Step 2: Verify placeholders** - -Run: `grep '@TIMSRUST_VERSION' cmake/timsrust_cpp_bridgeConfigVersion.cmake.in` -Expected: Multiple matches for MAJOR, MINOR, PATCH placeholders. - -- [ ] **Step 3: Commit** - -```bash -git add cmake/timsrust_cpp_bridgeConfigVersion.cmake.in -git commit -m "feat: add CMake version config template with SameMinorVersion semantics" -``` - ---- - -## Chunk 2: Packaging Script - -### Task 3: Create package.sh - -**Files:** -- Create: `scripts/package.sh` - -This script runs after `cargo build --release` and assembles the release tarball. It must work under both Linux/macOS bash and Windows Git Bash (GitHub Actions `shell: bash`). - -- [ ] **Step 1: Create the packaging script** - -```bash -#!/usr/bin/env bash -# -# Package timsrust_cpp_bridge release artifacts. -# -# Usage: scripts/package.sh -# e.g.: scripts/package.sh 0.1.0 x86_64-unknown-linux-gnu -# -# Expects cargo build --release to have been run already. -# Outputs: timsrust_cpp_bridge-v-.tar.gz (or .zip on Windows) - -set -euo pipefail - -VERSION="${1:?Usage: package.sh }" -TARGET="${2:?Usage: package.sh }" - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -# Map Rust target triple to platform label and library filename -case "$TARGET" in - x86_64-unknown-linux-gnu) - PLATFORM="linux-x86_64" - LIB_FILENAME="libtimsrust_cpp_bridge.a" - ;; - aarch64-unknown-linux-gnu) - PLATFORM="linux-aarch64" - LIB_FILENAME="libtimsrust_cpp_bridge.a" - ;; - aarch64-apple-darwin) - PLATFORM="macos-arm64" - LIB_FILENAME="libtimsrust_cpp_bridge.a" - ;; - x86_64-pc-windows-msvc) - PLATFORM="windows-x86_64" - LIB_FILENAME="timsrust_cpp_bridge.lib" - ;; - *) - echo "Error: unknown target triple: $TARGET" >&2 - exit 1 - ;; -esac - -ARCHIVE_NAME="timsrust_cpp_bridge-v${VERSION}-${PLATFORM}" -STAGING_DIR="$PROJECT_ROOT/target/package/${ARCHIVE_NAME}/timsrust_cpp_bridge" - -# Clean and create staging directory -rm -rf "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}" -mkdir -p "$STAGING_DIR/include" -mkdir -p "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge" - -# Copy header -cp "$PROJECT_ROOT/include/timsrust_cpp_bridge.h" "$STAGING_DIR/include/" - -# Copy static library -cp "$PROJECT_ROOT/target/release/$LIB_FILENAME" "$STAGING_DIR/lib/" - -# Configure CMake config file (replace @TIMSRUST_LIB_FILENAME@ placeholder) -sed "s|@TIMSRUST_LIB_FILENAME@|${LIB_FILENAME}|g" \ - "$PROJECT_ROOT/cmake/timsrust_cpp_bridgeConfig.cmake.in" \ - > "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake" - -# Parse version components -IFS='.' read -r V_MAJOR V_MINOR V_PATCH <<< "$VERSION" - -# Configure version config file -sed -e "s|@TIMSRUST_VERSION_MAJOR@|${V_MAJOR}|g" \ - -e "s|@TIMSRUST_VERSION_MINOR@|${V_MINOR}|g" \ - -e "s|@TIMSRUST_VERSION_PATCH@|${V_PATCH}|g" \ - "$PROJECT_ROOT/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in" \ - > "$STAGING_DIR/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfigVersion.cmake" - -# Create archive -cd "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}" -if [[ "$PLATFORM" == windows-* ]]; then - 7z a -tzip "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.zip" timsrust_cpp_bridge/ - echo "Created: target/package/${ARCHIVE_NAME}.zip" -else - tar czf "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.tar.gz" timsrust_cpp_bridge/ - echo "Created: target/package/${ARCHIVE_NAME}.tar.gz" -fi -``` - -Write this to `scripts/package.sh`. - -- [ ] **Step 2: Make the script executable** - -Run: `chmod +x scripts/package.sh` - -- [ ] **Step 3: Verify the script parses without errors** - -Run: `bash -n scripts/package.sh` -Expected: No output (clean parse). - -- [ ] **Step 4: Commit** - -```bash -git add scripts/package.sh -git commit -m "feat: add packaging script for release artifact assembly" -``` - ---- - -## Chunk 3: GitHub Actions CI/Release Workflow - -### Task 4: Create the CI/Release Workflow - -**Files:** -- Create: `.github/workflows/release.yml` - -- [ ] **Step 1: Create the workflow file** - -```yaml -name: CI & Release - -on: - push: - branches: [master] - tags: ['v*'] - pull_request: - branches: [master] - -jobs: - build: - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-24.04 - target: x86_64-unknown-linux-gnu - platform: linux-x86_64 - archive_ext: tar.gz - - - os: ubuntu-24.04-arm - target: aarch64-unknown-linux-gnu - platform: linux-aarch64 - archive_ext: tar.gz - - - os: macos-14 - target: aarch64-apple-darwin - platform: macos-arm64 - archive_ext: tar.gz - - - os: windows-2025 - target: x86_64-pc-windows-msvc - platform: windows-x86_64 - archive_ext: zip - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Cache Cargo - uses: Swatinem/rust-cache@v2 - - - name: Run tests - run: cargo test --features with_timsrust - - - name: Build release - run: cargo build --features with_timsrust --release - - - name: Smoke test (Linux) - if: runner.os == 'Linux' - shell: bash - run: | - g++ -std=c++17 examples/cpp_client.cpp \ - -Iinclude \ - -Ltarget/release -ltimsrust_cpp_bridge \ - -lpthread -ldl -lm \ - -o target/smoke_test - # Run with no args — expect exit code 1 (usage), fail only on signal/crash - target/smoke_test || if [ $? -gt 128 ]; then exit 1; fi - - - name: Smoke test (macOS) - if: runner.os == 'macOS' - shell: bash - run: | - clang++ -std=c++17 examples/cpp_client.cpp \ - -Iinclude \ - -Ltarget/release -ltimsrust_cpp_bridge \ - -framework Security -framework SystemConfiguration \ - -lresolv -lpthread \ - -o target/smoke_test - # Run with no args — expect exit code 1 (usage), fail only on signal/crash - target/smoke_test || if [ $? -gt 128 ]; then exit 1; fi - - - name: Setup MSVC dev environment - if: runner.os == 'Windows' - uses: ilammy/msvc-dev-cmd@v1 - - - name: Smoke test (Windows) - if: runner.os == 'Windows' - shell: bash - run: | - cl.exe /std:c++17 /EHsc /Fe:target/smoke_test.exe \ - /I include examples/cpp_client.cpp \ - target/release/timsrust_cpp_bridge.lib \ - ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib - target/smoke_test.exe || if [ $? -gt 128 ]; then exit 1; fi - - - name: Extract version from tag - if: startsWith(github.ref, 'refs/tags/v') - id: version - shell: bash - run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" - - - name: Package - if: startsWith(github.ref, 'refs/tags/v') - shell: bash - run: bash scripts/package.sh "${{ steps.version.outputs.version }}" "${{ matrix.target }}" - - - name: Upload artifact - if: startsWith(github.ref, 'refs/tags/v') - uses: actions/upload-artifact@v4 - with: - name: timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }} - path: target/package/timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }} - - release: - if: startsWith(github.ref, 'refs/tags/v') - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts/ - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: artifacts/**/* - generate_release_notes: true -``` - -Write this to `.github/workflows/release.yml`. - -- [ ] **Step 2: Validate YAML syntax** - -Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))"` -If python3-yaml is not available, run: `python3 -c "import json, sys; print('YAML check skipped')"` - -- [ ] **Step 3: Commit** - -```bash -git add .github/workflows/release.yml -git commit -m "feat: add CI/release workflow for multi-platform builds" -``` - ---- - -## Chunk 4: Local Validation - -### Task 5: Validate the Full Pipeline Locally - -This task validates that all the pieces fit together by doing a dry run of the packaging script against a local build. - -- [ ] **Step 1: Build the library locally** - -Run: `cargo build --features with_timsrust --release` -Expected: Successful build, `target/release/libtimsrust_cpp_bridge.a` exists. - -- [ ] **Step 2: Run the packaging script locally** - -Run: `bash scripts/package.sh 0.1.0 x86_64-unknown-linux-gnu` -Expected: Output says `Created: target/package/timsrust_cpp_bridge-v0.1.0-linux-x86_64.tar.gz` - -- [ ] **Step 3: Verify tarball contents** - -Run: `tar tzf target/package/timsrust_cpp_bridge-v0.1.0-linux-x86_64.tar.gz | sort` -Expected: -``` -timsrust_cpp_bridge/ -timsrust_cpp_bridge/include/ -timsrust_cpp_bridge/include/timsrust_cpp_bridge.h -timsrust_cpp_bridge/lib/ -timsrust_cpp_bridge/lib/cmake/ -timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/ -timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake -timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfigVersion.cmake -timsrust_cpp_bridge/lib/libtimsrust_cpp_bridge.a -``` - -- [ ] **Step 4: Verify CMake config file has no remaining placeholders** - -Run: `tar xzf target/package/timsrust_cpp_bridge-v0.1.0-linux-x86_64.tar.gz -C /tmp && grep '@' /tmp/timsrust_cpp_bridge/lib/cmake/timsrust_cpp_bridge/*.cmake` -Expected: No output (all placeholders replaced). - -- [ ] **Step 5: Verify find_package works with a minimal CMake consumer** - -Create a temporary test directory and verify CMake can find and link the package: - -```bash -mkdir -p /tmp/timsrust_test && cat > /tmp/timsrust_test/CMakeLists.txt << 'CMAKEOF' -cmake_minimum_required(VERSION 3.11) -project(test_consumer C CXX) -set(CMAKE_CXX_STANDARD 17) -find_package(timsrust_cpp_bridge REQUIRED) -add_executable(test_consumer test.cpp) -target_link_libraries(test_consumer timsrust_cpp_bridge::timsrust_cpp_bridge) -CMAKEOF - -cat > /tmp/timsrust_test/test.cpp << 'CPPEOF' -#include "timsrust_cpp_bridge.h" -int main() { return 0; } -CPPEOF - -cmake -S /tmp/timsrust_test -B /tmp/timsrust_test/build \ - -DCMAKE_PREFIX_PATH=/tmp/timsrust_cpp_bridge -cmake --build /tmp/timsrust_test/build -/tmp/timsrust_test/build/test_consumer -``` - -Expected: Configures, builds, and runs without error. - -- [ ] **Step 6: Clean up and commit any fixes** - -If any issues were found, fix them and commit. Otherwise, no commit needed. - -```bash -rm -rf /tmp/timsrust_test /tmp/timsrust_cpp_bridge target/package -``` diff --git a/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md b/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md deleted file mode 100644 index 25ce7e5..0000000 --- a/docs/superpowers/specs/2026-03-11-cmake-packaging-ci-release-design.md +++ /dev/null @@ -1,163 +0,0 @@ -# Design: CMake Packaging & CI/Release Pipeline for timsrust_cpp_bridge - -## Goal - -Make timsrust_cpp_bridge consumable by OpenMS (and other CMake projects) via `find_package()`, with a CI pipeline that produces pre-built release artifacts for all platforms OpenMS supports. - -## Decision: Pre-built Release Tarballs + CMake Config Package - -Chosen over FetchContent-based download (adds a new pattern OpenMS doesn't use) and Corrosion/build-from-source (forces Rust toolchain on all consumers). Pre-built artifacts with a CMake config package match OpenMS's existing `find_package()` dependency pattern exactly. - -Static linking chosen because: -- Simplifies distribution (no runtime dependency to ship) -- C ABI is compiler-agnostic — Rust-built `.a`/`.lib` links with gcc, clang, and MSVC -- The CMake config file declares required system link deps so consumers don't need to know about them - -## Release Artifact Structure - -Each GitHub Release contains 4 archives, one per platform/arch: - -``` -timsrust_cpp_bridge-v-linux-x86_64.tar.gz -timsrust_cpp_bridge-v-linux-aarch64.tar.gz -timsrust_cpp_bridge-v-macos-arm64.tar.gz -timsrust_cpp_bridge-v-windows-x86_64.zip -``` - -Each archive layout: - -``` -timsrust_cpp_bridge/ -├── include/ -│ └── timsrust_cpp_bridge.h -└── lib/ - ├── libtimsrust_cpp_bridge.a (timsrust_cpp_bridge.lib on Windows — no lib prefix with MSVC) - └── cmake/ - └── timsrust_cpp_bridge/ - ├── timsrust_cpp_bridgeConfig.cmake - └── timsrust_cpp_bridgeConfigVersion.cmake -``` - -The `lib/cmake//` path follows the conventional CMake installed package layout and is a default search path for `find_package()`. - -Only the static library (`.a`/`.lib`) is included. The shared library (`cdylib`) output from Cargo is intentionally excluded since OpenMS will link statically. - -### OpenMS consumption - -```cmake -find_package(timsrust_cpp_bridge REQUIRED) -target_link_libraries(OpenMS PRIVATE timsrust_cpp_bridge::timsrust_cpp_bridge) -``` - -OpenMS points `CMAKE_PREFIX_PATH` at the extracted archive (or installs it to a contrib-like location). - -## CI Pipeline - -Single workflow at `.github/workflows/release.yml`. - -### Triggers - -- Push/PR to `master`: build + smoke test on all platforms (validates compilation and linking) -- Tags matching `v*` (e.g. `v0.1.0`): build + package + create GitHub Release with artifacts - -### Build Matrix - -| Runner | Target | Rust target triple | Archive format | -|---|---|---|---| -| `ubuntu-24.04` | linux-x86_64 | `x86_64-unknown-linux-gnu` | `.tar.gz` | -| `ubuntu-24.04-arm` | linux-aarch64 | `aarch64-unknown-linux-gnu` | `.tar.gz` | -| `macos-14` | macos-arm64 | `aarch64-apple-darwin` | `.tar.gz` | -| `windows-2025` | windows-x86_64 | `x86_64-pc-windows-msvc` | `.zip` | - -### Job Steps (per matrix entry) - -1. Install Rust toolchain (`dtolnay/rust-toolchain`, stable) -2. Cache Cargo registry and target directory (`Swatinem/rust-cache`) -3. `cargo test --features with_timsrust` (validates Rust code compiles and any future tests pass) -4. `cargo build --features with_timsrust --release` -5. Smoke test: compile and link `examples/cpp_client.cpp` against the built static library, run with no arguments to verify it does not crash/segfault (non-zero exit code is expected and allowed) -6. Package: run `scripts/package.sh` to assemble tarball (header + static lib + configured CMake files). Uses `shell: bash` on all platforms (including Windows, where GitHub Actions provides Git Bash) -7. On tag builds: upload artifact via `actions/upload-artifact` - -### Release Job - -Runs after all matrix jobs succeed on a tag push: -- Collects all 4 platform artifacts -- Creates a GitHub Release named after the tag -- Attaches all tarballs/zips as release assets - -### Versioning - -Driven by git tags. The `timsrust_cpp_bridgeConfigVersion.cmake` encodes the version extracted from the tag. Uses `SameMinorVersion` compatibility (CMake 3.11+), so 0.1.1 satisfies a request for 0.1.0, but 0.2.0 does not. This respects semver conventions during the 0.x phase where minor versions may contain breaking changes. - -## CMake Config Files - -### `timsrust_cpp_bridgeConfig.cmake` - -Stored as a template at `cmake/timsrust_cpp_bridgeConfig.cmake.in`, configured at package time with the correct library filename per platform: -- Linux/macOS: `libtimsrust_cpp_bridge.a` -- Windows (MSVC): `timsrust_cpp_bridge.lib` - -```cmake -# Config file lives at /lib/cmake/timsrust_cpp_bridge/timsrust_cpp_bridgeConfig.cmake -get_filename_component(_TIMSRUST_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE) - -if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) - add_library(timsrust_cpp_bridge::timsrust_cpp_bridge STATIC IMPORTED) - - set_target_properties(timsrust_cpp_bridge::timsrust_cpp_bridge PROPERTIES - IMPORTED_LOCATION "${_TIMSRUST_PREFIX}/lib/@TIMSRUST_LIB_FILENAME@" - INTERFACE_INCLUDE_DIRECTORIES "${_TIMSRUST_PREFIX}/include" - ) - - # Platform-specific system dependencies - if(UNIX AND NOT APPLE) - target_link_libraries(timsrust_cpp_bridge::timsrust_cpp_bridge - INTERFACE pthread dl m) - elseif(APPLE) - target_link_libraries(timsrust_cpp_bridge::timsrust_cpp_bridge - INTERFACE "-framework Security" "-framework SystemConfiguration" resolv) - elseif(WIN32) - target_link_libraries(timsrust_cpp_bridge::timsrust_cpp_bridge - INTERFACE ws2_32 userenv bcrypt ntdll advapi32) - endif() -endif() -``` - -Note: exact system link dependencies (especially macOS frameworks) will be validated empirically during CI. The smoke test catches any missing deps immediately. - -### `timsrust_cpp_bridgeConfigVersion.cmake` - -Stored as a template at `cmake/timsrust_cpp_bridgeConfigVersion.cmake.in`, version injected from the git tag at package time. Generated using `write_basic_package_version_file()` from CMakePackageConfigHelpers with `SameMinorVersion` compatibility. Alternatively, the packaging script can write this file directly using the standard CMake version-file boilerplate. - -## Smoke Test - -Validates the build artifact is usable without requiring real `.d` datasets: - -1. Compile `examples/cpp_client.cpp` against the static library and header -2. Run the binary with no arguments — verifies it does not crash or segfault. The binary already prints usage and returns exit code 1 when called with no arguments, which is correct CLI behavior. The CI step allows non-zero exit codes (e.g. `./cpp_client || true`) and only fails on signals (segfault, abort). - -**Catches:** missing system link deps, ABI mismatches, platform-specific linking issues. - -**Does not catch:** data reading correctness (requires real `.d` files, remains manual). - -No changes needed to `examples/cpp_client.cpp` — the existing early-exit-with-usage behavior is suitable for the smoke test. - -## Repository Structure Changes - -New files: - -``` -.github/ - workflows/ - release.yml # CI + release workflow -cmake/ - timsrust_cpp_bridgeConfig.cmake.in # CMake config template - timsrust_cpp_bridgeConfigVersion.cmake.in # Version config template -scripts/ - package.sh # Assembles release tarball -``` - -**Not added:** -- No top-level `CMakeLists.txt` — this is a Cargo project that produces CMake-consumable artifacts, not a CMake project itself -- No `build.rs` — cargo build is sufficient as-is diff --git a/scripts/package.sh b/scripts/package.sh index e88e4a6..a241643 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -13,6 +13,12 @@ set -euo pipefail VERSION="${1:?Usage: package.sh }" TARGET="${2:?Usage: package.sh }" +# Validate version format (semver or 0.0.0-dev for CI) +if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "Error: VERSION must be semver (e.g. 1.2.3), got: $VERSION" >&2 + exit 1 +fi + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" @@ -72,6 +78,7 @@ sed -e "s|@TIMSRUST_VERSION_MAJOR@|${V_MAJOR}|g" \ # Create archive cd "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}" if [[ "$PLATFORM" == windows-* ]]; then + rm -f "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.zip" 7z a -tzip "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.zip" timsrust_cpp_bridge/ echo "Created: target/package/${ARCHIVE_NAME}.zip" else From fe85b16aa322ee05c0ee15cd739bcf2eb4ce88ac Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 20:02:17 +0100 Subject: [PATCH 09/14] Fix macOS build: add CoreFoundation framework dependency The iana_time_zone crate (transitive dep via chrono) requires CoreFoundation symbols. This was exposed by switching to explicit static archive linking. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 2 +- cmake/timsrust_cpp_bridgeConfig.cmake.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a594ecd..9e99b6a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,7 +74,7 @@ jobs: clang++ -std=c++17 examples/cpp_client.cpp \ -Iinclude \ target/release/libtimsrust_cpp_bridge.a \ - -framework Security -framework SystemConfiguration \ + -framework Security -framework SystemConfiguration -framework CoreFoundation \ -lresolv -lpthread \ -o target/smoke_test # Run with no args — expect exit code 1 (usage), fail only on signal/crash diff --git a/cmake/timsrust_cpp_bridgeConfig.cmake.in b/cmake/timsrust_cpp_bridgeConfig.cmake.in index 26c05d4..c49177c 100644 --- a/cmake/timsrust_cpp_bridgeConfig.cmake.in +++ b/cmake/timsrust_cpp_bridgeConfig.cmake.in @@ -23,7 +23,7 @@ if(NOT TARGET timsrust_cpp_bridge::timsrust_cpp_bridge) INTERFACE_LINK_LIBRARIES pthread dl m) elseif(APPLE) set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY - INTERFACE_LINK_LIBRARIES "-framework Security" "-framework SystemConfiguration" resolv) + INTERFACE_LINK_LIBRARIES "-framework Security" "-framework SystemConfiguration" "-framework CoreFoundation" resolv) elseif(WIN32) set_property(TARGET timsrust_cpp_bridge::timsrust_cpp_bridge APPEND PROPERTY INTERFACE_LINK_LIBRARIES ws2_32 userenv bcrypt ntdll advapi32) From 349de5514d218045f0fbafcfc9adf81e5f323e47 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 20:07:48 +0100 Subject: [PATCH 10/14] Address remaining review comments - Windows smoke test: replace `|| ver>nul` with errorlevel check that only allows exit code 1 (usage) and fails on crashes (>=2) - CMake version config: explicitly reject unsupported version ranges Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 3 ++- cmake/timsrust_cpp_bridgeConfigVersion.cmake.in | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e99b6a..cb18b88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,7 +90,8 @@ jobs: shell: cmd run: | cl.exe /std:c++17 /EHsc /Fe:target\smoke_test.exe /I include examples\cpp_client.cpp target\release\timsrust_cpp_bridge.lib ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib - target\smoke_test.exe || ver>nul + target\smoke_test.exe + if errorlevel 2 exit /b %errorlevel% - name: Set version id: version diff --git a/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in b/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in index 742a421..19f6679 100644 --- a/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in +++ b/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in @@ -3,7 +3,10 @@ set(PACKAGE_VERSION "@TIMSRUST_VERSION_MAJOR@.@TIMSRUST_VERSION_MINOR@.@TIMSRUST_VERSION_PATCH@") -if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) +if(DEFINED PACKAGE_FIND_VERSION_RANGE) + # Range semantics are not implemented in this template. + set(PACKAGE_VERSION_COMPATIBLE FALSE) +elseif(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) set(PACKAGE_VERSION_COMPATIBLE FALSE) else() # SameMinorVersion: major and minor must match exactly From 4871a5de5bf481e5028a7cd4453606a20ea45c91 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 20:13:11 +0100 Subject: [PATCH 11/14] Fix Windows smoke test: add /MD flag for dynamic CRT The Rust static library's C dependencies (sqlite3, zstd) are compiled with the dynamic CRT (/MD). The consumer must match to avoid unresolved __imp_ symbol errors. The previous || ver>/dev/null was masking this failure. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb18b88..ff497d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -89,7 +89,7 @@ jobs: if: runner.os == 'Windows' shell: cmd run: | - cl.exe /std:c++17 /EHsc /Fe:target\smoke_test.exe /I include examples\cpp_client.cpp target\release\timsrust_cpp_bridge.lib ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib + cl.exe /std:c++17 /EHsc /MD /Fe:target\smoke_test.exe /I include examples\cpp_client.cpp target\release\timsrust_cpp_bridge.lib ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib target\smoke_test.exe if errorlevel 2 exit /b %errorlevel% From 7cb713ba507b4127720620de48813fedf20da95c Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 20:16:11 +0100 Subject: [PATCH 12/14] Fix Windows smoke test: reset errorlevel after check CMD retains the errorlevel from target\smoke_test.exe (1 = usage). The GEQ 2 check correctly skips, but the script exits with 1. Add explicit exit /b 0 to succeed when errorlevel is 0 or 1. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff497d0..2a5a4f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,7 +91,8 @@ jobs: run: | cl.exe /std:c++17 /EHsc /MD /Fe:target\smoke_test.exe /I include examples\cpp_client.cpp target\release\timsrust_cpp_bridge.lib ws2_32.lib userenv.lib bcrypt.lib ntdll.lib advapi32.lib target\smoke_test.exe - if errorlevel 2 exit /b %errorlevel% + if %errorlevel% GEQ 2 exit /b %errorlevel% + exit /b 0 - name: Set version id: version From a0c77d7300d4ec2b65c282a9f41e3a4a1d9a5df5 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 20:21:53 +0100 Subject: [PATCH 13/14] Update GitHub Actions to Node.js 24 compatible versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - actions/checkout v4.3.1 → v6.0.2 - actions/upload-artifact v4.6.2 → v7.0.0 - actions/download-artifact v4.3.0 → v8.0.1 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a5a4f4..4d3a153 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,8 +36,8 @@ jobs: runs-on: ${{ matrix.os }} steps: - # actions/checkout v4.3.1 - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + # actions/checkout v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Rust toolchain # dtolnay/rust-toolchain stable branch @@ -132,8 +132,8 @@ jobs: - name: Upload artifact if: startsWith(github.ref, 'refs/tags/v') - # actions/upload-artifact v4.6.2 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + # actions/upload-artifact v7.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: name: timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }} path: target/package/timsrust_cpp_bridge-v${{ steps.version.outputs.version }}-${{ matrix.platform }}.${{ matrix.archive_ext }} @@ -147,8 +147,8 @@ jobs: steps: - name: Download all artifacts - # actions/download-artifact v4.3.0 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + # actions/download-artifact v8.0.1 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: path: artifacts/ From 955b5932e3cafa257927d191c770e6cd128dcaa2 Mon Sep 17 00:00:00 2001 From: Timo Sachsenberg Date: Wed, 11 Mar 2026 20:33:10 +0100 Subject: [PATCH 14/14] Set MACOSX_DEPLOYMENT_TARGET=14.0 to match OpenMS Ensures the static library is built with the same deployment target as OpenMS/pyOpenMS, avoiding mismatch warnings at link time. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d3a153..97dc340 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,12 @@ jobs: runs-on: ${{ matrix.os }} + # Must match OpenMS's MACOSX_DEPLOYMENT_TARGET (see pyOpenMS pyproject.toml + # and openms_ci_matrix_full.yml) to avoid deployment target mismatch warnings + # when linking the static library into OpenMS/pyOpenMS. + env: + MACOSX_DEPLOYMENT_TARGET: ${{ startsWith(matrix.os, 'macos') && '14.0' || '' }} + steps: # actions/checkout v6.0.2 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd