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+
37Platform-specific Python wheels containing the official
48[ plastimatch] ( https://gitlab.com/plastimatch/plastimatch ) command line executables, so that
59plastimatch can be installed with
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
8279plastimatch 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
8885cmake -S superbuild -B build -DCMAKE_BUILD_TYPE=Release
8986cmake --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
9695cmake -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
101100Everything 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
121120nothing 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
274290Because two ` .postN ` wheels can wrap different builds of the same upstream tag, the exact
275291upstream commit is recorded in each archive's filename and pinned in ` plastimatchUrls.cmake ` ,
0 commit comments