ci/cd: build every branch, check what the Makefile checks, and make a tag cut a release - #18
Conversation
… checks Two gaps, both the same shape: CI was configured once and then the project moved without it. The push trigger listed `main`, `feat/**` and `design/**`. Every branch this repo has actually opened since is a `fix/**`, and none of them was built on push: `fix/dialog-cursor-access-hash` sat on the remote for a day with no run against it at all, because the `pull_request` trigger is the only thing that was covering that prefix and no pull request had been opened yet. The list now carries `fix/**`, `chore/**` and `docs/**` as well. The mypy step named seven paths. The Makefile's strict set names thirty-two, and the two lists had drifted so far apart that `tlgr/transport`, the whole `tlgr/daemon` strict subset, `tlgr/core/config.py`, `tlgr/core/peers.py`, `tlgr/parity.py` and `tlgr/version.py` were type-checked on developer machines by `make typecheck` and not in CI. All three steps now run the Makefile target instead of a second copy of the command, so the strict set has one definition and CI cannot silently check less than a contributor does. The set passes as it stands; nothing had regressed, it simply was not being watched.
There was no CD. `ci.yml` was the only workflow, 2.0.0 had been written into `tlgr/__init__.py` and CHANGELOG.md and merged to main five days ago, and the newest thing on the Releases page was v1.0.0 from March, with no artefacts attached to it. A release was an unwritten manual procedure that nobody had run. It is a tag now. `git push origin vX.Y.Z` triggers `release.yml`, which in order: checks that the tag, `tlgr.__version__` and the `## [x.y.z]` heading in CHANGELOG.md all say the same version; re-runs the ARCHITECTURE 12.3 acceptance subset against the tagged tree, so a tag placed on the wrong commit fails here instead of on a user's machine; builds the sdist and the wheel; installs that wheel into an empty environment and asks the installed `tlgr` for its version; and only then creates the GitHub release, with the changelog section as its notes and both artefacts attached. `tools/release_notes.py` is the first of those steps and is runnable on its own, which is the point of it being a file rather than four lines of shell: the version agreement can be checked before the tag is pushed. It exits 1 with the disagreement named, so the release job stops before anything is published and a bad tag costs a `git push --delete` and nothing else. Nothing here publishes to PyPI. tlgr is not on PyPI and this does not put it there; the wheel on the release is the artefact.
…there `pip install tlgr` is the second line of the README and it has never worked: there is no `tlgr` project on PyPI, and https://pypi.org/simple/tlgr/ is a 404 today. The v1.0.0 release did not publish one either. Anyone following the README either got an error or, if the name is ever claimed by somebody else, something worse than an error. UPGRADING.md inherited the same assumption in section 2 — `pipx upgrade tlgr`, `pip install --force tlgr`, `pip install -U tlgr` — none of which has a source to upgrade from. The editable-install path below it was already correct, because that one describes what actually happens. Both now name the two things that do work: an install from this repository, and the wheel attached to a release. The `pipx` form is first because tlgr runs a long-lived daemon and wants an environment of its own. Claiming the name on PyPI is a separate decision and this does not make it.
The Testing section predates the Makefile. It asked for `python -m py_compile tlgr/**/*.py` and a manual try against a test account, which is neither what CI runs nor enough to pass it — a contributor following it would open a PR that fails on ruff, on mypy, or on a stale generated reference they were never told to regenerate. It now points at `make check` and names the two shorter forms for the inner loop, and says that `docs/reference` and the parity index are artefacts with tests that fail when they go stale. The new Releases section writes down the procedure `release.yml` implements: which three files state the version, that they have to agree, and that the tag is the trigger for everything else. Also a line on branch prefixes, since which ones CI builds on push is not guessable from the outside.
…hing The `pip install tlgr` in the README should become true rather than be deleted, so the release workflow gains a second job that puts the sdist and the wheel on PyPI. It publishes the artefacts the first job built, downloaded rather than rebuilt: the wheel that was smoke-tested in a clean environment and the wheel a user installs have to be the same bytes, and two `uv build` runs are not a guarantee of that. It runs after the GitHub release exists, so a PyPI failure leaves the release standing and can be re-run on its own. Trusted publishing rather than an API token: PyPI verifies the workflow's OIDC token against a publisher configured for this repository, so there is no publishing secret here to leak or rotate, and `permissions` drops to `read` at the top of the file with each job asking for exactly what it needs (`contents: write` to create the release, `id-token: write` to mint the token). The `pypi` environment is the second gate and is where a required reviewer goes if this should ever stop being automatic. The publisher has to exist before the first release that uses it, as a *pending* publisher since the project is not on PyPI yet; CONTRIBUTING names the five fields.
|
Added a fifth piece after review: the release publishes to PyPI.
It publishes the artefacts the first job built, downloaded rather than rebuilt: One thing has to happen on pypi.org before the first tag, since the project The README and UPGRADING wording in this PR still says tlgr is not on PyPI, |
Adding `fix/**` and `chore/**` to the push trigger made a problem visible that `feat/**` already had: a branch with an open pull request is built twice, the same commit through the same ten-job matrix, once for the push and once for the pull request. Half of those jobs are macOS, which is the scarcest runner on the account, so the duplicate is not free — it is the queue in front of the run that actually gates the merge. Four branches pushed in one afternoon sat queued for thirty-five minutes without a single job starting. A `guard` job on ubuntu decides. A `pull_request` event always builds; `main` always builds; a push to any other branch builds only when that branch has no open pull request. So a branch pushed before its pull request exists is still covered — which is the gap this file had, and the reason it sat unbuilt for a day — and the moment a pull request opens, the push run steps aside instead of racing it. The matrix itself is untouched. This removes duplicated work, not coverage.
Four findings from an audit of the repository's CI, releases and install
instructions, and the fix for each.
1. CI did not build
fix/**branchesThe push trigger listed
main,feat/**anddesign/**. Every branch thisrepo has opened since is a
fix/**, and none was built on push;fix/dialog-cursor-access-hashsat on the remote for a day with no runagainst it.
fix/**,chore/**anddocs/**are on the list now.2. CI type-checked seven of the thirty-two strict paths
ci.ymlspelled out its own mypy invocation and the Makefile'sSTRICTsethad grown past it.
tlgr/transport, the stricttlgr/daemonsubset,tlgr/core/config.py,tlgr/core/peers.py,tlgr/parity.pyandtlgr/version.pywere checked bymake typechecklocally and not in CI.Lint, typecheck and test now all run the Makefile target, so there is one
definition of each gate. The full set passes as it stands: nothing had
regressed, it was simply unwatched.
3. There was no CD, and no 2.0.0 release
2.0.0 has been on
mainsince 4 September and the newest release was v1.0.0from March, with no artefacts on it.
release.ymlmakes a tag the wholeprocedure. On
v*it checks that the tag,tlgr.__version__and the## [x.y.z]changelog heading agree; re-runs the ARCHITECTURE 12.3acceptance subset against the tagged tree; builds the sdist and wheel;
installs the wheel into an empty environment and asks it for its version; and
only then creates the release with the changelog section as notes and both
artefacts attached. Any step failing means no release, so a bad tag costs a
git push --delete.tools/release_notes.pyis the version-agreement check, runnable on its ownbefore a tag is pushed.
Nothing here publishes to PyPI. That is a separate decision.
4.
pip install tlgrhas never workedThere is no
tlgrproject on PyPI (https://pypi.org/simple/tlgr/is a 404),and it is the second line of the README. UPGRADING.md section 2 inherited the
same assumption. Both now name what works: an install from this repository,
and the wheel attached to a release.
CONTRIBUTING.md gets the release procedure written down, and its Testing
section replaced: it predated the Makefile and asked for
python -m py_compile, which is neither what CI runs nor enough to pass it.Verification
Run locally against this branch on 3.12:
make lint,make typecheck(thefull strict set, 139 files),
make acceptance(10586 passed),uv build,and an install of the resulting wheel into a clean venv reporting
tlgr, version 2.0.0.