astral-sh/ruff

19 workflows · maturity 67% · 15 patterns · GitHub ↗

Security 39.74/100

Security dimensions

permissions
19.7
security scan
0
supply chain
20
secret handling
0
harden runner
0

Workflows (19)

build-binaries matrix perms .github/workflows/build-binaries.yml
Triggers
workflow_call, pull_request
Runs on
ubuntu-latest, macos-14, macos-14, windows-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
sdist, macos-x86_64, macos-aarch64, windows, linux, linux-cross, musllinux, musllinux-cross
Matrix
platform, platform.arch, platform.manylinux, platform.maturin_docker_options, platform.target, target→ -e JEMALLOC_SYS_WITH_LG_PAGE=16, 217, 231, aarch64, aarch64-pc-windows-msvc, aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl, arm, arm-unknown-linux-musleabihf, armv7, armv7-unknown-linux-gnueabihf, armv7-unknown-linux-musleabihf, auto, i686-pc-windows-msvc, i686-unknown-linux-gnu, i686-unknown-linux-musl, powerpc64le-unknown-linux-gnu, ppc64le, riscv64, riscv64gc-unknown-linux-gnu, s390x, s390x-unknown-linux-gnu, x64, x86, x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
Actions
PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, PyO3/maturin-action, uraimo/run-on-arch-action, PyO3/maturin-action, PyO3/maturin-action, uraimo/run-on-arch-action
Commands
  • python scripts/transform_readme.py --target pypi
  • pip install dist/"${PACKAGE_NAME}"-*.tar.gz --force-reinstall "${MODULE_NAME}" --help python -m "${MODULE_NAME}" --help
  • python scripts/transform_readme.py --target pypi
  • TARGET=x86_64-apple-darwin ARCHIVE_NAME=ruff-$TARGET ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz mkdir -p $ARCHIVE_NAME cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff tar czvf $ARCHIVE_FILE $ARCHIVE_NAME shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
  • python scripts/transform_readme.py --target pypi
  • pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall ruff --help python -m ruff --help
  • TARGET=aarch64-apple-darwin ARCHIVE_NAME=ruff-$TARGET ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz mkdir -p $ARCHIVE_NAME cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff tar czvf $ARCHIVE_FILE $ARCHIVE_NAME shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
  • python scripts/transform_readme.py --target pypi
View raw YAML
# Build ruff 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: build-binaries-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions: {}

env:
  PACKAGE_NAME: ruff
  MODULE_NAME: ruff
  PYTHON_VERSION: "3.13"
  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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          command: sdist
          args: --out dist
      - name: "Test sdist"
        run: |
          pip install dist/"${PACKAGE_NAME}"-*.tar.gz --force-reinstall
          "${MODULE_NAME}" --help
          python -m "${MODULE_NAME}" --help
      - name: "Upload sdist"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-sdist
          path: dist

  macos-x86_64:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: macos-14
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          target: x86_64
          args: --release --locked --out dist --compatibility pypi
      - name: "Upload wheels"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-macos-x86_64
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=x86_64-apple-darwin
          ARCHIVE_NAME=ruff-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          target: aarch64
          args: --release --locked --out dist --compatibility pypi
      - name: "Test wheel - aarch64"
        run: |
          pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
          ruff --help
          python -m ruff --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-aarch64-apple-darwin
          path: dist
      - name: "Archive binary"
        run: |
          TARGET=aarch64-apple-darwin
          ARCHIVE_NAME=ruff-$TARGET
          ARCHIVE_FILE=$ARCHIVE_NAME.tar.gz

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        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
          - target: aarch64-pc-windows-msvc
            arch: x64
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          target: ${{ matrix.platform.target }}
          args: --release --locked --out dist --compatibility pypi
        env:
          # aarch64 build fails, see https://github.com/PyO3/maturin/issues/2110
          XWIN_VERSION: 16
      - name: "Test wheel"
        if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
        shell: bash
        run: |
          python -m pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
          "${MODULE_NAME}" --help
          python -m "${MODULE_NAME}" --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          ARCHIVE_FILE=ruff-${{ matrix.platform.target }}.zip
          7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe
          sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          target: ${{ matrix.target }}
          manylinux: 2_17
          args: --release --locked --out dist --compatibility pypi
      - name: "Test wheel"
        if: ${{ startsWith(matrix.target, 'x86_64') }}
        run: |
          pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
          "${MODULE_NAME}" --help
          python -m "${MODULE_NAME}" --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

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

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

  linux-cross:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform:
          - target: aarch64-unknown-linux-gnu
            arch: aarch64
            manylinux: 2_17
            # 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
            manylinux: 2_17
          - target: s390x-unknown-linux-gnu
            arch: s390x
            manylinux: 2_17
          - target: powerpc64le-unknown-linux-gnu
            arch: ppc64le
            manylinux: 2_17
            # see https://github.com/astral-sh/ruff/issues/10073
            maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
          - target: arm-unknown-linux-musleabihf
            # Use the cross container, but tag as `linux_armv6l`
            manylinux: auto
            arch: arm
          - target: riscv64gc-unknown-linux-gnu
            arch: riscv64
            # Minimum manylinux target for riscv64
            manylinux: 2_31

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          target: ${{ matrix.platform.target }}
          manylinux: ${{ matrix.platform.manylinux }}
          docker-options: ${{ matrix.platform.maturin_docker_options }}
          args: --release --locked --out dist --compatibility pypi
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        if: ${{ matrix.platform.arch != 'ppc64le'}}
        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 libatomic1
            pip3 install -U pip
          run: |
            pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
            ruff --help
      - name: "Upload wheels"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

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

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          target: ${{ matrix.target }}
          manylinux: musllinux_1_2
          args: --release --locked --out dist --compatibility pypi
      - name: "Test wheel"
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          docker run --rm -v ${{ github.workspace }}:/io -w /io --env MODULE_NAME --env PACKAGE_NAME alpine:latest sh -c "
            apk add python3;
            python -m venv .venv;
            .venv/bin/pip3 install ${PACKAGE_NAME} --no-index --find-links dist/ --force-reinstall;
            .venv/bin/${MODULE_NAME} --help;
            "
      - name: "Upload wheels"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-${{ matrix.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

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

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        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

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        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@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          maturin-version: v1.11.5
          target: ${{ matrix.platform.target }}
          manylinux: musllinux_1_2
          args: --release --locked --out dist --compatibility pypi
          docker-options: ${{ matrix.platform.maturin_docker_options }}
      - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
        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
      - name: "Upload wheels"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: wheels-${{ matrix.platform.target }}
          path: dist
      - name: "Archive binary"
        shell: bash
        run: |
          set -euo pipefail

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

          mkdir -p $ARCHIVE_NAME
          cp target/$TARGET/release/ruff $ARCHIVE_NAME/ruff
          tar czvf $ARCHIVE_FILE $ARCHIVE_NAME
          shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
      - name: "Upload binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: artifacts-${{ matrix.platform.target }}
          path: |
            *.tar.gz
            *.sha256
build-docker matrix perms .github/workflows/build-docker.yml
Triggers
workflow_call, pull_request
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
docker-build, docker-publish, docker-publish-extra, docker-republish
Matrix
image-mapping, platform→ alpine:3.23,alpine3.23,alpine, buildpack-deps:trixie,trixie,debian, debian:trixie-slim,trixie-slim,debian-slim, linux/amd64, linux/arm64
Actions
docker/setup-buildx-action, docker/login-action, docker/metadata-action, docker/build-push-action, docker/setup-buildx-action, docker/metadata-action, docker/login-action, actions/attest-build-provenance, docker/setup-buildx-action, docker/login-action, docker/metadata-action, docker/build-push-action, actions/attest-build-provenance, docker/setup-buildx-action, docker/metadata-action, docker/login-action, actions/attest-build-provenance
Commands
  • version=$(grep -m 1 "^version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g') if [ "${TAG}" != "${version}" ]; then echo "The input tag does not match the version from pyproject.toml:" >&2 echo "${TAG}" >&2 echo "${version}" >&2 exit 1 else echo "Releasing ${version}" fi
  • platform=${{ matrix.platform }} echo "PLATFORM_TUPLE=${platform//\//-}" >> "$GITHUB_ENV"
  • mkdir -p /tmp/digests touch "/tmp/digests/${digest#sha256:}"
  • # shellcheck disable=SC2046 docker buildx imagetools create \ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf "${RUFF_BASE_IMG}@sha256:%s " *)
  • digest="$( docker buildx imagetools inspect \ "${IMAGE}:${VERSION}" \ --format '{{json .Manifest}}' \ | jq -r '.digest' )" echo "digest=${digest}" >> "$GITHUB_OUTPUT"
  • set -euo pipefail # Extract the image and tags from the matrix variable IFS=',' read -r BASE_IMAGE BASE_TAGS <<< "${{ matrix.image-mapping }}" # Generate Dockerfile content cat <<EOF > Dockerfile FROM ${BASE_IMAGE} COPY --from=${RUFF_BASE_IMG}:latest /ruff /usr/local/bin/ruff ENTRYPOINT [] CMD ["/usr/local/bin/ruff"] EOF # Initialize a variable to store all tag docker metadata patterns TAG_PATTERNS="" # Loop through all base tags and append its docker metadata pattern to the list # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version IFS=','; for TAG in ${BASE_TAGS}; do TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${TAG_VALUE}\n" TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${TAG_VALUE}\n" TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n" done # Remove the trailing newline from the pattern list TAG_PATTERNS="${TAG_PATTERNS%\\n}" # Export image cache name echo "IMAGE_REF=${BASE_IMAGE//:/-}" >> "$GITHUB_ENV" # Export tag patterns using the multiline env var syntax { echo "TAG_PATTERNS<<EOF" echo -e "${TAG_PATTERNS}" echo EOF } >> "$GITHUB_ENV"
  • readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done # shellcheck disable=SC2046 docker buildx imagetools create \ "${annotations[@]}" \ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf "${RUFF_BASE_IMG}@sha256:%s " *)
  • digest="$( docker buildx imagetools inspect \ "${IMAGE}:${VERSION}" \ --format '{{json .Manifest}}' \ | jq -r '.digest' )" echo "digest=${digest}" >> "$GITHUB_OUTPUT"
View raw YAML
# Build and publish a Docker image.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
# artifacts job within `cargo-dist`.
#
# TODO(charlie): Ideally, the publish step would happen as a publish job within `cargo-dist`, but
# sharing the built image as an artifact between jobs is challenging.
name: "[ruff] Build Docker image"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string
  pull_request:
    paths:
      - .github/workflows/build-docker.yml

env:
  RUFF_BASE_IMG: ghcr.io/${{ github.repository_owner }}/ruff

permissions:
  contents: read
  # TODO(zanieb): Ideally, this would be `read` on dry-run but that will require
  # significant changes to the workflow.
  packages: write # zizmor: ignore[excessive-permissions]

jobs:
  docker-build:
    name: Build Docker image (ghcr.io/astral-sh/ruff) for ${{ matrix.platform }}
    runs-on: ubuntu-latest
    environment:
      name: release
    strategy:
      fail-fast: false
      matrix:
        platform:
          - linux/amd64
          - linux/arm64
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          submodules: recursive
          persist-credentials: false

      - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Check tag consistency
        if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
        env:
          TAG: ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }}
        run: |
          version=$(grep -m 1 "^version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
          if [ "${TAG}" != "${version}" ]; then
            echo "The input tag does not match the version from pyproject.toml:" >&2
            echo "${TAG}" >&2
            echo "${version}" >&2
            exit 1
          else
            echo "Releasing ${version}"
          fi

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        with:
          images: ${{ env.RUFF_BASE_IMG }}
          # Defining this makes sure the org.opencontainers.image.version OCI label becomes the actual release version and not the branch name
          tags: |
            type=raw,value=dry-run,enable=${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
            type=pep440,pattern={{ version }},value=${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }},enable=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}

      - name: Normalize Platform Pair (replace / with -)
        run: |
          platform=${{ matrix.platform }}
          echo "PLATFORM_TUPLE=${platform//\//-}" >> "$GITHUB_ENV"

      # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
        with:
          context: .
          platforms: ${{ matrix.platform }}
          cache-from: type=gha,scope=ruff-${{ env.PLATFORM_TUPLE }}
          cache-to: type=gha,mode=min,scope=ruff-${{ env.PLATFORM_TUPLE }}
          labels: ${{ steps.meta.outputs.labels }}
          outputs: type=image,name=${{ env.RUFF_BASE_IMG }},push-by-digest=true,name-canonical=true,push=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}

      - name: Export digests
        env:
          digest: ${{ steps.build.outputs.digest }}
        run: |
          mkdir -p /tmp/digests
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload digests
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: digests-${{ env.PLATFORM_TUPLE }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  docker-publish:
    name: Publish Docker image (ghcr.io/astral-sh/ruff)
    runs-on: ubuntu-latest
    environment:
      name: release
    needs:
      - docker-build
    if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
    permissions:
      attestations: write
      id-token: write
      packages: write
    steps:
      - name: Download digests
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        with:
          images: ${{ env.RUFF_BASE_IMG }}
          # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
          tags: |
            type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
            type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
      - name: Create manifest list and push
        working-directory: /tmp/digests
        # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array
        # The printf will expand the base image with the `<RUFF_BASE_IMG>@sha256:<sha256> ...` for each sha256 in the directory
        # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <RUFF_BASE_IMG>@sha256:<sha256_1> <RUFF_BASE_IMG>@sha256:<sha256_2> ...`
        run: |
          # shellcheck disable=SC2046
          docker buildx imagetools create \
            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf "${RUFF_BASE_IMG}@sha256:%s " *)

      - name: Export manifest digest
        id: manifest-digest
        env:
          IMAGE: ${{ env.RUFF_BASE_IMG }}
          VERSION: ${{ steps.meta.outputs.version }}
        run: |
          digest="$(
            docker buildx imagetools inspect \
              "${IMAGE}:${VERSION}" \
              --format '{{json .Manifest}}' \
            | jq -r '.digest'
          )"
          echo "digest=${digest}" >> "$GITHUB_OUTPUT"

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.RUFF_BASE_IMG }}
          subject-digest: ${{ steps.manifest-digest.outputs.digest }}

  docker-publish-extra:
    name: Publish additional Docker image based on ${{ matrix.image-mapping }}
    runs-on: ubuntu-latest
    environment:
      name: release
    needs:
      - docker-publish
    if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
    permissions:
      attestations: write
      id-token: write
      packages: write
    strategy:
      fail-fast: false
      matrix:
        # Mapping of base image followed by a comma followed by one or more base tags (comma separated)
        # Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first)
        image-mapping:
          - alpine:3.23,alpine3.23,alpine
          - debian:trixie-slim,trixie-slim,debian-slim
          - buildpack-deps:trixie,trixie,debian
    steps:
      - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Generate Dynamic Dockerfile Tags
        shell: bash
        env:
          TAG_VALUE: ${{ fromJson(inputs.plan).announcement_tag }}
        run: |
          set -euo pipefail

          # Extract the image and tags from the matrix variable
          IFS=',' read -r BASE_IMAGE BASE_TAGS <<< "${{ matrix.image-mapping }}"

          # Generate Dockerfile content
          cat <<EOF > Dockerfile
          FROM ${BASE_IMAGE}
          COPY --from=${RUFF_BASE_IMG}:latest /ruff /usr/local/bin/ruff
          ENTRYPOINT []
          CMD ["/usr/local/bin/ruff"]
          EOF

          # Initialize a variable to store all tag docker metadata patterns
          TAG_PATTERNS=""

          # Loop through all base tags and append its docker metadata pattern to the list
          # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
          IFS=','; for TAG in ${BASE_TAGS}; do
            TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${TAG_VALUE}\n"
            TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${TAG_VALUE}\n"
            TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n"
          done

          # Remove the trailing newline from the pattern list
          TAG_PATTERNS="${TAG_PATTERNS%\\n}"

          # Export image cache name
          echo "IMAGE_REF=${BASE_IMAGE//:/-}" >> "$GITHUB_ENV"

          # Export tag patterns using the multiline env var syntax
          {
            echo "TAG_PATTERNS<<EOF"
            echo -e "${TAG_PATTERNS}"
            echo EOF
          } >> "$GITHUB_ENV"

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        # ghcr.io prefers index level annotations
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: index
        with:
          images: ${{ env.RUFF_BASE_IMG }}
          flavor: |
            latest=false
          tags: |
            ${{ env.TAG_PATTERNS }}

      - name: Build and push
        id: build-and-push
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          # We do not really need to cache here as the Dockerfile is tiny
          #cache-from: type=gha,scope=ruff-${{ env.IMAGE_REF }}
          #cache-to: type=gha,mode=min,scope=ruff-${{ env.IMAGE_REF }}
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          annotations: ${{ steps.meta.outputs.annotations }}

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.RUFF_BASE_IMG }}
          subject-digest: ${{ steps.build-and-push.outputs.digest }}

  # This is effectively a duplicate of `docker-publish` to make https://github.com/astral-sh/ruff/pkgs/container/ruff
  # show the ruff base image first since GitHub always shows the last updated image digests
  # This works by annotating the original digests (previously non-annotated) which triggers an update to ghcr.io
  docker-republish:
    name: Annotate Docker image (ghcr.io/astral-sh/ruff)
    runs-on: ubuntu-latest
    environment:
      name: release
    needs:
      - docker-publish-extra
    if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
    permissions:
      attestations: write
      id-token: write
      packages: write
    steps:
      - name: Download digests
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        env:
          DOCKER_METADATA_ANNOTATIONS_LEVELS: index
        with:
          images: ${{ env.RUFF_BASE_IMG }}
          # Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
          tags: |
            type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
            type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}

      - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
      - name: Create manifest list and push
        working-directory: /tmp/digests
        # The readarray part is used to make sure the quoting and special characters are preserved on expansion (e.g. spaces)
        # The jq command expands the docker/metadata json "tags" array entry to `-t tag1 -t tag2 ...` for each tag in the array
        # The printf will expand the base image with the `<RUFF_BASE_IMG>@sha256:<sha256> ...` for each sha256 in the directory
        # The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <RUFF_BASE_IMG>@sha256:<sha256_1> <RUFF_BASE_IMG>@sha256:<sha256_2> ...`
        run: |
          readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done

          # shellcheck disable=SC2046
          docker buildx imagetools create \
            "${annotations[@]}" \
            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf "${RUFF_BASE_IMG}@sha256:%s " *)

      - name: Export manifest digest
        id: manifest-digest
        env:
          IMAGE: ${{ env.RUFF_BASE_IMG }}
          VERSION: ${{ steps.meta.outputs.version }}
        run: |
          digest="$(
            docker buildx imagetools inspect \
              "${IMAGE}:${VERSION}" \
              --format '{{json .Manifest}}' \
            | jq -r '.digest'
          )"
          echo "digest=${digest}" >> "$GITHUB_OUTPUT"

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.RUFF_BASE_IMG }}
          subject-digest: ${{ steps.manifest-digest.outputs.digest }}
