name: CI

on:
  push:
    branches-ignore: [
      # We don't need to run on dependabot PRs.
      "dependabot/**",
      # This is the branch the merge queue creates.
      "gh-readonly-queue/**"
    ]
    tags: [v0.*]
  pull_request:
  merge_group:

env:
  #
  # Dependency versioning
  #

  # Sourced from https://vulkan.lunarg.com/sdk/home#linux
  #
  # We don't include the 4th version number, as it's not used in any URL.
  VULKAN_SDK_VERSION: "1.3.290"
  # Sourced from https://www.nuget.org/packages/Microsoft.Direct3D.WARP
  WARP_VERSION: "1.0.13"

  # Sourced from https://github.com/microsoft/DirectXShaderCompiler/releases
  #
  # Must also be changed in shaders.yaml
  DXC_RELEASE: "v1.8.2407"
  DXC_FILENAME: "dxc_2024_07_31_clang_cl.zip"

  # Sourced from https://archive.mesa3d.org/. Bumping this requires
  # updating the mesa build in https://github.com/gfx-rs/ci-build and creating a new release.
  MESA_VERSION: "24.2.3"
  # Corresponds to https://github.com/gfx-rs/ci-build/releases
  CI_BINARY_BUILD: "build19"

  # We sometimes need nightly to use special things in CI.
  #
  # In order to prevent CI regressions, we pin the nightly version.
  NIGHTLY_VERSION: "nightly-2024-10-17"
  # This is the MSRV used by `wgpu` itself and all surrounding infrastructure.
  REPO_MSRV: "1.83"
  # 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.76"

  #
  # Environment variables
  #

  CARGO_INCREMENTAL: false
  CARGO_TERM_COLOR: always
  WGPU_DX12_COMPILER: dxc
  RUST_LOG: debug
  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: c # cache busting
  WGPU_TESTING: true

