diff --git a/FORK.md b/FORK.md index 1112929c..7cce537f 100644 --- a/FORK.md +++ b/FORK.md @@ -51,17 +51,25 @@ Consumers install a wheel from a GitHub Release; a `git+https://` install has no [The `build/` directory](#the-build-directory)). ```bash -git tag -a quadbio-1.1.61.postN -m "" main -git push origin quadbio-1.1.61.postN +git tag -a quadbio-1.1.61+quadbio.N -m "" main +git push origin quadbio-1.1.61+quadbio.N ``` `.github/workflows/release.yml` builds the client, runs `scripts/check_wheel.py`, serves the wheel -once, and attaches `cirrocumulus-1.1.61.postN-py3-none-any.whl`. Every push to `main` runs all of -that except the publish, so tag once `main` is green. `workflow_dispatch` reruns it by hand. - -`postN` is sequential; restart at `.post1` when upstream releases a new version. The `quadbio-` -prefix keeps our tags out of upstream's namespace and is stripped by setuptools_scm, so the -version is `1.1.61.postN` — after upstream's 1.1.61, before their 1.1.62. +once, and attaches `cirrocumulus-1.1.61+quadbio.N-py3-none-any.whl`. Every push to `main` runs all +of that except the publish, so tag once `main` is green. `workflow_dispatch` reruns it by hand. + +`1.1.61` is the upstream version we sit on; `+quadbio.N` is a **PEP 440 local version identifier**, +sequential, restarting at `.1` when upstream releases a new version. Local versions are the +mechanism for "upstream's release plus our patches", and PyPI *must not* accept them — which is +exactly why ours cannot collide with upstream's. That matters here: upstream has 86 `.postN` tags +on PyPI and has already released `1.1.61`, so the old `1.1.61.postN` scheme was claiming version +strings upstream can publish at will. + +The `quadbio-` tag prefix keeps our tags out of upstream's namespace. `tag_regex` in +`pyproject.toml` strips it while keeping the local segment, which setuptools_scm's default would +otherwise drop; `check_wheel.py` fails the build if `+quadbio.` ever goes missing. Off-tag builds +keep the marker too — one commit past a tag gives `1.1.62.dev1+quadbio.N.g`. **Never delete a release tag.** For `quadbio-1.1.61.post1` and `.post2` this is literal: they were cut from the old force-pushed `integration` and are reachable from no branch at all, so the tag is diff --git a/README.rst b/README.rst index 65555bbf..2bcb00bb 100755 --- a/README.rst +++ b/README.rst @@ -1,3 +1,11 @@ +.. + + **This is the quadbio fork of cirrocumulus.** It carries fixes for anndata 0.13, zarr 3 and + pandas 3 that upstream does not have. Wheels are versioned ``1.1.61+quadbio.N`` and published + as GitHub Releases on `quadbio/cirrocumulus `_, never + to PyPI. Upstream is `lilab-bcb/cirrocumulus `_; see + `FORK.md `_ for what differs. + Cirrocumulus ---------------- diff --git a/pyproject.toml b/pyproject.toml index ff4791c0..1ea03554 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,12 @@ readme = 'README.rst' authors = [ { name = 'Joshua Gould', email = 'cirrocumulus@googlegroups.com' }, ] -urls.Source = 'https://github.com/lilab-bcb/cirrocumulus' +maintainers = [ + { name = 'Marius Lange' }, +] +urls.Homepage = 'https://github.com/quadbio/cirrocumulus' +urls.Source = 'https://github.com/quadbio/cirrocumulus' +urls.Upstream = 'https://github.com/lilab-bcb/cirrocumulus' urls.Documentation = "https://cirrocumulus.readthedocs.io/" license = { file = 'LICENSE' } @@ -61,7 +66,12 @@ only-include = ["cirrocumulus", "build"] [tool.hatch.version] source = 'vcs' -dev = ['pre-commit', 'isort', 'black'] +# Our tags are `quadbio-` where carries a PEP 440 local segment, +# e.g. quadbio-1.1.61+quadbio.4. setuptools_scm's default tag_regex captures [^\+]* +# and would silently drop the local part -- which is the half that makes the version +# unforgeable, since PyPI must not accept local versions. +[tool.hatch.version.raw-options] +tag_regex = '^quadbio-(?P.+)$' [tool.ruff] line-length = 88 diff --git a/scripts/check_wheel.py b/scripts/check_wheel.py index 7c83068d..67d84c19 100755 --- a/scripts/check_wheel.py +++ b/scripts/check_wheel.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -"""Fail unless a built wheel actually ships the web client. +"""Fail unless a built wheel ships the web client and carries the fork's version. An empty ``build/`` yields a client-less wheel silently -- no error, just a 404 on ``/`` -at runtime. See FORK.md. +at runtime. And a wheel without our ``+quadbio.N`` local version claims a version string +upstream is free to publish. See FORK.md. python scripts/check_wheel.py dist/cirrocumulus-*.whl """ @@ -10,11 +11,13 @@ from __future__ import annotations import fnmatch +import os import sys import zipfile INDEX = "cirrocumulus/client/index.html" BUNDLE = "cirrocumulus/client/static/js/*.js" +LOCAL = "+quadbio." def main(path: str) -> int: @@ -26,7 +29,16 @@ def main(path: str) -> int: f"({INDEX}: {INDEX in names}, {BUNDLE}: {len(bundles)}).\n" "Populate build/ with `yarn build` and rebuild -- see FORK.md." ) - print(f"{path}: {len(names)} files, client entry point {bundles[0]}") + # Wheel filenames are name-version-pytag-abitag-platform.whl, and PEP 440 local + # separators are normalised to '.' in the filename, so '+quadbio.' survives intact. + version = os.path.basename(path).split("-")[1] + if LOCAL not in version: + sys.exit( + f"{path} is versioned {version!r}, which has no {LOCAL!r} local segment.\n" + "Either the tag was not `quadbio-+quadbio.N`, or tag_regex in\n" + "pyproject.toml stopped capturing it -- see FORK.md." + ) + print(f"{path}: {len(names)} files, version {version}, entry point {bundles[0]}") return 0