build-wasm matrix perms .github/workflows/build-wasm.yml
Triggers
workflow_call, pull_request
Runs on
ubuntu-latest
Jobs
build
Matrix
target→ bundler, nodejs, web
Actions
jetli/wasm-pack-action, jetli/wasm-bindgen-action
Commands
  • rustup target add wasm32-unknown-unknown
  • wasm-pack build --target ${{ matrix.target }} crates/ruff_wasm
  • jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json mv /tmp/package.json crates/ruff_wasm/pkg
  • cp LICENSE crates/ruff_wasm/pkg
View raw YAML
# Build ruff_wasm for npm.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a local
# artifacts job within `cargo-dist`.
name: "Build wasm"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string
  pull_request:
    paths:
      - .github/workflows/build-wasm.yml

concurrency:
  group: build-wasm-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions: {}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  build:
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: [web, bundler, nodejs]
      fail-fast: false
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install Rust toolchain"
        run: rustup target add wasm32-unknown-unknown
      - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
        with:
          version: v0.13.1
      - uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
      - name: "Run wasm-pack build"
        run: wasm-pack build --target ${{ matrix.target }} crates/ruff_wasm
      - name: "Rename generated package"
        run: | # Replace the package name w/ jq
          jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
          mv /tmp/package.json crates/ruff_wasm/pkg
      - run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
      - name: "Upload wasm artifact"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          # Avoid prefixing the name with `artifacts-` here to exclude it from the GitHub release.
          name: wasm-npm-${{ matrix.target }}
          path: crates/ruff_wasm/pkg
ci matrix perms .github/workflows/ci.yaml
Triggers
push, pull_request, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}, depot-ubuntu-22.04-16, ${{ matrix.platform }}, ubuntu-latest, ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}, ubuntu-latest, ubuntu-latest, ubuntu-latest, ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}, ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}, ubuntu-latest, ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}, ubuntu-latest, ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-24.04, depot-ubuntu-24.04-4, ubuntu-24.04, depot-ubuntu-22.04-arm-4, codspeed-macro
Jobs
determine_changes, cargo-fmt, cargo-clippy, cargo-test-linux, cargo-test-linux-release, cargo-test-other, cargo-test-wasm, cargo-build-msrv, cargo-fuzz-build, fuzz-parser, scripts, ecosystem, fuzz-ty, cargo-shear, ty-completion-evaluation, python-package, prek, docs, check-formatter-instability-and-black-similarity, check-ruff-lsp, check-playground, benchmarks-instrumented-ruff, benchmarks-instrumented-ty-build, benchmarks-instrumented-ty-run, benchmarks-walltime-build, benchmarks-walltime-run
Matrix
benchmark, platform→ ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}, attrs|hydra|datetype, check_file|micro|anyio, colour_science, macos-latest, pandas|tanjun|altair, pydantic|multithreaded|freqtrade, static_frame|sympy
Actions
Swatinem/rust-cache, Swatinem/rust-cache, rui314/setup-mold, taiki-e/install-action, taiki-e/install-action, astral-sh/setup-uv, Swatinem/rust-cache, rui314/setup-mold, taiki-e/install-action, astral-sh/setup-uv, Swatinem/rust-cache, taiki-e/install-action, astral-sh/setup-uv, Swatinem/rust-cache, jetli/wasm-pack-action, SebRollen/toml-action, Swatinem/rust-cache, rui314/setup-mold, Swatinem/rust-cache, rui314/setup-mold, cargo-bins/cargo-binstall, astral-sh/setup-uv, Swatinem/rust-cache, Swatinem/rust-cache, astral-sh/setup-uv, astral-sh/setup-uv, rui314/setup-mold, Swatinem/rust-cache, astral-sh/setup-uv, Swatinem/rust-cache, rui314/setup-mold, cargo-bins/cargo-binstall, astral-sh/setup-uv, Swatinem/rust-cache, rui314/setup-mold, Swatinem/rust-cache, PyO3/maturin-action, astral-sh/setup-uv, Swatinem/rust-cache, astral-sh/setup-uv, Swatinem/rust-cache, extractions/setup-just, Swatinem/rust-cache, Swatinem/rust-cache, jetli/wasm-bindgen-action, Swatinem/rust-cache, astral-sh/setup-uv, taiki-e/install-action, CodSpeedHQ/action, Swatinem/rust-cache, taiki-e/install-action, astral-sh/setup-uv, taiki-e/install-action, CodSpeedHQ/action, Swatinem/rust-cache, astral-sh/setup-uv, taiki-e/install-action, astral-sh/setup-uv, taiki-e/install-action, CodSpeedHQ/action
Commands
  • sha=$(git merge-base HEAD "origin/${BASE_REF}") echo "sha=${sha}" >> "$GITHUB_OUTPUT"
  • if git diff --quiet "${MERGE_BASE}...HEAD" -- \ ':Cargo.toml' \ ':Cargo.lock' \ ':crates/ruff_python_trivia/**' \ ':crates/ruff_source_file/**' \ ':crates/ruff_text_size/**' \ ':crates/ruff_python_ast/**' \ ':crates/ruff_python_parser/**' \ ':.github/workflows/ci.yaml' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi
  • if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \ ':Cargo.lock' \ ':crates/**' \ ':!crates/ty*/**' \ ':!crates/ruff_python_formatter/**' \ ':!crates/ruff_formatter/**' \ ':!crates/ruff_dev/**' \ ':!crates/ruff_benchmark/**' \ ':scripts/*' \ ':python/**' \ ':.github/workflows/ci.yaml' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi
  • if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \ ':Cargo.lock' \ ':crates/ruff_python_formatter/**' \ ':crates/ruff_formatter/**' \ ':crates/ruff_python_trivia/**' \ ':crates/ruff_python_ast/**' \ ':crates/ruff_source_file/**' \ ':crates/ruff_python_index/**' \ ':crates/ruff_python_index/**' \ ':crates/ruff_text_size/**' \ ':crates/ruff_python_parser/**' \ ':scripts/*' \ ':python/**' \ ':.github/workflows/ci.yaml' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi
  • if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \ ':Cargo.lock' \ ':fuzz/fuzz_targets/**' \ ':.github/workflows/ci.yaml' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi
  • if git diff --quiet "${MERGE_BASE}...HEAD" -- 'python/py-fuzzer/**' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi
  • # NOTE: Do not exclude all Markdown files here, but rather use # specific exclude patterns like 'docs/**'), because tests for # 'ty' are written in Markdown. if git diff --quiet "${MERGE_BASE}...HEAD" -- \ ':!docs/**' \ ':!assets/**' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi
  • if git diff --quiet "${MERGE_BASE}...HEAD" -- \ ':playground/**' \ ; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi
