Skip to content

Commit 3c7ce0f

Browse files
fedorovclaude
andcommitted
Update the README now that 1.10.0 is published
Several sections still described the state before the release: - "Current state" said the package was not yet on PyPI. Replaced with PyPI, Python-version and license badges at the top, which stay correct on their own. - The seam paragraph said the pins "currently reference a fork's archives while the build layer is being brought up". They reference this repository's own binaries-1.10.0-1 now; the fork episode is rewritten as what it turned out to be, evidence that swapping the binary source is a one-file change. - Publishing listed registering Trusted Publishers as an outstanding prerequisite with pending publishers. Both are registered, so it now records the configuration instead, including that the environment name was left blank -- which matches any environment and is worth naming later as hardening. - Cutting a release was written around the first upload. Generalised to any version, with the full new-upstream-version sequence and the note that a packaging-only fix reuses the existing archives and needs only a .postN tag. Also corrects "all three platforms" (four are built) and changes the PLASTIMATCH_GIT_TAG override example from a tag to a commit SHA, since the surrounding text argues against pinning by mutable tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent fa27993 commit 3c7ce0f

1 file changed

Lines changed: 66 additions & 50 deletions

File tree

README.md

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# plastimatch Python distributions
22

3+
[![PyPI](https://img.shields.io/pypi/v/plastimatch.svg)](https://pypi.org/project/plastimatch/)
4+
[![Python versions](https://img.shields.io/pypi/pyversions/plastimatch.svg)](https://pypi.org/project/plastimatch/)
5+
[![License](https://img.shields.io/pypi/l/plastimatch.svg)](LICENSE)
6+
37
Platform-specific Python wheels containing the official
48
[plastimatch](https://gitlab.com/plastimatch/plastimatch) command line executables, so that
59
plastimatch can be installed with
@@ -38,13 +42,6 @@ and
3842
| `plastimatch` | The main multi-command driver (`convert`, `register`, `warp`, `synth`, `dice`, …) |
3943
| `dicom_uid` | Generate DICOM UIDs |
4044

41-
## Current state
42-
43-
Not yet published to PyPI. The two layers are joined up: `plastimatchUrls.cmake` pins the
44-
archives from [`binaries-1.10.0-1`](../../releases/tag/binaries-1.10.0-1), built by this
45-
repository's own build layer, so what remains before a first release is registering a Trusted
46-
Publisher and tagging `v1.10.0` — see [Publishing](#publishing).
47-
4845
## Supported platforms
4946

5047
| Platform | Wheel tag | Minimum OS |
@@ -82,20 +79,22 @@ self-contained archive per platform to a GitHub release. This exists because ups
8279
plastimatch publishes source archives only — there is no official binary to download.
8380

8481
`superbuild/` is an `ExternalProject` chain in the style of dcmqi's `CMakeExternals/`, so one
85-
CMake invocation builds everything in dependency order on all three platforms:
82+
CMake invocation builds everything in dependency order on every supported platform:
8683

8784
```console
8885
cmake -S superbuild -B build -DCMAKE_BUILD_TYPE=Release
8986
cmake --build build
9087
```
9188

92-
Which plastimatch gets packaged is configurable, and defaults to the upstream GitLab
93-
repository — this project does not fork plastimatch:
89+
Which plastimatch gets packaged is configurable, and defaults to the pinned commit in
90+
`superbuild/CMakeLists.txt` on the upstream GitLab repository — this project does not fork
91+
plastimatch. Override only to build something else, and prefer a full SHA over a tag for the
92+
same reason the default is one:
9493

9594
```console
9695
cmake -S superbuild -B build \
9796
-DPLASTIMATCH_GIT_REPOSITORY=https://gitlab.com/plastimatch/plastimatch.git \
98-
-DPLASTIMATCH_GIT_TAG=1.10.0
97+
-DPLASTIMATCH_GIT_TAG=db24480dc1086df3278992d62a86e70d8654cb5d
9998
```
10099

101100
Everything the build consumes is pinned by content, not by name. The four dependencies carry
@@ -119,10 +118,10 @@ changes.
119118

120119
`plastimatchUrls.cmake` is the seam. It names the archive and checksum for each platform and
121120
nothing else, which means the wheel layer is indifferent to who produced the binaries. That
122-
indifference is not hypothetical — it is what lets the pins currently reference a fork's
123-
archives while the build layer is still being brought up, and it is why pointing this file at
124-
upstream's own binaries and deleting the build layer would be the entire migration if
125-
plastimatch ever publishes them.
121+
indifference is not hypothetical: the pins referenced a separate repository's archives while
122+
this build layer was being brought up, and moving them here was a one-file change. By the same
123+
token, if upstream plastimatch ever publishes its own release binaries, pointing this file at
124+
them and deleting the build layer is the entire migration.
126125

127126
## Repointing the pins
128127

@@ -219,34 +218,37 @@ development build cannot be uploaded by accident.
219218

220219
### Publishing
221220

222-
Two repository-level prerequisites, in this order:
221+
Uploads use Trusted Publishing (OIDC), so there is no API token stored anywhere. The publishers
222+
are registered on both indexes against:
223223

224-
**1. The repository must be public before the pins point at it.** The wheel layer downloads the
225-
pinned archives over plain HTTPS with no credentials — `FetchContent` has none to offer, and
226-
neither does `pip install` on a developer's machine. Release assets on a private repository are
227-
not reachable that way, so repointing the pins at a private repository breaks every wheel
228-
build, not just CI. This is why the pins still reference the fork: that repository is public.
224+
| Field | Value |
225+
| --- | --- |
226+
| Project name | `plastimatch` |
227+
| Owner | `ImagingDataCommons` |
228+
| Repository name | `plastimatch-python-distributions` |
229+
| Workflow name | `cd.yml` |
229230

230-
**2. Register a Trusted Publisher**, so there is no API token to manage. Because the project
231-
does not exist on either index yet, these are *pending* publishers:
231+
The owner is checked against an OIDC claim, so it has to match wherever the repository actually
232+
lives when the workflow runs — worth re-checking after any repository transfer, since a stale
233+
owner fails the upload rather than falling back to anything.
232234

233-
| Field | PyPI | TestPyPI |
234-
| --- | --- | --- |
235-
| Register at | <https://pypi.org/manage/account/publishing/> | <https://test.pypi.org/manage/account/publishing/> |
236-
| Project name | `plastimatch` | `plastimatch` |
237-
| Owner | `ImagingDataCommons` | `ImagingDataCommons` |
238-
| Repository name | `plastimatch-python-distributions` | `plastimatch-python-distributions` |
239-
| Workflow name | `cd.yml` | `cd.yml` |
240-
| Environment name | `pypi` | `testpypi` |
241-
242-
The owner must match wherever the repository actually lives when the workflow runs — an OIDC
243-
claim is checked against it, so registering the wrong owner fails the upload. Register after
244-
any planned move, not before. The environment names must match the `environment:` keys on the
245-
`upload_pypi` and `upload_testpypi` jobs in `cd.yml`.
246-
247-
Both environments are created automatically on first use, but creating them explicitly with a
248-
required reviewer makes every publish need a human approval — recommended for `pypi`, given
249-
that uploads cannot be undone.
235+
The `pypi` and `testpypi` GitHub environments both require a reviewer, so every upload pauses
236+
for a human approval. That is the last reversible moment: once a version is on an index it can
237+
never be reused, even after deleting the release.
238+
239+
> The publishers currently leave the **environment name blank**, which matches any environment.
240+
> Naming `pypi` and `testpypi` there as well would mean a workflow change that bypassed the
241+
> environment is rejected by the index too, rather than only by GitHub. Optional hardening; the
242+
> current setup works.
243+
244+
Two prerequisites that are already satisfied but are easy to break:
245+
246+
- **The repository must stay public.** The wheel layer fetches the pinned archives over plain
247+
HTTPS with no credentials — `FetchContent` has none to offer, and neither does `pip install`
248+
on a developer's machine. Making the repository private breaks every wheel build, not just
249+
CI, because the release assets stop being reachable.
250+
- **The `[project.urls]` and license metadata are baked into published artifacts** and cannot
251+
be corrected in place afterwards, only superseded by a new `.postN`.
250252

251253
### Cutting a release
252254

@@ -258,18 +260,32 @@ both and a rehearsal cannot consume the real version number:
258260
| pre-release | `v1.10.0rc1` | TestPyPI |
259261
| full release | `v1.10.0` | PyPI |
260262

261-
So the sequence is: tag `v1.10.0rc1` and mark the GitHub release as a pre-release, confirm the
262-
upload and that `pip install -i https://test.pypi.org/simple/ plastimatch` gives a working
263-
executable, then tag `v1.10.0` as a full release. `1.10.0rc1` sorts before `1.10.0`, so the
264-
rehearsal is also a legitimate pre-release rather than a number burned for nothing.
263+
So a full release goes: tag `v<version>rc1`, mark the GitHub release as a pre-release, confirm
264+
the TestPyPI upload and that
265+
266+
```console
267+
pip install --pre -i https://test.pypi.org/simple/ plastimatch
268+
```
269+
270+
gives a working executable, then tag `v<version>` as a full release. An `rc` sorts before the
271+
release it rehearses, so it is a legitimate pre-release rather than a number burned for
272+
nothing — which matters because **a published version can never be reused**, on either index,
273+
even after deleting the release.
274+
275+
Only tagged *upstream releases* get published here. Packaging an untagged upstream snapshot has
276+
no good PEP 440 spelling — `.postN` is already spoken for, and a `.devN` would sort before the
277+
release it comes after — so snapshots stay as GitHub release assets.
278+
279+
The full sequence for a new upstream version is therefore:
265280

266-
Two things worth knowing before the first upload:
281+
1. Bump `PLASTIMATCH_GIT_TAG` (and `PLASTIMATCH_VERSION_LABEL`) in `superbuild/CMakeLists.txt`
282+
to the new commit SHA.
283+
2. Cut `binaries-<version>-1` and repoint the pins — see
284+
[Repointing the pins](#repointing-the-pins).
285+
3. Tag `v<version>rc1` as a pre-release, check TestPyPI, then `v<version>` as a full release.
267286

268-
- **A published version can never be reused**, even after deleting the release. Rehearse on
269-
TestPyPI, not on the real index.
270-
- Only tagged *upstream releases* get published here. Packaging an untagged upstream snapshot
271-
has no good PEP 440 spelling — `.postN` is already spoken for, and a `.devN` would sort
272-
before the release it comes after — so snapshots stay as GitHub release assets.
287+
A packaging-only fix skips steps 1 and 2 entirely and just needs a `v<version>.postN` tag,
288+
since the existing archives are reused unchanged.
273289

274290
Because two `.postN` wheels can wrap different builds of the same upstream tag, the exact
275291
upstream commit is recorded in each archive's filename and pinned in `plastimatchUrls.cmake`,

0 commit comments

Comments
 (0)