name: CI
on:
  merge_group:
  pull_request:
    branches:
      - "**"

defaults:
  run:
    shell: bash

permissions:
  contents: read

concurrency:
  group: "${{ github.workflow }}-${{ github.ref }}"
  cancel-in-progress: true

env:
  MDBOOK_VERSION: 0.5.1

jobs:
  conclusion:
    needs:
      - build_std
      # Disabled with temporary third-party libssh2-sys import.
      # - clippy
      - msrv
      - docs
      - lint-docs
      - lockfile
      - resolver
      - report-timings
      - rustfmt
      - schema
      # Disabled with temporary third-party libssh2-sys import.
      # - spellcheck
      - test
      - test_gitoxide
    permissions:
      contents: none
    # We need to ensure this job does *not* get skipped if its dependencies fail,
    # because a skipped job is considered a success by GitHub. So we have to
    # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
    # when the workflow is canceled manually.
    #
    # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
    if: ${{ !cancelled() }}
    runs-on: ubuntu-latest
    steps:
      # Manually check the status of all dependencies. `if: failure()` does not work.
      - name: Conclusion
        run: |
          # Print the dependent jobs to see them in the CI log
          jq -C <<< '${{ toJson(needs) }}'
          # Check if all jobs that we depend on (in the needs array) were successful.
          jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'

  # Check Code style quickly by running `rustfmt` over all code
  rustfmt:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - run: rustup update stable && rustup default stable
    - run: rustup component add rustfmt
    - run: cargo fmt --all --check

  # Ensure there are no clippy warnings
  clippy:
    strategy:
      matrix:
        include:
        - name: Linux x86_64
          os: ubuntu-latest
        - name: macOS aarch64
          os: macos-14
        - name: Windows x86_64 MSVC
          os: windows-latest
    name: Clippy ${{ matrix.name }}
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v5
      - run: rustup update stable && rustup default stable
      - run: rustup component add clippy
      - run: cargo clippy --workspace --all-targets --no-deps -- -D warnings

  stale-label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: rustup update stable && rustup default stable
      - run: cargo stale-label

  lint-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: rustup update stable && rustup default stable
      - run: cargo lint-docs --check

  # Ensure Cargo.lock is up-to-date
  lockfile:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: rustup update stable && rustup default stable
      - run: cargo update -p cargo --locked

  check-version-bump:
    runs-on: ubuntu-latest
    if: github.repository_owner == 'rust-lang'
    env:
      BASE_SHA: ${{ github.event.pull_request.base.sha }}
      HEAD_SHA: ${{ github.event.pull_request.head.sha != '' && github.event.pull_request.head.sha || github.sha  }}
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
      - run: rustup update stable && rustup default stable
      - name: Install cargo-semver-checks
        run: |
          mkdir installed-bins
          curl -Lf https://github.com/obi1kenobi/cargo-semver-checks/releases/download/v0.47.0/cargo-semver-checks-x86_64-unknown-linux-gnu.tar.gz \
            | tar -xz --directory=./installed-bins
          echo `pwd`/installed-bins >> $GITHUB_PATH
      - run: ci/validate-version-bump.sh

  test:
    runs-on: ${{ matrix.os }}
    env:
      CARGO_PROFILE_DEV_DEBUG: 1
      CARGO_PROFILE_TEST_DEBUG: 1
      CARGO_INCREMENTAL: 0
      CARGO_PUBLIC_NETWORK_TESTS: 1
      # Workaround for https://github.com/rust-lang/rustup/issues/3036
      RUSTUP_WINDOWS_PATH_ADD_BIN: 0
    strategy:
      matrix:
        include:
        - name: Linux x86_64 stable
          os: ubuntu-latest
          rust: stable
          other: i686-unknown-linux-gnu
        - name: Linux x86_64 beta
          os: ubuntu-latest
          rust: beta
          other: i686-unknown-linux-gnu
        - name: Linux x86_64 nightly
          os: ubuntu-latest
          rust: nightly
          other: i686-unknown-linux-gnu
        - name: Linux aarch64 stable
          os: ubuntu-24.04-arm
          rust: stable
          other: TODO # cross-compile tests are disabled, this shouldn't matter.
        - name: Linux aarch64 nightly
          os: ubuntu-24.04-arm
          rust: nightly
          other: TODO # cross-compile tests are disabled, this shouldn't matter.
        - name: macOS aarch64 stable
          os: macos-14
          rust: stable
          other: x86_64-apple-darwin
        - name: macOS aarch64 nightly
          os: macos-14
          rust: nightly
          other: x86_64-apple-darwin
        - name: Windows x86_64 MSVC stable
          os: windows-latest
          rust: stable-msvc
          other: i686-pc-windows-msvc
        - name: Windows x86_64 MSVC nightly
          os: windows-latest
          rust: nightly-msvc
          other: i686-pc-windows-msvc
        - name: Windows aarch64 MSVC stable
          os: windows-11-arm
          rust: stable-msvc
          other: i686-pc-windows-msvc
        - name: Windows aarch64 MSVC nightly
          os: windows-11-arm
          rust: nightly-msvc
          other: i686-pc-windows-msvc
        - name: Windows x86_64 gnu nightly # runs out of space while trying to link the test suite
          os: windows-latest
          rust: nightly-gnu
          other: i686-pc-windows-gnu
    name: Tests ${{ matrix.name }}
    steps:
    - uses: actions/checkout@v5
    - name: Dump Environment
      run: ci/dump-environment.sh
    # Some tests require stable. Make sure it is set to the most recent stable
    # so that we can predictably handle updates if necessary (and not randomly
    # when GitHub updates its image).
    - run: rustup update --no-self-update stable
    - run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
    - run: rustup target add ${{ matrix.other }}
      if: matrix.os != 'ubuntu-24.04-arm' # cross-compile tests are disabled on ARM machines
    - run: rustup target add wasm32-unknown-unknown
    - run: rustup target add aarch64-unknown-none # need this for build-std mock tests
      if: startsWith(matrix.rust, 'nightly')
    - run: rustup component add rustc-dev llvm-tools-preview rust-docs
      if: startsWith(matrix.rust, 'nightly')
    - run: sudo apt update -y && sudo apt install lldb gcc-multilib libsecret-1-0 libsecret-1-dev -y
      if: matrix.os == 'ubuntu-latest'
    - run: rustup component add rustfmt || echo "rustfmt not available"
    - name: Add Windows debuggers bin to PATH
      shell: pwsh
      run: Add-Content $env:GITHUB_PATH "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64"
      if: matrix.os == 'windows-latest'
    - name: Add Windows debuggers bin to PATH
      shell: pwsh
      run: Add-Content $env:GITHUB_PATH "C:\Program Files (x86)\Windows Kits\10\Debuggers\arm64"
      if: matrix.os == 'windows-11-arm'
    - name: Configure extra test environment
      run: echo CARGO_CONTAINER_TESTS=1 >> $GITHUB_ENV
      if: matrix.os == 'ubuntu-latest'
    - run: cargo test -p cargo
    - name: Clear intermediate test output
      run: ci/clean-test-output.sh

    - name: gitoxide tests (all git-related tests)
      run: cargo test -p cargo git
      env:
        __CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2: 1
      # The testsuite generates a huge amount of data, and fetch-smoke-test was
    # running out of disk space.
    - name: Clear test output
      run: ci/clean-test-output.sh

    # This only tests `cargo fix` because fix-proxy-mode is one of the most
    # complicated subprocess management in Cargo.
    - name: Check operability of rustc invocation with argfile
      run: 'cargo test -p cargo --test testsuite -- fix::'
      env:
        __CARGO_TEST_FORCE_ARGFILE: 1
    - run: cargo test --workspace --exclude cargo --exclude benchsuite --exclude resolver-tests
    - name: Check benchmarks
      run: |
        # This only tests one benchmark since it can take over 10 minutes to
        # download all workspaces.
        cargo test -p benchsuite --all-targets -- cargo
        cargo check -p capture
    # The testsuite generates a huge amount of data, and fetch-smoke-test was
    # running out of disk space.
    - name: Clear benchmark output
      run: ci/clean-test-output.sh

    - name: Fetch smoke test
      run: ci/fetch-smoke-test.sh

  schema:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - run: rustup update stable && rustup default stable
    - run: cargo test -p cargo-util-schemas -F unstable-schema

  resolver:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - run: rustup update stable && rustup default stable
    - run: cargo test -p resolver-tests

  test_gitoxide:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: rustup update --no-self-update stable && rustup default stable
      - run: rustup target add i686-unknown-linux-gnu
      - run: rustup target add wasm32-unknown-unknown
      - run: sudo apt update -y && sudo apt install gcc-multilib libsecret-1-0 libsecret-1-dev -y
      - run: rustup component add rustfmt || echo "rustfmt not available"
      - run: cargo test -p cargo
        env:
          __CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2: 1

  build_std:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - run: rustup update nightly && rustup default nightly
    - run: rustup component add rust-src
    - run: cargo build
    - run: cargo test -p cargo --test build-std
      env:
        CARGO_RUN_BUILD_STD_TESTS: 1
  docs:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - run: rustup update nightly && rustup default nightly
    - run: rustup update stable
    - run: rustup component add rust-docs
    - run: rustup component add rustfmt --toolchain stable
    - run: ci/validate-man.sh
    # This requires rustfmt, use stable.
    - name: Run semver-check
      run: cargo +stable run -p semver-check
    - name: Ensure intradoc links are valid
      run: cargo doc --workspace --document-private-items --no-deps
      env:
        RUSTDOCFLAGS: -D warnings
    - name: Install mdbook
      run: |
        mkdir mdbook
        curl -Lf https://github.com/rust-lang/mdBook/releases/download/v${MDBOOK_VERSION}/mdbook-v${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
        echo `pwd`/mdbook >> $GITHUB_PATH
    - run: cd src/doc && mdbook build --dest-dir ../../target/doc
    - name: Run linkchecker.sh
      run: |
        cd target
        curl -sSLO https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh
        sh linkcheck.sh --all --path ../src/doc cargo

  msrv:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - uses: taiki-e/install-action@cargo-hack
    - run: cargo hack check --all-targets --rust-version --workspace --ignore-private --locked

  spellcheck:
    name: Spell Check with Typos
    runs-on: ubuntu-latest
    steps:
    - name: Checkout Actions Repository
      uses: actions/checkout@v5
    - name: Spell Check Repo
      uses: crate-ci/typos@v1.44.0

  report-timings:
    name: Timing HTML report
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - run: rustup update nightly && rustup default nightly
    - run: cargo build
    - name: Generate timing report for rustfix
      run: |
        cargo run -- build -p rustfix -Zbuild-analysis -Zsection-timings --config build.analysis.enabled=true --config 'build.build-dir="tmp"'
        cargo run -- report timings -Zbuild-analysis
    - uses: actions/upload-artifact@v6
      with:
        name: timing-report
        path: target/cargo-timings/*.html
        if-no-files-found: error