View raw YAML
name: CI

permissions: {}

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

defaults:
  run:
    shell: bash

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  PACKAGE_NAME: ruff
  PYTHON_VERSION: "3.14"
  NEXTEST_PROFILE: ci
  # Enable mdtests that require external dependencies
  MDTEST_EXTERNAL: "1"

jobs:
  determine_changes:
    name: "Determine changes"
    runs-on: ubuntu-latest
    outputs:
      # Flag that is raised when any code that affects parser is changed
      parser: ${{ steps.check_parser.outputs.changed }}
      # Flag that is raised when any code that affects linter is changed
      linter: ${{ steps.check_linter.outputs.changed }}
      # Flag that is raised when any code that affects formatter is changed
      formatter: ${{ steps.check_formatter.outputs.changed }}
      # Flag that is raised when any code is changed
      # This is superset of the linter and formatter
      code: ${{ steps.check_code.outputs.changed }}
      # Flag that is raised when any code that affects the fuzzer is changed
      fuzz: ${{ steps.check_fuzzer.outputs.changed }}
      # Flag that is set to "true" when code related to ty changes.
      ty: ${{ steps.check_ty.outputs.changed }}
      # Flag that is set to "true" when code related to the py-fuzzer folder changes.
      py-fuzzer: ${{ steps.check_py_fuzzer.outputs.changed }}
      # Flag that is set to "true" when code related to the playground changes.
      playground: ${{ steps.check_playground.outputs.changed }}
      # Flag that is set to "true" when code related to the benchmarks changes.
      benchmarks: ${{ steps.check_benchmarks.outputs.changed }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Determine merge base
        id: merge_base
        env:
          BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }}
        run: |
          sha=$(git merge-base HEAD "origin/${BASE_REF}")
          echo "sha=${sha}" >> "$GITHUB_OUTPUT"

      - name: Check if the parser code changed
        id: check_parser
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- \
            ':Cargo.toml' \
            ':Cargo.lock' \
            ':crates/ruff_python_trivia/**' \
            ':crates/ruff_source_file/**' \
            ':crates/ruff_text_size/**' \
            ':crates/ruff_python_ast/**' \
            ':crates/ruff_python_parser/**' \
            ':.github/workflows/ci.yaml' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if the linter code changed
        id: check_linter
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
            ':Cargo.lock' \
            ':crates/**' \
            ':!crates/ty*/**' \
            ':!crates/ruff_python_formatter/**' \
            ':!crates/ruff_formatter/**' \
            ':!crates/ruff_dev/**' \
            ':!crates/ruff_benchmark/**' \
            ':scripts/*' \
            ':python/**' \
            ':.github/workflows/ci.yaml' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if the formatter code changed
        id: check_formatter
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
            ':Cargo.lock' \
            ':crates/ruff_python_formatter/**' \
            ':crates/ruff_formatter/**' \
            ':crates/ruff_python_trivia/**' \
            ':crates/ruff_python_ast/**' \
            ':crates/ruff_source_file/**' \
            ':crates/ruff_python_index/**' \
            ':crates/ruff_python_index/**' \
            ':crates/ruff_text_size/**' \
            ':crates/ruff_python_parser/**' \
            ':scripts/*' \
            ':python/**' \
            ':.github/workflows/ci.yaml' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if the fuzzer code changed
        id: check_fuzzer
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- ':Cargo.toml' \
            ':Cargo.lock' \
            ':fuzz/fuzz_targets/**' \
            ':.github/workflows/ci.yaml' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if the py-fuzzer code changed
        id: check_py_fuzzer
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- 'python/py-fuzzer/**' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if there was any code related change
        id: check_code
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          # NOTE: Do not exclude all Markdown files here, but rather use
          # specific exclude patterns like 'docs/**'), because tests for
          # 'ty' are written in Markdown.
          if git diff --quiet "${MERGE_BASE}...HEAD" -- \
            ':!docs/**' \
            ':!assets/**' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if there was any playground related change
        id: check_playground
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- \
            ':playground/**' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if the ty code changed
        id: check_ty
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- \
            ':Cargo.toml' \
            ':Cargo.lock' \
            ':crates/ty*/**' \
            ':crates/ruff_db/**' \
            ':crates/ruff_annotate_snippets/**' \
            ':crates/ruff_python_ast/**' \
            ':crates/ruff_python_parser/**' \
            ':crates/ruff_python_trivia/**' \
            ':crates/ruff_source_file/**' \
            ':crates/ruff_text_size/**' \
            ':.github/workflows/ci.yaml' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if the benchmark code changed
        id: check_benchmarks
        env:
          MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
        run: |
          if git diff --quiet "${MERGE_BASE}...HEAD" -- \
            ':Cargo.toml' \
            ':Cargo.lock' \
            ':crates/ruff_benchmark/**' \
            ':.github/workflows/ci.yaml' \
          ; then
              echo "changed=false" >> "$GITHUB_OUTPUT"
          else
              echo "changed=true" >> "$GITHUB_OUTPUT"
          fi

  cargo-fmt:
    name: "cargo fmt"
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install Rust toolchain"
        run: rustup component add rustfmt
      - run: cargo fmt --all --check

  cargo-clippy:
    name: "cargo clippy"
    runs-on: ubuntu-latest
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: |
          rustup component add clippy
          rustup target add wasm32-unknown-unknown
      - name: "Clippy"
        run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
      - name: "Clippy (wasm)"
        run: cargo clippy -p ruff_wasm -p ty_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings

  cargo-test-linux:
    name: "cargo test (linux)"
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          shared-key: ruff-linux-debug
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
      - name: "Install cargo nextest"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-nextest
      - name: "Install cargo insta"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-insta
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
          enable-cache: "true"
      - name: ty mdtests (GitHub annotations)
        if: ${{ needs.determine_changes.outputs.ty == 'true' }}
        env:
          NO_COLOR: 1
          MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1
        # Ignore errors if this step fails; we want to continue to later steps in the workflow anyway.
        # This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
        run: cargo test -p ty_python_semantic --test mdtest || true
      - name: "Run tests"
        run: cargo insta test --all-features --unreferenced reject --test-runner nextest
      - name: Dogfood ty on py-fuzzer
        run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
      - name: Dogfood ty on the scripts directory
        run: uv run --project=./scripts cargo run -p ty check --project=./scripts
      - name: Dogfood ty on ty_benchmark
        run: uv run --project=./scripts/ty_benchmark cargo run -p ty check --project=./scripts/ty_benchmark
      # Check for broken links in the documentation.
      - run: cargo doc --all --no-deps
        env:
          RUSTDOCFLAGS: "-D warnings"
      # Use --document-private-items so that all our doc comments are kept in
      # sync, not just public items. Eventually we should do this for all
      # crates; for now add crates here as they are warning-clean to prevent
      # regression.
      - run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db -p ruff_python_formatter --document-private-items
        env:
          # Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
          RUSTDOCFLAGS: "-D warnings"

  cargo-test-linux-release:
    name: "cargo test (linux, release)"
    # release builds timeout on GitHub runners, so this job is just skipped on forks in the `if` check
    runs-on: depot-ubuntu-22.04-16
    needs: determine_changes
    if: |
      github.repository == 'astral-sh/ruff' &&
      !contains(github.event.pull_request.labels.*.name, 'no-test') &&
      (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main')
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
      - name: "Install cargo nextest"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-nextest
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
          enable-cache: "true"
      - name: "Run tests"
        run: cargo nextest run --cargo-profile profiling --all-features
      - name: "Run doctests"
        run: cargo test --doc --profile profiling --all-features

  cargo-test-other:
    strategy:
      matrix:
        platform:
          - ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
          - macos-latest
    name: "cargo test (${{ matrix.platform }})"
    runs-on: ${{ matrix.platform }}
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install cargo nextest"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-nextest
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
          enable-cache: "true"
      - name: "Run tests"
        run: |
          cargo nextest run --all-features --profile ci
          cargo test --all-features --doc

  cargo-test-wasm:
    name: "cargo test (wasm)"
    runs-on: ubuntu-latest
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup target add wasm32-unknown-unknown
      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: 24
          cache: "npm"
          cache-dependency-path: playground/package-lock.json
      - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0
        with:
          version: v0.13.1
      - name: "Test ruff_wasm"
        run: |
          cd crates/ruff_wasm
          wasm-pack test --node
      - name: "Test ty_wasm"
        run: |
          cd crates/ty_wasm
          wasm-pack test --node

  cargo-build-msrv:
    name: "cargo build (msrv)"
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: SebRollen/toml-action@b1b3628f55fc3a28208d4203ada8b737e9687876 # v1.2.0
        id: msrv
        with:
          file: "Cargo.toml"
          field: "workspace.package.rust-version"
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        env:
          MSRV: ${{ steps.msrv.outputs.value }}
        run: rustup default "${MSRV}"
      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
      - name: "Build tests"
        env:
          MSRV: ${{ steps.msrv.outputs.value }}
        run: cargo "+${MSRV}" test --no-run --all-features

  cargo-fuzz-build:
    name: "cargo fuzz build"
    runs-on: ubuntu-latest
    needs: determine_changes
    if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' || needs.determine_changes.outputs.code == 'true' }}
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: "fuzz -> target"
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
      - name: "Install cargo-binstall"
        uses: cargo-bins/cargo-binstall@1800853f2578f8c34492ec76154caef8e163fbca # v1.17.7
      - name: "Install cargo-fuzz"
        # Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
        run: cargo binstall cargo-fuzz --force  --disable-strategies crate-meta-data --no-confirm
      - run: cargo fuzz build -s none

  fuzz-parser:
    name: "fuzz parser"
    runs-on: ubuntu-latest
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.parser == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
    timeout-minutes: 20
    env:
      FORCE_COLOR: 1
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          shared-key: ruff-linux-debug
          save-if: false
      - name: "Install Rust toolchain"
        run: rustup show
      - name: Build Ruff binary
        run: cargo build --bin ruff
      - name: Fuzz
        run: |
          (
            uv run \
            --python="${PYTHON_VERSION}" \
            --project=./python/py-fuzzer \
            --locked \
            fuzz \
            --test-executable=target/debug/ruff \
            --bin=ruff \
            0-500
          )

  scripts:
    name: "test scripts"
    runs-on: ubuntu-latest
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - name: "Install Rust toolchain"
        run: rustup component add rustfmt
      # Run all code generation scripts, and verify that the current output is
      # already checked into git.
      - run: python crates/ruff_python_ast/generate.py
      - run: python crates/ruff_python_formatter/generate.py
      - run: test -z "$(git status --porcelain)"
      # Verify that adding a plugin or rule produces clean code.
      - run: ./scripts/add_rule.py --name DoTheThing --prefix F --code 999 --linter pyflakes
      - run: cargo check
      - run: |
          ./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
          ./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
      - run: cargo check
      # Lint/format/type-check py-fuzzer
      # (dogfooding with ty is done in a separate job)
      - run: uv run --directory=./python/py-fuzzer mypy
      - run: uv run --directory=./python/py-fuzzer ruff format --check
      - run: uv run --directory=./python/py-fuzzer ruff check

  ecosystem:
    name: "ecosystem"
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
    needs: determine_changes
    # Only runs on pull requests, since that is the only we way we can find the base version for comparison.
    # Ecosystem check needs linter and/or formatter changes.
    if: |
      !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' &&
      (
        needs.determine_changes.outputs.linter == 'true' ||
        needs.determine_changes.outputs.formatter == 'true'
      )
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          ref: ${{ github.event.pull_request.base.ref }}
          persist-credentials: false

      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          python-version: ${{ env.PYTHON_VERSION }}
          activate-environment: true
          version: "0.10.12"

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

      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          shared-key: ruff-linux-debug
          save-if: false

      - name: Build baseline version
        run: |
          cargo build --bin ruff
          mv target/debug/ruff target/debug/ruff-baseline

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
          clean: false

      - name: Build comparison version
        run: cargo build --bin ruff

      - name: Install ruff-ecosystem
        run: |
          uv pip install ./python/ruff-ecosystem

      - name: Run `ruff check` stable ecosystem check
        if: ${{ needs.determine_changes.outputs.linter == 'true' }}
        run: |
          # Set pipefail to avoid hiding errors with tee
          set -eo pipefail

          ruff-ecosystem check ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable

          cat ecosystem-result-check-stable > "$GITHUB_STEP_SUMMARY"
          echo "### Linter (stable)"  > ecosystem-result
          cat ecosystem-result-check-stable >> ecosystem-result
          echo "" >> ecosystem-result

      - name: Run `ruff check` preview ecosystem check
        if: ${{ needs.determine_changes.outputs.linter == 'true' }}
        run: |
          # Set pipefail to avoid hiding errors with tee
          set -eo pipefail

          ruff-ecosystem check ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview

          cat ecosystem-result-check-preview > "$GITHUB_STEP_SUMMARY"
          echo "### Linter (preview)" >> ecosystem-result
          cat ecosystem-result-check-preview >> ecosystem-result
          echo "" >> ecosystem-result

      - name: Run `ruff format` stable ecosystem check
        if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
        run: |
          # Set pipefail to avoid hiding errors with tee
          set -eo pipefail

          ruff-ecosystem format ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable

          cat ecosystem-result-format-stable > "$GITHUB_STEP_SUMMARY"
          echo "### Formatter (stable)" >> ecosystem-result
          cat ecosystem-result-format-stable >> ecosystem-result
          echo "" >> ecosystem-result

      - name: Run `ruff format` preview ecosystem check
        if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
        run: |
          # Set pipefail to avoid hiding errors with tee
          set -eo pipefail

          ruff-ecosystem format ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview

          cat ecosystem-result-format-preview > "$GITHUB_STEP_SUMMARY"
          echo "### Formatter (preview)" >> ecosystem-result
          cat ecosystem-result-format-preview >> ecosystem-result
          echo "" >> ecosystem-result

      # NOTE: astral-sh-bot uses this artifact to post comments on PRs.
      # Make sure to update the bot if you rename the artifact.
      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        name: Upload Results
        with:
          name: ecosystem-result
          path: ecosystem-result
          if-no-files-found: "error"

      - name: Fail on ecosystem errors
        run: |
          if grep -q "project error" ecosystem-result; then
            exit 1
          fi

  fuzz-ty:
    name: "Fuzz for new ty panics"
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
    needs:
      - determine_changes
    # Only runs on pull requests, since that is the only we way we can find the base version for comparison.
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
    timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 10 || 20 }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
      - name: Fuzz
        env:
          FORCE_COLOR: 1
        run: |
          echo "new commit"
          git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
          cargo build --profile=profiling --bin=ty
          mv target/profiling/ty ty-new

          MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
          git checkout -b old_commit "$MERGE_BASE"
          echo "old commit (merge base)"
          git rev-list --format=%s --max-count=1 old_commit
          cargo build --profile=profiling --bin=ty
          mv target/profiling/ty ty-old

          (
            uv run \
            --python="${PYTHON_VERSION}" \
            --project=./python/py-fuzzer \
            --locked \
            fuzz \
            --test-executable=ty-new \
            --baseline-executable=ty-old \
            --only-new-bugs \
            --bin=ty \
            0-1000
          )

  cargo-shear:
    name: "cargo shear"
    runs-on: ubuntu-latest
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: cargo-bins/cargo-binstall@1800853f2578f8c34492ec76154caef8e163fbca # v1.17.7
      - run: cargo binstall --no-confirm cargo-shear
      - run: cargo shear

  ty-completion-evaluation:
    name: "ty completion evaluation"
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
    needs: determine_changes
    if: ${{ needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main' }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
      - name: "Run ty completion evaluation"
        run: cargo run --profile profiling --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
      - name: "Ensure there are no changes"
        run: diff ./crates/ty_completion_eval/completion-evaluation-tasks.csv /tmp/completion-evaluation-tasks.csv

  python-package:
    name: "python package"
    runs-on: ubuntu-latest
    timeout-minutes: 20
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') }}
    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 }}
          architecture: x64
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Prep README.md"
        run: python scripts/transform_readme.py --target pypi
      - name: "Build wheels"
        uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.50.1
        with:
          args: --out dist
      - name: "Test wheel"
        run: |
          pip install --force-reinstall --find-links dist "${PACKAGE_NAME}"
          ruff --help
          python -m ruff --help
      - name: "Remove wheels from cache"
        run: rm -rf target/wheels

  prek:
    name: "prek"
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: 24
      - name: "Cache prek"
        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
        with:
          path: ~/.cache/prek
          key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
      - name: "Run prek"
        run: |
          echo '```console' > "$GITHUB_STEP_SUMMARY"
          # Enable color output for prek and remove it for the summary
          # Use --hook-stage=manual to enable slower hooks that are skipped by default
          SKIP=rustfmt uvx prek run --all-files --show-diff-on-failure --color always --hook-stage manual | \
            tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
          exit_code="${PIPESTATUS[0]}"
          echo '```' >> "$GITHUB_STEP_SUMMARY"
          exit "$exit_code"

  docs:
    name: "mkdocs"
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: Install uv
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          python-version: 3.13
          activate-environment: true
          version: "0.10.12"
      - name: "Install dependencies"
        run: uv pip install -r docs/requirements.txt
      - name: "Update README File"
        run: python scripts/transform_readme.py --target mkdocs
      - name: "Generate docs"
        run: python scripts/generate_mkdocs.py
      - name: "Check docs formatting"
        run: python scripts/check_docs_formatted.py
      - name: "Build docs"
        run: mkdocs build --strict -f mkdocs.yml

  check-formatter-instability-and-black-similarity:
    name: "formatter instabilities and black similarity"
    runs-on: ubuntu-latest
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.formatter == 'true' || github.ref == 'refs/heads/main') }}
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Run checks"
        run: scripts/formatter_ecosystem_checks.sh
      - name: "Github step summary"
        run: cat target/formatter-ecosystem/stats.txt > "$GITHUB_STEP_SUMMARY"
      - name: "Remove checkouts from cache"
        run: rm -r target/formatter-ecosystem

  check-ruff-lsp:
    name: "test ruff-lsp"
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: determine_changes
    if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
    steps:
      - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        name: "Checkout ruff source"
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          shared-key: ruff-linux-debug
          save-if: false

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

      - name: Build Ruff binary
        run: cargo build -p ruff --bin ruff

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        name: "Checkout ruff-lsp source"
        with:
          persist-credentials: false
          repository: "astral-sh/ruff-lsp"
          path: ruff-lsp

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          # installation fails on 3.13 and newer
          python-version: "3.12"

      - name: Install ruff-lsp dependencies
        run: |
          cd ruff-lsp
          just install

      - name: Run ruff-lsp tests
        run: |
          # Setup development binary
          pip uninstall --yes ruff
          export PATH="${PWD}/target/debug:${PATH}"
          ruff version

          cd ruff-lsp
          just test

  check-playground:
    name: "check playground"
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs:
      - determine_changes
    if: ${{ (needs.determine_changes.outputs.playground == 'true') }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install Rust toolchain"
        run: rustup target add wasm32-unknown-unknown
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: 24
          cache: "npm"
          cache-dependency-path: playground/package-lock.json
      - uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
      - name: "Install Node dependencies"
        run: npm ci --ignore-scripts
        working-directory: playground
      - name: "Build playgrounds"
        run: npm run dev:wasm
        working-directory: playground
      - name: "Run TypeScript checks"
        run: npm run check
        working-directory: playground
      - name: "Check formatting"
        run: npm run fmt:check
        working-directory: playground

  benchmarks-instrumented-ruff:
    name: "benchmarks instrumented (ruff)"
    runs-on: ubuntu-24.04
    needs: determine_changes
    if: |
      github.repository == 'astral-sh/ruff' &&
      (
        github.ref == 'refs/heads/main' ||
        needs.determine_changes.outputs.formatter == 'true' ||
        needs.determine_changes.outputs.linter == 'true' ||
        needs.determine_changes.outputs.parser == 'true' ||
        needs.determine_changes.outputs.benchmarks == 'true'
      )
    timeout-minutes: 20
    permissions:
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

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

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Build benchmarks"
        run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser

      - name: "Run benchmarks"
        uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4.11.1
        with:
          mode: simulation
          run: cargo codspeed run

  benchmarks-instrumented-ty-build:
    name: "benchmarks instrumented ty (build)"
    runs-on: depot-ubuntu-24.04-4
    needs: determine_changes
    if: |
      github.repository == 'astral-sh/ruff' &&
      (
        github.ref == 'refs/heads/main' ||
        needs.determine_changes.outputs.ty == 'true' ||
        needs.determine_changes.outputs.benchmarks == 'true'
      )
    timeout-minutes: 20
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}

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

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Build benchmarks"
        run: cargo codspeed build -m instrumentation --features "codspeed,ty_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty

      - name: "Upload benchmark binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: benchmarks-instrumented-ty-binary
          path: target/codspeed
          if-no-files-found: "error"
          retention-days: 1

  benchmarks-instrumented-ty-run:
    name: "benchmarks instrumented ty (${{ matrix.benchmark }})"
    runs-on: ubuntu-24.04
    needs: benchmarks-instrumented-ty-build
    timeout-minutes: 20
    permissions:
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed
    strategy:
      fail-fast: false
      matrix:
        benchmark:
          - "check_file|micro|anyio"
          - "attrs|hydra|datetype"
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Download benchmark binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: benchmarks-instrumented-ty-binary
          path: target/codspeed

      - name: "Restore binary permissions"
        # codspeed keeps changing the paths to the executable, so we have to be a bit more flexible
        run: find target/codspeed -type f -exec chmod +x {} +

      - name: "Run benchmarks"
        uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4.11.1
        with:
          mode: simulation
          run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"

  benchmarks-walltime-build:
    name: "benchmarks walltime (build)"
    # We only run this job if `github.repository == 'astral-sh/ruff'`,
    # so hardcoding depot here is fine
    runs-on: depot-ubuntu-22.04-arm-4
    needs: determine_changes
    if: |
      github.repository == 'astral-sh/ruff' &&
      (
        !contains(github.event.pull_request.labels.*.name, 'no-test') &&
        (
          needs.determine_changes.outputs.ty == 'true' ||
          needs.determine_changes.outputs.benchmarks == 'true' ||
          github.ref == 'refs/heads/main'
        )
      )
    timeout-minutes: 20
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

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

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Build benchmarks"
        run: cargo codspeed build -m walltime --features "codspeed,ty_walltime" --profile profiling --no-default-features -p ruff_benchmark

      - name: "Upload benchmark binary"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: benchmarks-walltime-binary
          path: target/codspeed
          if-no-files-found: "error"
          retention-days: 1

  benchmarks-walltime-run:
    name: "benchmarks walltime (${{ matrix.benchmark }})"
    runs-on: codspeed-macro
    needs: benchmarks-walltime-build
    timeout-minutes: 20
    permissions:
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed
    strategy:
      matrix:
        benchmark:
          - colour_science
          - "pandas|tanjun|altair"
          - "static_frame|sympy"
          - "pydantic|multithreaded|freqtrade"
    steps:
      - name: "Checkout Branch"
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"

      - name: "Install codspeed"
        uses: taiki-e/install-action@cbb1dcaa26e1459e2876c39f61c1e22a1258aac5 # v2.68.33
        with:
          tool: cargo-codspeed

      - name: "Download benchmark binary"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: benchmarks-walltime-binary
          path: target/codspeed

      - name: "Restore binary permissions"
        # codspeed keeps changing the paths to the executable, so we have to be a bit more flexible
        run: find target/codspeed -type f -exec chmod +x {} +

      - name: "Run benchmarks"
        uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4.11.1
        env:
          # enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
          # appear to provide much useful insight for our walltime benchmarks right now
          # (see https://github.com/astral-sh/ruff/pull/20419)
          CODSPEED_PERF_ENABLED: false
        with:
          mode: walltime
          run: cargo codspeed run --bench ty_walltime -m walltime "${{ matrix.benchmark }}"
