From 4f2d2ae11ae2ebbf11a58fec8f8b56b154540c9b Mon Sep 17 00:00:00 2001 From: HanifKoh <76276251+HanifKoh@users.noreply.github.com> Date: Mon, 12 Oct 2026 23:17:41 +0800 Subject: [PATCH] Wait for the PR's Deps Cache in clang-tidy When deps/ Changes (#16374) * Wait for the PR's Deps Cache in clang-tidy When deps/ Changes * Fail the clang-tidy Deps Wait When Listing Caches Keeps Failing * Wait for Deps Through ci_deps_cache.sh in clang-tidy Reuse the macOS deps wait for Linux: the job names, workflow and cache ref are passed in, and a new 'own' mode waits for the PR's own build when it changes deps/. A PR that keeps deps/ now waits for a running base build with the same deps, and warns when it falls back to older ones. The wait ends as soon as the deps job fails. --- .github/workflows/clang_tidy.yml | 74 ++++++++++++++++++++++++++++++-- scripts/ci_deps_cache.sh | 60 ++++++++++++++++++-------- 2 files changed, 113 insertions(+), 21 deletions(-) diff --git a/.github/workflows/clang_tidy.yml b/.github/workflows/clang_tidy.yml index 2f27a0eaff..445f294fcc 100644 --- a/.github/workflows/clang_tidy.yml +++ b/.github/workflows/clang_tidy.yml @@ -16,6 +16,15 @@ on: permissions: contents: read + # Listing caches and builds while waiting for a deps cache. + actions: read + +# Read by scripts/ci_deps_cache.sh. +env: + REPO: ${{ github.repository }} + WORKFLOW: build_all.yml + BASE_REF: ${{ github.base_ref }} + JOB: build_linux (${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}) concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} @@ -35,7 +44,7 @@ jobs: # The PR merge commit plus its first parent, the base it is diffed against. fetch-depth: 2 - - name: Look for changed C++ files + - name: Look for changed files id: changes run: | if git -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then @@ -43,18 +52,75 @@ jobs: else echo "No C++ changes under src/ or tests/." fi + if ! git diff --quiet HEAD^1 -- deps; then + echo "deps=true" >> "$GITHUB_OUTPUT" + fi + echo "deps-key=linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}" >> "$GITHUB_OUTPUT" - # Parsing needs the dependency headers, not a build of this PR's deps/, so - # a PR that changes deps/ falls back to the newest cache main has. + # Parsing needs the dependency headers, not a build of this PR's deps/. + # Without an exact match the job waits for the build that saves it: this + # PR's own when it changes deps/, otherwise a running push build of the + # base branch with the same deps. With neither, a PR that keeps deps/ + # falls back to the newest cache. - name: Restore cached deps + id: deps_cache if: steps.changes.outputs.cpp == 'true' uses: actions/cache/restore@v6 with: path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep - key: linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} + key: ${{ steps.changes.outputs.deps-key }} + + - name: Look for a build to wait for + id: wait_check + if: steps.changes.outputs.cpp == 'true' && steps.deps_cache.outputs.cache-hit != 'true' + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: bash scripts/ci_deps_cache.sh ${{ steps.changes.outputs.deps == 'true' && 'own' || 'check' }} + + # Linux Build Deps takes about 35 minutes on GitHub-hosted runners. The + # wait ends early when the deps job fails. + - name: Wait for the deps cache + if: steps.wait_check.outputs.wait-for != '' + timeout-minutes: 100 + env: + GH_TOKEN: ${{ github.token }} + KEY: ${{ steps.changes.outputs.deps-key }} + CACHE_REF: ${{ steps.changes.outputs.deps == 'true' && github.ref || format('refs/heads/{0}', github.base_ref) }} + KEY_INPUTS: ${{ steps.wait_check.outputs.key-inputs }} + WAIT_FOR: ${{ steps.wait_check.outputs.wait-for }} + WAIT_SINCE: ${{ steps.wait_check.outputs.wait-since }} + WAIT_MINUTES: 90 + run: bash scripts/ci_deps_cache.sh wait + + - name: Restore the deps cache after waiting + id: waited_cache + if: steps.wait_check.outputs.wait-for != '' + uses: actions/cache/restore@v6 + with: + path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep + key: ${{ steps.changes.outputs.deps-key }} + + - name: Fail without this PR's deps cache + if: steps.changes.outputs.deps == 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true' + run: | + echo "::error::No deps cache ${{ steps.changes.outputs.deps-key }}. Check this PR's Linux Build Deps job, then rerun clang-tidy." + exit 1 + + - name: Restore the newest cached deps + id: newest_cache + if: steps.changes.outputs.deps != 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true' + uses: actions/cache/restore@v6 + with: + path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep + key: ${{ steps.changes.outputs.deps-key }} restore-keys: linux-clang-cache-orcaslicer_deps-build- fail-on-cache-miss: true + - name: Warn about older deps + if: steps.newest_cache.outcome == 'success' && steps.newest_cache.outputs.cache-hit != 'true' + run: echo "::warning title=Older deps::No cache matches this PR's deps/, so clang-tidy parsed against ${{ steps.newest_cache.outputs.cache-matched-key }}. If it reports errors in code that uses a dependency, rerun clang-tidy once the base branch's Build all has saved the new deps." + - name: Apt-Install Dependencies if: steps.changes.outputs.cpp == 'true' && !vars.SELF_HOSTED uses: ./.github/actions/apt-install-deps diff --git a/scripts/ci_deps_cache.sh b/scripts/ci_deps_cache.sh index a27b7e2522..0c3ae85415 100644 --- a/scripts/ci_deps_cache.sh +++ b/scripts/ci_deps_cache.sh @@ -1,21 +1,27 @@ #!/usr/bin/env bash -# Lets a pull request's macOS deps build wait for a push build of the base -# branch that builds the same deps, from a Linux job. `check` runs after a cache -# miss and names a queued or running base build with the same deps/ and .github/ -# to wait for, unless the pull request changes either. `wait` waits until that -# build saves KEY. actions/cache then confirms the entry can be restored. +# Lets a pull request's macOS deps build, or its clang-tidy job, wait for a +# build that saves the deps cache it needs, from a Linux job. `check` runs after +# a cache miss and names a queued or running push build of the base branch with +# the same deps/ and .github/ to wait for, unless the pull request changes either. +# `own` names the pull request's own build instead, for clang-tidy when the pull +# request changes deps/. `wait` waits until that build saves KEY under CACHE_REF, +# or its deps job fails. actions/cache then confirms the entry can be restored. # -# Environment: GH_TOKEN, REPO, WORKFLOW_REF and BASE_REF, plus KEY_INPUTS, -# WAIT_FOR, WAIT_SINCE, WAIT_MINUTES, KEY and ARCH for `wait`. +# Environment: GH_TOKEN, REPO, WORKFLOW_REF (or WORKFLOW, the build workflow's +# file name) and BASE_REF, plus HEAD_SHA for `own`, and KEY_INPUTS, WAIT_FOR, +# WAIT_SINCE, WAIT_MINUTES, KEY, CACHE_REF (default the base branch) and ARCH +# (or JOB, the matrix job's name) for `wait`. # Writes key-inputs, wait-for and wait-since to $GITHUB_OUTPUT. set -uo pipefail -workflow=${WORKFLOW_REF%%@*} -workflow=${workflow##*/} +workflow=${WORKFLOW_REF:-} +workflow=${workflow%%@*} +workflow=${WORKFLOW:-${workflow##*/}} # Must match the job names in build_all.yml, build_check_cache.yml and build_deps.yml. -check_job_name="build_macos_arch (${ARCH:-}) / Check Cache" -deps_job_name="build_macos_arch (${ARCH:-}) / Build Deps / Build Deps" +job=${JOB:-build_macos_arch (${ARCH:-})} +check_job_name="$job / Check Cache" +deps_job_name="$job / Build Deps / Build Deps" out() { echo "$1" >> "${GITHUB_OUTPUT:-/dev/stdout}"; } @@ -63,8 +69,27 @@ check() { fi } +own() { + out "key-inputs=$(local_key_inputs HEAD)" + local id + # The build can be created a moment after this job. + for _ in 1 2 3 4 5; do + id=$(gh api -X GET "repos/$REPO/actions/workflows/$workflow/runs" \ + -f head_sha="$HEAD_SHA" -f event=pull_request -f per_page=1 --jq '.workflow_runs[0].id // empty' < /dev/null) + [ -n "$id" ] && break + sleep 30 + done + if [ -n "$id" ]; then + echo "Waiting for the deps cache of build $id of this pull request." + out "wait-for=$id" + out "wait-since=$(date +%s)" + else + echo "No build of this pull request is building its deps." + fi +} + key_saved() { - gh cache list -R "$REPO" --ref "refs/heads/$BASE_REF" --key "$KEY" --json key < /dev/null \ + gh cache list -R "$REPO" --ref "${CACHE_REF:-refs/heads/$BASE_REF}" --key "$KEY" --json key < /dev/null \ | jq -e --arg key "$KEY" 'any(.[]; .key == $key)' > /dev/null } @@ -87,11 +112,11 @@ wait_for() { key_saved && { echo "Found $KEY."; return; } sleep 20 done - echo "Build $id of $BASE_REF finished its deps without saving $KEY." + echo "Build $id finished its deps without saving $KEY." return ;; "completed cancelled") ;; completed*) - echo "The deps job of build $id of $BASE_REF did not succeed." + echo "The deps job of build $id did not succeed." return ;; # The deps job exists only once the build's cache check has succeeded. "") @@ -100,7 +125,7 @@ wait_for() { completed*) check=$(jq -r --arg name "$check_job_name" 'first(.jobs[] | select(.name == $name) | .conclusion) // ""' <<< "$jobs") if [ -z "$warned" ] && { [ -z "$check" ] || [ "$check" = success ]; }; then - echo "::warning title=Deps wait::Build $id of $BASE_REF has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request." + echo "::warning title=Deps wait::Build $id has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request." warned=1 fi ;; *) @@ -118,11 +143,12 @@ wait_for() { seen="$seen $id" echo "Following build $id of $BASE_REF." done - echo "No build of $BASE_REF saved $KEY." + echo "No build saved $KEY." } case "${1:-}" in check) check ;; + own) own ;; wait) wait_for ;; - *) echo "usage: $0 check|wait" >&2; exit 2 ;; + *) echo "usage: $0 check|own|wait" >&2; exit 2 ;; esac