# The three commands CONTRIBUTING.md asks a contributor to run, run for them. # # Split in two on purpose. Style is a property of the source, not of the machine, so # `lint` runs once and its failure is unambiguous. Behaviour is a property of the # machine, so `test` runs everywhere we claim to work — the description promises # Windows, macOS, and Linux, and until this file existed that promise had only ever been # checked on one of them. name: CI on: push: branches: [main] pull_request: workflow_dispatch: # Nothing here writes to the repository; say so rather than inherit the default. permissions: contents: read # A second push while the first is still running makes the first answer obsolete. # Cancel it — a run on a stale commit is a queue slot and a wait spent on nothing. concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: lint: name: lint runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.14" cache: pip cache-dependency-path: pyproject.toml - run: pip install -e ".[dev]" # Both gates, and the second runs even when the first fails: they report # different things and seeing only the earlier one wastes a round trip. - run: ruff check . - if: ${{ !cancelled() }} run: ruff format --check . test: name: test ${{ matrix.os }} py${{ matrix.python }} runs-on: ${{ matrix.os }} timeout-minutes: 15 strategy: # Let every leg finish. The point of a run is the whole picture of what broke, # and one red leg cancelling its siblings hides the rest until the next push. fail-fast: false matrix: # Linux carries the full Python range and the slower-to-schedule runners # cover the ends of it, which is where a version-dependent break actually # shows up. It was shaped this way when the repository was private and # minutes were metered; on a public one they are not, so this can widen to # every OS times every version whenever the wait for a full run is worth it. # # 3.10 stays on Ubuntu 24.04. It reaches end of life in October 2026, and a # newer image that ubuntu-latest moves to may not offer it at all. That would # turn the badge red with nothing broken. Drop this leg when the floor rises. include: - { os: ubuntu-24.04, python: "3.10" } - { os: ubuntu-latest, python: "3.11" } - { os: ubuntu-latest, python: "3.12" } - { os: ubuntu-latest, python: "3.13" } - { os: ubuntu-latest, python: "3.14" } - { os: windows-latest, python: "3.10" } - { os: windows-latest, python: "3.14" } - { os: macos-latest, python: "3.14" } steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} cache: pip cache-dependency-path: pyproject.toml - run: pip install -e ".[dev]" # Includes tests/test_gallery.py, the dual-platform readability gate: every # screen has to stay readable at 72 columns regular and 53 on the PicoCalc, and # every PicoCalc case is a hard failure rather than a warning. - run: python -m pytest -q # Builds the distributions and checks them the way PyPI would, publishing nothing. # Worth its minute: packaging breaks silently and is otherwise only discovered on # the one day you least want to discover it. The wheel is installed into a clean # environment and the command run from outside the source tree, because "it built" # and "it works when installed" are different claims. build: name: build runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.14" - run: pip install build twine - run: python -m build - run: twine check dist/* - name: Install the wheel clean and run the command run: | python -m venv /tmp/fresh /tmp/fresh/bin/pip install --quiet dist/*.whl cd /tmp /tmp/fresh/bin/meshterm --help > /dev/null # A tool-provided subcommand, not just --help: the tools are discovered at # runtime, so a broken package still answers --help perfectly well. /tmp/fresh/bin/meshterm contacts --help > /dev/null echo "the wheel installs, the console script runs, and its tools registered"