daily_fuzz perms .github/workflows/daily_fuzz.yaml
Triggers
workflow_dispatch, schedule, pull_request
Runs on
ubuntu-latest, ubuntu-latest
Jobs
fuzz, create-issue-on-failure
Actions
astral-sh/setup-uv, rui314/setup-mold, Swatinem/rust-cache
Commands
  • rustup show
  • cargo build --locked
  • # shellcheck disable=SC2046 ( uv run \ --python=3.14 \ --project=./python/py-fuzzer \ --locked \ fuzz \ --test-executable=target/debug/ruff \ --bin=ruff \ $(shuf -i 0-9999999999999999999 -n 1000) )
View raw YAML
name: Daily parser fuzz

on:
  workflow_dispatch:
  schedule:
    - cron: "0 0 * * *"
  pull_request:
    paths:
      - ".github/workflows/daily_fuzz.yaml"

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  PACKAGE_NAME: ruff
  FORCE_COLOR: 1

jobs:
  fuzz:
    name: Fuzz
    runs-on: ubuntu-latest
    timeout-minutes: 20
    # Don't run the cron job on forks:
    if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - name: "Install Rust toolchain"
        run: rustup show
      - name: "Install mold"
        uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
      - name: Build ruff
        # A debug build means the script runs slower once it gets started,
        # but this is outweighed by the fact that a release build takes *much* longer to compile in CI
        run: cargo build --locked
      - name: Fuzz
        run: |
          # shellcheck disable=SC2046
          (
            uv run \
            --python=3.14 \
            --project=./python/py-fuzzer \
            --locked \
            fuzz \
            --test-executable=target/debug/ruff \
            --bin=ruff \
            $(shuf -i 0-9999999999999999999 -n 1000)
          )

  create-issue-on-failure:
    name: Create an issue if the daily fuzz surfaced any bugs
    runs-on: ubuntu-latest
    needs: fuzz
    if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
    permissions:
      issues: write
    steps:
      - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            await github.rest.issues.create({
              owner: "astral-sh",
              repo: "ruff",
              title: `Daily parser fuzz failed on ${new Date().toDateString()}`,
              body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
              labels: ["bug", "parser", "fuzzer"],
            })