# 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
# 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-22.04
            target: x86_64-unknown-linux-gnu
            tier: 1
            kind: native

          - name: Linux aarch64
            os: ubuntu-22.04
            target: aarch64-unknown-linux-gnu
            tier: 1
            kind: native

          # Android
          - name: Android aarch64
            os: ubuntu-22.04
            target: aarch64-linux-android
            tier: 2
            kind: native

          # WebGPU/WebGL
          - name: WebAssembly
            os: ubuntu-22.04
            target: wasm32-unknown-unknown
            tier: 2
            kind: web

          - name: Emscripten
            os: ubuntu-22.04
            target: wasm32-unknown-emscripten
            tier: 2
            kind: wgpu-only
          
          # TODO: Uncomment once web-sys updates past 0.3.76
          # See https://github.com/rustwasm/wasm-bindgen/pull/4378 for details
          # - name: WebAssembly Core 1.0
          #   os: ubuntu-22.04
          #   target: wasm32v1-none
          #   tier: 2
          #   kind: no_std
          
          # Bare-metal x86-64
          # TODO: Remove once web-sys updates past 0.3.76
          # Included while wasm32v1-none is failing to ensure `no_std` does not regress
          - name: no_std x86_64
            os: ubuntu-22.04
            target: x86_64-unknown-none
            tier: 2
            kind: no_std

    name: Clippy ${{ matrix.name }}
    runs-on: ${{ matrix.os }}

    steps:
      - name: checkout repo
        uses: actions/checkout@v4

      - 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
        shell: bash
        run: |
          mkdir -p .cargo
          echo """
          [profile.dev]
          debug = false" >> .cargo/config.toml

      - 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

          # build for WebGPU
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --features glsl,spirv,fragile-send-sync-non-atomic-wasm
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --features glsl,spirv
          cargo doc --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-deps --features glsl,spirv

          # check with no features
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-default-features

          # all features
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --all-features
          cargo 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 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 clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --all-features
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --all-features
        
      # Building for no_std platforms where every feature is enabled except "std".
      - name: check no_std
        if: matrix.kind == 'no_std'
        shell: bash
        run: |
          set -e

          # check with no features
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features

          # Check with all features except "std".
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features --features strict_asserts,fragile-send-sync-non-atomic-wasm,serde,counters

      # Building for native platforms with standard tests.
      - name: check native
        if: matrix.kind == 'native'
        shell: bash
        run: |
          set -e

          # check with no features
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --no-default-features

          # Check with all features.
          cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} --tests --benches --all-features

          # build docs
          cargo 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 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-22.04
            target: x86_64-unknown-linux-gnu

    name: MSRV Check ${{ matrix.name }}
    runs-on: ${{ matrix.os }}

    steps:
      - name: checkout repo
        uses: actions/checkout@v4

      - 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
        shell: bash
        run: |
          mkdir -p .cargo
          echo """
          [profile.dev]
          debug = false" >> .cargo/config.toml

      - name: caching
        uses: Swatinem/rust-cache@v2
        with:
          key: msrv-check-${{ matrix.target }}-${{ env.CACHE_SUFFIX }}

      - 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

  naga-minimal-versions:
    # runtime is normally 2 minutes
    timeout-minutes: 10

    name: MSRV naga Minimal Versions
    runs-on: ubuntu-22.04

    steps:
      - name: checkout repo
        uses: actions/checkout@v4

      - name: Install Core MSRV toolchain
        run: |
          rustup toolchain install ${{ env.CORE_MSRV }} --no-self-update --profile=minimal --component clippy
          rustup override set ${{ env.CORE_MSRV }}
          cargo -V

      - name: Install Nightly Toolchain
        run: |
          rustup toolchain install ${{ env.NIGHTLY_VERSION }} --no-self-update --profile=minimal --component clippy
          cargo +${{ env.NIGHTLY_VERSION }} -V

      - name: Install cargo-hack
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-hack

      - name: disable debug
        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.NIGHTLY_VERSION }} hack generate-lockfile --remove-dev-deps -Z minimal-versions -p naga -p naga-cli

      - name: Clippy
        shell: bash
        run: |
          set -e

          cargo clippy --all-features -p naga -p naga-cli

  wasm-test:
    # runtime is normally 2 minutes
    timeout-minutes: 10

    name: Test WebAssembly
    runs-on: ubuntu-latest
    needs: [check]

    steps:
      - name: checkout repo
        uses: actions/checkout@v4

      - 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
          wasm-pack test --headless --chrome --no-default-features --features wgsl,webgl --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-22.04

    name: Test ${{ matrix.name }}
    runs-on: ${{ matrix.os }}
    needs: [check]

    steps:
      - name: checkout repo
        uses: actions/checkout@v4

      - 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

        # 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
            xtask -> xtask/target

      - name: (windows) install dxc
        if: matrix.os == 'windows-2022'
        shell: bash
        run: |
          set -e

          curl.exe -L --retry 5 https://github.com/microsoft/DirectXShaderCompiler/releases/download/$DXC_RELEASE/$DXC_FILENAME -o dxc.zip
          7z.exe e dxc.zip -odxc bin/x64/{dxc.exe,dxcompiler.dll,dxil.dll}

          # We need to use cygpath to convert PWD to a windows path as we're using bash.
          cygpath --windows "$PWD/dxc" >> "$GITHUB_PATH"

      - name: (windows) install warp
        if: matrix.os == 'windows-2022'
        shell: bash
        run: |
          set -e

          # Make sure dxc is in path.
          dxc --version

          curl.exe -L --retry 5 https://www.nuget.org/api/v2/package/Microsoft.Direct3D.WARP/$WARP_VERSION -o warp.zip
          7z.exe e warp.zip -owarp build/native/bin/x64/d3d10warp.dll

          mkdir -p target/llvm-cov-target/debug/deps

          cp -v warp/d3d10warp.dll target/llvm-cov-target/debug/
          cp -v warp/d3d10warp.dll target/llvm-cov-target/debug/deps

      - name: (windows) install mesa
        if: matrix.os == 'windows-2022'
        shell: bash
        run: |
          set -e

          curl.exe -L --retry 5 https://github.com/pal1000/mesa-dist-win/releases/download/$MESA_VERSION/mesa3d-$MESA_VERSION-release-msvc.7z -o mesa.7z
          7z.exe e mesa.7z -omesa x64/{opengl32.dll,libgallium_wgl.dll,libglapi.dll,vulkan_lvp.dll,lvp_icd.x86_64.json}

          cp -v mesa/* target/llvm-cov-target/debug/
          cp -v mesa/* target/llvm-cov-target/debug/deps

          # We need to use cygpath to convert PWD to a windows path as we're using bash.
          echo "VK_DRIVER_FILES=`cygpath --windows $PWD/mesa/lvp_icd.x86_64.json`" >> "$GITHUB_ENV"
          echo "GALLIUM_DRIVER=llvmpipe" >> "$GITHUB_ENV"

      - name: (linux) install vulkan sdk
        if: matrix.os == 'ubuntu-22.04'
        shell: bash
        run: |
          set -e

          sudo apt-get update -y -qq

          # vulkan sdk
          wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -
          sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-$VULKAN_SDK_VERSION-jammy.list https://packages.lunarg.com/vulkan/$VULKAN_SDK_VERSION/lunarg-vulkan-$VULKAN_SDK_VERSION-jammy.list

          sudo apt-get update
          sudo apt install -y vulkan-sdk

      - name: (linux) install mesa
        if: matrix.os == 'ubuntu-22.04'
        shell: bash
        run: |
          set -e

          curl -L --retry 5 https://github.com/gfx-rs/ci-build/releases/download/$CI_BINARY_BUILD/mesa-$MESA_VERSION-linux-x86_64.tar.xz -o mesa.tar.xz
          mkdir mesa
          tar xpf mesa.tar.xz -C mesa

          # The ICD provided by the mesa build is hardcoded to the build environment.
          #
          # We write out our own ICD file to point to the mesa vulkan
          cat <<- EOF > icd.json
          {
            "ICD": {
                "api_version": "1.1.255",
                "library_path": "$PWD/mesa/lib/x86_64-linux-gnu/libvulkan_lvp.so"
            },
            "file_format_version": "1.0.0"
          }
          EOF

          echo "VK_DRIVER_FILES=$PWD/icd.json" >> "$GITHUB_ENV"
          echo "LD_LIBRARY_PATH=$PWD/mesa/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH" >> "$GITHUB_ENV"
          echo "LIBGL_DRIVERS_PATH=$PWD/mesa/lib/x86_64-linux-gnu/dri" >> "$GITHUB_ENV"

      - name: disable debug
        shell: bash
        run: |
          mkdir -p .cargo
          echo """
          [profile.dev]
          debug = 1" >> .cargo/config.toml

      - name: run wgpu-info
        shell: bash
        run: |
          echo "$PATH"

          export RUST_LOG=trace

          # This needs to match the command in xtask/tests.rs
          cargo llvm-cov --no-cfg-coverage --no-report run --bin wgpu-info -- -vv

      - name: run tests
        shell: bash
        run: |
          set -e

          cargo 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 llvm-cov report --lcov --output-path lcov.info

      - name: upload coverage report to codecov
        uses: codecov/codecov-action@v4
        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@v4

      - 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: disable debug
        shell: bash
        run: |
          mkdir -p .cargo
          echo """
          [profile.dev]
          debug = 1" >> .cargo/config.toml

      - name: caching
        uses: Swatinem/rust-cache@v2
        with:
          key: doctests-${{ env.CACHE_SUFFIX }}

      - name: run doctests
        shell: bash
        run: |
          set -e

          cargo 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@v4

      - 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 fmt -- --check
          cargo fmt --manifest-path xtask/Cargo.toml -- --check
          cargo fmt --manifest-path naga/xtask/Cargo.toml -- --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.29.4

  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@v4

      - 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
        shell: bash
        run: |
          mkdir -p .cargo
          echo """
          [profile.dev]
          debug = 1" >> .cargo/config.toml

      - name: caching
        uses: Swatinem/rust-cache@v2
        with:
          key: cts-runner-${{ env.CACHE_SUFFIX }}

      - name: build Deno
        run: |
          cargo 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@v4

      - name: Run `cargo deny check`
        uses: EmbarkStudios/cargo-deny-action@v2
        with:
          command: check advisories
          arguments: --all-features --workspace
          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@v4

      - name: Run `cargo deny check`
        uses: EmbarkStudios/cargo-deny-action@v2
        with:
          command: check bans licenses sources
          arguments: --all-features --workspace
          rust-version: ${{ env.REPO_MSRV }}
