CI: approved fork PRs run heavy jobs on self-hosted-fast in private directories

An approved-for-ci label on a fork pull request sends Clippy, Build tests,
the test shards and Doc tests to the trusted runners, with private copies
of the build directory, Cargo home and rustup home that are deleted after
the job. Only the labeling event qualifies; later pushes use the old fork
routing. The skipped run of an unrelated label names its ci job differently.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruck
2026-10-18 18:06:30 +00:00
parent 4b6838acd0
commit 439f2f7dab
2 changed files with 80 additions and 4 deletions
+36 -4
View File
@@ -102,6 +102,11 @@ jobs:
full: ${{ steps.paths.outputs.full }}
# Code changed on a pull request that has no approved-for-ci label yet: only Quick compile.
quick: ${{ steps.paths.outputs.quick }}
# An approved fork pull request whose approval is this very run: its heavy jobs run on the
# trusted self-hosted-fast runners, in private copies of the warm directories (fork-scratch.sh).
# Only the `labeled` event qualifies, so a later push to the fork's branch (unreviewed code)
# falls back to the fork routing above until a maintainer labels it again. `local` stays false.
fork_heavy: ${{ github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'approved-for-ci' && github.event.pull_request.head.repo.full_name != github.repository }}
packages: ${{ steps.paths.outputs.packages }}
oracle: ${{ steps.paths.outputs.oracle }}
# Whether the other Linux jobs use the self-hosted runners too.
@@ -282,10 +287,13 @@ jobs:
name: Native Linux Clippy
needs: changes
if: needs.changes.outputs.native == 'true'
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }}
runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Private build, Cargo and rustup directories (approved fork pull request)
if: needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh clippy
- name: Warm build directory (self-hosted runner)
if: runner.environment == 'self-hosted' && !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)
run: scripts/ci/warm-target.sh clippy
@@ -308,12 +316,15 @@ jobs:
scope=(--workspace)
if [ "$FULL" != true ]; then scope=(); for p in $PACKAGES; do scope+=(-p "$p"); done; fi
cargo clippy "${scope[@]}" --all-targets --profile ci --locked -- -D warnings
- name: Delete the private directories
if: always() && needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh --clean
build-tests:
name: Build tests
needs: changes
if: needs.changes.outputs.native == 'true'
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04' }}
runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04' }}
# Fork builds compile inside gVisor on shared boxes; give them room to finish and upload.
timeout-minutes: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 90 || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 60 || 45 }}
env:
@@ -323,6 +334,9 @@ jobs:
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Private build, Cargo and rustup directories (approved fork pull request)
if: needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh
- name: Size parallel compiles to the job's memory
run: scripts/ci/build-jobs.sh
- uses: dtolnay/rust-toolchain@stable
@@ -355,11 +369,14 @@ jobs:
- name: Compiler cache statistics
if: always()
run: sccache --show-stats
- name: Delete the private directories
if: always() && needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh --clean
tests:
name: Native Linux tests (${{ matrix.shard }}/4)
needs: [changes, build-tests]
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04') }}
runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || needs.changes.outputs.local == 'true' && 'self-hosted-fast' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -374,6 +391,9 @@ jobs:
SOLIDCRAFT_REQUIRE_GPU: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Private build, Cargo and rustup directories (approved fork pull request)
if: needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh
- name: Install a software Vulkan driver (lavapipe)
# Preinstalled on the self-hosted runner, which has no sudo.
if: runner.environment == 'github-hosted'
@@ -425,6 +445,9 @@ jobs:
name: junit-${{ matrix.shard }}
path: target/nextest/ci/junit.xml
retention-days: 7
- name: Delete the private directories
if: always() && needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh --clean
action-matrix:
# Every ordinary action on every face, edge, vertex and body of the design corpus, against
@@ -522,7 +545,7 @@ jobs:
name: Doc tests
needs: changes
if: needs.changes.outputs.native == 'true'
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }}
runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }}
timeout-minutes: 45
env:
CARGO_INCREMENTAL: "0"
@@ -531,6 +554,9 @@ jobs:
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Private build, Cargo and rustup directories (approved fork pull request)
if: needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh doc-tests
- name: Size parallel compiles to the job's memory
run: scripts/ci/build-jobs.sh
- name: Warm build directory (self-hosted runner)
@@ -570,6 +596,9 @@ jobs:
exit 0
fi
cargo test "${scope[@]}" --profile ci --locked --doc
- name: Delete the private directories
if: always() && needs.changes.outputs.fork_heavy == 'true'
run: scripts/ci/fork-scratch.sh --clean
oracle:
name: Reference tests
@@ -671,6 +700,9 @@ jobs:
# Preserve the existing check name for branch protection, including docs-only PRs.
ci:
# On the skipped run another label starts, this job is named differently, so that skipped job
# can never stand in for the required `ci` check of the pull request's real run.
name: ${{ github.event.action == 'labeled' && github.event.label.name != 'approved-for-ci' && 'ci (ignored label)' || 'ci' }}
needs: [changes, checks, quick-compile, clippy, build-tests, tests, doc-tests, oracle, windows-fxc, windows-startup]
if: always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci')
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-light' || 'ubuntu-24.04' }}
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Self-hosted runner, approved fork pull requests only: give the job private copies of the build
# directory, Cargo home and rustup home, so the job never writes to the shared warm directories
# that main and merge-queue runs reuse (a build script or test from a fork could otherwise leave
# files there). The copies start as the warm state (reflinks where the filesystem has them) and
# `fork-scratch.sh --clean` deletes them at the end of the job.
# Usage: fork-scratch.sh [NAME] NAME = the warm-target.sh directory to start the build from
# fork-scratch.sh --clean
# This keeps shared state out of reach of ordinary writes. It is not a sandbox: fork code still
# runs as the runner's user, which is why the workflow starts it only from a `labeled` event a
# maintainer triggered, never from a push.
set -euo pipefail
base=${RUNNER_TEMP:?}/fork-scratch
if [ "${1:-}" = --clean ]; then
rm -rf "$base"
exit 0
fi
if [ "${GITHUB_EVENT_NAME:-}" != pull_request ]; then
echo "fork-scratch: only for pull request runs" >&2
exit 1
fi
name=${1:-}
rm -rf "$base"
mkdir -p "$base"
if [ -n "$name" ]; then
warm=/home/runner/work/cargo-target/$name
if [ -d "$warm" ]; then
cp -a --reflink=auto "$warm" "$base/target"
else
mkdir -p "$base/target"
fi
echo "CARGO_TARGET_DIR=$base/target" >> "$GITHUB_ENV"
fi
cargo_home=${CARGO_HOME:-$HOME/.cargo}
rustup_home=${RUSTUP_HOME:-$HOME/.rustup}
mkdir -p "$base/cargo-home" "$base/rustup-home"
[ -d "$cargo_home" ] && cp -a --reflink=auto "$cargo_home/." "$base/cargo-home/"
[ -d "$rustup_home" ] && cp -a --reflink=auto "$rustup_home/." "$base/rustup-home/"
{
echo "CARGO_HOME=$base/cargo-home"
echo "RUSTUP_HOME=$base/rustup-home"
} >> "$GITHUB_ENV"
echo "$base/cargo-home/bin" >> "$GITHUB_PATH"
echo "fork-scratch: private copies in $base ($(du -sh "$base" | cut -f1))"