memory_report perms .github/workflows/memory_report.yaml
Triggers
pull_request
Runs on
${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
Jobs
memory_report
Actions
Swatinem/rust-cache
Commands
  • rustup show
  • # Build the executable for the old and new commit ( cd ruff echo "new commit" git rev-list --format=%s --max-count=1 "$GITHUB_SHA" cargo build --bin ty --profile profiling mv target/profiling/ty ty-new MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")" git checkout -b old_commit "$MERGE_BASE" echo "old commit (merge base)" git rev-list --format=%s --max-count=1 old_commit cargo build --bin ty --profile profiling mv target/profiling/ty ty-old ) ( echo "Creating comment with memory comparison" cd ruff git switch - --detach python "./scripts/memory_report.py" run \ --old-ty "./ty-old" \ --new-ty "./ty-new" \ --projects-dir "${GITHUB_WORKSPACE}/memory_projects" \ --output ../memory_report_diff.diff )
View raw YAML
name: Run memory report

permissions: {}

on:
  pull_request:
    paths:
      - "crates/ty*/**"
      - "!crates/ty_ide/**"
      - "!crates/ty_server/**"
      - "!crates/ty_test/**"
      - "!crates/ty_completion_eval/**"
      - "!crates/ty_wasm/**"
      - "crates/ruff_db"
      - "crates/ruff_python_ast"
      - "crates/ruff_python_parser"
      - "scripts/memory_report.py"
      - ".github/workflows/memory_report.yaml"
      - "Cargo.lock"
      - "!**.md"
      - "!**.snap"
      # It's tempting to skip all Python files in every directory,
      # but changes to Python files in `ty_vendored` can affect the output of ecosystem runs,
      # so we apply a narrow exemption for all files in the corpus directory instead.
      - "!crates/ty_python_semantic/resources/corpus/**"

concurrency:
  group: memory-report-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  RUST_BACKTRACE: 1
  PYTHON_VERSION: 3.14

jobs:
  memory_report:
    name: Compute memory usage diff
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          path: ruff
          fetch-depth: 0
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: "ruff"

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

      - name: Install Rust toolchain
        run: rustup show

      - name: Compute memory usage diff
        shell: bash
        env:
          TY_MEMORY_REPORT: json
          # Disabling multithreading improves the stability of memory usage numbers.
          TY_MAX_PARALLELISM: 1
        run: |
          # Build the executable for the old and new commit
          (
            cd ruff

            echo "new commit"
            git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
            cargo build --bin ty --profile profiling
            mv target/profiling/ty ty-new

            MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
            git checkout -b old_commit "$MERGE_BASE"
            echo "old commit (merge base)"
            git rev-list --format=%s --max-count=1 old_commit
            cargo build --bin ty --profile profiling
            mv target/profiling/ty ty-old
          )

          (
            echo "Creating comment with memory comparison"
            cd ruff
            git switch - --detach

            python "./scripts/memory_report.py" run \
              --old-ty "./ty-old" \
              --new-ty "./ty-new" \
              --projects-dir "${GITHUB_WORKSPACE}/memory_projects" \
              --output ../memory_report_diff.diff
          )

      # NOTE: astral-sh-bot uses this artifact to post comments on PRs.
      # Make sure to update the bot if you rename the artifact.
      - name: Upload diff
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: memory_report_diff
          path: memory_report_diff.diff
notify-dependents .github/workflows/notify-dependents.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
update-dependents
View raw YAML
# Notify downstream repositories of a new release.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: "[ruff] Notify dependents"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

jobs:
  update-dependents:
    name: Notify dependents
    runs-on: ubuntu-latest
    steps:
      - name: "Update pre-commit mirror"
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }}
          script: |
            github.rest.actions.createWorkflowDispatch({
              owner: 'astral-sh',
              repo: 'ruff-pre-commit',
              workflow_id: 'main.yml',
              ref: 'main',
            })
publish-docs perms .github/workflows/publish-docs.yml
Triggers
workflow_dispatch, workflow_call
Runs on
ubuntu-latest
Jobs
mkdocs
Actions
Swatinem/rust-cache
Commands
  • # if version is missing, use 'latest' if [ -z "$version" ]; then echo "Using 'latest' as version" version="latest" fi # Use version as display name for now display_name="$version" echo "version=$version" >> "$GITHUB_ENV" echo "display_name=$display_name" >> "$GITHUB_ENV"
  • timestamp="$(date +%s)" # create branch_display_name from display_name by replacing all # characters disallowed in git branch names with hyphens branch_display_name="$(echo "${display_name}" | tr -c '[:alnum:]._' '-' | tr -s '-')" echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV" echo "timestamp=$timestamp" >> "$GITHUB_ENV"
  • rustup show
  • pip install -r docs/requirements.txt
  • python scripts/transform_readme.py --target mkdocs python scripts/generate_mkdocs.py
  • mkdocs build --strict -f mkdocs.yml
  • git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
  • rm -rf astral-docs/site/ruff && mkdir -p astral-docs/site && cp -r site/ruff astral-docs/site/
View raw YAML
# Publish the Ruff documentation.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: mkdocs

on:
  workflow_dispatch:
    inputs:
      ref:
        description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified."
        default: ""
        type: string
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions:
  contents: read

jobs:
  mkdocs:
    environment:
      name: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          ref: ${{ inputs.ref }}
          persist-credentials: true

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

      - name: "Set docs version"
        env:
          version: ${{ (inputs.plan != '' && fromJson(inputs.plan).announcement_tag) || inputs.ref }}
        run: |
          # if version is missing, use 'latest'
          if [ -z "$version" ]; then
            echo "Using 'latest' as version"
            version="latest"
          fi

          # Use version as display name for now
          display_name="$version"

          echo "version=$version" >> "$GITHUB_ENV"
          echo "display_name=$display_name" >> "$GITHUB_ENV"

      - name: "Set branch name"
        run: |
          timestamp="$(date +%s)"

          # create branch_display_name from display_name by replacing all
          # characters disallowed in git branch names with hyphens
          branch_display_name="$(echo "${display_name}" | tr -c '[:alnum:]._' '-' | tr -s '-')"

          echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
          echo "timestamp=$timestamp" >> "$GITHUB_ENV"

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

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1

      - name: "Install dependencies"
        run: pip install -r docs/requirements.txt

      - name: "Copy README File"
        run: |
          python scripts/transform_readme.py --target mkdocs
          python scripts/generate_mkdocs.py

      - name: "Build docs"
        run: mkdocs build --strict -f mkdocs.yml

      - name: "Clone docs repo"
        run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs

      - name: "Copy docs"
        run: rm -rf astral-docs/site/ruff && mkdir -p astral-docs/site && cp -r site/ruff astral-docs/site/

      - name: "Commit docs"
        working-directory: astral-docs
        run: |
          git config user.name "astral-docs-bot"
          git config user.email "176161322+astral-docs-bot@users.noreply.github.com"

          git checkout -b "${branch_name}"
          git add site/ruff
          git commit -m "Update ruff documentation for $version"

      - name: "Create Pull Request"
        working-directory: astral-docs
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
        run: |
          # set the PR title
          pull_request_title="Update ruff documentation for ${display_name}"

          # Delete any existing pull requests that are open for this version
          # by checking against pull_request_title because the new PR will
          # supersede the old one.
          gh pr list --state open --json title --jq '.[] | select(.title == "$pull_request_title") | .number' | \
            xargs -I {} gh pr close {}

          # push the branch to GitHub
          git push origin "${branch_name}"

          # create the PR
          gh pr create \
            --base=main \
            --head="${branch_name}" \
            --title="${pull_request_title}" \
            --body="Automated documentation update for ${display_name}" \
            --label="documentation"

      - name: "Merge Pull Request"
        if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
        working-directory: astral-docs
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
        run: |
          # auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
          # give the PR a few seconds to be created before trying to auto-merge it
          sleep 10
          gh pr merge --squash "${branch_name}"
publish-mirror perms .github/workflows/publish-mirror.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
publish-mirror
Commands
  • aws s3 cp --recursive --output table --color on \ --exclude '*' \ --include '*.zip' --include '*.zip.sha256' \ --include '*.tar.gz' --include '*.tar.gz.sha256' \ --include sha256.sum --include '*.ps1' --include '*.sh' \ --cache-control "public, max-age=31536000, immutable" \ artifacts/ \ "s3://${R2_BUCKET}/github/${PROJECT}/releases/download/${VERSION}/"
View raw YAML
# Publish ruff releases to a mirror
#
# Assumed to run as a subworkflow of .github/workflows/release.yml as a custom publish job
name: publish-mirror

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions: {}

jobs:
  publish-mirror:
    runs-on: ubuntu-latest
    environment:
      name: release
    env:
      VERSION: ${{ fromJson(inputs.plan).announcement_tag }}
    steps:
      - name: "Download GitHub Artifacts"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: artifacts-*
          path: artifacts
          merge-multiple: true
      - name: "Upload to R2"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.MIRROR_R2_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.MIRROR_R2_SECRET_ACCESS_KEY }}
          AWS_ENDPOINT_URL: https://${{ secrets.MIRROR_R2_CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
          AWS_DEFAULT_REGION: auto
          R2_BUCKET: ${{ secrets.MIRROR_R2_BUCKET_NAME }}
          PROJECT: ruff
        run: |
          aws s3 cp --recursive --output table --color on \
            --exclude '*' \
            --include '*.zip' --include '*.zip.sha256' \
            --include '*.tar.gz' --include '*.tar.gz.sha256' \
            --include sha256.sum --include '*.ps1' --include '*.sh' \
            --cache-control "public, max-age=31536000, immutable" \
            artifacts/ \
            "s3://${R2_BUCKET}/github/${PROJECT}/releases/download/${VERSION}/"
publish-playground perms .github/workflows/publish-playground.yml
Triggers
workflow_dispatch, workflow_call
Runs on
ubuntu-latest
Jobs
publish
Actions
jetli/wasm-bindgen-action, cloudflare/wrangler-action
Commands
  • rustup target add wasm32-unknown-unknown
  • npm ci --ignore-scripts
  • npm run check
  • npm run build --workspace ruff-playground
View raw YAML
# Publish the Ruff playground.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: "[Playground] Release"

on:
  workflow_dispatch:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

permissions: {}

