diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..97dc340 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,166 @@ +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 }} + + # 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 + + - name: Install Rust toolchain + # dtolnay/rust-toolchain stable branch + uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 + with: + toolchain: stable + + - name: Cache Cargo + # Swatinem/rust-cache v2.8.2 + uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 + + - 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 \ + 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 + 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 \ + target/release/libtimsrust_cpp_bridge.a \ + -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 + target/smoke_test || if [ $? -gt 128 ]; then exit 1; fi + + - name: Setup MSVC dev environment + if: runner.os == 'Windows' + # ilammy/msvc-dev-cmd v1.13.0 + uses: ilammy/msvc-dev-cmd@a102174a2b586eec2ea151a69e6fd14404a8ce7c + + - name: Smoke test (Windows) + if: runner.os == 'Windows' + shell: cmd + 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% GEQ 2 exit /b %errorlevel% + exit /b 0 + + - name: Set version + id: version + shell: bash + 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 + 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') + # 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 }} + + release: + if: startsWith(github.ref, 'refs/tags/v') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + # actions/download-artifact v8.0.1 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + path: artifacts/ + + - name: Create GitHub Release + # 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_bridgeConfig.cmake.in b/cmake/timsrust_cpp_bridgeConfig.cmake.in new file mode 100644 index 0000000..c49177c --- /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" "-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) + 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..19f6679 --- /dev/null +++ b/cmake/timsrust_cpp_bridgeConfigVersion.cmake.in @@ -0,0 +1,23 @@ +# timsrust_cpp_bridgeConfigVersion.cmake +# Auto-generated version compatibility file — SameMinorVersion semantics. + +set(PACKAGE_VERSION "@TIMSRUST_VERSION_MAJOR@.@TIMSRUST_VERSION_MINOR@.@TIMSRUST_VERSION_PATCH@") + +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 + 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 VERSION_EQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 0000000..a241643 --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,87 @@ +#!/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 }" + +# 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)" + +# 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 + 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 + tar czf "$PROJECT_ROOT/target/package/${ARCHIVE_NAME}.tar.gz" timsrust_cpp_bridge/ + echo "Created: target/package/${ARCHIVE_NAME}.tar.gz" +fi