Skip to content

answer for the imports a renamed module leaves behind #655

answer for the imports a renamed module leaves behind

answer for the imports a renamed module leaves behind #655

Workflow file for this run

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
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Faster to do this separately than to use `fetch-depth: 0` with `actions/checkout`
- name: Fetch full history without tags
run: git fetch --no-tags --filter=blob:none --unshallow origin
- 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' \
':rust-toolchain.toml' \
':cargo/config.toml' \
':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' \
':rust-toolchain.toml' \
':cargo/config.toml' \
':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' \
':rust-toolchain.toml' \
':cargo/config.toml' \
':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' \
':rust-toolchain.toml' \
':cargo/config.toml' \
':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' \
':rust-toolchain.toml' \
':cargo/config.toml' \
':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' \
':rust-toolchain.toml' \
':cargo/config.toml' \
':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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup component add rustfmt
- run: cargo fmt --all --check
shellcheck:
name: "shellcheck"
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.26.5"
- name: "Run ShellCheck"
env:
GOSUMDB: sum.golang.org
run: |
files=()
while IFS= read -r -d '' file; do
files+=("${file}")
done < <(git ls-files -z '*.sh')
if ((${#files[@]} == 0)); then
exit 0
fi
go run github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1 "${files[@]}"
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
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
# upstream's `cargo-publish-dry-run` is dropped: the fork keeps ruff/ty crate
# versions in lock-step with astral's published crates.io versions but modifies
# their apis, so `cargo publish --dry-run` pulls the real published crate for a
# transitive dep and fails on the api skew. the fork publishes only the
# `basedpython` wheel to pypi, never these crates to crates.io
# `by compile` builds against the running interpreter's headers, links its abi and has
# to answer exactly as that interpreter does, so *which* interpreter is a first-class
# variable and belongs in the workflow rather than in whatever a runner image ships.
#
# the supported set is python 3.13 and 3.14, each in its gil and its free-threaded
# build. `PYTHON_VERSION` is the primary and `cargo test (linux)` runs the whole suite
# against it; this job covers the other three. 3.12 is deliberately absent — it is
# below the project's floor, and it was only ever reached because an image shipped it.
#
# only the version-sensitive targets run here, because only they start an interpreter:
# * the six backend crates, which emit and then load real extension modules
# * `by_transforms`' `*_runtime` tests, which transpile and then execute
# * `ty`'s divergence and end-to-end tests, which do the same
# the rest of the suite reads a vendored typeshed and a configured `python-version`,
# so its answers do not move with the interpreter and repeating it buys nothing.
#
# a job of its own rather than steps on `cargo test (linux)`: as a step it pushed that
# job past its budget and the doctest and dogfood steps after it never ran at all
cargo-test-interpreter:
name: "cargo test (python ${{ matrix.python }})"
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: 45
strategy:
# one interpreter failing says nothing about the others, and which ones are green
# is the whole answer this job exists to give
fail-fast: false
matrix:
include:
- python: "3.13"
backend_filter: "all()"
# the free-threaded builds run the same tests, minus one that cannot be
# observed there: `a_closure_does_not_leak_its_environment` asserts that
# `sys.getrefcount` *rises* while a closure holds a string, and a
# free-threaded interpreter reports the immortal sentinel for that string
# (4294967295 on 3.13t, 3221225472 on 3.14t) whoever holds it. an ordinary
# cpython closure fails the same assertion, so it measures the instrument
# rather than the backend. the leak half of that test — the count is
# unchanged after 20000 closures — runs and passes on both
- python: "3.13t"
backend_filter: "not test(=a_closure_does_not_leak_its_environment)"
- python: "3.14t"
backend_filter: "not test(=a_closure_does_not_leak_its_environment)"
env:
CARGO_PROFILE_DEV_DEBUG: line-tables-only
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
shared-key: ruff-linux-debug
save-if: false
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2.83.2
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.11.31"
enable-cache: "true"
# `PYTHON` is the load-bearing pin: every harness that starts an interpreter reads
# it first. `PATH` is set as well because each of those harnesses falls back to a
# bare name if the variable is ever unset, and `by_transforms` tries `python3.13`
# *before* `python3` — so an unpinned run does not merely drift, it drifts towards
# one particular version. uv's managed installs put a `python3` next to the
# versioned name, and in a free-threaded install that `python3` is itself the
# free-threaded build
- name: "Pin the interpreter"
env:
PY: ${{ matrix.python }}
run: |
uv python install "$PY"
python_path="$(uv python find "$PY")"
echo "PYTHON=$python_path" >> "$GITHUB_ENV"
dirname "$python_path" >> "$GITHUB_PATH"
# a leg that silently resolved to a different interpreter would report a green that
# belongs to some other version, which is the failure mode this whole job is about
- name: "Verify the interpreter is the one asked for"
env:
PY: ${{ matrix.python }}
run: |
echo "PYTHON=$PYTHON"
"$PYTHON" -VV
echo "python3 on PATH: $(command -v python3)"
# a free-threaded leg has to be genuinely free-threaded rather than a gil
# build answering to a `t` name, so the probe reports both facts at once
want_version="${PY%t}"
want="$want_version false"
if [ "$PY" != "$want_version" ]; then
want="$want_version true"
fi
probe='import sys, sysconfig; print("%d.%d" % sys.version_info[:2], "true" if sysconfig.get_config_var("Py_GIL_DISABLED") else "false")'
got=$("$PYTHON" -c "$probe")
echo "PYTHON reports: $got (want: $want)"
[ "$got" = "$want" ] || { echo "::error::PYTHON is not $PY"; exit 1; }
# the harnesses fall back to a bare `python3` when the variable is unset, so
# that has to be the same interpreter too
got_path=$(python3 -c "$probe")
echo "python3 reports: $got_path (want: $want)"
[ "$got_path" = "$want" ] || { echo "::error::python3 on PATH is not $PY"; exit 1; }
- name: "Run the native backend"
env:
BACKEND_FILTER: ${{ matrix.backend_filter }}
run: |
cargo nextest run \
-p by_build -p by_ir -p by_irbuild -p by_opt -p by_codegen_c -p by_rt \
-E "$BACKEND_FILTER"
# a separate invocation on purpose: the backend's differential tests fork processes
# and compile C, and sharing a run with `by_transforms`' ~1800 other tests starves
# them into failures that pass on their own
- name: "Run the transpiler's runtime tests"
run: cargo nextest run -p by_transforms -E 'binary(/_runtime$/)'
- name: "Run ty's divergence and end-to-end tests"
run: |
cargo nextest run -p ty \
-E 'binary(mdtest_divergence) + binary(by_e2e) + binary(django_lookup_runtime)'
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') }}
# the fork's `ubuntu-latest` runner is far slower than astral's 16-core depot
# runner, so the full suite overruns the depot-tuned 20-minute budget
#
# 45 was in turn too tight: the job was already taking 34m32s on main, and runner speed
# varies by more than the quarter of an hour that left. On 2026-08-13 the same commit range
# ran its tests in 1603s, 1645s and then 1942s — the slow one reporting 1455 tests over the
# one-second mark against 1263 for the fast one, which is a slower machine rather than more
# work — and the 1942s run reached 45m mid-way through the dogfood steps and was killed.
# Nextest's own total is the number to compare here: it stays flat across the merge, so the
# budget is for the runner's variance, not for the suite growing.
timeout-minutes: 75
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
# Enable mdtests that require external dependencies. These `uv`-install real
# packages and type-check against them — platform-independent behaviour, so
# linux coverage suffices. Keeping them off the windows/macos jobs avoids the
# fork's slow runners blowing the nextest deadlock timeout on heavy stubs
# (django-stubs type-checks in ~8s locally but >180s unoptimised on the fork
# windows runner's slow file I/O).
MDTEST_EXTERNAL: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
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@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- name: "Install cargo nextest and insta"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
with:
tool: |
cargo-nextest
cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
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
# the tests that execute python run against whatever `python3` is first on `PATH`,
# which without this is whichever interpreter the runner image happens to ship —
# a 3.14 on macos and a 3.12 on linux, so the version under test moved with the
# image rather than with the project. `by compile` builds against the running
# interpreter's headers and has to answer as that interpreter does, so which one
# it is belongs in the workflow.
#
# `PATH` as well as `PYTHON`: the harnesses all read the variable first, but each
# falls back to a bare name when it is unset, and `by_transforms` tries
# `python3.13` *before* `python3` — so a run that lost the variable would not
# drift randomly, it would drift to one particular version. the rest of the
# supported set is covered by `cargo test (python …)`
- name: "Pin the interpreter the python-executing tests use"
run: |
uv python install "${PYTHON_VERSION}"
python_path="$(uv python find "${PYTHON_VERSION}")"
echo "PYTHON=$python_path" >> "$GITHUB_ENV"
dirname "$python_path" >> "$GITHUB_PATH"
- name: "Run tests"
run: cargo insta test --all-features --unreferenced reject --test-runner nextest --disable-nextest-doctest
- name: "Run doctests"
run: cargo test --doc --all-features
- 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_python_core -p ty_module_resolver -p ty_site_packages -p ty_combine -p ty_project -p ty_ide -p ty_wasm -p ty_vendored -p ty_static -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-mode build + test times out on standard github runners, so this
# needs depot's larger runners. gated to astral-sh/ruff so it skips on the
# fork; adopt depot (see ROADMAP) then flip the predicate back to
# 'KotlinIsland/basedpython' to re-enable
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
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_PROFILING_DEBUG: line-tables-only
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
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
# these two used to take whatever interpreter the runner image shipped. that drift did
# find real bugs — a 3.14 macos image surfaced six failures nobody was looking for —
# but it found them as a red that named no version, and a green said nothing about
# which version was covered. the same job could change answer between two reruns of
# the same commit. so the interpreter is pinned here too, and the coverage the drift
# used to buy by accident is bought on purpose by `cargo test (python …)` instead.
#
# the two platforms take different versions, so the platform axis and the version axis
# are both covered without a third job: windows takes the newest because its toolchain
# path is the most version-sensitive code in the backend (it has to name the
# interpreter's import library, whose stem carries both the version and the
# free-threaded `t`), and macos takes the floor version
cargo-test-other:
strategy:
# each leg is now a distinct platform *and* version, so cancelling the other one
# on the first failure throws away half the answer
fail-fast: false
matrix:
include:
- platform: ${{ github.repository == 'astral-sh/ruff' && 'namespace-profile-windows-2022-x86-64-16x32' || 'windows-latest' }}
python: "3.14"
- platform: ${{ github.repository == 'astral-sh/ruff' && 'namespace-profile-macos-15' || 'macos-latest' }}
python: "3.13"
name: "cargo test (${{ matrix.platform }}, python ${{ matrix.python }})"
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') }}
# the fork's `windows-latest`/`macos-latest` runners are slower than astral's
# depot runners, so the full suite overruns the depot-tuned 20-minute budget
#
# windows is the slow half of this matrix and came within two minutes of the previous
# 45-minute budget on 2026-08-13 (42m54s, against 24m56s for macos on the same run), so it
# gets the same headroom the linux job needed for the same reason
timeout-minutes: 75
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# Fix for https://github.com/Swatinem/rust-cache/issues/341
cache-bin: "false"
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
enable-cache: "true"
# `PATH` as well as `PYTHON`, for the fallback reason described on
# `cargo test (python …)`. the interpreter reports its own directory rather than
# `dirname` doing it: on windows this shell is git bash but `uv python find` answers
# with a backslash-separated path, which `dirname` reads as a single component
- name: "Pin the interpreter the python-executing tests use"
env:
PY: ${{ matrix.python }}
run: |
uv python install "$PY"
python_path="$(uv python find "$PY")"
echo "PYTHON=$python_path" >> "$GITHUB_ENV"
"$python_path" -c 'import os, sys; print(os.path.dirname(sys.executable))' >> "$GITHUB_PATH"
- name: "Run tests"
run: |
python3 -VV
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.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-24.04-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
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
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@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
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@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- name: "Build tests"
env:
MSRV: ${{ steps.msrv.outputs.value }}
run: cargo "+${MSRV}" test --no-run --all-features
# TODO: fork the fuzzer
# 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@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
# - name: "Install cargo-binstall"
# uses: cargo-bins/cargo-binstall@dc19f1e48450eefe5a29b8da6c6b00a87d730b37 # v1.18.1
# - 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
shared-key: ruff-linux-debug
save-if: false
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- name: Build buff binary
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
# basedpython renames the `ruff` binary to `buff`. `--bin=ruff` below is
# the py-fuzzer's tool-mode selector (an enum), not a binary name, so it
# stays `ruff`
run: cargo build --bin buff
- name: Fuzz
run: |
(
uv run \
--python="${PYTHON_VERSION}" \
--project=./python/py-fuzzer \
--locked \
fuzz \
--test-executable=target/debug/buff \
--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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- 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
# The round-trip report has to fit GitHub's comment size limit, and has to
# not report a project as changed just because two binaries produced the
# same failure. Both are only observable in a `pull_request` run, so they
# are covered by unit tests instead.
- run: uv run --no-project --with pytest pytest scripts/test_check_ecosystem_roundtrip.py
# 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-24.04-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'
)
# max (6h): the fork runs on slow `ubuntu-latest`, not astral's depot
# runners, so the 70-project ecosystem check overruns a tighter budget
timeout-minutes: 360
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.base.ref }}
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
activate-environment: true
version: "0.12.3"
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
shared-key: ruff-linux-debug
save-if: false
- name: Build baseline version
# basedpython renames the `ruff` binary to `buff`
run: |
cargo build --bin buff
mv target/debug/buff target/debug/buff-baseline
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
clean: false
- name: Build comparison version
run: cargo build --bin buff
- 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/buff-baseline ./target/debug/buff --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/buff-baseline ./target/debug/buff --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
# `--max-parallelism 2`: the default (50) oversubscribes the fork's small
# `ubuntu-latest` runner — dozens of concurrent multi-threaded `buff format`
# walks plus git clone/reset churn make the parallel file walk intermittently
# surface a transient `No such file or directory` on one large project (a
# different one each run), which the "Fail on ecosystem errors" step turns
# into a hard failure. astral's 32-core depot runner absorbs the load; ours
# needs the concurrency capped. (format only — the read-only check runs are
# unaffected.)
- 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/buff-baseline ./target/debug/buff --cache ./checkouts --output-format markdown --max-parallelism 2 | 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/buff-baseline ./target/debug/buff --cache ./checkouts --output-format markdown --force-preview --max-parallelism 2 | 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
# `workflow_run` only populates `pull_requests` for same-repo PRs, so the
# commenting workflow cannot recover the number for a fork PR on its own
- name: Record PR number for the comment workflow
run: echo "${{ github.event.pull_request.number }}" > pr-number.txt
# NOTE: astral-sh-bot uses this artifact to post comments on PRs, and so
# does the in-repo `ecosystem_comment` workflow.
# Make sure to update both if you rename the artifact.
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
name: Upload Results
with:
name: ecosystem-result
path: |
ecosystem-result
pr-number.txt
if-no-files-found: "error"
- name: Fail on ecosystem errors
run: |
if ! grep -q "project error" ecosystem-result; then
echo "no ecosystem project errors"
exit 0
fi
# a project error is only a `grep` hit in the bare step otherwise, so
# the maintainer can't tell a transient fork-runner `No such file or
# directory` (see the parallelism note above) from a real regression
# without downloading the artifact. surface the errored projects and
# their messages straight into the step log.
projects=$(grep -oE '">[^<]+</a> \(error\)' ecosystem-result \
| sed -E 's/">([^<]+)<\/a> \(error\)/\1/' | paste -sd', ' -)
echo "::group::ecosystem project errors (${projects:-unknown})"
grep -F "project error" ecosystem-result || true
echo
# print each errored project's section: its `(error)` summary line
# through the closing </details>, which brackets the message body
awk '
/\(error\)<\/summary>/ { show = 1 }
show { print }
/<\/details>/ && show { show = 0; print "" }
' ecosystem-result
echo "::endgroup::"
echo "::error title=ecosystem project errors::${projects:-see log} — full report in the ecosystem-result artifact"
exit 1
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') }}
# the fork's ubuntu-latest runners are far slower than astral's depot, and
# basedpython parser changes give the fuzzer more to explore, so allow the
# full budget rather than cancelling a healthy run
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 10 || 360 }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Faster to do this separately than to use `fetch-depth: 0` with `actions/checkout`
- name: Fetch full history without tags
run: git fetch --no-tags --filter=blob:none --unshallow origin
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- name: Fuzz
env:
FORCE_COLOR: 1
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_PROFILING_DEBUG: line-tables-only
run: |
# basedpython renames the `ty` binary to `by`. `--bin=ty` passed to
# the py-fuzzer below is its tool-mode selector (an enum), not a
# binary name, so it stays `ty`
echo "new commit"
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
cargo build --profile=profiling --bin=by
mv target/profiling/by 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=by
mv target/profiling/by 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@e00d2c94cc0067b77737821097a62d91c0301baa # v1.21.1
- run: cargo binstall --no-confirm cargo-shear@1.12.4
- run: cargo shear --deny-warnings
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' }}
env:
# Line-tables-only debug info: faster builds, backtraces still work.
CARGO_PROFILE_PROFILING_DEBUG: line-tables-only
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
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@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
- name: "Cache prek"
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
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 uv run --only-group dev --locked 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: 3.13
activate-environment: true
version: "0.12.3"
- 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
basedpython-docs:
name: "zensical"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: 3.13
version: "0.11.31"
enable-cache: true
- name: "Install docs dependencies"
run: uv sync --group docs --no-install-project --locked
- name: "Build docs"
run: uv run --no-sync zensical build --clean --strict
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
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') }}
# env:
# # Line-tables-only debug info: faster builds, backtraces still work.
# CARGO_PROFILE_DEV_DEBUG: line-tables-only
# steps:
# - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# name: "Checkout ruff source"
# with:
# persist-credentials: false
#
# - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
# with:
# shared-key: ruff-linux-debug
# save-if: false
#
# - name: "Install Rust toolchain"
# run: rustup show
#
# - name: "Install mold"
# uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
#
# - name: Build Ruff binary
# run: cargo build -p ruff --bin ruff
#
# - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# name: "Checkout ruff-lsp source"
# with:
# persist-credentials: false
# repository: "astral-sh/ruff-lsp"
# path: ruff-lsp
#
# - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
# with:
# # installation fails on 3.13 and newer
# python-version: "3.12"
#
# - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
#
# - name: Install ruff-lsp dependencies
# run: |
# cd ruff-lsp
# uv venv
# uv pip install --no-deps -r requirements.txt
# uv pip install --no-deps -r requirements-dev.txt
#
# - name: Run ruff-lsp tests
# run: |
# # Setup development binary
# export PATH="${PWD}/target/debug:${PATH}"
# ruff version
#
# cd ruff-lsp
# uv pip uninstall ruff
# uv run --no-sync pytest
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.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
# TODO(basedpython): enable benchmarks for the fork. requires:
# 1. register KotlinIsland/basedpython at https://codspeed.io
# (project must be created with OIDC auth enabled, or set the
# CODSPEED_TOKEN repo secret and pass `token: ${{ secrets.CODSPEED_TOKEN }}`
# to the CodSpeedHQ/action step below)
# 2. flip the predicate below back to 'KotlinIsland/basedpython'
# gated on astral-sh/ruff for now so the job skips on the fork; uploads
# from KotlinIsland/basedpython otherwise fail OIDC auth with 401
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build -m simulation -m memory --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@0ca9cbbf4623b599a6c3ed4fc8a922942705d9f1 # v5.0.2
with:
mode: "simulation,memory"
run: cargo codspeed run
benchmarks-instrumented-ty-build:
name: "benchmarks instrumented ty (build)"
runs-on: depot-ubuntu-24.04-4
needs: determine_changes
# TODO(basedpython): see `benchmarks-instrumented-ruff` above for how to
# enable benchmarks for the fork (register repo at codspeed.io, then flip
# the predicate below back to 'KotlinIsland/basedpython')
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build -m simulation -m memory --features "codspeed,module_resolution,ty_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench module_resolution --bench ty --bench ty_constraint_set
- name: "Upload benchmark binary"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmarks-instrumented-ty-binary
compression-level: 1
path: target/codspeed
if-no-files-found: "error"
retention-days: 1
benchmarks-instrumented-ty-run:
name: "benchmarks instrumented ty (${{ matrix.mode }}: ${{ matrix.name }})"
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:
include:
- name: micro
target: ty
filter: micro
mode: simulation
- name: micro
target: ty
filter: micro
mode: memory
- name: constraint_set
target: ty_constraint_set
filter: micro
mode: simulation
- name: constraint_set
target: ty_constraint_set
filter: micro
mode: memory
- name: projects
target: ty
filter: "check_file|anyio|attrs|hydra|datetype"
mode: simulation
- name: projects
target: ty
filter: "check_file|anyio|attrs|hydra|datetype"
mode: memory
- name: module_resolution
target: module_resolution
filter: ty_module_resolver
mode: simulation
steps:
- name: "Checkout Branch"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- name: "Install codspeed"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
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@0ca9cbbf4623b599a6c3ed4fc8a922942705d9f1 # v5.0.2
with:
mode: ${{ matrix.mode }}
run: cargo codspeed run --bench "${{ matrix.target }}" "${{ matrix.filter }}"
benchmarks-walltime-build:
name: "benchmarks walltime (build)"
# depot runners hardcoded here are fine since the job is upstream-only.
runs-on: depot-ubuntu-22.04-arm-4
needs: determine_changes
# TODO(basedpython): see `benchmarks-instrumented-ruff` above for how to
# enable benchmarks for the fork (register repo at codspeed.io, then flip
# the predicate below back to 'KotlinIsland/basedpython')
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
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 --bench ty_walltime
- name: "Upload benchmark binary"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmarks-walltime-binary
compression-level: 1
path: target/codspeed
if-no-files-found: "error"
retention-days: 1
benchmarks-walltime-run:
name: "benchmarks walltime (${{ matrix.benchmark.name }})"
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:
- name: sympy
target: ty_walltime
filter: sympy
- name: "pandas|tanjun"
target: ty_walltime
filter: "pandas|tanjun"
- name: "colour_science|static_frame"
target: ty_walltime
filter: "colour_science|static_frame"
- name: "pydantic|freqtrade|multithreaded|altair"
target: ty_walltime
filter: "pydantic|freqtrade|multithreaded|altair"
steps:
- name: "Checkout Branch"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.12.3"
- name: "Install codspeed"
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8
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@0ca9cbbf4623b599a6c3ed4fc8a922942705d9f1 # v5.0.2
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 "${{ matrix.benchmark.target }}" -m walltime "${{ matrix.benchmark.filter }}"
required-checks-passed:
name: "all required checks passed"
if: always()
needs:
- cargo-fmt
- cargo-clippy
- cargo-test-linux
- cargo-test-interpreter
- cargo-test-wasm
- cargo-build-msrv
- shellcheck
- scripts
- prek
- docs
- basedpython-docs
- python-package
runs-on: ubuntu-slim
steps:
- name: "Check required jobs passed"
run: |
failing=$(echo "$NEEDS_JSON" | jq -r 'to_entries[] | select(.value.result != "success" and .value.result != "skipped") | "\(.key): \(.value.result)"')
if [ -n "$failing" ]; then
echo "$failing"
exit 1
fi
env:
NEEDS_JSON: ${{ toJSON(needs) }}