jobs:
  publish:
    runs-on: ubuntu-latest
    environment: release-playground
    env:
      CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install Rust toolchain"
        run: rustup target add wasm32-unknown-unknown
      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: 24
          package-manager-cache: false
      - uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
      - name: "Install Node dependencies"
        run: npm ci --ignore-scripts
        working-directory: playground
      - name: "Run TypeScript checks"
        run: npm run check
        working-directory: playground
      - name: "Build Ruff playground"
        run: npm run build --workspace ruff-playground
        working-directory: playground
      - name: "Deploy to Cloudflare Pages"
        if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
        uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          # `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
          command: pages deploy playground/ruff/dist --project-name=ruff-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
publish-pypi .github/workflows/publish-pypi.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
pypi-publish
Actions
astral-sh/setup-uv
Commands
  • uv publish -v wheels/*
View raw YAML
# Publish a release to PyPI.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job
# within `cargo-dist`.
name: "[ruff] Publish to PyPI"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

jobs:
  pypi-publish:
    name: Upload to PyPI
    runs-on: ubuntu-latest
    environment:
      name: release
    permissions:
      # For PyPI's trusted publishing.
      id-token: write
    steps:
      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: wheels-*
          path: wheels
          merge-multiple: true
      - name: Publish to PyPi
        run: uv publish -v wheels/*
publish-ty-playground perms .github/workflows/publish-ty-playground.yml
Triggers
push
Runs on
ubuntu-latest
Jobs
publish
Actions
jetli/wasm-bindgen-action, cloudflare/wrangler-action
Commands
  • rustup target add wasm32-unknown-unknown
  • npm ci --ignore-scripts
  • npm run check
  • npm run build --workspace ty-playground
View raw YAML
# Publish the ty playground.
name: "[ty Playground] Release"

permissions: {}

on:
  push:
    branches: [main]
    paths:
      - "crates/ty*/**"
      - "crates/ruff_db/**"
      - "crates/ruff_python_ast/**"
      - "crates/ruff_python_parser/**"
      - "playground/**"
      - ".github/workflows/publish-ty-playground.yml"

