name: CI

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: true

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  PYTHON_VERSION: "3.12"

jobs:
  cargo-fmt:
    name: "cargo fmt"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: "Install Rust toolchain"
        run: rustup component add rustfmt
      - name: "rustfmt"
        run: cargo fmt --all --check

  cargo-clippy:
    name: "cargo clippy"
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
      fail-fast: false
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - name: "Install Rust toolchain"
        run: rustup component add clippy
      - uses: Swatinem/rust-cache@v2
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Clippy"
        run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings

  cargo-test:
    strategy:
      matrix:
        include:
          # We use the large GitHub actions runners
          # For Ubuntu and Windows, this requires Organization-level configuration
          # See: https://docs.github.com/en/actions/using-github-hosted-runners/about-larger-runners/about-larger-runners#about-ubuntu-and-windows-larger-runners
          - { os: "ubuntu", runner: "ubuntu-latest-large" }
          - { os: "windows", runner: "windows-latest-large" }
          - { os: "macos", runner: "macos-14" }
      fail-fast: false
    runs-on:
      labels: ${{ matrix.runner }}
    name: "cargo test | ${{ matrix.os }}"
    steps:
      - uses: actions/checkout@v4

      - if: ${{ matrix.os == 'macos' }}
        name: "Install bootstrap dependencies"
        run: brew install coreutils

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: "Install pipx"
        run: pip install pipx

      - name: "Install required Python versions"
        run: pipx run scripts/bootstrap/install.py

      - name: "Install Rust toolchain"
        run: rustup show

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

      - if: ${{ matrix.os != 'windows' }}
        uses: rui314/setup-mold@v1

      - uses: Swatinem/rust-cache@v2

      - name: "Cargo test"
        run: |
          cargo nextest run --workspace --status-level skip --failure-output immediate-final --no-fail-fast -j 12 --final-status-level slow

      - name: "Smoke test (unix)"
        if: ${{ matrix.os != 'windows' }}
        run: |
          uv="./target/debug/uv"
          $uv venv
          $uv pip install ruff

      - name: "Smoke test (windows)"
        if: ${{ matrix.os == 'windows' }}
        run: |
          Set-Alias -Name uv -Value ./target/debug/uv
          uv venv
          uv pip install ruff

  # Separate job for the nightly crate
  windows-trampoline:
    runs-on: windows-latest
    name: "check windows trampoline"
    steps:
      - uses: actions/checkout@v4
      - name: "Install Rust toolchain"
        working-directory: crates/uv-trampoline
        run: |
          rustup target add x86_64-pc-windows-msvc
          rustup component add clippy rust-src --toolchain nightly-2024-01-23-x86_64-pc-windows-msvc
      - uses: rui314/setup-mold@v1
      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: "crates/uv-trampoline"
      - name: "Clippy"
        working-directory: crates/uv-trampoline
        run: cargo clippy --all-features --locked --target x86_64-pc-windows-msvc -- -D warnings
      - name: "Build"
        working-directory: crates/uv-trampoline
        run: cargo build --release --target x86_64-pc-windows-msvc

  build-binary-linux:
    runs-on:
      labels: ubuntu-latest-large
    name: "build binary | linux"
    steps:
      - uses: actions/checkout@v4

      - uses: rui314/setup-mold@v1

      - name: "Setup musl"
        run: |
          sudo apt-get install musl-tools
          rustup target add x86_64-unknown-linux-musl

      - uses: Swatinem/rust-cache@v2
      - name: "Build"
        run: cargo build --target x86_64-unknown-linux-musl

      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}
          path: ./target/x86_64-unknown-linux-musl/debug/uv
          retention-days: 1

  build-binary-macos-aarch64:
    runs-on:
      labels: macos-14
    name: "build binary | macos aarch64"
    steps:
      - uses: actions/checkout@v4

      - uses: rui314/setup-mold@v1

      - uses: Swatinem/rust-cache@v2
      - name: "Build"
        run: cargo build

      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: uv-macos-aarch64-${{ github.sha }}
          path: ./target/debug/uv
          retention-days: 1

  build-binary-macos-x86_64:
    runs-on:
      labels: macos-latest-large
    name: "build binary | macos x86_64"
    steps:
      - uses: actions/checkout@v4

      - uses: rui314/setup-mold@v1

      - uses: Swatinem/rust-cache@v2
      - name: "Build"
        run: cargo build

      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: uv-macos-x86_64-${{ github.sha }}
          path: ./target/debug/uv
          retention-days: 1

  build-binary-windows:
    runs-on:
      labels: windows-latest-large
    name: "build binary | windows"
    steps:
      - uses: actions/checkout@v4

      - uses: rui314/setup-mold@v1

      - uses: Swatinem/rust-cache@v2
      - name: "Build"
        run: cargo build

      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: uv-windows-${{ github.sha }}
          path: ./target/debug/uv.exe
          retention-days: 1

  system-test-debian:
    needs: build-binary-linux
    name: "check system | python on debian"
    runs-on: ubuntu-latest
    container: debian:bullseye
    steps:
      - uses: actions/checkout@v4

      - name: "Install Python"
        run: apt-get update && apt-get install -y python3.9 python3-pip python3.9-venv

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3.9)

      - name: "Validate global Python install"
        run: python3.9 scripts/check_system_python.py --uv ./uv

  system-test-ubuntu:
    needs: build-binary-linux
    name: "check system | python on ubuntu"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: python scripts/check_system_python.py --uv ./uv

  system-test-centos:
    needs: build-binary-linux
    name: "check system | python on centos"
    runs-on: ubuntu-latest
    container: centos:8
    steps:
      - uses: actions/checkout@v4

      - name: "Install Python"
        run: |
          # Add CentOS stream repositories, you cannot install from the EOL CentOS 8 mirrors
          dnf -y --disablerepo '*' --enablerepo extras swap centos-linux-repos centos-stream-repos
          # `python` gives you `python3.6` which we do not support
          dnf install python39 python39-pip which -y

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  system-test-pypy:
    needs: build-binary-linux
    name: "check system | pypy on ubuntu"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "pypy3.9"

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which pypy)

      - name: "Validate global Python install"
        run: pypy scripts/check_system_python.py --uv ./uv

  system-test-pyston:
    needs: build-binary-linux
    name: "check system | pyston"
    runs-on: ubuntu-latest
    container: pyston/pyston:2.3.5
    steps:
      - uses: actions/checkout@v4

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which pyston)

      - name: "Validate global Python install"
        run: pyston scripts/check_system_python.py --uv ./uv

  system-test-alpine:
    needs: build-binary-linux
    name: "check system | alpine"
    runs-on: ubuntu-latest
    container: alpine:latest
    steps:
      - uses: actions/checkout@v4

      - name: "Install Python"
        run: apk add --update --no-cache python3 py3-pip

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv --externally-managed

  system-test-macos-aarch64:
    needs: build-binary-macos-aarch64
    name: "check system | python on macos aarch64"
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - name: "Install Python"
        run: brew install python@3.8

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-macos-aarch64-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  system-test-macos-x86_64:
    needs: build-binary-macos-x86_64
    name: "check system | python on macos x86_64"
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      # We test with GitHub's Python as a regression test for
      # https://github.com/astral-sh/uv/issues/2450
      - uses: actions/setup-python@v5

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-macos-x86_64-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv

  system-test-windows-python-310:
    needs: build-binary-windows
    name: "check system | python3.10 on windows"
    runs-on: windows-latest
    env:
      # Avoid debug build stack overflows.
      UV_STACK_SIZE: 2000000 # 2 megabyte, double the default on windows
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-windows-${{ github.sha }}

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: py -3.10 ./scripts/check_system_python.py --uv ./uv.exe

  system-test-windows-python-313:
    needs: build-binary-windows
    name: "check system | python3.13 on windows"
    runs-on: windows-latest
    env:
      # Avoid debug build stack overflows.
      UV_STACK_SIZE: 2000000 # 2 megabyte, double the default on windows
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
          allow-prereleases: true
          cache: pip

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-windows-${{ github.sha }}

      - name: "Print Python path"
        run: echo $(which python)

      - name: "Validate global Python install"
        run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe

  system-test-choco:
    needs: build-binary-windows
    name: "check system | python3.12 via chocolatey"
    runs-on: windows-latest
    env:
      # Avoid debug build stack overflows.
      UV_STACK_SIZE: 2000000 # 2 megabyte, double the default on windows
    steps:
      - uses: actions/checkout@v4

      - name: "Install Python"
        run: choco install python3 --verbose --version=3.9.13

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-windows-${{ github.sha }}

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: py -3.9 ./scripts/check_system_python.py --uv ./uv.exe

  system-test-pyenv:
    needs: build-binary-linux
    name: "check system | python via pyenv"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: "Install pyenv"
        uses: "gabrielfalcao/pyenv-action@v18"
        with:
          default: 3.9.7

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3.9)

      - name: "Validate global Python install"
        run: python3.9 scripts/check_system_python.py --uv ./uv

  system-test-conda:
    needs:
      [build-binary-windows, build-binary-macos-aarch64, build-binary-linux]
    name: check system | conda${{ matrix.python-version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        os: ["linux", "windows", "macos"]
        python-version: ["3.8", "3.11"]
        include:
          - { os: "linux", target: "linux", runner: "ubuntu-latest" }
          - { os: "windows", target: "windows", runner: "windows-latest" }
          - { os: "macos", target: "macos-aarch64", runner: "macos-14" }
    steps:
      - uses: actions/checkout@v4

      - uses: conda-incubator/setup-miniconda@v3
        with:
          miniconda-version: "latest"
          activate-environment: uv
          python-version: ${{ matrix.python-version }}

      - name: Conda info
        shell: bash -el {0}
        run: conda info

      - name: Conda list
        shell: pwsh
        run: conda list

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-${{ matrix.target }}-${{ github.sha }}

      - name: "Prepare binary"
        if: ${{ matrix.os != 'windows' }}
        run: chmod +x ./uv

      - name: "Print Python path"
        shell: bash -el {0}
        run: echo $(which python)

      - name: "Validate global Python install"
        shell: bash -el {0}
        run: python ./scripts/check_system_python.py --uv ./uv

  system-test-amazonlinux:
    needs: build-binary-linux
    name: "check system | amazonlinux"
    runs-on: ubuntu-latest
    container: amazonlinux:2023
    steps:
      - name: "Install base requirements"
        run: |
          # Needed for `actions/checkout`
          yum install tar gzip which -y
      - uses: actions/checkout@v4
      - name: "Install Python"
        run: yum install python3 python3-pip -y

      - name: "Download binary"
        uses: actions/download-artifact@v4
        with:
          name: uv-linux-${{ github.sha }}

      - name: "Prepare binary"
        run: chmod +x ./uv

      - name: "Print Python path"
        run: echo $(which python3)

      - name: "Validate global Python install"
        run: python3 scripts/check_system_python.py --uv ./uv
