diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf81de9..b04a704 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,12 @@ name: CI +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: workflow_dispatch: pull_request: @@ -20,9 +27,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ${{ matrix.os }} @@ -47,13 +52,17 @@ jobs: uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - name: Install cargo-llvm-cov + if: matrix.rust_version == 'stable' uses: taiki-e/install-action@671164c891d3e20f5b8ece04798d0154d1262d65 # cargo-llvm-cov - name: Generate code coverage + id: generate_coverage + if: matrix.rust_version == 'stable' run: cargo llvm-cov --lib --all-features --lcov --output-path lcov.info - name: Filter out tests - uses: scouten/uncover-tests@f3bbdfb953b5989f48d8dfad0a88a83c4d914389 # v1.1.1 + if: matrix.rust_version == 'stable' + uses: scouten/uncover-tests@f3bbdfb953b5989f48d8dfad0a88a83c4d914389 # v1 with: input-file: lcov.info output-file: lcov.filtered.info @@ -62,10 +71,9 @@ jobs: # so we don't attempt to upload code coverage in that case. - name: Upload code coverage results if: | - github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' + steps.generate_coverage.outcome == 'success' && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: token: ${{ secrets.CODECOV_ORG_TOKEN }} @@ -73,18 +81,16 @@ jobs: verbose: true files: ./lcov.filtered.info + - name: Run unit tests (MSRV) + if: matrix.rust_version != 'stable' + run: cargo test --lib --all-features + doc-tests: - name: Doc tests (requires nightly Rust) - # TODO: Remove this once cargo-llvm-cov can run doc tests and generate - # coverage. (This requires a bug fix that is only available in nightly Rust.) - # Watch https://github.com/taiki-e/cargo-llvm-cov/issues/2 - # for progress. + name: Doc tests if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -99,46 +105,15 @@ jobs: - name: Cache Rust dependencies uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@671164c891d3e20f5b8ece04798d0154d1262d65 # cargo-llvm-cov - - # Disabling code coverage for doc tests due to a new bug in Rust nightly - # as of 2025-01-08. Will investigate later to see if there's a repro case. - # Meanwhile, simply run the tests so we know if there are any failing - # doc tests. - - - name: Run doc tests (COVERAGE DISABLED) - run: | - cargo test --workspace --all-features --doc - - # - name: Generate code coverage - # env: - # RUST_BACKTRACE: "1" - # run: | - # cargo llvm-cov --workspace --all-features --lcov --doctests --output-path lcov.info - - # Tokens aren't available for PRs originating from forks, - # so we don't attempt to upload code coverage in that case. - # - name: Upload code coverage results - # if: | - # github.event_name != 'pull_request' || - # github.event.pull_request.author_association == 'COLLABORATOR' || - # github.event.pull_request.author_association == 'MEMBER' || - # github.event.pull_request.user.login == 'dependabot[bot]' - # uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 - # with: - # token: ${{ secrets.CODECOV_TOKEN }} - # fail_ci_if_error: true - # verbose: true + - name: Run doc tests + run: cargo test --all-features --doc tests-cross: - name: Unit tests (cross) + name: Unit tests (cross, ${{ matrix.target }}, Rust ${{ matrix.rust_version }}) if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -160,7 +135,8 @@ jobs: targets: ${{ matrix.target }} - name: Install cross-compilation toolset - run: cargo install cross --locked + # Includes the rustup toolchain-detection fix and regression tests. + run: cargo install cross --locked --git https://github.com/cross-rs/cross --rev 2332ad818a3cd763934d7ac14857ed60b7ca8e90 - name: Cache Rust dependencies uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 @@ -172,14 +148,76 @@ jobs: - name: Run unit tests (cross build) run: cross test --lib --all-features --target ${{ matrix.target }} + tests-windows-arm: + name: Unit tests (Windows ARM) + + if: | + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository || + contains(github.event.pull_request.labels.*.name, 'safe to test') + + runs-on: windows-11-arm + + steps: + - name: Checkout repository + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master + with: + toolchain: stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2 + + - name: Run unit tests + run: cargo test --lib --all-features + + tests-android: + name: Build (Android, ${{ matrix.target }}) + + if: | + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository || + contains(github.event.pull_request.labels.*.name, 'safe to test') + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + target: + - aarch64-linux-android + - armv7-linux-androideabi + - i686-linux-android + - x86_64-linux-android + + steps: + - name: Checkout repository + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master + with: + toolchain: stable + targets: ${{ matrix.target }} + + - name: Install cross-compilation toolset + # Includes the rustup toolchain-detection fix and regression tests. + run: cargo install cross --locked --git https://github.com/cross-rs/cross --rev 2332ad818a3cd763934d7ac14857ed60b7ca8e90 + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2 + + - name: Build library (cross build) + run: cross build --lib --all-features --target ${{ matrix.target }} + tests-ios: name: Unit tests (iOS simulator) if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: macos-14 @@ -198,7 +236,7 @@ jobs: run: | VERSION=0.8.6 URL="https://github.com/sonos/dinghy/releases/download/${VERSION}/cargo-dinghy-macos-${VERSION}.tgz" - wget -O - $URL | tar -xz --strip-components=1 -C ~/.cargo/bin + wget -O - "$URL" | tar -xz --strip-components=1 -C ~/.cargo/bin - name: Check cargo-dinghy version. run: cargo dinghy --version @@ -207,14 +245,14 @@ jobs: # Use the first installed iOS runtime and the first (i.e. oldest) supported iPhone device. run: | RUNTIME=$(xcrun simctl list runtimes --json | jq '.runtimes | map(select(.name | contains("iOS"))) | .[0]') - RUNTIME_ID=$(echo $RUNTIME | jq -r '.identifier') - echo "Using runtime:" $RUNTIME_ID - DEVICE_ID=$(echo $RUNTIME | jq -r '.supportedDeviceTypes | map(select(.productFamily == "iPhone")) | .[0].identifier') - echo "Using device:" $DEVICE_ID - SIM_ID=$(xcrun simctl create Test-iPhone $DEVICE_ID $RUNTIME_ID) - echo "Created simulator:" $SIM_ID - xcrun simctl boot $SIM_ID - echo "device=$SIM_ID" >> $GITHUB_ENV + RUNTIME_ID=$(echo "$RUNTIME" | jq -r '.identifier') + echo "Using runtime:" "$RUNTIME_ID" + DEVICE_ID=$(echo "$RUNTIME" | jq -r '.supportedDeviceTypes | map(select(.productFamily == "iPhone")) | .[0].identifier') + echo "Using device:" "$DEVICE_ID" + SIM_ID=$(xcrun simctl create Test-iPhone "$DEVICE_ID" "$RUNTIME_ID") + echo "Created simulator:" "$SIM_ID" + xcrun simctl boot "$SIM_ID" + echo "device=$SIM_ID" >> "$GITHUB_ENV" - name: Cache Rust dependencies uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 @@ -222,14 +260,43 @@ jobs: - name: Run unit tests (cross build) run: cargo dinghy -p auto-ios-aarch64-sim -d ${{ env.device }} test --all-targets --all-features + tests-ios-build: + name: Build (iOS device, ${{ matrix.target }}) + + if: | + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository || + contains(github.event.pull_request.labels.*.name, 'safe to test') + + runs-on: macos-14 + + strategy: + fail-fast: false + matrix: + target: [aarch64-apple-ios, x86_64-apple-ios] + + steps: + - name: Checkout repository + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master + with: + toolchain: stable + targets: ${{ matrix.target }} + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2 + + - name: Build library + run: cargo build --lib --all-features --target ${{ matrix.target }} + tests-wasm: name: Unit tests (Wasm) if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') || contains(github.event.pull_request.labels.*.name, 'check-release') @@ -268,9 +335,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') || contains(github.event.pull_request.labels.*.name, 'check-release') @@ -290,7 +355,7 @@ jobs: - name: Install wasmtime run: | curl https://wasmtime.dev/install.sh -sSf | bash - echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + echo "$HOME/.wasmtime/bin" >> "$GITHUB_PATH" - name: Install clang run: | @@ -316,9 +381,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ${{ matrix.os }} @@ -349,9 +412,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -374,9 +435,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -402,9 +461,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -426,9 +483,7 @@ jobs: name: Enforce TOML format if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -448,9 +503,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -460,9 +513,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Rust toolchain - # Nightly is used here because the docs.rs build - # uses nightly and we use doc_cfg features that are - # not in stable Rust as of this writing (Rust 1.88). + # Match the nightly toolchain used by docs.rs. uses: dtolnay/rust-toolchain@2c76c034f9e32a5d6e2c104dc4df23a22476db04 # nightly - name: Cache Rust dependencies @@ -481,9 +532,7 @@ jobs: if: | github.event_name != 'pull_request' || - github.event.pull_request.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.user.login == 'dependabot[bot]' || + github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'safe to test') runs-on: ubuntu-latest @@ -506,3 +555,38 @@ jobs: uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1 with: command: check ${{ matrix.checks }} + + internal_docs: + name: Verify internal crate documentation + + if: | + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository || + contains(github.event.pull_request.labels.*.name, 'safe to test') + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + + - name: Install nightly Rust toolchain + uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master + with: + toolchain: nightly + + - name: Check internal documentation + env: + RUSTDOCFLAGS: -Dwarnings + run: cargo +nightly doc --no-deps --document-private-items + + actionlint: + name: Lint GitHub Actions workflows + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + + - name: Check workflows + run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml index 4ddb573..4dbeb69 100644 --- a/.github/workflows/pr_title.yml +++ b/.github/workflows/pr_title.yml @@ -1,5 +1,12 @@ name: PR title +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: pull_request: types: [opened, synchronize, reopened, edited] @@ -94,10 +101,10 @@ jobs: cargo install --quiet commitlint-rs set -e - echo   - echo   - echo --- commitlint results for PR title \"$PR_TITLE\" --- - echo   + echo + echo + echo "--- commitlint results for PR title \"$PR_TITLE\" ---" + echo echo "$PR_TITLE" | commitlint -g .commitlintrc.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e92516..115379e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,10 @@ permissions: pull-requests: write contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + on: push: branches: @@ -14,6 +18,10 @@ jobs: name: Release-plz runs-on: ubuntu-latest + env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + CARGO_NET_GIT_FETCH_WITH_CLI: "true" + steps: - name: Checkout repository uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 @@ -22,19 +30,63 @@ jobs: token: ${{ secrets.RELEASE_PLZ_ORG_TOKEN }} - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master + with: + toolchain: stable - - name: Run release-plz - uses: MarcoIeni/release-plz-action@487eb7b5c085a664d5c5ca05f4159bd9b591182a # v0.5.120 + - name: Publish releases + uses: MarcoIeni/release-plz-action@8921286633dfa0c4020f7f30bd1c14fe19cbb10e # v0.5.142 + with: + command: release env: GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_ORG_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_ORG_TOKEN }} - - name: Clean up stale release-plz branches + release-pr: + name: Release PR + runs-on: ubuntu-latest + needs: release-plz + + steps: + - name: Checkout repository after publication + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_PLZ_ORG_TOKEN }} + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master + with: + toolchain: stable + + - name: Open or update release PR + uses: MarcoIeni/release-plz-action@8921286633dfa0c4020f7f30bd1c14fe19cbb10e # v0.5.142 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_ORG_TOKEN }} + + - name: Clean up closed release-plz branches + env: + GH_TOKEN: ${{ secrets.RELEASE_PLZ_ORG_TOKEN }} run: | - git --no-pager branch --remote |\ - grep 'origin/release-plz-' |\ - sort -r |\ - tail -n +2 |\ - sed 's/origin\///' |\ - xargs -I {} git push origin --delete {} + set -euo pipefail + + open_branches=$(gh api --paginate \ + "repos/$GITHUB_REPOSITORY/pulls?state=open&per_page=100" \ + --jq '.[] | select(.head.repo.full_name == env.GITHUB_REPOSITORY) | .head.ref' | + jq -Rsc 'split("\n") | map(select(length > 0))') + closed_branches=$(gh api --paginate \ + "repos/$GITHUB_REPOSITORY/pulls?state=closed&base=$GITHUB_REF_NAME&per_page=100" \ + --jq '.[] | select(.head.repo.full_name == env.GITHUB_REPOSITORY) | .head.ref' | + jq -Rr --argjson open "$open_branches" \ + 'select(startswith("release-plz-")) | select(. as $branch | $open | index($branch) | not)' | + sort -u) + + while IFS= read -r branch; do + [ -n "$branch" ] || continue + remote_ref=$(git ls-remote --heads origin "refs/heads/$branch") + if [ -n "$remote_ref" ]; then + git push origin --delete "$branch" + fi + done <<< "$closed_branches"