concurrency:
  group: publish-ty-playground-${{ github.ref_name }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10

jobs:
  publish:
    runs-on: ubuntu-latest
    environment: release-playground
    env:
      CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: "Install Rust toolchain"
        run: rustup target add wasm32-unknown-unknown
      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: 24
          package-manager-cache: false
      - uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
      - name: "Install Node dependencies"
        run: npm ci --ignore-scripts
        working-directory: playground
      - name: "Run TypeScript checks"
        run: npm run check
        working-directory: playground
      - name: "Build ty playground"
        run: npm run build --workspace ty-playground
        working-directory: playground
      - name: "Deploy to Cloudflare Pages"
        if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
        uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
        with:
          apiToken: ${{ secrets.CF_API_TOKEN }}
          accountId: ${{ secrets.CF_ACCOUNT_ID }}
          # `github.head_ref` is only set during pull requests and for manual runs or tags we use `main` to deploy to production
          command: pages deploy playground/ty/dist --project-name=ty-playground --branch ${{ github.head_ref || 'main' }} --commit-hash ${GITHUB_SHA}
publish-versions perms .github/workflows/publish-versions.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
publish-versions
Actions
astral-sh/setup-uv
Commands
  • echo "BRANCH_NAME=update-versions-$VERSION-$(date +%s)" >> "$GITHUB_ENV"
  • git clone https://${{ secrets.ASTRAL_VERSIONS_PAT }}@github.com/astral-sh/versions.git astral-versions
  • printf '%s' "$PLAN" | uv run astral-versions/scripts/convert-cargo-dist-plan.py | uv run astral-versions/scripts/insert-versions.py --name ruff
  • git config user.name "astral-versions-bot" git config user.email "176161322+astral-versions-bot@users.noreply.github.com" git checkout -b "$BRANCH_NAME" git add -A git commit -m "Update ruff to version $VERSION"
  • pull_request_title="Add ruff $VERSION" gh pr list --state open --json title --jq ".[] | select(.title == \"$pull_request_title\") | .number" | \ xargs -I {} gh pr close {} git push origin "$BRANCH_NAME" gh pr create --base main --head "$BRANCH_NAME" \ --title "$pull_request_title" \ --body "Automated versions update for $VERSION" \ --label "automation"
  • # Wait for PR to be created before merging sleep 10 gh pr merge --squash "$BRANCH_NAME"
View raw YAML
# Publish ruff version information to the versions repository.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a post-announce
# job within `cargo-dist`.
name: publish-versions

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

permissions: {}

jobs:
  publish-versions:
    runs-on: ubuntu-latest
    environment: release
    env:
      VERSION: ${{ fromJson(inputs.plan).announcement_tag }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: "Set branch name"
        run: echo "BRANCH_NAME=update-versions-$VERSION-$(date +%s)" >> "$GITHUB_ENV"

      - name: "Clone versions repo"
        run: git clone https://${{ secrets.ASTRAL_VERSIONS_PAT }}@github.com/astral-sh/versions.git astral-versions

      - name: "Install uv"
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0

      - name: "Update versions"
        env:
          PLAN: ${{ inputs.plan }}
        run: printf '%s' "$PLAN" | uv run astral-versions/scripts/convert-cargo-dist-plan.py | uv run astral-versions/scripts/insert-versions.py --name ruff

      - name: "Commit versions"
        working-directory: astral-versions
        run: |
          git config user.name "astral-versions-bot"
          git config user.email "176161322+astral-versions-bot@users.noreply.github.com"

          git checkout -b "$BRANCH_NAME"
          git add -A
          git commit -m "Update ruff to version $VERSION"

      - name: "Create Pull Request"
        working-directory: astral-versions
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
        run: |
          pull_request_title="Add ruff $VERSION"

          gh pr list --state open --json title --jq ".[] | select(.title == \"$pull_request_title\") | .number" | \
            xargs -I {} gh pr close {}

          git push origin "$BRANCH_NAME"

          gh pr create --base main --head "$BRANCH_NAME" \
            --title "$pull_request_title" \
            --body "Automated versions update for $VERSION" \
            --label "automation"

      - name: "Merge Pull Request"
        if: ${{ !fromJson(inputs.plan).announcement_tag_is_implicit }}
        working-directory: astral-versions
        env:
          GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
        run: |
          # Wait for PR to be created before merging
          sleep 10
          gh pr merge --squash "$BRANCH_NAME"
publish-wasm matrix .github/workflows/publish-wasm.yml
Triggers
workflow_call
Runs on
ubuntu-latest
Jobs
publish
Matrix
target→ bundler, nodejs, web
Commands
  • npm install -g npm@11.12.0
  • npm publish --dry-run pkg/
  • npm publish --provenance --access public pkg/
View raw YAML
# Publish ruff_wasm to npm.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish
# job within `cargo-dist`.
name: "Publish wasm"

on:
  workflow_call:
    inputs:
      plan:
        required: true
        type: string

jobs:
  publish:
    runs-on: ubuntu-latest
    environment: release
    permissions:
      contents: read
      id-token: write
    strategy:
      matrix:
        target: [web, bundler, nodejs]
      fail-fast: false
    steps:
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: wasm-npm-${{ matrix.target }}
          path: pkg
      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: 24
          registry-url: "https://registry.npmjs.org"
      - name: "Upgrade npm"
        # npm trusted publishing requires npm 11.5.1 or later
        run: npm install -g npm@11.12.0
      - name: "Publish (dry-run)"
        if: ${{ inputs.plan == '' || fromJson(inputs.plan).announcement_tag_is_implicit }}
        run: npm publish --dry-run pkg/
      - name: "Publish"
        if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
        run: npm publish --provenance --access public pkg/
release perms .github/workflows/release.yml
Triggers
pull_request, workflow_dispatch
Runs on
depot-ubuntu-latest-4, depot-ubuntu-latest-4, depot-ubuntu-latest-4, depot-ubuntu-latest-4
Jobs
plan, custom-build-binaries, custom-build-docker, custom-build-wasm, build-global-artifacts, host, custom-publish-pypi, custom-publish-wasm, announce, custom-notify-dependents, custom-publish-docs, custom-publish-playground, custom-publish-versions, custom-publish-mirror
Actions
actions/attest-build-provenance
Commands
  • curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh
  • dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json echo "dist ran successfully" cat plan-dist-manifest.json echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
  • chmod +x ~/.cargo/bin/dist
  • dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json echo "dist ran successfully" # Parse out what we just built and upload it to scratch storage echo "paths<<EOF" >> "$GITHUB_OUTPUT" jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" cp dist-manifest.json "$BUILD_MANIFEST_NAME"
  • chmod +x ~/.cargo/bin/dist
  • dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json echo "artifacts uploaded and released successfully" cat dist-manifest.json echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
  • # Remove the granular manifests rm -f artifacts/*-dist-manifest.json
  • # Write and read notes from a file to avoid quoting breaking things echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
View raw YAML
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
#
# Copyright 2022-2024, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
#
# Note that the GitHub Release will be created with a generated
# title/body based on your changelogs.

name: Release
permissions:
  "contents": "write"

# This task will run whenever you workflow_dispatch with a tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
  pull_request:
  workflow_dispatch:
    inputs:
      tag:
        description: Release Tag
        required: true
        default: dry-run
        type: string

jobs:
  # Run 'dist plan' (or host) to determine what tasks we need to do
  plan:
    runs-on: "depot-ubuntu-latest-4"
    outputs:
      val: ${{ steps.plan.outputs.manifest }}
      tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }}
      tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }}
      publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }}
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install dist
        # we specify bash to get pipefail; it guards against the `curl` command
        # failing. otherwise `sh` won't catch that `curl` returned non-0
        shell: bash
        run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh"
      - name: Cache dist
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/dist
      # sure would be cool if github gave us proper conditionals...
      # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
      # functionality based on whether this is a pull_request, and whether it's from a fork.
      # (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
      # but also really annoying to build CI around when it needs secrets to work right.)
      - id: plan
        run: |
          dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json
          echo "dist ran successfully"
          cat plan-dist-manifest.json
          echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
      - name: "Upload dist-manifest.json"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          name: artifacts-plan-dist-manifest
          path: plan-dist-manifest.json

  custom-build-binaries:
    needs:
      - plan
    if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
    uses: ./.github/workflows/build-binaries.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-build-docker:
    needs:
      - plan
    if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
    uses: ./.github/workflows/build-docker.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    permissions:
      "attestations": "write"
      "contents": "read"
      "id-token": "write"
      "packages": "write"

  custom-build-wasm:
    needs:
      - plan
    if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }}
    uses: ./.github/workflows/build-wasm.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  # Build and package all the platform-agnostic(ish) things
  build-global-artifacts:
    needs:
      - plan
      - custom-build-binaries
      - custom-build-docker
      - custom-build-wasm
    runs-on: "depot-ubuntu-latest-4"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install cached dist
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/
      - run: chmod +x ~/.cargo/bin/dist
      # Get all the local artifacts for the global tasks to use (for e.g. checksums)
      - name: Fetch local artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true
      - id: cargo-dist
        shell: bash
        run: |
          dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
          echo "dist ran successfully"

          # Parse out what we just built and upload it to scratch storage
          echo "paths<<EOF" >> "$GITHUB_OUTPUT"
          jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
          echo "EOF" >> "$GITHUB_OUTPUT"

          cp dist-manifest.json "$BUILD_MANIFEST_NAME"
      - name: "Upload artifacts"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          name: artifacts-build-global
          path: |
            ${{ steps.cargo-dist.outputs.paths }}
            ${{ env.BUILD_MANIFEST_NAME }}
  # Determines if we should publish/announce
  host:
    needs:
      - plan
      - custom-build-binaries
      - custom-build-docker
      - custom-build-wasm
      - build-global-artifacts
    # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
    if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') && (needs.custom-build-wasm.result == 'skipped' || needs.custom-build-wasm.result == 'success') }}
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    runs-on: "depot-ubuntu-latest-4"
    outputs:
      val: ${{ steps.host.outputs.manifest }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
        with:
          persist-credentials: false
          submodules: recursive
      - name: Install cached dist
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          name: cargo-dist-cache
          path: ~/.cargo/bin/
      - run: chmod +x ~/.cargo/bin/dist
      # Fetch artifacts from scratch-storage
      - name: Fetch artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: artifacts-*
          path: target/distrib/
          merge-multiple: true
      # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce"
      - id: host
        shell: bash
        run: |
          dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
          echo "artifacts uploaded and released successfully"
          cat dist-manifest.json
          echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
      - name: "Upload dist-manifest.json"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        with:
          # Overwrite the previous copy
          name: artifacts-dist-manifest
          path: dist-manifest.json

  custom-publish-pypi:
    needs:
      - plan
      - host
    if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
    uses: ./.github/workflows/publish-pypi.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    # publish jobs get escalated permissions
    permissions:
      "id-token": "write"
      "packages": "write"

  custom-publish-wasm:
    needs:
      - plan
      - host
    if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
    uses: ./.github/workflows/publish-wasm.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    # publish jobs get escalated permissions
    permissions:
      "contents": "read"
      "id-token": "write"
      "packages": "write"

  # Create a GitHub Release while uploading all files to it
  announce:
    needs:
      - plan
      - host
      - custom-publish-pypi
      - custom-publish-wasm
    # use "always() && ..." to allow us to wait for all publish jobs while
    # still allowing individual publish jobs to skip themselves (for prereleases).
    # "host" however must run to completion, no skipping allowed!
    if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-wasm.result == 'skipped' || needs.custom-publish-wasm.result == 'success') }}
    runs-on: "depot-ubuntu-latest-4"
    permissions:
      "attestations": "write"
      "contents": "write"
      "id-token": "write"
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
        with:
          persist-credentials: false
          submodules: recursive
      # Create a GitHub Release while uploading all files to it
      - name: "Download GitHub Artifacts"
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: artifacts-*
          path: artifacts
          merge-multiple: true
      - name: Cleanup
        run: |
          # Remove the granular manifests
          rm -f artifacts/*-dist-manifest.json
      - name: Attest
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32
        with:
          subject-path: |
            artifacts/*.json
            artifacts/*.sh
            artifacts/*.ps1
            artifacts/*.zip
            artifacts/*.tar.gz
      - name: Create GitHub Release
        env:
          PRERELEASE_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}"
          ANNOUNCEMENT_TITLE: "${{ fromJson(needs.host.outputs.val).announcement_title }}"
          ANNOUNCEMENT_BODY: "${{ fromJson(needs.host.outputs.val).announcement_github_body }}"
          RELEASE_COMMIT: "${{ github.sha }}"
        run: |
          # Write and read notes from a file to avoid quoting breaking things
          echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt

          gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*

  custom-notify-dependents:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/notify-dependents.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-docs:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-docs.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-playground:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-playground.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-versions:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-versions.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit

  custom-publish-mirror:
    needs:
      - plan
      - announce
    uses: ./.github/workflows/publish-mirror.yml
    with:
      plan: ${{ needs.plan.outputs.val }}
    secrets: inherit
    permissions:
      "contents": "read"
sync_typeshed .github/workflows/sync_typeshed.yaml
Triggers
workflow_dispatch, schedule
Runs on
ubuntu-latest, windows-latest, macos-latest, ubuntu-latest
Jobs
sync, docstrings-windows, docstrings-macos-and-pr, create-issue-on-failure
Actions
astral-sh/setup-uv, astral-sh/setup-uv, astral-sh/setup-uv
Commands
  • git config --global user.name typeshedbot git config --global user.email '<>'
  • rm -rf "ruff/${VENDORED_TYPESHED}" mkdir "ruff/${VENDORED_TYPESHED}" cp typeshed/README.md "ruff/${VENDORED_TYPESHED}" cp typeshed/LICENSE "ruff/${VENDORED_TYPESHED}" # The pyproject.toml file is needed by a later job for the black configuration. # It's deleted before creating the PR. cp typeshed/pyproject.toml "ruff/${VENDORED_TYPESHED}" cp -r typeshed/stdlib "ruff/${VENDORED_TYPESHED}/stdlib" rm -rf "ruff/${VENDORED_TYPESHED}/stdlib/@tests" git -C typeshed rev-parse HEAD > "ruff/${VENDORED_TYPESHED}/source_commit.txt" cd ruff git checkout -b "${UPSTREAM_BRANCH}" git add . git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)" --allow-empty
  • cd ruff ./scripts/codemod_docstrings.sh git commit -am "Sync Linux docstrings" --allow-empty
  • git -C ruff push --force --set-upstream origin "${UPSTREAM_BRANCH}"
  • git config --global user.name typeshedbot git config --global user.email '<>'
  • ./scripts/codemod_docstrings.sh
  • git commit -am "Sync Windows docstrings" --allow-empty git push
  • git config --global user.name typeshedbot git config --global user.email '<>'
View raw YAML
name: Sync typeshed

# How this works:
#
# 1. A Linux worker:
#    a. Checks out Ruff and typeshed
#    b. Deletes the vendored typeshed stdlib stubs from Ruff
#    c. Copies the latest versions of the stubs from typeshed
#    d. Uses docstring-adder to sync all docstrings available on Linux
#    e. Creates a new branch on the upstream astral-sh/ruff repository
#    f. Commits the changes it's made and pushes them to the new upstream branch
# 2. Once the Linux worker is done, a Windows worker:
#    a. Checks out the branch created by the Linux worker
#    b. Syncs all docstrings available on Windows that are not available on Linux
#    c. Commits the changes and pushes them to the same upstream branch
# 3. Once the Windows worker is done, a MacOS worker:
#    a. Checks out the branch created by the Linux worker
#    b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
#    c. Formats the code again
#    d. Commits the changes and pushes them to the same upstream branch
#    e. Creates a PR against the `main` branch using the branch all three workers have pushed to
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository

on:
  workflow_dispatch:
  schedule:
    # Run on the 1st and the 15th of every month:
    - cron: "0 0 1,15 * *"

defaults:
  run:
    shell: bash

env:
  # Don't set this flag globally for the workflow: it does strange things
  # to the snapshots in the `cargo insta test --accept` step in the MacOS job.
  #
  # FORCE_COLOR: 1

  CARGO_TERM_COLOR: always
  NEXTEST_PROFILE: "ci"
  GH_TOKEN: ${{ github.token }}

  # The name of the upstream branch that the first worker creates,
  # and which all three workers push to.
  UPSTREAM_BRANCH: typeshedbot/sync-typeshed

  # The path to the directory that contains the vendored typeshed stubs,
  # relative to the root of the Ruff repository.
  VENDORED_TYPESHED: crates/ty_vendored/vendor/typeshed

jobs:
  # Sync typeshed stubs, and sync all docstrings available on Linux.
  # Push the changes to a new branch on the upstream repository.
  sync:
    name: Sync typeshed
    runs-on: ubuntu-latest
    timeout-minutes: 20
    # Don't run the cron job on forks:
    if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        name: Checkout Ruff
        with:
          path: ruff
          persist-credentials: true
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        name: Checkout typeshed
        with:
          repository: python/typeshed
          path: typeshed
          persist-credentials: false
      - name: Setup git
        run: |
          git config --global user.name typeshedbot
          git config --global user.email '<>'
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - name: Sync typeshed stubs
        run: |
          rm -rf "ruff/${VENDORED_TYPESHED}"
          mkdir "ruff/${VENDORED_TYPESHED}"
          cp typeshed/README.md "ruff/${VENDORED_TYPESHED}"
          cp typeshed/LICENSE "ruff/${VENDORED_TYPESHED}"

          # The pyproject.toml file is needed by a later job for the black configuration.
          # It's deleted before creating the PR.
          cp typeshed/pyproject.toml "ruff/${VENDORED_TYPESHED}"

          cp -r typeshed/stdlib "ruff/${VENDORED_TYPESHED}/stdlib"
          rm -rf "ruff/${VENDORED_TYPESHED}/stdlib/@tests"
          git -C typeshed rev-parse HEAD > "ruff/${VENDORED_TYPESHED}/source_commit.txt"
          cd ruff
          git checkout -b "${UPSTREAM_BRANCH}"
          git add .
          git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)" --allow-empty
      - name: Sync Linux docstrings
        if: ${{ success() }}
        env:
          FORCE_COLOR: 1
        run: |
          cd ruff
          ./scripts/codemod_docstrings.sh
          git commit -am "Sync Linux docstrings" --allow-empty
      - name: Push the changes
        id: commit
        if: ${{ success() }}
        run: git -C ruff push --force --set-upstream origin "${UPSTREAM_BRANCH}"

  # Checkout the branch created by the sync job,
  # and sync all docstrings available on Windows that are not available on Linux.
  # Commit the changes and push them to the same branch.
  docstrings-windows:
    runs-on: windows-latest
    timeout-minutes: 20
    needs: [sync]

    # Don't run the cron job on forks.
    # The job will also be skipped if the sync job failed, because it's specified in `needs` above,
    # and we haven't used `always()` in the `if` condition here
    # (https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-requiring-successful-dependent-jobs)
    if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}

    permissions:
      contents: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        name: Checkout Ruff
        with:
          persist-credentials: true
          ref: ${{ env.UPSTREAM_BRANCH}}
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - name: Setup git
        run: |
          git config --global user.name typeshedbot
          git config --global user.email '<>'
      - name: Sync Windows docstrings
        id: docstrings
        env:
          FORCE_COLOR: 1
        run: ./scripts/codemod_docstrings.sh
      - name: Commit the changes
        if: ${{ steps.docstrings.outcome == 'success' }}
        run: |
          git commit -am "Sync Windows docstrings" --allow-empty
          git push

  # Checkout the branch created by the sync job,
  # and sync all docstrings available on macOS that are not available on Linux or Windows.
  # Push the changes to the same branch and create a PR against the `main` branch using that branch.
  docstrings-macos-and-pr:
    runs-on: macos-latest
    timeout-minutes: 20
    needs: [sync, docstrings-windows]

    # Don't run the cron job on forks.
    # The job will also be skipped if the sync or docstrings-windows jobs failed,
    # because they're specified in `needs` above and we haven't used an `always()` condition in the `if` here
    # (https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-requiring-successful-dependent-jobs)
    if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}

    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        name: Checkout Ruff
        with:
          persist-credentials: true
          ref: ${{ env.UPSTREAM_BRANCH}}
      - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          version: "0.10.12"
      - name: Setup git
        run: |
          git config --global user.name typeshedbot
          git config --global user.email '<>'
      - name: Sync macOS docstrings
        if: ${{ success() }}
        env:
          FORCE_COLOR: 1
        run: |
          ./scripts/codemod_docstrings.sh
          git commit -am "Sync macOS docstrings" --allow-empty
      - name: Format the changes
        if: ${{ success() }}
        env:
          FORCE_COLOR: 1
        run: |
          # Here we just reformat the codemodded stubs so that they are
          # consistent with the other typeshed stubs around them.
          # Typeshed formats code using black in their CI, so we just invoke
          # black on the stubs the same way that typeshed does.
          uvx black "${VENDORED_TYPESHED}/stdlib" --config "${VENDORED_TYPESHED}/pyproject.toml" || true
          git commit -am "Format codemodded docstrings" --allow-empty
      - name: Remove typeshed pyproject.toml file
        if: ${{ success() }}
        run: |
          rm "${VENDORED_TYPESHED}/pyproject.toml"
          git commit -am "Remove pyproject.toml file"
      - name: Push changes upstream and create a PR
        if: ${{ success() }}
        run: |
          git push
          gh pr list --repo "${GITHUB_REPOSITORY}" --head "${UPSTREAM_BRANCH}" --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
          gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty"

  create-issue-on-failure:
    name: Create an issue if the typeshed sync failed
    runs-on: ubuntu-latest
    needs: [sync, docstrings-windows, docstrings-macos-and-pr]
    if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
    permissions:
      issues: write
    steps:
      - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            await github.rest.issues.create({
              owner: "astral-sh",
              repo: "ruff",
              title: `Automated typeshed sync failed on ${new Date().toDateString()}`,
              body: "Run listed here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}",
              labels: ["bug", "ty"],
            })
ty-ecosystem-analyzer perms .github/workflows/ty-ecosystem-analyzer.yaml
Triggers
pull_request
Runs on
${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
Jobs
ty-ecosystem-analyzer
Actions
astral-sh/setup-uv, Swatinem/rust-cache
Commands
  • rustup show
  • cd ruff echo "Enabling configuration overloads (see .github/ty-ecosystem.toml)" mkdir -p ~/.config/ty cp .github/ty-ecosystem.toml ~/.config/ty/ty.toml echo "new commit" git checkout -b new_commit "$GITHUB_SHA" git rev-list --format=%s --max-count=1 new_commit cp crates/ty_python_semantic/resources/primer/good.txt projects_new.txt cp crates/ty_python_semantic/resources/primer/flaky.txt projects_flaky.txt echo "old commit (merge base)" MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")" git checkout -b old_commit "$MERGE_BASE" git rev-list --format=%s --max-count=1 old_commit cp crates/ty_python_semantic/resources/primer/good.txt projects_old.txt cd .. uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@73fe4d9a7c940023ac2addc008097758c877b7ec" ecosystem-analyzer \ --repository ruff \ --flaky-runs 10 \ diff \ --profile=profiling \ --projects-old ruff/projects_old.txt \ --projects-new ruff/projects_new.txt \ --projects-flaky ruff/projects_flaky.txt \ --old old_commit \ --new new_commit \ --output-old diagnostics-old.json \ --output-new diagnostics-new.json mkdir dist ecosystem-analyzer \ generate-diff \ diagnostics-old.json \ diagnostics-new.json \ --old-name "main (merge base)" \ --new-name "$REF_NAME" \ --output-html dist/diff.html set +e ecosystem-analyzer \ generate-diff-statistics \ diagnostics-old.json \ diagnostics-new.json \ --fail-on-new-abnormal-exits \ --old-name "main (merge base)" \ --new-name "$REF_NAME" \ --output diff-statistics.md DIFF_STATISTICS_EXIT_CODE=$? set -e ecosystem-analyzer \ generate-timing-diff \ diagnostics-old.json \ diagnostics-new.json \ --old-name "main (merge base)" \ --new-name "$REF_NAME" \ --output-html dist/timing.html cat diff-statistics.md >> comment.md cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY" echo "diff_statistics_exit_code=$DIFF_STATISTICS_EXIT_CODE" >> "$GITHUB_OUTPUT"
  • exit 1
View raw YAML
name: ty ecosystem-analyzer

permissions: {}

on:
  pull_request:
    paths:
      - "crates/ty*/**"
      - "!crates/ty_ide/**"
      - "!crates/ty_server/**"
      - "!crates/ty_test/**"
      - "!crates/ty_completion_eval/**"
      - "!crates/ty_wasm/**"
      - "crates/ruff_db"
      - "crates/ruff_python_ast"
      - "crates/ruff_python_parser"
      - ".github/workflows/ty-ecosystem-analyzer.yaml"
      - ".github/ty-ecosystem.toml"
      - "crates/ty_python_semantic/resources/primer/**"
      - "Cargo.lock"
      - "!**.md"
      - "!**.snap"
      # It's tempting to skip all Python files in every directory,
      # but changes to Python files in `ty_vendored` can affect the output of ecosystem analysis,
      # so we apply a narrow exemption for all files in the corpus directory instead.
      - "!crates/ty_python_semantic/resources/corpus/**"

