From 6169749236f506d6dc3d1f2cd398ea763a991c36 Mon Sep 17 00:00:00 2001 From: Nic-dorman Date: Wed, 4 Nov 2026 14:21:19 +0000 Subject: [PATCH 1/2] ci(notices): generate third-party notices only when a PR changes their inputs The notices job downloads licence texts from crate repositories and musl.libc.org, so an outage at either site turned unrelated PRs red, and since CI Gate it blocks the gate too. Generate only when Cargo.lock, Cargo.toml, scripts/third_party_notices or ci.yml change; otherwise the job succeeds with a notice, since CI Gate treats a skipped job as a failure. Anything other than a PR merge commit still generates. Closes V2-1815 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 45 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58bcb0b4..c549f253 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,8 @@ env: # build, and `CI Gate` is the single "is this PR green" check: # quick-checks fmt, clippy, docs, the no-default-features lib check # msrv the declared minimum toolchain -# notices THIRD-PARTY-NOTICES.txt generation +# notices THIRD-PARTY-NOTICES.txt generation, when the PR changes its +# inputs # test (3 OS) every lib, bin and integration test target; Linux also runs # the doc tests, the ignored five-node devnet test and the lib # tests without default features @@ -60,19 +61,57 @@ jobs: - run: cargo check --all-targets --all-features --locked # The release workflow ships THIRD-PARTY-NOTICES.txt in every archive. - # Generating it on each pull request catches a dependency that ships no - # licence text, or a new crate that compiles bundled C code, before release. + # Generating it on a pull request catches a dependency that ships no licence + # text, or a new crate that compiles bundled C code, before release. + # + # Generation downloads licence texts from crate repositories and from + # musl.libc.org, so a PR that doesn't change the dependency set or the + # generator would only be testing those sites (V2-1815). Such a PR passes this + # job without generating anything. The job still runs and succeeds rather than + # being skipped, because CI Gate counts a skipped job as a failure. A rustc + # release can move the musl version, and no PR changes that; the release + # workflow regenerates the notices for every build, so it catches that. notices: name: Third-party notices runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 + with: + # The PR's merge commit and its first parent, the base branch tip, so + # the step below can list what the PR changes. + fetch-depth: 2 + - name: Does this PR change what the notices are generated from? + id: inputs + shell: bash + run: | + set -euo pipefail + # Fail safe: anything other than a merge commit with its base parent + # present (a re-run on an odd ref, say) generates the notices. + if ! git rev-parse --verify -q 'HEAD^2' > /dev/null \ + || ! git rev-parse --verify -q 'HEAD^1' > /dev/null; then + echo "not a pull request merge commit; generating" + echo "generate=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + changed=$(git diff --name-only 'HEAD^1' HEAD -- \ + Cargo.lock Cargo.toml scripts/third_party_notices .github/workflows/ci.yml) + if [ -n "$changed" ]; then + echo "inputs changed:" + echo "$changed" + echo "generate=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::No change to Cargo.lock, Cargo.toml, scripts/third_party_notices or this workflow; notices not regenerated" + echo "generate=false" >> "$GITHUB_OUTPUT" + fi - uses: dtolnay/rust-toolchain@stable + if: steps.inputs.outputs.generate == 'true' - uses: actions/setup-python@v5 + if: steps.inputs.outputs.generate == 'true' with: python-version: "3.12" - name: Generate THIRD-PARTY-NOTICES.txt + if: steps.inputs.outputs.generate == 'true' env: GITHUB_TOKEN: ${{ github.token }} run: >- From f6b03935b954ba6a17b582de67102c0deffdbecf Mon Sep 17 00:00:00 2001 From: Nic-dorman Date: Thu, 5 Nov 2026 12:29:55 +0000 Subject: [PATCH 2/2] ci(notices): also regenerate when the toolchain pin, cargo config or any manifest changes Hermes round 1 (P2): a PR that adds or changes rust-toolchain or rust-toolchain.toml skipped generation, yet rustup prefers that pin to the stable toolchain the job installs. That changes the rustc that generate.py reads with `rustc -vV` to pick the musl release, and the toolchain ships the standard library's copyright text, so the notices change too. The job now watches every input of the generator that a PR can change: every Cargo manifest (a nested one too, not only the root), Cargo.lock, .cargo (source replacement, path overrides), both toolchain-pin file names, scripts/third_party_notices and ci.yml. Checked against simulated PR merge commits: a toolchain pin of either name, a nested manifest and .cargo/config.toml now generate (all four skipped before); README-only, source-only and lookalike names such as docs/Cargo.toml.md still skip; a non-merge HEAD still generates. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c549f253..f386802d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,8 +65,8 @@ jobs: # text, or a new crate that compiles bundled C code, before release. # # Generation downloads licence texts from crate repositories and from - # musl.libc.org, so a PR that doesn't change the dependency set or the - # generator would only be testing those sites (V2-1815). Such a PR passes this + # musl.libc.org, so a PR that doesn't change the dependency set, the toolchain + # pin or the generator would only be testing those sites (V2-1815). Such a PR passes this # job without generating anything. The job still runs and succeeds rather than # being skipped, because CI Gate counts a skipped job as a failure. A rustc # release can move the musl version, and no PR changes that; the release @@ -94,14 +94,29 @@ jobs: echo "generate=true" >> "$GITHUB_OUTPUT" exit 0 fi - changed=$(git diff --name-only 'HEAD^1' HEAD -- \ - Cargo.lock Cargo.toml scripts/third_party_notices .github/workflows/ci.yml) + # Everything the generated notices depend on that a PR can change: + # every Cargo manifest and the lockfile (cargo tree, cargo metadata); + # cargo's own config (source replacement, path overrides); the + # toolchain pin (rustup prefers it to the stable toolchain installed + # below, `rustc -vV` picks the musl release, and the toolchain ships + # the standard library's copyright text); the generator; and this + # workflow, which holds the generator's arguments. + watched=( + Cargo.lock + ':(glob)**/Cargo.toml' + .cargo + rust-toolchain + rust-toolchain.toml + scripts/third_party_notices + .github/workflows/ci.yml + ) + changed=$(git diff --name-only 'HEAD^1' HEAD -- "${watched[@]}") if [ -n "$changed" ]; then echo "inputs changed:" echo "$changed" echo "generate=true" >> "$GITHUB_OUTPUT" else - echo "::notice::No change to Cargo.lock, Cargo.toml, scripts/third_party_notices or this workflow; notices not regenerated" + echo "::notice::No change to a Cargo manifest, Cargo.lock, .cargo, the toolchain pin, scripts/third_party_notices or this workflow; notices not regenerated" echo "generate=false" >> "$GITHUB_OUTPUT" fi - uses: dtolnay/rust-toolchain@stable