# Build uv on all platforms.
#
# Generates both wheels (for PyPI) and archived binaries (for GitHub releases).
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
# artifacts job within `cargo-dist`.
name: "Build binaries"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string
  pull_request:
    paths:
      # When we change pyproject.toml, we want to ensure that the maturin builds still work.
      - pyproject.toml
      # And when we change this workflow itself...
      - .github/workflows/build-binaries.yml

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

env:
  PACKAGE_NAME: uv
  MODULE_NAME: uv
  PYTHON_VERSION: "3.11"
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  sdist:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build sdist"
        uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --out dist
      - name: "Test sdist"
        run: |
          pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
          ${{ env.MODULE_NAME }} --help
          python -m ${{ env.MODULE_NAME }} --help
      - name: "Upload sdist"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-sdist
          path: dist

  macos-x86_64:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: macos-12
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels - x86_64"
        uses: PyO3/maturin-action@v1
        with:
          target: x86_64
          args: --release --locked --out dist --features self-update
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-macos-x86_64
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=x86_64-apple-darwin
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-macos-x86_64
          path: |
            *.tar.gz
            *.sha256

  macos-aarch64:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: arm64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels - aarch64"
        uses: PyO3/maturin-action@v1
        with:
          target: aarch64
          args: --release --locked --out dist --features self-update
      - name: "Test wheel - aarch64"
        run: |
          pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
          ${{ env.MODULE_NAME }} --help
          python -m ${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-aarch64-apple-darwin
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=aarch64-apple-darwin
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-aarch64-apple-darwin
          path: |
            *.tar.gz
            *.sha256

  windows:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: windows-latest
    strategy:
      matrix:
        platform:
          - target: x86_64-pc-windows-msvc
            arch: x64
          - target: i686-pc-windows-msvc
            arch: x86
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: ${{ matrix.platform.arch }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.platform.target }}
          args: --release --locked --out dist --features self-update
      - name: "Test wheel"
        if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
        shell: bash
        run: |
          python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
          ${{ env.MODULE_NAME }} --help
          python -m ${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_FILE=uv-${{ matrix.platform.target }}.zip
          7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/uv.exe
          sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.zip
            *.sha256

  linux:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-gnu
          - i686-unknown-linux-gnu
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: auto
          args: --release --locked --out dist --features self-update
          # See: https://github.com/sfackler/rust-openssl/issues/2036#issuecomment-1724324145
          before-script-linux: |
            # If we're running on rhel centos, install needed packages.
            if command -v yum &> /dev/null; then
                yum update -y && yum install -y perl-core openssl openssl-devel pkgconfig libatomic

                # If we're running on i686 we need to symlink libatomic
                # in order to build openssl with -latomic flag.
                if [[ ! -d "/usr/lib64" ]]; then
                    ln -s /usr/lib/libatomic.so.1 /usr/lib/libatomic.so
                fi
            else
                # If we're running on debian-based system.
                apt update -y && apt-get install -y libssl-dev openssl pkg-config
            fi
      - name: "Test wheel"
        if: ${{ startsWith(matrix.target, 'x86_64') }}
        run: |
          pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
          ${{ env.MODULE_NAME }} --help
          python -m ${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.target }}
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-${{ matrix.target }}
          path: |
            *.tar.gz
            *.sha256

  linux-arm:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform:
          - target: aarch64-unknown-linux-gnu
            arch: aarch64
            # see https://github.com/astral-sh/ruff/issues/3791
            # and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
          - target: armv7-unknown-linux-gnueabihf
            arch: armv7
          - target: arm-unknown-linux-musleabihf
            arch: arm

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.platform.target }}
          # On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`.
          manylinux: ${{ matrix.platform.arch == 'aarch64' && '2_28' || 'auto' }}
          docker-options: ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --features self-update
      - uses: uraimo/run-on-arch-action@v2
        name: Test wheel
        with:
          arch: ${{ matrix.platform.arch == 'arm' && 'armv6' || matrix.platform.arch }}
          distro: ${{ matrix.platform.arch == 'arm' && 'bullseye' || 'ubuntu20.04' }}
          githubToken: ${{ github.token }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip
            pip3 install -U pip
          run: |
            pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            ${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.platform.target }}
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

  # Like `linux-arm`, but use `--no-default-features --features flate2/rust_backend` when
  # building uv.
  linux-s390x:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform:
          - target: s390x-unknown-linux-gnu
            arch: s390x

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.platform.target }}
          manylinux: auto
          docker-options: ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --no-default-features --features flate2/rust_backend --features self-update
      - uses: uraimo/run-on-arch-action@v2
        if: matrix.platform.arch != 'ppc64'
        name: Test wheel
        with:
          arch: ${{ matrix.platform.arch }}
          distro: ubuntu20.04
          githubToken: ${{ github.token }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip
            pip3 install -U pip
          run: |
            pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            ${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.platform.target }}
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

  # Like `linux-arm`, but use `--no-default-features --features flate2/rust_backend` when
  # building uv, and install the `gcc-powerpc64-linux-gnu` package.
  linux-powerpc:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform:
          - target: powerpc64le-unknown-linux-gnu
            arch: ppc64le
          - target: powerpc64-unknown-linux-gnu
            arch: ppc64

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.platform.target }}
          manylinux: auto
          docker-options: ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --no-default-features --features flate2/rust_backend --features self-update
          before-script-linux: |
            if command -v yum &> /dev/null; then
                yum update -y
                yum -y install epel-release
                yum repolist
                yum install -y gcc-powerpc64-linux-gnu
            fi
      - uses: uraimo/run-on-arch-action@v2
        if: matrix.platform.arch != 'ppc64'
        name: Test wheel
        with:
          arch: ${{ matrix.platform.arch }}
          distro: ubuntu20.04
          githubToken: ${{ github.token }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip
            pip3 install -U pip
          run: |
            pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            ${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.platform.target }}
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256

  musllinux:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target:
          - x86_64-unknown-linux-musl
          - i686-unknown-linux-musl
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          architecture: x64
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: musllinux_1_1
          args: --release --locked --out dist --features self-update
      - name: "Test wheel"
        if: matrix.target == 'x86_64-unknown-linux-musl'
        uses: addnab/docker-run-action@v3
        with:
          image: alpine:3.12
          options: -v ${{ github.workspace }}:/io -w /io
          run: |
            apk add python3
            python3 -m venv .venv
            .venv/bin/pip install --upgrade pip
            .venv/bin/pip install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            .venv/bin/${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.target }}
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-${{ matrix.target }}
          path: |
            *.tar.gz
            *.sha256

  musllinux-cross:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform:
          - target: aarch64-unknown-linux-musl
            arch: aarch64
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
          - target: armv7-unknown-linux-musleabihf
            arch: armv7
          - target: powerpc64le-unknown-linux-musl
            arch: ppc64le
            toolchain: nightly

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.platform.target }}
          manylinux: musllinux_1_1
          args: --release --locked --out dist --features self-update ${{ matrix.platform.arch == 'aarch64' && '--compatibility 2_17' || ''}}
          docker-options: ${{ matrix.platform.maturin_docker_options }}
          rust-toolchain: ${{ matrix.platform.toolchain || 'stable' }}
      - uses: uraimo/run-on-arch-action@v2
        name: Test wheel
        with:
          arch: ${{ matrix.platform.arch }}
          distro: alpine_latest
          githubToken: ${{ github.token }}
          install: |
            apk add python3
          run: |
            python -m venv .venv
            .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            .venv/bin/${{ env.MODULE_NAME }} --help
      - uses: uraimo/run-on-arch-action@v2
        name: Test wheel (manylinux)
        if: matrix.platform.arch == 'aarch64'
        with:
          arch: aarch64
          distro: ubuntu20.04
          githubToken: ${{ github.token }}
          install: |
            apt-get update
            apt-get install -y --no-install-recommends python3 python3-pip
            pip3 install -U pip
          run: |
            pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            ${{ env.MODULE_NAME }} --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

          TARGET=${{ matrix.platform.target }}
          ARCHIVE_NAME=uv-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/uv $ARCHIVE_NAME/uv
          cp target/$TARGET/release/uvx $ARCHIVE_NAME/uvx
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256
