diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58bcb0b4..f386802d 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,72 @@ 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, 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 + # 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 + # 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 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 + 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: >-