diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000000..8f201acded4 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,49 @@ +name: changelog + +on: + pull_request: + paths: + - 'CHANGELOG.md' + - 'xtask/**/*' + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +env: + # + # Dependency versioning + # + + # This is the MSRV used by `wgpu` itself and all surrounding infrastructure. + REPO_MSRV: "1.88" + + # + # Environment variables + # + + CARGO_INCREMENTAL: false + CARGO_TERM_COLOR: always + RUST_LOG: info + RUST_BACKTRACE: '1' + CACHE_SUFFIX: c # cache busting + +jobs: + changelog: + timeout-minutes: 1 + + name: Check changelog for errors + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + # NOTE: Keep label name(s) in sync. with `xtask`'s implementation. + - name: Run `cargo xtask changelog …` + run: | + cargo xtask changelog --emit-github-messages "origin/${{ github.event.pull_request.base.ref }}" ${{ contains(github.event.pull_request.labels.*.name, 'changelog: released entry changed') && '--warn-only' || '' }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index d22937e8fb1..00000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,768 +0,0 @@ -name: CI - -on: - push: - branches-ignore: [ - # Renovate branches are always PRs, so they will be covered - # by the pull_request event. - "renovate/**", - # Branches with the `gh-readonly-queue` prefix are used by the - # merge queue, so they are already covered by the `merge_group` event. - "gh-readonly-queue/**", - ] - pull_request: - merge_group: - -env: - # - # Dependency versioning - # - - # This is the MSRV used by `wgpu` itself and all surrounding infrastructure. - REPO_MSRV: "1.88" - # This is the MSRV used by the `wgpu-core`, `wgpu-hal`, and `wgpu-types` crates, - # to ensure that they can be used with firefox. - CORE_MSRV: "1.82.0" - - # - # Environment variables - # - - CARGO_INCREMENTAL: false - CARGO_TERM_COLOR: always - WGPU_DX12_COMPILER: dxc - RUST_LOG: debug,wasm_bindgen_wasm_interpreter=warn,wasm_bindgen_cli_support=warn,walrus=warn,naga=info - RUST_BACKTRACE: full - PKG_CONFIG_ALLOW_CROSS: 1 # allow android to work - RUSTFLAGS: -D warnings - RUSTDOCFLAGS: -D warnings - WASM_BINDGEN_TEST_TIMEOUT: 300 # 5 minutes - CACHE_SUFFIX: d # cache busting - WGPU_CI: true - -# Every time a PR is pushed to, cancel any previous jobs. This -# makes us behave nicer to github and get faster turnaround times -# on PRs that are pushed to multiple times in rapid succession. -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: ${{github.event_name == 'pull_request'}} - -# We distinguish the following kinds of builds: -# - native: build for the same target as we compile on -# - web: build for the Web -# - em: build for the Emscripten - -# For build time and size optimization we disable debug symbols -# entirely on clippy jobs and reduce it to line-numbers -# only for ones where we run tests. -# -# Additionally, we disable incremental builds entirely -# as our caching system doesn't actually cache our crates. -# It adds overhead to the build and another point of failure. - -jobs: - check: - # runtime is normally 2-8 minutes - # - # currently high due to documentation time problems on mac. - # https://github.com/rust-lang/rust/issues/114891 - timeout-minutes: 30 - - strategy: - fail-fast: false - matrix: - include: - # Windows - - name: Windows x86_64 - os: windows-2022 - target: x86_64-pc-windows-msvc - tier: 1 - kind: native - - # Windows - - name: Windows aarch64 - os: windows-2022 - target: aarch64-pc-windows-msvc - tier: 2 - kind: native - - # MacOS - - name: MacOS x86_64 - os: macos-14 - target: x86_64-apple-darwin - tier: 1 - kind: native - - - name: MacOS aarch64 - os: macos-14 - target: aarch64-apple-darwin - tier: 1 - kind: native - - # IOS - - name: IOS aarch64 - os: macos-14 - target: aarch64-apple-ios - tier: 2 - kind: native - - # VisionOS - - name: VisionOS aarch64 - os: macos-14 - target: aarch64-apple-visionos - kind: wgpu-only - tier: 3 - extra-flags: -Zbuild-std - - # Linux - - name: Linux x86_64 - os: ubuntu-24.04 - target: x86_64-unknown-linux-gnu - tier: 1 - kind: native - - - name: Linux aarch64 - os: ubuntu-24.04 - target: aarch64-unknown-linux-gnu - tier: 1 - kind: native - - # FreeBSD - - name: FreeBSD x86_64 - os: ubuntu-24.04 - target: x86_64-unknown-freebsd - tier: 2 - kind: wgpu-only - - # Android - - name: Android aarch64 - os: ubuntu-24.04 - target: aarch64-linux-android - tier: 2 - kind: native - - # Android - - name: Android ARMv7 - os: ubuntu-24.04 - target: armv7-linux-androideabi - tier: 2 - kind: wgpu-only - - # Android - - name: Android x86_64 - os: ubuntu-24.04 - target: x86_64-linux-android - tier: 2 - kind: wgpu-only - - # OpenHarmony - - name: OpenHarmony aarch64 - os: ubuntu-24.04 - target: aarch64-unknown-linux-ohos - tier: 2 - kind: native - - # WebGPU/WebGL - - name: WebAssembly - os: ubuntu-24.04 - target: wasm32-unknown-unknown - tier: 2 - kind: web - - - name: Emscripten - os: ubuntu-24.04 - target: wasm32-unknown-emscripten - tier: 2 - kind: wgpu-only - - - name: WebAssembly Core 1.0 - os: ubuntu-24.04 - target: wasm32v1-none - tier: 2 - kind: no_std - - # 32-bit PowerPC Linux - # Included to test support for `portable-atomic` - - name: Linux ppc32 - os: ubuntu-24.04 - target: powerpc-unknown-linux-gnu - tier: 2 - kind: wgpu-only - - name: Clippy ${{ matrix.name }} - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install toolchain (repo MSRV - tier 1 or 2) - if: matrix.tier == 1 || matrix.tier == 2 - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy - rustup target add ${{ matrix.target }} --toolchain ${{ env.REPO_MSRV }} - rustup override set ${{ env.REPO_MSRV }} - cargo -V - - # In order to build on platforms that require a nightly toolchain, we install stable as expected, - # add the rust-src component, then tell stable to consider itself nightly by setting RUSTC_BOOTSTRAP=1. - # - # This is not formally "correct" thing to do, but it saves significant maintainer burden. If we were to - # use a proper nightly toolchain we would have to manually find a date that works. Even with a date that is - # carefully coordinated with the version of stable we are using, there are often mismatches of clippy lints - # between nightly and stable. This is a real mess. By using RUSTC_BOOTSTRAP=1, we get access to all the nice - # nightly features without needing to go through the hassle of maintaining a nightly toolchain. - # - # RUSTC_BOOTSTRAP=1 is how the rust project builds itself when bootstrapping the compiler, so while not "stable" - # it has been around for many years and don't anticipate it going away any time soon. - - name: Install toolchain (repo MSRV - tier 3) - if: matrix.tier == 3 - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy,rust-src - echo "RUSTC_BOOTSTRAP=1" >> "$GITHUB_ENV" - - - name: Disable debug symbols - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = false - EOF - - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: clippy-${{ matrix.target }}-${{ matrix.kind }}-${{ env.CACHE_SUFFIX }} - - - name: (Linux `aarch64`) Install `aarch64-linux-gnu` `g++` - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - set -e - - sudo apt-get update -y -qq - - sudo apt-get install g++-aarch64-linux-gnu - - - name: (Android) Add Android APK to `PATH` - if: matrix.target == 'aarch64-linux-android' - run: | - # clang++ will be detected correctly by CC from path - echo "$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH - - # the android sdk doesn't use the conventional name for ar, so explicitly set it. - echo "AR_aarch64_linux_android=llvm-ar" >> "$GITHUB_ENV" - - # Building for wasm32 requires a series of specific tests for the WebGPU backend. - - name: Check web - if: matrix.kind == 'web' - shell: bash - run: | - set -e - - export RUSTFLAGS="$RUSTFLAGS --cfg getrandom_backend=\"wasm_js\"" - - # build for WebGPU - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --features glsl,spirv,fragile-send-sync-non-atomic-wasm - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --features glsl,spirv - cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-deps --features glsl,spirv - - # check with only the web feature - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-default-features --features=web - - # all features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --all-features - cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-deps --all-features - - # Building for platforms where the tests do not compile. - - name: Check `wgpu` only - if: matrix.kind == 'wgpu-only' - shell: bash - run: | - set -e - - # check with no features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu -p wgpu-hal --no-default-features - - # Don't check samples since we use winit in our samples which has dropped support for Emscripten. - - # Check with all features. - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --all-features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --all-features - - # Building for no_std platforms. - - name: Check `no_std` - if: matrix.kind == 'no_std' - shell: bash - run: | - set -e - - # check with no features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p naga --no-default-features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --no-default-features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --no-default-features - - # Check with all compatible features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features --features strict_asserts,fragile-send-sync-non-atomic-wasm,serde,counters - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p naga --no-default-features --features dot-out,spv-in,spv-out - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --no-default-features --features fragile-send-sync-non-atomic-wasm - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --no-default-features --features serde - - # Building for native platforms with standard tests. - - name: Check native - if: matrix.kind == 'native' - shell: bash - run: | - set -e - - # check with no features - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-default-features - - # Check with all features. - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --benches --all-features - - # Check with all features and profiling macro code. - # If we don't check this then errors inside `profiling::scope!()` will not be caught. - cargo --locked clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --benches --all-features --features test-build-with-profiling - - # build docs - cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} --all-features --no-deps - - - name: Check private item docs - if: matrix.kind == 'native' - shell: bash - run: | - set -e - - # wgpu_core package - cargo --locked doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} \ - --package wgpu-core \ - --package wgpu-hal \ - --package naga \ - --all-features --no-deps --document-private-items - - # We run minimal checks on the MSRV of the core crates, ensuring that - # its dependency tree does not cause issues for firefox. - # - # We don't test all platforms, just ones with different dependency stacks. - check-core-msrv: - # runtime is normally 1-3 minutes - timeout-minutes: 10 - - strategy: - fail-fast: false - matrix: - include: - # Windows - - name: Windows x86_64 - os: windows-2022 - target: x86_64-pc-windows-msvc - - # MacOS - - name: MacOS x86_64 - os: macos-14 - target: x86_64-apple-darwin - - # Linux - - name: Linux x86_64 - os: ubuntu-24.04 - target: x86_64-unknown-linux-gnu - - name: MSRV Check ${{ matrix.name }} - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install core MSRV toolchain - run: | - rustup toolchain install ${{ env.CORE_MSRV }} --no-self-update --profile=minimal --component clippy --target ${{ matrix.target }} - rustup override set ${{ env.CORE_MSRV }} - cargo -V - - - name: Disable debug symbols - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = false - EOF - - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: msrv-check-${{ matrix.target }}-${{ env.CACHE_SUFFIX }} - - - name: Reduce MSRV on dependencies - shell: bash - run: | - set -e - - # 1.23.2 requires bytemuck_derive 1.10.1 - cargo update -p bytemuck --precise 1.23.1 - # 1.9.0 requires MSRV 1.84 - cargo update -p bytemuck_derive --precise 1.8.1 - - - name: Check native - shell: bash - run: | - set -e - - # check `wgpu-core` with all features. This will also get `wgpu-hal` and `wgpu-types`. - cargo check --target ${{ matrix.target }} --all-features -p wgpu-core - - # Check that the libraries build — but not that there are no warnings or that tests pass - - # with `-Zdirect-minimal-versions` which lowers all dependencies from the workspace packages - # to non-workspace packages to their minimum allowed version. - check-minimal-versions: - # runtime is normally 2 minutes - timeout-minutes: 10 - - name: MSRV Minimal Versions - runs-on: ubuntu-24.04 - env: - # Override flags to NOT include `-D warnings`, because warnings may be due to harmless problems in deps. - # Also, allow unexpected_cfgs because it is very common and spammy when using old deps. - RUSTFLAGS: -A unexpected_cfgs - - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal - rustup override set ${{ env.REPO_MSRV }} - cargo -V - - - name: Disable debug symbols - shell: bash - run: | - mkdir -p .cargo - echo """ - [profile.dev] - debug = false" >> .cargo/config.toml - - - name: Set minimal versions - shell: bash - run: | - set -e - - cargo +${{ env.REPO_MSRV }} update -Zdirect-minimal-versions - env: - RUSTC_BOOTSTRAP: 1 - - - name: Run cargo check - shell: bash - run: | - set -e - - cargo check --all-targets --all-features - - wasm-test: - # runtime is normally 2 minutes - timeout-minutes: 10 - - name: Test WebAssembly - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install repo MSRV toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy --target wasm32-unknown-unknown - rustup override set ${{ env.REPO_MSRV }} - cargo -V - - - name: Install `wasm-pack` - uses: taiki-e/install-action@v2 - with: - tool: wasm-pack - - - name: Execute tests - run: | - cd wgpu - - export RUSTFLAGS="$RUSTFLAGS --cfg getrandom_backend=\"wasm_js\"" - - wasm-pack test --headless --chrome --no-default-features --features wgsl,webgl,web --workspace - - gpu-test: - # runtime is normally 5-15 minutes - timeout-minutes: 30 - - strategy: - fail-fast: false - matrix: - include: - # Windows - - name: Windows x86_64 - os: windows-2022 - - # Mac - - name: Mac aarch64 - os: macos-14 - - # Linux - - name: Linux x86_64 - os: ubuntu-24.04 - - name: Test ${{ matrix.name }} - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install repo MSRV toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal -c llvm-tools - cargo -V - - - name: Install `cargo-nextest` and `cargo-llvm-cov` - uses: taiki-e/install-action@v2 - with: - tool: cargo-nextest,cargo-llvm-cov - - - name: Debug symbols to line-tables-only - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = "line-tables-only" - EOF - - # Cache step must go before warp and mesa install on windows as they write into the - # target directory, and rust-cache will overwrite the entirety of the target directory. - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: test-${{ matrix.os }}-${{ env.CACHE_SUFFIX }} - workspaces: | - . -> target - - - name: (Windows) Install DXC - if: matrix.os == 'windows-2022' - uses: ./.github/actions/install-dxc - - - name: (Windows) Install WARP - if: matrix.os == 'windows-2022' - uses: ./.github/actions/install-warp - with: - target-dirs: "target/llvm-cov-target/debug target/llvm-cov-target/debug/deps" - - - name: (Windows) Install Mesa - if: matrix.os == 'windows-2022' - uses: ./.github/actions/install-mesa - - - name: (Windows) Install Vulkan SDK - if: matrix.os == 'windows-2022' - uses: ./.github/actions/install-vulkan-sdk - - - name: (Mac) Install Vulkan SDK - if: matrix.os == 'macos-14' - uses: ./.github/actions/install-vulkan-sdk - - - name: (Linux) Install Vulkan SDK - if: matrix.os == 'ubuntu-24.04' - uses: ./.github/actions/install-vulkan-sdk - - - name: (Linux) Install Mesa - if: matrix.os == 'ubuntu-24.04' - uses: ./.github/actions/install-mesa - - - name: Delete Naga snapshots - shell: bash - run: | - set -e - - # Delete snapshots so we can ensure there aren't any excess output files. - rm -r naga/tests/out - - - name: Run `wgpu-info` - shell: bash - run: | - echo "$PATH" - - export RUST_LOG=trace - - # This needs to match the command in xtask/tests.rs - cargo --locked llvm-cov --no-cfg-coverage --no-report run --bin wgpu-info -- -vv - - - name: Run tests - shell: bash - run: | - set -e - - cargo --locked xtask test --llvm-cov - - - name: Check Naga snapshots - # git diff doesn't check untracked files, we need to stage those then compare with HEAD. - run: git add . && git diff --exit-code HEAD naga/tests/out - - - uses: actions/upload-artifact@v4 - if: always() # We want artifacts even if the tests fail. - with: - name: comparison-images-${{ matrix.os }} - path: | - **/*-actual.png - **/*-difference.png - - - name: Generate coverage report - id: coverage - shell: bash - continue-on-error: true - run: | - set -e - - cargo --locked llvm-cov report --lcov --output-path lcov.info - - - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5 - if: steps.coverage.outcome == 'success' - with: - files: lcov.info - token: ${{ secrets.CODECOV_TOKEN }} - - doctest: - # runtime is normally 2 minutes - timeout-minutes: 10 - - name: Doctest - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install repo MSRV toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component rustfmt - rustup override set ${{ env.REPO_MSRV }} - cargo -V - - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: doctests-${{ env.CACHE_SUFFIX }} - - - name: Run doctests - shell: bash - run: | - set -e - - cargo --locked test --doc - - fmt: - # runtime is normally 15 seconds - timeout-minutes: 2 - - name: Format & Typos - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install repo MSRV toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component rustfmt - rustup override set ${{ env.REPO_MSRV }} - cargo -V - - - name: Run `cargo fmt` - run: | - cargo --locked fmt -- --check - - - name: Install Taplo - uses: uncenter/setup-taplo@v1 - with: - version: "0.9.3" - - - name: Run `taplo fmt` - run: taplo format --check --diff - - - name: Check for typos - uses: crate-ci/typos@v1.38.1 - - check-cts-runner: - # runtime is normally 2 minutes - timeout-minutes: 10 - - name: Clippy cts_runner - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Install MSRV toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy - rustup override set ${{ env.REPO_MSRV }} - cargo -V - - - name: Disable debug symbols - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = false - EOF - - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: cts-runner-${{ env.CACHE_SUFFIX }} - - - name: Build Deno - run: | - cargo --locked clippy --manifest-path cts_runner/Cargo.toml - - # Separate job so that new advisories don't block CI. - # - # This job is not required to pass for PRs to be merged. - cargo-deny-check-advisories: - # runtime is normally 1 minute - timeout-minutes: 5 - - name: "cargo-deny advisories" - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Run `cargo deny check` - uses: EmbarkStudios/cargo-deny-action@v2 - with: - command: check advisories - arguments: --all-features --workspace - command-arguments: -Dwarnings -Aunmatched-organization - rust-version: ${{ env.REPO_MSRV }} - - cargo-deny-check-rest: - # runtime is normally 1 minute - timeout-minutes: 5 - - name: "cargo-deny" - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v5 - - - name: Run `cargo deny check` - uses: EmbarkStudios/cargo-deny-action@v2 - with: - command: check bans licenses sources - arguments: --all-features --workspace - command-arguments: -Dwarnings -Aunmatched-organization - rust-version: ${{ env.REPO_MSRV }} diff --git a/.github/workflows/cts.yml b/.github/workflows/cts.yml deleted file mode 100644 index 36b1b4f9832..00000000000 --- a/.github/workflows/cts.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: CTS - -on: - push: - branches-ignore: [ - # Renovate branches are always PRs, so they will be covered - # by the pull_request event. - "renovate/**", - # Branches with the `gh-readonly-queue` prefix are used by the - # merge queue, so they are already covered by the `merge_group` event. - "gh-readonly-queue/**", - ] - pull_request: - merge_group: - -env: - CARGO_INCREMENTAL: false - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - MSRV: "1.88" - -# Every time a PR is pushed to, cancel any previous jobs. This -# makes us behave nicer to github and get faster turnaround times -# on PRs that are pushed to multiple times in rapid succession. -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: ${{github.event_name == 'pull_request'}} - -jobs: - cts: - strategy: - fail-fast: false - matrix: - include: - # Windows - - name: Windows x86_64 - os: windows-2022 - target: x86_64-pc-windows-msvc - backend: dx12 - - # Mac - - name: Mac aarch64 - os: macos-14 - target: x86_64-apple-darwin - backend: metal - - # Linux - - name: Linux x86_64 - os: ubuntu-24.04 - target: x86_64-unknown-linux-gnu - backend: vulkan - - name: CTS ${{ matrix.name }} - runs-on: ${{ matrix.os }} - - steps: - - name: checkout repo - uses: actions/checkout@v5 - - - name: Install Repo MSRV toolchain - run: | - rustup toolchain install ${{ env.MSRV }} --no-self-update --profile=minimal --target ${{ matrix.target }} --component llvm-tools - rustup override set ${{ env.MSRV }} - cargo -V - - - name: Install `cargo-llvm-cov` - uses: taiki-e/install-action@v2 - with: - tool: cargo-llvm-cov - - - name: caching - uses: Swatinem/rust-cache@v2 - with: - prefix-key: v2-rust # Increment version for cache busting - cache-directories: cts - - # We enable line numbers for panics, but that's it - - name: disable debug - shell: bash - run: | - mkdir -p .cargo - cat <> .cargo/config.toml - [profile.dev] - debug = "line-tables-only" - EOF - - - name: (Windows) Install DXC - if: matrix.os == 'windows-2022' - uses: ./.github/actions/install-dxc - - - name: (Windows) Install WARP - if: matrix.os == 'windows-2022' - uses: ./.github/actions/install-warp - with: - target-dirs: "target/llvm-cov-target/debug" - - - name: (Linux) Install Mesa - if: matrix.os == 'ubuntu-24.04' - uses: ./.github/actions/install-mesa - - - name: run CTS - shell: bash - run: cargo --locked xtask cts --llvm-cov --backend ${{ matrix.backend }} - - - name: Generate coverage report - id: coverage - shell: bash - continue-on-error: true - run: | - set -e - - cargo --locked llvm-cov report --lcov --output-path lcov.info - - - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5 - if: steps.coverage.outcome == 'success' - with: - files: lcov.info - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 83adc7629ec..00000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Docs - -on: - pull_request: - paths: - - ".github/workflows/docs.yml" - push: - branches: - - trunk - -env: - # This is the MSRV used by `wgpu` itself and all surrounding infrastructure. - REPO_MSRV: "1.88" - - CARGO_INCREMENTAL: false - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - -# Every time a PR is pushed to, cancel any previous jobs. This -# makes us behave nicer to github and get faster turnaround times -# on PRs that are pushed to multiple times in rapid succession. -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: ${{github.event_name == 'pull_request'}} - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Install documentation toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal - rustup override set ${{ env.REPO_MSRV }} - - - name: Disable debug symbols - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = false - EOF - - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: doc-build - - - name: Build the docs - run: | - cargo doc --no-deps --lib --document-private-items - env: - RUSTDOCFLAGS: --cfg docsrs - RUSTC_BOOTSTRAP: 1 - - - name: Deploy the docs - uses: JamesIves/github-pages-deploy-action@v4.7.3 - if: github.ref == 'refs/heads/trunk' - with: - token: ${{ secrets.WEB_DEPLOY }} - folder: target/doc - repository-name: gfx-rs/wgpu-rs.github.io - branch: master - target-folder: doc diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml deleted file mode 100644 index cd2d4af901e..00000000000 --- a/.github/workflows/generate.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: cargo-generate - -on: - push: - branches-ignore: [ - # Renovate branches are always PRs, so they will be covered - # by the pull_request event. - "renovate/**", - # Branches with the `gh-readonly-queue` prefix are used by the - # merge queue, so they are already covered by the `merge_group` event. - "gh-readonly-queue/**", - ] - pull_request: - merge_group: - -env: - # - # Dependency versioning - # - - # This is the MSRV used by `wgpu` itself and all surrounding infrastructure. - REPO_MSRV: "1.88" - RUSTFLAGS: -D warnings - -# Every time a PR is pushed to, cancel any previous jobs. This -# makes us behave nicer to github and get faster turnaround times -# on PRs that are pushed to multiple times in rapid succession. -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: ${{github.event_name == 'pull_request'}} - -jobs: - cargo-generate: - timeout-minutes: 5 - - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - include: - - name: "01-hello-compute" - path: "examples/standalone/01_hello_compute" - - name: "02-hello-window" - path: "examples/standalone/02_hello_window" - - name: "custom_backend" - path: "examples/standalone/custom_backend" - - name: "${{ matrix.name }}" - - steps: - - uses: actions/checkout@v5 - - # We can't rely on an override here, as that would only set - # the toolchain for the current directory, not the newly generated project. - - name: Install repo MSRV toolchain - run: | - rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal - cargo -V - - - name: Disable debug symbols - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = false - EOF - - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: cargo-generate-${{ matrix.name }} - - - name: Install `cargo-generate` - uses: taiki-e/install-action@v2 - with: - tool: cargo-generate - - - name: Run `cargo-generate` - run: | - cd .. - cargo generate --path wgpu --name ${{ matrix.name }} ${{ matrix.path }} - - - name: Check generated files - run: | - cd ../${{ matrix.name }}/ - cat <> Cargo.toml - [patch.crates-io] - wgpu = { path = "../wgpu/wgpu" } - EOF - cargo +${{ env.REPO_MSRV }} check diff --git a/.github/workflows/lazy.yml b/.github/workflows/lazy.yml deleted file mode 100644 index d0e526799b1..00000000000 --- a/.github/workflows/lazy.yml +++ /dev/null @@ -1,162 +0,0 @@ -# Lazy jobs running on trunk post merges. -name: Lazy -on: - pull_request: - paths: - - ".github/workflows/lazy.yml" - push: - branches: [trunk] - -env: - CARGO_INCREMENTAL: false - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - -# Every time a PR is pushed to, cancel any previous jobs. This -# makes us behave nicer to github and get faster turnaround times -# on PRs that are pushed to multiple times in rapid succession. -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: ${{github.event_name == 'pull_request'}} - -jobs: - parse-dota2: - name: "Validate Shaders: Dota2" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - run: mkdir naga/data - - - name: Download shaders - run: curl https://user.fm/files/v2-5573e18b9f03f42c6ae53c392083da35/dota2-shaders.zip -o naga/data/all.zip - - - name: Unpack shaders - run: | - cd naga/data - unzip all.zip - - - name: Build Naga - run: | - cd naga - cargo build --release -p naga-cli - - - name: Convert shaders - run: | - cd naga - for file in data/*.spv ; do echo "Translating" ${file} && ../target/release/naga --validate 27 ${file} ${file}.metal; done - - parse-vulkan-tutorial-shaders: - name: "Validate Shaders: Sascha Willems Vulkan Tutorial" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - name: Download shaders - run: cd naga && git clone https://github.com/SaschaWillems/Vulkan.git - - - name: Build Naga - run: | - cd naga - cargo build --release -p naga-cli - - - name: Convert Metal shaders - run: | - # No needed to stop workflow if we can't validate one file - set +e - cd naga - touch counter - SUCCESS_RESULT_COUNT=0 - FILE_COUNT=0 - mkdir -p out - find "Vulkan/data/shaders/glsl/" -name '*.spv' | while read fname; - do - echo "Convert: $fname" - FILE_COUNT=$((FILE_COUNT+1)) - ../target/release/naga --validate 27 $(realpath ${fname}) out/$(basename ${fname}).metal - if [[ $? -eq 0 ]]; then - SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) - fi - echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter - done - cat counter - - dneto0_spirv-samples: - name: "Validate Shaders: dneto0 spirv-samples" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - name: Download shaders - run: | - cd naga - git clone https://github.com/dneto0/spirv-samples.git - - - name: Build Naga - run: | - cargo build --release -p naga-cli - - - name: Install `spirv-tools` - run: | - cd naga - wget -q https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/linux-clang-release/continuous/1489/20210629-121459/install.tgz - tar zxf install.tgz - ./install/bin/spirv-as --version - - - name: Compile `spv` from `spvasm` - run: | - cd naga/spirv-samples - mkdir -p spv - - find "./spvasm" -name '*.spvasm' | while read fname; - do - echo "Convert to spv with spirv-as: $fname" - ../install/bin/spirv-as --target-env spv1.3 $(realpath ${fname}) -o ./spv/$(basename ${fname}).spv - done; - - - name: Validate `spv` and generate `wgsl` - run: | - set +e - cd naga/spirv-samples - SUCCESS_RESULT_COUNT=0 - FILE_COUNT=0 - mkdir -p spv - mkdir -p wgsl - - echo "==== Validate spv and generate wgsl ====" - rm -f counter - touch counter - - find "./spv" -name '*.spv' | while read fname; - do - echo "Convert: $fname" - FILE_COUNT=$((FILE_COUNT+1)) - ../../target/release/naga --validate 27 $(realpath ${fname}) ./wgsl/$(basename ${fname}).wgsl - if [[ $? -eq 0 ]]; then - SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) - fi - echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter - done - cat counter - - - name: Validate output `wgsl` - run: | - set +e - cd naga/spirv-samples - SUCCESS_RESULT_COUNT=0 - FILE_COUNT=0 - - rm -f counter - touch counter - - find "./wgsl" -name '*.wgsl' | while read fname; - do - echo "Validate: $fname" - FILE_COUNT=$((FILE_COUNT+1)) - ../../target/release/naga --validate 27 $(realpath ${fname}) - if [[ $? -eq 0 ]]; then - SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) - fi - echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter - done - cat counter diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 1a4385da0ac..00000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Publish - -on: - pull_request: - paths: - - ".github/workflows/publish.yml" - push: - branches: - - trunk - -env: - CARGO_INCREMENTAL: false - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - -# Every time a PR is pushed to, cancel any previous jobs. This -# makes us behave nicer to github and get faster turnaround times -# on PRs that are pushed to multiple times in rapid succession. -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: ${{github.event_name == 'pull_request'}} - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Install Rust WASM target - run: rustup target add wasm32-unknown-unknown - - - name: Get `wasm-bindgen` version - run: | - WASM_BINDGEN_VERSION=$(cargo metadata --format-version 1 --all-features | jq '.packages[] | select(.name == "wasm-bindgen") | .version' | tr -d '"') - - echo $WASM_BINDGEN_VERSION - - echo "WASM_BINDGEN_VERSION=$WASM_BINDGEN_VERSION" >> "$GITHUB_ENV" - - - name: Install `wasm-bindgen` - run: cargo +stable install wasm-bindgen-cli --version=$WASM_BINDGEN_VERSION - - - name: Debug symbols to line-tables-only - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = "line-tables-only" - EOF - - - name: Caching - uses: Swatinem/rust-cache@v2 - with: - key: publish-build - - - name: Build examples - run: cargo xtask run-wasm --no-serve - - - name: Deploy WebGPU examples - uses: JamesIves/github-pages-deploy-action@v4.7.3 - if: github.ref == 'refs/heads/trunk' - with: - token: ${{ secrets.WEB_DEPLOY }} - folder: target/generated - repository-name: gfx-rs/wgpu-rs.github.io - branch: master - target-folder: examples/ diff --git a/.github/workflows/shaders.yml b/.github/workflows/shaders.yml deleted file mode 100644 index 062d6ecb142..00000000000 --- a/.github/workflows/shaders.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Shaders - -on: - push: - branches-ignore: [ - # Renovate branches are always PRs, so they will be covered - # by the pull_request event. - "renovate/**", - # Branches with the `gh-readonly-queue` prefix are used by the - # merge queue, so they are already covered by the `merge_group` event. - "gh-readonly-queue/**", - ] - pull_request: - merge_group: - -# Every time a PR is pushed to, cancel any previous jobs. This -# makes us behave nicer to github and get faster turnaround times -# on PRs that are pushed to multiple times in rapid succession. -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: ${{github.event_name == 'pull_request'}} - -jobs: - naga-validate-windows: - name: "Validate: HLSL" - runs-on: windows-latest - steps: - - uses: actions/checkout@v5 - - - name: Debug symbols to `line-tables-only` - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = "line-tables-only" - EOF - - - uses: Swatinem/rust-cache@v2 - - # We must have the FXC job before the DXC job, so the DXC PATH has priority - # over the FXC PATH. This is because the windows kits also include an older - # version of DXC, which we don't want to use. - - name: Setup FXC - run: | - Get-Childitem -Path "C:\Program Files (x86)\Windows Kits\10\bin\**\x64\fxc.exe" ` - | Sort-Object -Property LastWriteTime -Descending ` - | Select-Object -First 1 ` - | Split-Path -Parent ` - | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append - shell: powershell - - - name: Setup DXC - uses: ./.github/actions/install-dxc - - - name: Validate - shell: bash - run: | - set -e - - dxc --version - - cd naga - cargo xtask validate hlsl dxc - cargo xtask validate hlsl fxc - - naga-validate-macos: - name: "Validate: MSL" - runs-on: macos-15 - steps: - - uses: actions/checkout@v5 - - - name: Debug symbols to line-tables-only - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = "line-tables-only" - EOF - - - uses: Swatinem/rust-cache@v2 - - - run: | - cd naga - cargo xtask validate msl - - naga-validate-linux: - name: "Validate: SPIR-V/GLSL/DOT/WGSL" - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v5 - - - name: Install Vulkan SDK - uses: ./.github/actions/install-vulkan-sdk - - - name: Install Graphviz - run: sudo apt-get install graphviz - - - name: Debug symbols to `line-tables-only` - shell: bash - run: | - mkdir -p .cargo - - cat <> .cargo/config.toml - [profile.dev] - debug = "line-tables-only" - EOF - - - uses: Swatinem/rust-cache@v2 - - - run: cd naga; cargo xtask validate spv - - - run: cd naga; cargo xtask validate glsl - - - run: cd naga; cargo xtask validate dot - - - run: cd naga; cargo xtask validate wgsl diff --git a/xtask/src/changelog.rs b/xtask/src/changelog.rs new file mode 100644 index 00000000000..8e92be5ae7d --- /dev/null +++ b/xtask/src/changelog.rs @@ -0,0 +1,452 @@ +use pico_args::Arguments; +use xshell::Shell; + +pub(crate) fn check_changelog(shell: Shell, mut args: Arguments) -> anyhow::Result<()> { + const CHANGELOG_PATH_RELATIVE: &str = "./CHANGELOG.md"; + const WARN_LABEL: &str = "changelog: released entry changed"; + + let emit_github_messages = args.contains("--emit-github-messages"); + let warn_only = args.contains("--warn-only"); + + let from_branch = args + .free_from_str() + .ok() + .unwrap_or_else(|| "trunk".to_owned()); + let to_commit: Option = args.free_from_str().ok(); + + let from_commit = shell + .cmd("git") + .args(["merge-base", "--fork-point", &from_branch]) + .args(to_commit.as_ref()) + .read() + .unwrap(); + + let diff = shell + .cmd("git") + .args(["diff", &from_commit]) + // NOTE: If `to_commit` is not specified, we compare against the working tree, instead of + // between commits. + .args(to_commit.as_ref()) + .args(["--", CHANGELOG_PATH_RELATIVE]) + .read() + .unwrap(); + + // NOTE: If `to_commit` is not specified, we need to fetch from the file system, instead of + // `git show`. + let changelog_contents = if let Some(to_commit) = to_commit.as_ref() { + shell + .cmd("git") + .arg("show") + .arg(format!("{to_commit}:{CHANGELOG_PATH_RELATIVE}")) + .arg("--") + .read() + .unwrap() + } else { + shell.read_file(CHANGELOG_PATH_RELATIVE).unwrap() + }; + + let mut failed = false; + + let hunks_in_a_released_section = hunks_in_a_released_section(&changelog_contents, &diff); + log::info!( + "# of hunks in a released section of `{CHANGELOG_PATH_RELATIVE}`: {}", + hunks_in_a_released_section.len() + ); + if !hunks_in_a_released_section.is_empty() { + failed = true; + + #[expect(clippy::uninlined_format_args)] + { + eprintln!( + "Found hunk(s) in released sections of `{}`, which we don't want:\n", + CHANGELOG_PATH_RELATIVE, + ); + } + + for hunk in &hunks_in_a_released_section { + eprintln!("{}", hunk.contents); + + if emit_github_messages { + let severity = if warn_only { "warning" } else { "error" }; + let title = "Released changelog content changed"; + let mut message = + "This PR changes changelog content that is already released.".to_owned(); + if !warn_only { + // NOTE: Keep this label name in sync. with CI's branching logic. + message += &format!( + concat!( + " ", + "If you know what you're doing, ", + "you can add the `{}` label ", + "and reduce this error's severity to a warning." + ), + WARN_LABEL + ); + } + println!( + "::{severity} file=CHANGELOG.md,line={},endLine={},title={title}::{message}", + hunk.change_start_line_num, hunk.change_end_line_num, + ) + } + } + } + + if failed { + #[expect(clippy::uninlined_format_args)] + let msg = format!( + "one or more checks against `{}` failed; see above for details", + CHANGELOG_PATH_RELATIVE, + ); + if warn_only { + log::warn!("{msg}"); + Ok(()) + } else { + Err(anyhow::Error::msg(msg)) + } + } else { + Ok(()) + } +} + +struct Hunk<'a> { + change_start_line_num: u64, + change_end_line_num: u64, + contents: &'a str, +} + +/// Given some `changelog_contents` (in Markdown) containing the full end state of the provided +/// `diff` (in [unified diff format]), return all hunks that are (1) below a `## Unreleased` section +/// _and_ (2) above all other second-level (i.e., `## …`) headings. +/// +/// [unified diff format]: https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html +/// +/// This function makes a few assumptions that are necessary to uphold for correctness, in the +/// interest of a simple implementation: +/// +/// - The provided `diff`'s end state _must_ correspond to `changelog_contents`. +/// - The provided `diff` must _only_ contain a single entry for the file containing +/// `changelog_contents`. using hunk information to compare against `changelog_contents`. +/// +/// Failing to uphold these assumptons is not unsafe, but will yield incorrect results. +fn hunks_in_a_released_section<'a>(changelog_contents: &str, diff: &'a str) -> Vec> { + let mut changelog_lines = changelog_contents.lines(); + + let changelog_unreleased_line_num = + changelog_lines.position(|l| l == "## Unreleased").unwrap() as u64; + + let changelog_first_release_section_line_num = changelog_unreleased_line_num + + 1 + + changelog_lines.position(|l| l.starts_with("## ")).unwrap() as u64; + + let hunks = { + let first_hunk_match = diff.match_indices("\n@@").next(); + let Some((first_hunk_idx, _)) = first_hunk_match else { + log::info!("no diff found"); + return vec![]; + }; + SplitPrefixInclusive::new("\n@@", &diff[first_hunk_idx..]).map(|s| &s['\n'.len_utf8()..]) + }; + let hunks_in_a_released_section = hunks + .filter_map(|hunk| { + let (hunk_header, hunk_contents) = hunk.split_once('\n').unwrap(); + + // Reference: This is of the format `@@ -86,6 +88,10 @@ …`. + let post_change_hunk_start_offset = hunk_header + .strip_prefix("@@ ") + .unwrap() + .split_once(" @@") + .unwrap() + .0 + .split_once(" +") + .unwrap() + .1 + .split_once(",") + .unwrap() + .0 + .parse::() + .unwrap(); + + let mut change_lines = hunk_contents + .lines() + .enumerate() + .filter(|(_idx, l)| !l.starts_with(' ')) + .map(|(zero_based_idx, _l)| { + post_change_hunk_start_offset + (zero_based_idx as u64) + }); + + let change_start_line_num = change_lines.next().unwrap(); + + if change_start_line_num >= changelog_first_release_section_line_num { + Some(Hunk { + contents: hunk, + change_start_line_num, + change_end_line_num: change_lines.last().unwrap_or(change_start_line_num), + }) + } else { + None + } + }) + .collect::>(); + + hunks_in_a_released_section +} + +struct SplitPrefixInclusive<'haystack, 'prefix> { + haystack: Option<&'haystack str>, + prefix: &'prefix str, + current_pos: usize, +} + +impl<'haystack, 'prefix> SplitPrefixInclusive<'haystack, 'prefix> { + pub fn new(prefix: &'prefix str, haystack: &'haystack str) -> Self { + assert!(haystack.starts_with(prefix)); + Self { + haystack: Some(haystack), + prefix, + current_pos: 0, + } + } +} + +impl<'haystack> Iterator for SplitPrefixInclusive<'haystack, '_> { + type Item = &'haystack str; + + fn next(&mut self) -> Option { + let remaining = &self.haystack?[self.current_pos..]; + + let prefix_len = self.prefix.len(); + + // NOTE: We've guaranteed that the prefix is always at the start of what remains. So, skip + // the first match manually, and adjust match indices by `prefix_len` later. + let to_search = &remaining[prefix_len..]; + + match to_search.match_indices(self.prefix).next() { + None => { + self.haystack = None; + Some(remaining) + } + Some((idx, _match)) => { + let length = idx + prefix_len; + self.current_pos += length; + Some(&remaining[..length]) + } + } + } +} + +#[cfg(test)] +mod test_split_prefix_inclusive { + #[collapse_debuginfo(yes)] + macro_rules! assert_chunks { + ($prefix: expr, $haystack: expr, $expected: expr $(,)?) => { + assert_eq!( + super::SplitPrefixInclusive::new($prefix, $haystack).collect::>(), + $expected.into_iter().collect::>(), + ); + }; + } + + #[test] + fn it_works() { + assert_chunks! { + "\n@@", + " +@@ -1,4 +1,5 @@ + + +## Unreleased + +## Recently released + +- This change actually went into the release. +- This change was added after release, reject me! + +## An older release + +- Yada yada. +", + "\ +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -5,6 +5,7 @@ + ## Recently released +\u{0020} + - This change actually went into the release. ++- This change was added after release, reject me! +\u{0020} + ## An older release +", + [ + "\ +@@ -5,6 +5,7 @@ + ## Recently released +\u{0020} + - This change actually went into the release. ++- This change was added after release, reject me! +\u{0020} + ## An older release +", + ], + } + } + + #[test] + fn change_in_unreleased_not_rejected() {} + + #[test] + fn change_above_unreleased_not_rejected() {} + + #[test] + fn all_reject_and_not_reject_cases_at_once() { + assert_released_section_changes! { + "\ + + +## Unreleased + +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- This change should be accepted. + +## Recently released + +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- Pad out some changes here so we force multiple hunks in a diff. +- This change was added after release, reject me! + +## An older release + +- Yada yada. +", + "\ +--- ../CHANGELOG.md ++++ ../CHANGELOG.md +@@ -1,4 +1,5 @@ +