From 4be19a13e31bd79b8c661c82898b93adcbfb74d1 Mon Sep 17 00:00:00 2001 From: gouyt13clear Date: Sat, 26 Sep 2026 16:52:29 +0800 Subject: [PATCH] ci: refine wheel and validation workflows --- .github/workflows/docs.yml | 3 + .github/workflows/python.yml | 3 + .github/workflows/release.yml | 106 ++++++++++++++++++++++------------ .github/workflows/test.yaml | 32 ++++++++-- scripts/check-tidy.sh | 39 ++++++++++++- tests/CMakeLists.txt | 1 + 6 files changed, 141 insertions(+), 43 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dcea4ebf..0f61021f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,6 +16,9 @@ concurrency: group: documentation-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: build: name: Build documentation diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index f824817b..e44b92ec 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -10,6 +10,9 @@ concurrency: group: ${{ github.head_ref || github.sha }}-${{ github.workflow }} cancel-in-progress: true +permissions: + contents: read + jobs: changes: uses: ./.github/workflows/changes.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffd14be9..dc4fafec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,8 +13,8 @@ on: branches: ["main"] concurrency: - group: release-${{ github.event.workflow_run.head_sha || github.sha }} - cancel-in-progress: false + group: release-${{ github.event.pull_request.number || github.event.workflow_run.head_sha || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read @@ -55,8 +55,8 @@ jobs: GH_TOKEN: ${{ github.token }} run: python .github/scripts/prepare_release.py - linux-wheels: - name: Linux x86-64 wheels + linux-wheel-build: + name: Linux x86-64 ${{ matrix.abi }} wheel needs: [prepare, changes] if: >- !cancelled() && needs.prepare.result == 'success' && @@ -64,39 +64,44 @@ jobs: (needs.changes.result != 'success' || needs.changes.outputs.wheels == 'true') runs-on: ubuntu-latest timeout-minutes: 90 - + strategy: + fail-fast: false + matrix: + abi: ${{ fromJSON(github.event_name == 'pull_request' && '["cp311","cp314"]' || '["cp311","cp312","cp313","cp314"]') }} steps: - uses: actions/checkout@v7 with: ref: ${{ needs.prepare.outputs.sha }} - - uses: actions/setup-python@v7 with: python-version: "3.14" - - - name: Build and test PR wheel - if: github.event_name == 'pull_request' + - name: Build and test wheel uses: pypa/cibuildwheel@v4.2.0 env: - CIBW_BUILD: cp311-manylinux_x86_64 + CIBW_BUILD: ${{ matrix.abi }}-manylinux_x86_64 with: output-dir: wheelhouse - - - name: Build and test release wheels - if: github.event_name != 'pull_request' - uses: pypa/cibuildwheel@v4.2.0 - with: - output-dir: wheelhouse - - - name: Store wheel artifacts + - name: Store wheel artifact uses: actions/upload-artifact@v7 with: - name: linux-x86_64-wheels + name: linux-${{ matrix.abi }}-wheels path: wheelhouse/*.whl if-no-files-found: error - windows-wheels: - name: Windows x86-64 wheels + linux-wheels: + # Preserve the required check name and include every selected ABI. + name: Linux x86-64 wheels + needs: linux-wheel-build + if: ${{ always() && !cancelled() && needs.linux-wheel-build.result != 'skipped' }} + runs-on: ubuntu-latest + steps: + - name: Require all selected wheels + env: + BUILD_RESULT: ${{ needs.linux-wheel-build.result }} + run: test "$BUILD_RESULT" = success + + windows-wheel-build: + name: Windows x86-64 ${{ matrix.abi }} wheel needs: [prepare, changes] if: >- !cancelled() && needs.prepare.result == 'success' && @@ -104,6 +109,10 @@ jobs: (needs.changes.result != 'success' || needs.changes.outputs.wheels == 'true') runs-on: windows-2025-vs2026 timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + abi: ${{ fromJSON(github.event_name == 'pull_request' && '["cp311","cp314"]' || '["cp311","cp312","cp313","cp314"]') }} steps: - uses: actions/checkout@v7 with: @@ -111,27 +120,32 @@ jobs: - uses: actions/setup-python@v7 with: python-version: "3.14" - - name: Build and test PR wheel - if: github.event_name == 'pull_request' + - name: Build and test wheel uses: pypa/cibuildwheel@v4.2.0 env: - CIBW_BUILD: cp311-win_amd64 + CIBW_BUILD: ${{ matrix.abi }}-win_amd64 with: output-dir: wheelhouse - - name: Build and test release wheels - if: github.event_name != 'pull_request' - uses: pypa/cibuildwheel@v4.2.0 - with: - output-dir: wheelhouse - - name: Store wheel artifacts + - name: Store wheel artifact uses: actions/upload-artifact@v7 with: - name: windows-x86_64-wheels + name: windows-${{ matrix.abi }}-wheels path: wheelhouse/*.whl if-no-files-found: error - macos-wheels: - name: macOS arm64 wheels (CPython 3.11–3.14) + windows-wheels: + name: Windows x86-64 wheels + needs: windows-wheel-build + if: ${{ always() && !cancelled() && needs.windows-wheel-build.result != 'skipped' }} + runs-on: ubuntu-latest + steps: + - name: Require all selected wheels + env: + BUILD_RESULT: ${{ needs.windows-wheel-build.result }} + run: test "$BUILD_RESULT" = success + + macos-wheel-build: + name: macOS arm64 ${{ matrix.abi }} wheel needs: [prepare, changes] if: >- !cancelled() && needs.prepare.result == 'success' && @@ -139,6 +153,10 @@ jobs: (needs.changes.result != 'success' || needs.changes.outputs.wheels == 'true') runs-on: macos-14 timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + abi: ${{ fromJSON(github.event_name == 'pull_request' && '["cp311","cp314"]' || '["cp311","cp312","cp313","cp314"]') }} steps: - uses: actions/checkout@v7 with: @@ -148,17 +166,31 @@ jobs: python-version: "3.14" - name: Verify native Apple Silicon runner run: test "$(uname -m)" = arm64 - # All four ABIs are built, repaired, installed and tested on PRs as well. - - name: Build and test wheels with bundled OpenMP + - name: Build and test wheel with bundled OpenMP uses: pypa/cibuildwheel@v4.2.0 + env: + CIBW_BUILD: ${{ matrix.abi }}-macosx_arm64 with: output-dir: wheelhouse - - uses: actions/upload-artifact@v7 + - name: Store wheel artifact + uses: actions/upload-artifact@v7 with: - name: macos-arm64-wheels + name: macos-${{ matrix.abi }}-wheels path: wheelhouse/*.whl if-no-files-found: error + macos-wheels: + # Preserve the protected check name; PRs test the oldest and newest ABI. + name: macOS arm64 wheels (CPython 3.11–3.14) + needs: macos-wheel-build + if: ${{ always() && !cancelled() && needs.macos-wheel-build.result != 'skipped' }} + runs-on: ubuntu-latest + steps: + - name: Require all selected wheels + env: + BUILD_RESULT: ${{ needs.macos-wheel-build.result }} + run: test "$BUILD_RESULT" = success + release-tag: name: Create version tag needs: [prepare, linux-wheels, windows-wheels, macos-wheels] diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f7ca0ae2..f7f043cb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,6 +7,8 @@ on: concurrency: group: ${{ github.head_ref || github.sha }}-${{ github.workflow }} cancel-in-progress: true +permissions: + contents: read jobs: changes: uses: ./.github/workflows/changes.yml @@ -26,12 +28,19 @@ jobs: - name: Check formatting run: ./scripts/check-format.sh - clang-tidy: + clang-tidy-shard: needs: changes if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp_tidy == 'true') }} - name: C++ static analysis + name: C++ static analysis shard ${{ matrix.shard }} runs-on: ubuntu-latest timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + shard: [0, 1] + env: + TIDY_SHARD_INDEX: ${{ matrix.shard }} + TIDY_SHARD_COUNT: "2" steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 @@ -55,6 +64,17 @@ jobs: - name: Run clang-tidy run: ./scripts/check-tidy.sh build-tidy + clang-tidy: + name: C++ static analysis + needs: clang-tidy-shard + if: ${{ always() && !cancelled() && needs.clang-tidy-shard.result != 'skipped' }} + runs-on: ubuntu-latest + steps: + - name: Require both shards + env: + BUILD_RESULT: ${{ needs.clang-tidy-shard.result }} + run: test "$BUILD_RESULT" = success + shellcheck: needs: changes if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.shell == 'true') }} @@ -141,15 +161,19 @@ jobs: - uses: actions/checkout@v7 - name: Install dependencies run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build - - name: Build and install RaBitQ-Library + - name: Build RaBitQ-Library with portable tests run: | cmake -S . -B build-install -G Ninja \ -DRABITQ_BUILD_SAMPLES=OFF \ + -DRABITQ_BUILD_TESTS=ON \ -DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="$PWD/rabitqlib-install" cmake --build build-install - cmake --install build-install + - name: Test portable Release build + run: ctest --test-dir build-install --output-on-failure + - name: Install RaBitQ-Library + run: cmake --install build-install - name: Build downstream consumer run: | cmake -S tests/consumer -B build-consumer -G Ninja \ diff --git a/scripts/check-tidy.sh b/scripts/check-tidy.sh index 7e07c4f9..5af66720 100755 --- a/scripts/check-tidy.sh +++ b/scripts/check-tidy.sh @@ -75,6 +75,37 @@ for header in "${first_party_headers[@]}"; do done header_filter+=")$" +source_filter="^$repo_root/(src|python_bindings|sample|tests)/" +if [[ -n "${TIDY_SHARD_INDEX:-}" || -n "${TIDY_SHARD_COUNT:-}" ]]; then + source_filter="$(python - "$build_dir/compile_commands.json" "$repo_root" \ + "${TIDY_SHARD_INDEX:-}" "${TIDY_SHARD_COUNT:-}" <<'PYCODE' +import json +import re +import sys +from pathlib import Path + +database, root, index, count = sys.argv[1:] +root = Path(root).resolve() +index, count = int(index), int(count) +if count < 1 or not 0 <= index < count: + raise SystemExit("error: invalid clang-tidy shard index or count") + +entries = json.loads(Path(database).read_text()) +sources = set() +for entry in entries: + path = (Path(entry["directory"]) / entry["file"]).resolve() + if path.is_relative_to(root) and path.relative_to(root).parts[0] in { + "src", "python_bindings", "sample", "tests" + }: + sources.add(path) +shard = sorted(sources)[index::count] +if not shard: + raise SystemExit("error: clang-tidy shard has no translation units") +print("^(" + "|".join(re.escape(str(path)) for path in shard) + ")$") +PYCODE + )" +fi + tidy_output="$(mktemp)" if [[ -z "$tidy_output" || ! -f "$tidy_output" ]]; then echo "error: could not create a temporary clang-tidy output file" >&2 @@ -87,10 +118,14 @@ if ! "$tidy_runner" \ -p "$build_dir" \ -header-filter="^$header_filter" \ "${extra_args[@]}" \ - "^$repo_root/(src|python_bindings|sample|tests)/" \ + "$source_filter" \ >"$tidy_output" 2>&1; then cat "$tidy_output" >&2 exit 1 fi -echo "clang-tidy passed for all configured first-party translation units" +if [[ -n "${TIDY_SHARD_INDEX:-}" ]]; then + echo "clang-tidy passed for shard $TIDY_SHARD_INDEX of $TIDY_SHARD_COUNT" +else + echo "clang-tidy passed for all configured first-party translation units" +fi diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7c51202f..9c76145d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,6 +6,7 @@ FetchContent_Declare( GIT_TAG v1.14.0 ) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) # Automatically register all tests under unit and folders