concurrency:
  group: ty-ecosystem-analyzer-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  RUST_BACKTRACE: 1
  REF_NAME: ${{ github.ref_name }}

jobs:
  ty-ecosystem-analyzer:
    name: Compute diagnostic diff
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          path: ruff
          fetch-depth: 0
          persist-credentials: false

      - name: Install the latest version of uv
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          enable-cache: true
          version: "0.10.12"

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: "ruff"
          lookup-only: false

      - name: Install Rust toolchain
        run: rustup show

      - name: Compute diagnostic diff
        id: compute-diagnostic-diff
        shell: bash
        run: |
          cd ruff

          echo "Enabling configuration overloads (see .github/ty-ecosystem.toml)"
          mkdir -p ~/.config/ty
          cp .github/ty-ecosystem.toml ~/.config/ty/ty.toml

          echo "new commit"
          git checkout -b new_commit "$GITHUB_SHA"
          git rev-list --format=%s --max-count=1 new_commit
          cp crates/ty_python_semantic/resources/primer/good.txt projects_new.txt
          cp crates/ty_python_semantic/resources/primer/flaky.txt projects_flaky.txt

          echo "old commit (merge base)"
          MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
          git checkout -b old_commit "$MERGE_BASE"
          git rev-list --format=%s --max-count=1 old_commit
          cp crates/ty_python_semantic/resources/primer/good.txt projects_old.txt

          cd ..

          uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@73fe4d9a7c940023ac2addc008097758c877b7ec"

          ecosystem-analyzer \
            --repository ruff \
            --flaky-runs 10 \
            diff \
            --profile=profiling \
            --projects-old ruff/projects_old.txt \
            --projects-new ruff/projects_new.txt \
            --projects-flaky ruff/projects_flaky.txt \
            --old old_commit \
            --new new_commit \
            --output-old diagnostics-old.json \
            --output-new diagnostics-new.json

          mkdir dist

          ecosystem-analyzer \
            generate-diff \
            diagnostics-old.json \
            diagnostics-new.json \
            --old-name "main (merge base)" \
            --new-name "$REF_NAME" \
            --output-html dist/diff.html

          set +e
          ecosystem-analyzer \
            generate-diff-statistics \
            diagnostics-old.json \
            diagnostics-new.json \
            --fail-on-new-abnormal-exits \
            --old-name "main (merge base)" \
            --new-name "$REF_NAME" \
            --output diff-statistics.md
          DIFF_STATISTICS_EXIT_CODE=$?
          set -e

          ecosystem-analyzer \
            generate-timing-diff \
            diagnostics-old.json \
            diagnostics-new.json \
            --old-name "main (merge base)" \
            --new-name "$REF_NAME" \
            --output-html dist/timing.html

          cat diff-statistics.md >> comment.md

          cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"

          echo "diff_statistics_exit_code=$DIFF_STATISTICS_EXIT_CODE" >> "$GITHUB_OUTPUT"

      # NOTE: astral-sh-bot uses this artifact to post comments on PRs.
      # Make sure to update the bot if you rename the artifact.
      - name: "Upload full report"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: full-report
          path: dist/

      # NOTE: astral-sh-bot uses this artifact to post comments on PRs.
      # Make sure to update the bot if you rename the artifact.
      - name: Upload comment
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: comment.md
          path: comment.md

      - name: Upload diagnostics diff
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: diff.html
          path: dist/diff.html

      - name: Upload timing diff
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: timing.html
          path: dist/timing.html

      - name: Fail on new abnormal exits
        if: steps.compute-diagnostic-diff.outputs.diff_statistics_exit_code != '0'
        run: exit 1
ty-ecosystem-report perms .github/workflows/ty-ecosystem-report.yaml
Triggers
workflow_dispatch, schedule
Runs on
${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
Jobs
ty-ecosystem-report
Actions
astral-sh/setup-uv, Swatinem/rust-cache
Commands
  • rustup show
  • cd ruff echo "Enabling configuration overloads (see .github/ty-ecosystem.toml)" mkdir -p ~/.config/ty cp .github/ty-ecosystem.toml ~/.config/ty/ty.toml cd .. uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@73fe4d9a7c940023ac2addc008097758c877b7ec" ecosystem-analyzer \ --verbose \ --repository ruff \ --flaky-runs 20 \ analyze \ --profile=profiling \ --projects ruff/crates/ty_python_semantic/resources/primer/good.txt \ --output ecosystem-diagnostics.json mkdir dist ecosystem-analyzer \ generate-report \ --max-diagnostics-per-project=1000 \ ecosystem-diagnostics.json \ --output dist/index.html
View raw YAML
# This workflow is a cron job that generates a report describing
# all diagnostics ty emits across the whole ecosystem. The report
# is uploaded to https://ty-ecosystem-ext.pages.dev/ on a weekly basis.

name: ty ecosystem-report

permissions: {}

on:
  workflow_dispatch:
  schedule:
    # Run every Wednesday at 5:00 UTC:
    - cron: 0 5 * * 3

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  RUST_BACKTRACE: 1

jobs:
  ty-ecosystem-report:
    name: Create ecosystem report
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
    timeout-minutes: 40
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          path: ruff
          fetch-depth: 0
          persist-credentials: false

      - name: Install the latest version of uv
        uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
        with:
          enable-cache: true
          version: "0.10.12"

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: "ruff"
          lookup-only: false

      - name: Install Rust toolchain
        run: rustup show

      - name: Create report
        shell: bash
        run: |
          cd ruff

          echo "Enabling configuration overloads (see .github/ty-ecosystem.toml)"
          mkdir -p ~/.config/ty
          cp .github/ty-ecosystem.toml ~/.config/ty/ty.toml

          cd ..

          uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@73fe4d9a7c940023ac2addc008097758c877b7ec"

          ecosystem-analyzer \
            --verbose \
            --repository ruff \
            --flaky-runs 20 \
            analyze \
            --profile=profiling \
            --projects ruff/crates/ty_python_semantic/resources/primer/good.txt \
            --output ecosystem-diagnostics.json

          mkdir dist

          ecosystem-analyzer \
            generate-report \
            --max-diagnostics-per-project=1000 \
            ecosystem-diagnostics.json \
            --output dist/index.html

      # NOTE: astral-sh-bot uses this artifact to publish the ecosystem report.
      # Make sure to update the bot if you rename the artifact.
      - name: "Upload ecosystem report"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: full-report
          path: dist/
typing_conformance perms .github/workflows/typing_conformance.yaml
Triggers
pull_request
Runs on
${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
Jobs
typing_conformance
Actions
Swatinem/rust-cache
Commands
  • rustup show
  • # Build the executable for the old and new commit ( cd ruff echo "new commit" git rev-list --format=%s --max-count=1 "$GITHUB_SHA" cargo build --bin ty mv target/debug/ty ty-new MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")" git checkout -b old_commit "$MERGE_BASE" echo "old commit (merge base)" git rev-list --format=%s --max-count=1 old_commit cargo build --bin ty mv target/debug/ty ty-old ) ( echo "Creating comment with conformance comparison" cd ruff git switch - --detach python "./scripts/conformance.py" \ --old-ty "./ty-old" \ --new-ty "./ty-new" \ --tests-path "${GITHUB_WORKSPACE}/typing/conformance/" \ --python-version "$PYTHON_VERSION" \ --output ../typing_conformance_diagnostics.diff )
View raw YAML
name: Run typing conformance

permissions: {}

on:
  pull_request:
    paths:
      - "crates/ty*/**"
      - "!crates/ty_ide/**"
      - "!crates/ty_server/**"
      - "!crates/ty_test/**"
      - "!crates/ty_completion_eval/**"
      - "!crates/ty_wasm/**"
      - "crates/ruff_db"
      - "crates/ruff_python_ast"
      - "crates/ruff_python_parser"
      - "scripts/conformance.py"
      - ".github/workflows/typing_conformance.yaml"
      - "Cargo.lock"
      - "!**.md"
      - "!**.snap"
      # It's tempting to skip all Python files in every directory, but changes to
      # Python files in `ty_vendored` can affect the output of the ecosystem analysis,
      # so we apply a narrow exemption for all files in the corpus directory instead.
      - "!crates/ty_python_semantic/resources/corpus/**"

concurrency:
  group: typing-conformance-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  CARGO_INCREMENTAL: 0
  CARGO_NET_RETRY: 10
  CARGO_TERM_COLOR: always
  RUSTUP_MAX_RETRIES: 10
  RUST_BACKTRACE: 1
  CONFORMANCE_SUITE_COMMIT: 1df1565c69730d88ce6877009d268ba1d602af1e
  PYTHON_VERSION: 3.12

jobs:
  typing_conformance:
    name: Compute diagnostic diff
    runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          path: ruff
          fetch-depth: 0
          persist-credentials: false

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          repository: python/typing
          ref: ${{ env.CONFORMANCE_SUITE_COMMIT }}
          path: typing
          persist-credentials: false

      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          workspaces: "ruff"

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

      - name: Install Rust toolchain
        run: rustup show

      - name: Compute diagnostic diff
        shell: bash
        run: |
          # Build the executable for the old and new commit
          (
            cd ruff

            echo "new commit"
            git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
            cargo build --bin ty
            mv target/debug/ty ty-new

            MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
            git checkout -b old_commit "$MERGE_BASE"
            echo "old commit (merge base)"
            git rev-list --format=%s --max-count=1 old_commit
            cargo build --bin ty
            mv target/debug/ty ty-old
          )

          (
            echo "Creating comment with conformance comparison"
            cd ruff
            git switch - --detach

            python "./scripts/conformance.py" \
             --old-ty "./ty-old" \
             --new-ty "./ty-new" \
             --tests-path "${GITHUB_WORKSPACE}/typing/conformance/" \
             --python-version "$PYTHON_VERSION" \
             --output ../typing_conformance_diagnostics.diff
          )

      # NOTE: astral-sh-bot uses this artifact to post comments on PRs.
      # Make sure to update the bot if you rename the artifact.
      - name: Upload diff
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: typing_conformance_diagnostics_diff
          path: typing_conformance_diagnostics.diff