Skip to content

dev: move every check into one noxfile - #4208

Open
oresttokovenko wants to merge 8 commits into
apache:mainfrom
oresttokovenko:migrate-tooling-to-nox-just
Open

oresttokovenko wants to merge 8 commits into
apache:mainfrom
oresttokovenko:migrate-tooling-to-nox-just

Conversation

@oresttokovenko

@oresttokovenko oresttokovenko commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What

This change replaces Makefile and .pre-commit-config.yaml with a single noxfile.py, and leaves the justfile as a thin entry point with one command line per recipe. CI now calls the same nox sessions that a developer runs locally, so there is one place to look when a check fails.

  • noxfile.py defines 24 sessions, six of which form the gate while the rest are test suites and local actions
  • justfile holds no logic and is 141 lines long
  • Tool versions live in pyproject.toml and uv.lock only, so each session installs the dependency group it needs by name
  • The Python matrix comes from the trove classifiers in pyproject.toml rather than from a workflow matrix
  • Every workflow calls the nox sessions directly, and the justfile stays a convenience for local runs

This is not the shortest way to write this file, because I copied the old behaviour first: the pre-commit hooks let pre-commit choose the files to check, which nox does not do, so noxfile.py carries helpers that walk the tree, read .gitignore, and separate text files from binary ones. A native tool does that work itself, and if we move to tools that scan its own paths or already contains the search logic, I expect the file to shrink to about half. That is not this pull request, whose goal is narrow, to show that one file describes the same checks as the two old files while behaving the same way, all in Python. The shorter version can follow once this merges.

Also, I've had add two dependencies move into the project. mypy and its four stub packages become part of a types dependency group, because the pre-commit hook used to install them in an environment of its own and nothing else made them available. The ty configuration is untouched, since the type-checker work lands separately in #4207 (WIP), which adds the tests/ exclusion and the stricter rules; until that merges, just typecheck-ty will fail as it down now upstream

Why

This is an effort to modernize/centralize dev tooling, reduce version drift, and make the project easier to reason about. Currently three files described the same checks in three different ways, so learning what CI runs meant opening the Makefile, then .pre-commit-config.yaml, and then the workflow. Even worse, versions lived in two places for the same reason, since the hook configuration pinned every tool with rev:, and the CI used the Makefile directly which is clunky. Sometimes, commands were nested, like make lint ran prek run -a, which ran a hook, which ran a tool in an environment that pre-commit had built, etc. This introduces lots of place of misalignment of results locally vs CI. Now the logic exists in one single place, in Python and reads native dependencies, so a version bump touches pyproject.toml and uv.lock and nothing else.

Where the old recipes and checks live now

Makefile targets:

Old target Command now Session or recipe
help just --list default recipe
install-uv just install-uv justfile, uv must exist before nox starts
install just install install
check-license just check-license justfile, dev/check-license unchanged
lint just check, just fix the six gate sessions
test just test tests-3.11 to tests-3.14
test-integration just test-integration integration
test-integration-setup part of just test-integration _start_services
test-integration-exec part of just test-integration _pytest
test-integration-cleanup part of just test-integration _stop_services
test-integration-rebuild just test-integration-rebuild integration-rebuild
test-s3 just test-s3 s3
test-adls just test-adls adls
test-gcs just test-gcs gcs
test-coverage just test-coverage tests, integration, s3, adls, gcs, then coverage
coverage-report just coverage-report coverage
test-notebook just test-notebook test-notebook
docs-serve just docs-serve docs-serve
docs-build just docs-build docs-build
notebook just notebook notebook
notebook-infra just notebook-infra notebook-infra
clean just clean clean

Pre-commit hooks:

Old hook Session
trailing-whitespace hygiene
end-of-file-fixer hygiene
check-yaml hygiene
check-ast hygiene
ruff lint
ruff-format format
nbqa-ruff lint
mypy typecheck-mypy
ty typecheck-ty, local and experimental
markdownlint markdownlint
codespell codespell
uv-lock just lock-check

Behaviour changes

  • There are no Git hooks any more, so run just check before you push, which is exactly what CI runs.
  • just check writes no files, while just fix applies the fixes.
  • Choose an interpreter with nox's own flag, just install --force-python 3.12.
  • The gate is whatever lacks default=False, so a new check session joins it by default instead of needing a second registration.
  • Markdown linting runs on rumdl from PyPI, which reads the existing .markdownlint.yaml and avoids a Node.js install. Rule MD057 stays off because the reference/ pages are generated by mkdocstrings at build time.
  • check-yaml runs with --unsafe because mkdocs/mkdocs.yml uses the !!python/name: tag.
  • mypy checks every tracked Python file again, 215 in total and with --scripts-are-modules; an earlier draft of this change had narrowed it to the package alone.
  • The Windows job keeps the previous workflow's exclusion of the hive-kerberos extra, which pulls kerberos, a package that publishes no Windows wheels and would need MIT Kerberos headers the runner does not have.
  • Each test session rebuilds the in-place Cython extension when its ABI tag is missing, because a cached editable build for one interpreter otherwise leaves another one unable to import pyiceberg.avro.decoder_fast.
  • notebook-infra stops the Docker services when JupyterLab exits, unless KEEP_COMPOSE=1 asks to keep them.

Small fixes the port needed

  • The noxfile now owns file discovery, which pre-commit used to provide from the git index. It reads .gitignore only if the file is there, because the sdist does not ship one and dev/release/verify_rc.sh runs just install and just test-coverage from inside an unpacked sdist.

Tests

just check runs six sessions in about 13 seconds and is green. just test runs the four interpreters in parallel: 37 seconds on a cold tree, and the full unit suite on 3.13 takes a minute. just install --force-python 3.12 -- --dry-run prints uv sync --all-extras --python 3.12 while the plain form leaves the environment alone, and just docs-build builds in seven seconds. just clean then just install rebuilds from an empty tree, an unpacked sdist lets nox read the noxfile, and a deliberately dirty Markdown file fails just check while a clean one passes.

The integration and cloud suites, CI on GitHub, and dev/release/verify_rc.sh end to end were not run, because they need Docker services and a real runner. typecheck-ty is tagged experimental and stays out of the gate; on the current base it reports 894 diagnostics, which #4207 reduces once its exclusions and rules are in place.

The Makefile, the pre-commit config, and the CI workflows each described
the same checks in their own way, and tool versions were pinned in two of
them. One noxfile now holds the sessions, the justfile names them, and CI
runs the same sessions a developer runs locally.
The sdist carries no .gitignore, and release verification loads the
noxfile from an unpacked sdist, so the read must tolerate its absence.
The justfile is a convenience for local runs. CI has no reason to install
a task runner to reach the sessions it already names.
`uv run` syncs the project environment on demand, and the test and
coverage sessions build their own environments from uv.lock, so the
explicit install only synced the extras a second time.
The wrapper named every member after the string it held, and only two of
the six reached a call site, so it added a lookup without removing a
literal.
Those files are not new: the Makefile ran coverage with --parallel-mode
as well, and noxfile's clean session still removes them.
coverage already reads [tool.coverage.run] source from pyproject.toml, so
the session stops repeating --source, and the 85 default moves next to it
as [tool.coverage.report] fail_under. COVERAGE_FAIL_UNDER still overrides
it, because the integration job accepts lower coverage than the unit suite.
mypy finds the config file on its own, so --config-file was redundant, and
--scripts-are-modules is a setting rather than an invocation choice.
@rambleraptor

Copy link
Copy Markdown
Collaborator

Part of the CI failures will be fixed by #4216

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants