on:
  workflow_call:
    inputs:
      sha:
        description: "The commit SHA to use for artifact names"
        required: true
        type: string

permissions: {}

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

jobs:
  integration-test-nushell:
    name: "nushell"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Install nushell
        env:
          # This token only needs read access to the GitHub repository nushell/nushell.
          # This token is used (via gh-cli) to avoid hitting GitHub REST API rate limits.
          GITHUB_TOKEN: ${{ github.token }}
        run: |-
          # get latest nushell tag name
          nu_latest=$(gh release list --repo nushell/nushell --limit 1 --exclude-pre-releases --exclude-drafts --json "tagName" --jq '.[0].tagName')
          # trim any trailing whitespace from output
          nu_tag=${nu_latest%%[[:space:]]*}

          # download binary for x86_64-unknown-linux-gnu target
          gh release download ${nu_tag} --repo nushell/nushell --pattern "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz"

          # extract nu binary from tar.gz
          tar -xf "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz"
          # make the binary executable
          chmod +x "./nu-${nu_tag}-x86_64-unknown-linux-gnu/nu"
          # add it to PATH
          echo "${{ github.workspace }}/nu-${nu_tag}-x86_64-unknown-linux-gnu" >> "${GITHUB_PATH}"

      - name: Download binary
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: Create venv
        # The python version is arbitrary for this test.
        # We only want to ensure the activation script behaves properly
        run: ./uv venv

      - name: Activate venv
        shell: nu {0}
        run: overlay use ${{ github.workspace }}/.venv/bin/activate.nu

  integration-test-conda:
    name: "conda on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      # TODO(zanieb): Find an alternative for this action; it uses EOL Node 20
      - uses: conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167 # v3.3.0
        with:
          miniconda-version: latest
          activate-environment: uv
          python-version: "3.12"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

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

      - name: "Install a package"
        shell: bash -el {0}
        run: |
          echo "$CONDA_PREFIX"
          ./uv pip install anyio

  integration-test-conda-macos-x86_64:
    name: "conda on macos x86-64"
    timeout-minutes: 10
    runs-on: macos-15-intel # github-macos-15-x86_64-4
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      # TODO(zanieb): Find an alternative for this action; it uses EOL Node 20
      - uses: conda-incubator/setup-miniconda@fc2d68f6413eb2d87b895e92f8584b5b94a10167 # v3.3.0
        with:
          miniconda-version: latest
          activate-environment: uv
          python-version: "3.12"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-macos-x86_64-${{ inputs.sha }}

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

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

      - name: "Install a package with macOS macOS 11.0 wheels"
        shell: bash -el {0}
        run: |
          # greenlet has `macosx_11_0_universal2` wheels. Previously, conda interpreters on Intel
          # macOS reported the incorrect platform tag `macosx_10_16` causing uv to consider the
          # wheel incompatible. See https://github.com/astral-sh/uv/issues/14267
          #
          # Using `--only-binary` ensures this fails if the platform tags are wrong instead of
          # falling back to a source build.
          ./uv pip install --no-build 'greenlet==3.3.2'

  integration-test-deadsnakes-39-linux:
    name: "deadsnakes python3.9 on ubuntu"
    timeout-minutes: 15
    runs-on: ubuntu-latest
    steps:
      - name: "Install python3.9"
        run: |
          for i in {1..5}; do
            sudo add-apt-repository ppa:deadsnakes && break || { echo "Attempt $i failed, retrying in 10 seconds..."; sleep 10; }
            if [ $i -eq 5 ]; then
              echo "Failed to add repository after 5 attempts"
              exit 1
            fi
          done
          sudo apt-get update
          sudo apt-get install python3.9

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Check missing distutils"
        run: |
          ./uv venv -p 3.9 --python-preference only-system -v 2>&1 | tee log.txt || true
          # We should report that distutils is missing
          grep 'Python installation is missing `distutils`' log.txt

      - name: "Install distutils"
        run: |
          sudo apt-get install python3.9-distutils

      - name: "Create a virtualenv"
        run: |
          ./uv venv -p 3.9 --python-preference only-system -v

      - name: "Check version"
        run: |
          .venv/bin/python --version

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

  integration-test-linux-armv7-on-aarch64:
    name: "armv7 on aarch64 linux"
    timeout-minutes: 20
    runs-on: github-ubuntu-24.04-aarch64-4
    env:
      UV_PYTHON_INSTALL_DIR: ${{ github.workspace }}/.python

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-armv7-gnueabihf-${{ inputs.sha }}

      - name: "Install armhf runtime"
        run: |
          sudo dpkg --add-architecture armhf
          echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries > /dev/null
          sudo apt install -y --update libc6:armhf libgcc-s1:armhf

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

      - name: "Install Python via uv"
        run: |
          ./uv python install -v 3.13.12

      - name: "Verify hard-float variant selected"
        run: |
          python_path=$(./uv python find 3.13)
          expected="$GITHUB_WORKSPACE/.python/cpython-3.13-linux-armv7-gnueabihf/bin/python3.13"
          if [[ "$python_path" != "$expected" ]]; then
            echo "Expected: $expected"
            echo "Found:    $python_path"
            exit 1
          fi

      - name: "Create a virtual environment"
        run: |
          ./uv venv -c -p 3.13.12 --managed-python --no-python-downloads

  integration-test-free-threaded-windows-x86_64:
    name: "free-threaded on windows"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install free-threaded Python via uv"
        run: |
          ./uv python install -v 3.13t

      - name: "Create a virtual environment (stdlib)"
        run: |
          & (./uv python find 3.13t) -m venv .venv

      - name: "Check version (stdlib)"
        run: |
          .venv/Scripts/python --version

      - name: "Create a virtual environment (uv)"
        run: |
          ./uv venv -c -p 3.13t --managed-python

      - name: "Check version (uv)"
        run: |
          .venv/Scripts/python --version

      - name: "Check is free-threaded"
        run: |
          .venv/Scripts/python -c "import sys; exit(1) if sys._is_gil_enabled() else exit(0)"

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

      - name: "Check uv run"
        run: |
          ./uv run python -c ""
          ./uv run -p 3.13t python -c ""

  integration-test-windows-aarch64-implicit:
    name: "aarch64 windows implicit"
    timeout-minutes: 10
    runs-on: windows-11-arm

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-aarch64-${{ inputs.sha }}

      - name: "Install Python via uv (implicitly select x64)"
        run: |
          ./uv python install -v 3.13

      - name: "Create a virtual environment (stdlib)"
        run: |
          & (./uv python find 3.13) -m venv .venv

      - name: "Check version (stdlib)"
        run: |
          .venv/Scripts/python --version

      - name: "Create a virtual environment (uv)"
        run: |
          ./uv venv -c -p 3.13 --managed-python

      - name: "Check version (uv)"
        run: |
          .venv/Scripts/python --version

      - name: "Check is x64"
        run: |
          .venv/Scripts/python -c "import sys; exit(1) if 'AMD64' not in sys.version else exit(0)"

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

      - name: "Check uv run"
        run: |
          ./uv run python -c ""
          ./uv run -p 3.13 python -c ""

  integration-test-windows-aarch64-explicit:
    name: "aarch64 windows explicit"
    timeout-minutes: 10
    runs-on: windows-11-arm

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-aarch64-${{ inputs.sha }}

      - name: "Install Python via uv (explicitly select aarch64)"
        run: |
          ./uv python install -v cpython-3.13-windows-aarch64-none

      - name: "Create a virtual environment (stdlib)"
        run: |
          & (./uv python find 3.13) -m venv .venv

      - name: "Check version (stdlib)"
        run: |
          .venv/Scripts/python --version

      - name: "Create a virtual environment (uv)"
        run: |
          ./uv venv -c -p 3.13 --managed-python

      - name: "Check version (uv)"
        run: |
          .venv/Scripts/python --version

      - name: "Check is NOT x64"
        run: |
          .venv/Scripts/python -c "import sys; exit(1) if 'AMD64' in sys.version else exit(0)"

      - name: "Check install"
        run: |
          ./uv pip install -v anyio

      - name: "Check uv run"
        run: |
          ./uv run python -c ""
          ./uv run -p 3.13 python -c ""

  integration-test-windows-python-install-manager:
    name: "windows python install manager"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install Python via Python Install manager"
        run: |
          # https://www.python.org/downloads/release/pymanager-250/
          winget install --accept-package-agreements --accept-source-agreements 9NQ7512CXL7T
          # Call Python Install Manager's py.exe by full path to avoid legacy py.exe
          & "$env:LOCALAPPDATA\Microsoft\WindowsApps\py.exe" install 3.14

      # https://github.com/astral-sh/uv/issues/16204
      - name: "Check temporary environment creation"
        run: |
          ./uv run -p $env:LOCALAPPDATA\Python\pythoncore-3.14-64\python.exe --with numpy python -c "import sys; print(sys.executable)"

  # Test PEP 514 integration that installs Python into the Windows registry.
  integration-test-windows-registry:
    name: "windows registry"
    timeout-minutes: 10
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Test PEP 514 registration"
        run: python ./scripts/check_registry.py --uv ./uv.exe

  integration-test-pypy-linux:
    name: "pypy on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Install PyPy"
        run: ./uv python install -v pypy3.9

      - name: "Create a virtual environment"
        run: |
          ./uv venv -p pypy3.9 --managed-python

      - name: "Check for executables"
        run: |
          check_in_bin() {
              local executable_name=$1
              local bin_path=".venv/bin"

              if [[ -x "$bin_path/$executable_name" ]]; then
                  return 0
              else
                  echo "Executable '$executable_name' not found in folder '$bin_path'."
                  return 1
              fi
          }

          executables=("pypy" "pypy3" "python")

          all_found=true
          for executable_name in "${executables[@]}"; do
              check_in_bin "$executable_name" "$folder_path"
              result=$?

              if [[ $result -ne 0 ]]; then
                  all_found=false
              fi
          done

          if ! $all_found; then
            echo "One or more expected executables were not found."
            exit 1
          fi

      - name: "Check version"
        run: |
          .venv/bin/pypy --version
          .venv/bin/pypy3 --version
          .venv/bin/python --version

      - name: "Check install"
        run: |
          ./uv pip install anyio

  integration-test-python-install-wine:
    name: "python install under wine"
    timeout-minutes: 15
    runs-on: ubuntu-latest
    env:
      WINEARCH: win64
      WINEDEBUG: -all
      WINEPREFIX: /home/runner/uv-wine-prefix

    steps:
      - name: "Install Wine"
        run: |
          echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries > /dev/null
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends wine wine64

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Initialize Wine"
        run: |
          wine --version
          wineboot --init

      - name: "Install Python"
        # Regression test for https://github.com/astral-sh/uv/issues/19187.
        run: |
          mkdir -p "$RUNNER_TEMP/uv-cache" "$RUNNER_TEMP/uv-python"
          export UV_CACHE_DIR="$(winepath -w "$RUNNER_TEMP/uv-cache")"
          export UV_PYTHON_INSTALL_DIR="$(winepath -w "$RUNNER_TEMP/uv-python")"
          wine ./uv.exe python install -v --no-registry --no-bin 3.12.13

  integration-test-pypy-windows-x86_64:
    name: "pypy on windows"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install PyPy"
        run: .\uv.exe python install pypy3.9

      - name: "Create a virtual environment"
        run: |
          .\uv.exe venv -p pypy3.9 --managed-python

      - name: "Check for executables"
        shell: python
        run: |
          import sys
          from pathlib import Path

          def binary_exist(binary):
            binaries_path = Path(".venv\\Scripts")
            if (binaries_path / binary).exists():
              return True
            print(f"Executable '{binary}' not found in folder '{binaries_path}'.")

          all_found = True
          expected_binaries = [
              "pypy3.9.exe",
              "pypy3.9w.exe",
              "pypy3.exe",
              "pypyw.exe",
              "python.exe",
              "python3.9.exe",
              "python3.exe",
              "pythonw.exe",
          ]
          for binary in expected_binaries:
            if not binary_exist(binary):
              all_found = False

          if not all_found:
            print("One or more expected executables were not found.")
            sys.exit(1)

      - name: "Check version"
        run: |
          & .venv\Scripts\pypy3.9.exe --version
          & .venv\Scripts\pypy3.exe --version
          & .venv\Scripts\python.exe --version

      - name: "Check install"
        run: |
          .\uv.exe pip install anyio

  integration-test-graalpy-linux:
    name: "graalpy on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Install GraalPy"
        run: ./uv python install -v graalpy

      - name: "Create a virtual environment"
        run: |
          ./uv venv -p graalpy --managed-python

      - name: "Check for executables"
        run: |
          check_in_bin() {
              local executable_name=$1
              local bin_path=".venv/bin"

              if [[ -x "$bin_path/$executable_name" ]]; then
                  return 0
              else
                  echo "Executable '$executable_name' not found in folder '$bin_path'."
                  return 1
              fi
          }

          executables=("graalpy" "python3" "python")

          all_found=true
          for executable_name in "${executables[@]}"; do
              check_in_bin "$executable_name" "$folder_path"
              result=$?

              if [[ $result -ne 0 ]]; then
                  all_found=false
              fi
          done

          if ! $all_found; then
            echo "One or more expected executables were not found."
            exit 1
          fi

      - name: "Check version"
        run: |
          .venv/bin/graalpy --version
          .venv/bin/python3 --version
          .venv/bin/python --version

      - name: "Check install"
        run: |
          ./uv pip install anyio

      - name: "Check a GraalPy dev version (different version parsing)"
        run: |
          curl -sLf https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-25.0.0-ea.31/graalpy-25.0.0-ea.31-linux-amd64.tar.gz | tar xz
          ./uv run --no-project -p ./graalpy-25.0.0-dev-linux-amd64/bin/python python --version

  integration-test-graalpy-windows-x86_64:
    name: "graalpy on windows"
    timeout-minutes: 10
    runs-on: windows-latest

    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Install GraalPy"
        run: .\uv.exe python install graalpy

      - name: "Create a virtual environment"
        run: |
          .\uv.exe venv -p graalpy --managed-python

      - name: "Check for executables"
        shell: python
        run: |
          import sys
          from pathlib import Path

          def binary_exist(binary):
            binaries_path = Path(".venv\\Scripts")
            if (binaries_path / binary).exists():
              return True
            print(f"Executable '{binary}' not found in folder '{binaries_path}'.")

          all_found = True
          expected_binaries = [
              "graalpy.exe",
              "python.exe",
              "python3.exe",
          ]
          for binary in expected_binaries:
            if not binary_exist(binary):
              all_found = False

          if not all_found:
            print("One or more expected executables were not found.")
            sys.exit(1)

      - name: "Check version"
        run: |
          & .venv\Scripts\graalpy.exe --version
          & .venv\Scripts\python3.exe --version
          & .venv\Scripts\python.exe --version

      - name: "Check install"
        run: |
          .\uv.exe pip install anyio

  integration-test-pyodide-linux:
    name: "pyodide on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Create a native virtual environment"
        run: |
          ./uv venv venv-native -p 3.12
          # We use features added in 0.30.3 but there is no known breakage in
          # newer versions.
          ./uv pip install -p venv-native/bin/python pyodide-build==0.30.7 pip

      - name: "Install Pyodide interpreter"
        run: |
          source ./venv-native/bin/activate
          pyodide xbuildenv install 0.27.5
          PYODIDE_PYTHON=$(pyodide config get interpreter)
          PYODIDE_INDEX=$(pyodide config get package_index)
          echo "PYODIDE_PYTHON=$PYODIDE_PYTHON" >> $GITHUB_ENV
          echo "PYODIDE_INDEX=$PYODIDE_INDEX" >> $GITHUB_ENV

      - name: "Create Pyodide virtual environment"
        run: |
          ./uv venv -p $PYODIDE_PYTHON venv-pyodide
          source ./venv-pyodide/bin/activate
          ./uv pip install --extra-index-url=$PYODIDE_INDEX --no-build numpy
          python -c 'import numpy'

      - name: "Install Pyodide with uv python"
        run: |
          ./uv python install cpython-3.13.2-emscripten-wasm32-musl

      - name: "Create a Pyodide virtual environment using uv installed Python"
        run: |
          ./uv venv -p cpython-3.13.2-emscripten-wasm32-musl venv-pyodide2
          # TODO: be able to install Emscripten wheels here...
          source ./venv-pyodide2/bin/activate
          ./uv pip install packaging
          python -c 'import packaging'

  integration-test-pyodide-windows:
    name: "pyodide on windows"
    timeout-minutes: 10
    runs-on: windows-latest
    # Github Actions Windows runner uses D drive as pwd.
    # Pyodide CLI has an issue running cross-drive,
    # So ensure that cache and Pyodide paths are on D drive as well.
    env:
      UV_CACHE_DIR: D:\uv-cache
      PYODIDE_XBUILDENV_PATH: D:\pyodide-xbuildenv
    steps:
      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-windows-x86_64-${{ inputs.sha }}

      - name: "Create a native virtual environment"
        run: |
          .\uv.exe venv venv-native -p 3.13
          .\uv.exe pip install -p venv-native\Scripts\python.exe pyodide-build==0.31.1 pip

      - name: "Install Pyodide interpreter"
        run: |
          & .\venv-native\Scripts\Activate.ps1
          pyodide xbuildenv install 0.29.2
          $env:PYODIDE_PYTHON = pyodide config get interpreter
          $env:PYODIDE_INDEX = pyodide config get package_index
          echo "PYODIDE_PYTHON=$env:PYODIDE_PYTHON" >> $env:GITHUB_ENV
          echo "PYODIDE_INDEX=$env:PYODIDE_INDEX" >> $env:GITHUB_ENV

      - name: "Create Pyodide virtual environment"
        run: |
          .\uv.exe venv -p $env:PYODIDE_PYTHON venv-pyodide
          & .\venv-pyodide\Scripts\Activate.ps1
          .\uv.exe pip install --extra-index-url=$env:PYODIDE_INDEX --no-build numpy
          python -c 'import numpy'

  integration-test-termux:
    name: "termux on android"
    timeout-minutes: 15
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-musl-${{ inputs.sha }}

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

      - name: "Run uv on Termux"
        run: |
          docker run --rm \
            -v "$PWD/uv:/uv" \
            -v "$PWD/test/integration/termux.sh:/test-termux.sh" \
            ghcr.io/astral-sh/termux-python:latest \
            /entrypoint.sh bash /test-termux.sh

  integration-test-github-actions:
    name: "github actions"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.12.7"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Install a package without system opt-in"
        run: |
          ./uv pip install anyio && exit 1 || echo "Failed as expected"

      - name: "Install a package with system opt-in"
        run: |
          ./uv pip install anyio --system

      - name: Configure uv to use the system Python by default
        run: echo "UV_SYSTEM_PYTHON=1" >> $GITHUB_ENV

      - name: "Install a package with system opt-in via the environment"
        run: |
          ./uv pip install anyio --reinstall

      - name: "Create a project"
        run: |
          # Use Python 3.11 as the minimum required version
          ./uv init --python 3.11
          ./uv add anyio

      - name: "Sync to the system Python"
        run: ./uv sync -v --python 3.12
        env:
          UV_PROJECT_ENVIRONMENT: "/opt/hostedtoolcache/Python/3.12.7/x64"

      - name: "Attempt to sync to the system Python with an incompatible version"
        run: |
          ./uv sync -v --python 3.11 && { echo "ci: Error; should not succeed"; exit 1; } || { echo "ci: Ok; expected failure"; exit 0; }
        env:
          UV_PROJECT_ENVIRONMENT: "/opt/hostedtoolcache/Python/3.12.7/x64"

      - name: "Attempt to sync to a non-Python environment directory"
        run: |
          mkdir -p /home/runner/example
          touch /home/runner/example/some-file
          ./uv sync -v && { echo "ci: Error; should not succeed"; exit 1; } || { echo "ci: Ok; expected failure"; exit 0; }
        env:
          UV_PROJECT_ENVIRONMENT: "/home/runner/example"

  integration-test-github-actions-freethreaded:
    name: "free-threaded on github actions"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.13t"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Install a package without system opt-in"
        run: |
          ./uv pip install anyio && exit 1 || echo "Failed as expected"

      - name: "Install a package with system opt-in but without free-threaded opt-in"
        run: |
          ./uv pip install anyio --system --python 3.13 || echo "Failed as expected"
          # (we need to request 3.13 or we'll discover 3.12 on the system)

      - name: "Install a package with system and free-threaded opt-in"
        run: |
          ./uv pip install anyio --system --python 3.13t

      - name: "Create a virtual environment"
        run: |
          ./uv venv -p 3.13t --python-preference only-system

      - name: "Check is free-threaded"
        run: |
          .venv/bin/python -c "import sys; exit(1) if sys._is_gil_enabled() else exit(0)"

  integration-test-wsl:
    name: "pyenv on wsl"
    timeout-minutes: 15
    runs-on: windows-latest
    if: ${{ github.event.pull_request.head.repo.fork != true }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-musl-${{ inputs.sha }}

      # TODO(zanieb): Find an alternative for this action; it uses EOL Node 20
      - name: "Setup WSL"
        uses: Vampire/setup-wsl@d1da7f2c0322a5ee4f24975344f67fc0f5baf364 # v7.0.0
        with:
          distribution: Ubuntu-22.04

      - name: "Install pyenv-win"
        shell: pwsh
        run: |
          Write-Host "Installing pyenv-win..."
          Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"
          .\install-pyenv-win.ps1

          # Add pyenv-win to PATH for this session
          $env:PYENV = "$env:USERPROFILE\.pyenv\pyenv-win"
          $env:PATH = "$env:PYENV\bin;$env:PYENV\shims;$env:PATH"

          # Add to GITHUB_PATH so WSL can find the shims
          echo "$env:PYENV\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
          echo "$env:PYENV\shims" | Out-File -FilePath $env:GITHUB_PATH -Append

          Write-Host "Installing Python 3.11.9 via pyenv-win..."
          & "$env:PYENV\bin\pyenv.bat" install 3.11.9
          & "$env:PYENV\bin\pyenv.bat" global 3.11.9

          Write-Host "Verifying pyenv-win installation..."
          & "$env:PYENV\bin\pyenv.bat" versions

      - name: "Test uv"
        shell: wsl-bash {0}
        run: |
          set -x
          chmod +x ./uv

          # Check that we don't fail on `pyenv-win` shims
          ./uv python list -v

  integration-test-registries:
    name: "registries"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    if: ${{ github.event.pull_request.head.repo.fork != true }}
    environment:
      name: uv-test-registries
      deployment: false
    env:
      PYTHON_VERSION: 3.12
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "${{ env.PYTHON_VERSION }}"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Configure AWS credentials"
        uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: "Get AWS CodeArtifact token"
        run: |
          UV_TEST_AWS_TOKEN=$(aws codeartifact get-authorization-token \
            --domain tests \
            --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} \
            --region us-east-1 \
            --query authorizationToken \
            --output text)
          echo "::add-mask::$UV_TEST_AWS_TOKEN"
          echo "UV_TEST_AWS_TOKEN=$UV_TEST_AWS_TOKEN" >> $GITHUB_ENV

      - name: "Authenticate with GCP"
        id: "auth"
        uses: "google-github-actions/auth@fc2174804b84f912b1f6d334e9463f484f1c552d"
        with:
          credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}"

      - name: "Set up GCP SDK"
        uses: "google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db"

      - name: "Get GCP Artifact Registry token"
        id: get_token
        run: |
          UV_TEST_GCP_TOKEN=$(gcloud auth print-access-token)
          echo "::add-mask::$UV_TEST_GCP_TOKEN"
          echo "UV_TEST_GCP_TOKEN=$UV_TEST_GCP_TOKEN" >> $GITHUB_ENV

      - name: "Run registry tests with environment variable backend"
        run: ./uv run --no-project -p "${PYTHON_VERSION}" scripts/registries-test.py --uv ./uv --color always --all --auth-method env
        env:
          RUST_LOG: uv=debug
          UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
          UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }}
          UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }}
          UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }}
          UV_TEST_AWS_USERNAME: aws
          UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }}
          UV_TEST_AZURE_URL: ${{ secrets.UV_TEST_AZURE_URL }}
          UV_TEST_AZURE_USERNAME: dummy
          UV_TEST_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_CLOUDSMITH_TOKEN }}
          UV_TEST_CLOUDSMITH_URL: ${{ secrets.UV_TEST_CLOUDSMITH_URL }}
          UV_TEST_CLOUDSMITH_USERNAME: ${{ secrets.UV_TEST_CLOUDSMITH_USERNAME }}
          UV_TEST_GCP_URL: ${{ secrets.UV_TEST_GCP_URL }}
          UV_TEST_GCP_USERNAME: oauth2accesstoken
          UV_TEST_GEMFURY_TOKEN: ${{ secrets.UV_TEST_GEMFURY_TOKEN }}
          UV_TEST_GEMFURY_URL: ${{ secrets.UV_TEST_GEMFURY_URL }}
          UV_TEST_GEMFURY_USERNAME: ${{ secrets.UV_TEST_GEMFURY_USERNAME }}
          UV_TEST_GITLAB_TOKEN: ${{ secrets.UV_TEST_GITLAB_TOKEN }}
          UV_TEST_GITLAB_URL: ${{ secrets.UV_TEST_GITLAB_URL }}
          UV_TEST_GITLAB_USERNAME: token

      - name: "Run registry tests with text store backend"
        run: ./uv run --no-project -p "${PYTHON_VERSION}" scripts/registries-test.py --uv ./uv --color always --all --auth-method text-store
        env:
          RUST_LOG: uv=debug
          UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
          UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }}
          UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }}
          UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }}
          UV_TEST_AWS_USERNAME: aws
          UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }}
          UV_TEST_AZURE_URL: ${{ secrets.UV_TEST_AZURE_URL }}
          UV_TEST_AZURE_USERNAME: dummy
          UV_TEST_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_CLOUDSMITH_TOKEN }}
          UV_TEST_CLOUDSMITH_URL: ${{ secrets.UV_TEST_CLOUDSMITH_URL }}
          UV_TEST_CLOUDSMITH_USERNAME: ${{ secrets.UV_TEST_CLOUDSMITH_USERNAME }}
          UV_TEST_GCP_URL: ${{ secrets.UV_TEST_GCP_URL }}
          UV_TEST_GCP_USERNAME: oauth2accesstoken
          UV_TEST_GEMFURY_TOKEN: ${{ secrets.UV_TEST_GEMFURY_TOKEN }}
          UV_TEST_GEMFURY_URL: ${{ secrets.UV_TEST_GEMFURY_URL }}
          UV_TEST_GEMFURY_USERNAME: ${{ secrets.UV_TEST_GEMFURY_USERNAME }}
          UV_TEST_GITLAB_TOKEN: ${{ secrets.UV_TEST_GITLAB_TOKEN }}
          UV_TEST_GITLAB_URL: ${{ secrets.UV_TEST_GITLAB_URL }}
          UV_TEST_GITLAB_USERNAME: token

  integration-uv-build-backend:
    name: "uv_build"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "${{ env.PYTHON_VERSION }}"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Test uv_build package"
        run: |
          # Build the Python package, which is not covered by uv's integration tests since they can't depend on having
          # a Python package (only the binary itself is built before running Rust's tests)
          ./uv build -v crates/uv-build

          # Test the main path (`build_wheel`) through pip
          ./uv venv -v --seed
          ./uv run --no-project python -m pip install -v test/packages/built-by-uv --find-links crates/uv-build/dist --no-index --no-deps
          ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"

          # Test both `build_wheel` and `build_sdist` through uv
          ./uv venv -c -v
          ./uv build -v --force-pep517 test/packages/built-by-uv --find-links crates/uv-build/dist --offline
          ./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
          ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"

          # Test both `build_wheel` and `build_sdist` through the official `build`
          rm -rf test/packages/built-by-uv/dist/
          ./uv venv -c -v
          ./uv pip install build
          # Add the uv binary to PATH for `build` to find
          PATH="$(pwd):$PATH" UV_OFFLINE=1 UV_FIND_LINKS=crates/uv-build/dist ./uv run --no-project python -m build -v --installer uv test/packages/built-by-uv
          ./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
          ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"

  cache-test-ubuntu:
    name: "cache on linux"
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.14"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-linux-libc-${{ inputs.sha }}

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

      - name: "Download binary for last version"
        run: curl -LsSf "https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-unknown-linux-gnu.tar.gz" | tar -xvz

      - name: "Check cache compatibility"
        run: python scripts/check_cache_compat.py --uv-current ./uv --uv-previous ./uv-x86_64-unknown-linux-gnu/uv

  cache-test-macos-aarch64:
    name: "cache on macos aarch64"
    timeout-minutes: 10
    runs-on: macos-14
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.14"

      - name: "Download binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: uv-macos-aarch64-${{ inputs.sha }}

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

      - name: "Download binary for last version"
        run: curl -LsSf "https://github.com/astral-sh/uv/releases/latest/download/uv-aarch64-apple-darwin.tar.gz" | tar -xvz

      - name: "Check cache compatibility"
        run: python scripts/check_cache_compat.py --uv-current ./uv --uv-previous ./uv-aarch64-apple-darwin/uv
