diff --git a/.github/PUBLISHING.md b/.github/PUBLISHING.md index 9e7d1b3..8baf496 100644 --- a/.github/PUBLISHING.md +++ b/.github/PUBLISHING.md @@ -44,7 +44,8 @@ version number can never be reused once published. 1. Bump `__version__` in [`src/legaldown/__init__.py`](../src/legaldown/__init__.py) — it is the single source of truth; `pyproject.toml` reads it via `[tool.hatch.version]`. -2. Commit, then publish a GitHub Release with the tag `vX.Y.Z`. Its notes are where this +2. Commit and run **CI** on it (*Actions → CI → Run workflow*; it runs only on demand, or with the `ci` label on a pull request), then publish a + GitHub Release with the tag `vX.Y.Z`. Its notes are where this project records what changed in a release. The workflow builds the sdist and wheel, runs `twine check --strict`, **verifies the tag matches diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9868f9..1dca856 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,22 @@ name: CI +# On demand only, in either of two ways: +# - Actions → CI → Run workflow, on the branch you pick; +# - the `ci` label on a pull request: adding it runs CI, and CI then runs again +# on each push to that pull request until the label is removed. +# One job on one Python, the one the release is built with, so a run costs one +# runner's setup rather than six: lint, the tests (with the specification fixtures +# corpus unless switched off), and the build with its metadata check. + on: - push: - branches: [main] pull_request: + types: [labeled, synchronize, reopened] + workflow_dispatch: + inputs: + conformance: + description: "Also run the specification fixtures corpus (checks out ForLegalAI/LegalDown)" + type: boolean + default: true permissions: contents: read @@ -13,76 +26,59 @@ concurrency: cancel-in-progress: true jobs: - lint: - name: Lint (ruff) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: pip - - run: pip install ruff - - run: ruff check . - - test: - name: Tests (Python ${{ matrix.python-version }}) + check: + name: Lint, test, build + # A push or a reopening of a pull request runs it only while the pull request has + # the `ci` label; adding some other label does not. + if: >- + github.event_name == 'workflow_dispatch' || + (contains(github.event.pull_request.labels.*.name, 'ci') && + (github.event.action != 'labeled' || github.event.label.name == 'ci')) runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.11", "3.12", "3.13"] + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - run: pip install -e ".[dev]" - - run: pytest -q - conformance: - name: Specification conformance - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check out the LegalDown specification (fixtures corpus) + # The corpus is checked out and run for a pull request, and for a manual run + # unless its `conformance` input is off. + - name: Check out the LegalDown specification (fixtures corpus only) + if: github.event_name != 'workflow_dispatch' || inputs.conformance uses: actions/checkout@v4 with: repository: ForLegalAI/LegalDown ref: v0.2 path: .legaldown-spec + sparse-checkout: fixtures # A private specification repository needs a token with read access; # GITHUB_TOKEN cannot read another repository. token: ${{ secrets.SPEC_REPO_TOKEN || github.token }} + - uses: actions/setup-python@v5 with: python-version: "3.12" cache: pip + cache-dependency-path: pyproject.toml + + # The dev extra holds pytest, ruff, build and twine: one install for every step. - run: pip install -e ".[dev]" - - name: Validate against the fixtures corpus + + - run: ruff check . + + - name: Tests and specification conformance + if: github.event_name != 'workflow_dispatch' || inputs.conformance env: LEGALDOWN_FIXTURES_DIR: .legaldown-spec/fixtures - # Cases for unimplemented rules are skipped and named, so a failure - # here is a regression against a rule this implementation claims. - run: pytest tests/conformance -q + # Cases for unimplemented rules are skipped and named, so a failure in + # tests/conformance is a regression against a rule this implementation claims. + run: pytest -q + + - name: Tests + if: github.event_name == 'workflow_dispatch' && !inputs.conformance + run: pytest -q - build: - name: Build distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: pip - - run: pip install build twine - run: python -m build + - name: Check package metadata # --strict matches the publish workflow, so a metadata problem fails - # here on the pull request rather than at release time. + # here rather than at release time. run: twine check --strict dist/* - - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ diff --git a/CONFORMANCE.md b/CONFORMANCE.md index 7014d2b..c743054 100644 --- a/CONFORMANCE.md +++ b/CONFORMANCE.md @@ -201,7 +201,7 @@ and 38 skipped: eight of the other skips are the implemented rules named above, case, which is marked Full (fixtures README, step 4) — `tests/test_assembly.py` assembles it with a loader instead. Cases that need the final option run with it; cases that need an answers set are assembled with it, and each assembly case is compared byte for byte with its expected output. -CI runs this on every push and pull request. +CI runs this on demand (Actions → CI → Run workflow with the `conformance` input on, or the `ci` label on a pull request). ## Assembly (§15.7, §17.6) diff --git a/README.md b/README.md index e6cbfdd..e179cb1 100644 --- a/README.md +++ b/README.md @@ -604,9 +604,17 @@ checks it does not perform, so those are listed in git clone https://github.com/ForLegalAI/legaldown-validator cd legaldown-validator pip install -e ".[dev]" +ruff check . pytest ``` +**CI** runs on demand only, as one job on Python 3.12 (the version releases are built with): lint, +the tests with the specification fixtures corpus, and the build with its metadata check. Start it +with *Actions → CI → Run workflow* on the branch you pick (the `conformance` input switches the +corpus off for a quicker run), or by adding the **`ci` label** to a pull request: it then runs again +on each push to that pull request until the label is removed. Nothing else triggers it, so run it +before merging and before a release. + ### Conformance suite The fixtures corpus lives in the specification repository, so point the harness at a checkout: @@ -618,7 +626,7 @@ LEGALDOWN_FIXTURES_DIR=../LegalDown/fixtures pytest tests/conformance -q Cases for rules outside Core are skipped and named, so the run doubles as the coverage ledger in [CONFORMANCE.md](https://github.com/ForLegalAI/legaldown-validator/blob/main/CONFORMANCE.md), -which accounts for every skip. CI runs it on every push and pull request. +which accounts for every skip. CI runs it with the rest of the checks, on demand (below). Bug reports and pull requests are welcome in [Issues](https://github.com/ForLegalAI/legaldown-validator/issues); questions about the format