ci: publish cytnx to PyPI on tag (stable) and master push (.devN nightly)#866
ci: publish cytnx to PyPI on tag (stable) and master push (.devN nightly)#866IvanaGyro wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new script, tools/prepare_nightly_release.py, which automates updating pyproject.toml for nightly releases by parsing version information from version.cmake and appending a UTC timestamp. The code review feedback focuses on making the script more robust against formatting changes. Specifically, the reviewer suggests parsing version components individually rather than assuming they are adjacent, using flexible regular expressions instead of exact string replacement for updating package metadata, and refining the regex used to strip the scikit-build metadata block to prevent premature matching.
5db13b9 to
f5dcea7
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #866 +/- ##
==========================================
+ Coverage 29.05% 29.23% +0.17%
==========================================
Files 241 241
Lines 35519 35559 +40
Branches 14807 14822 +15
==========================================
+ Hits 10319 10394 +75
+ Misses 18039 17941 -98
- Partials 7161 7224 +63
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
f5dcea7 to
80fa62c
Compare
Two related cleanups to dependency declarations in pyproject.toml:
1. The `dev` aggregate under `[project.optional-dependencies]`
previously duplicated the contents of the `test` and `coverage`
groups verbatim, so any change to either leaf group required a
second edit to keep `dev` in sync. Replace the duplicated dep
list with self-references:
dev = ["cytnx[test]", "cytnx[coverage]"]
`pip install --editable .[dev]` resolves the references and pulls
the same packages as before, but the source of truth for each
group is now its own definition.
2. Add a new PEP 735 `[dependency-groups]` table with a
`release-tools` group containing `tomlkit`. The group is consumed
by release pipelines that programmatically rewrite pyproject.toml
(e.g. tools/prepare_nightly_release.py).
`[dependency-groups]` is used rather than
`[project.optional-dependencies]` because tools listed here are
needed at *build pipeline* time only, not at install or run time
of cytnx itself. `pip install --group release-tools` installs the
listed packages without invoking the project build backend, so a
release pipeline can install the helpers on a runner without
accidentally triggering a scikit-build-core compile of cytnx
before cibuildwheel runs in its own isolated environment.
Co-Authored-By: Claude <noreply@anthropic.com>
`set_target_properties(VERSION ...)`, `project(VERSION ...)`, and the shared-library SONAME require a strict MAJOR.MINOR.PATCH, so CYTNX_VERSION cannot itself carry a PEP 440 dev/local suffix. Introduce a separate string `CYTNX_VERSION_FULL`, initialised to the numeric `CYTNX_VERSION` and extended with the contents of the `CYTNX_VERSION_TAG` environment variable when that variable is set and non-empty. Use `CYTNX_VERSION_FULL` for the `CYTNX_VERSION` compile definition consumed by pybind, which becomes the runtime `cytnx.__version__`. Numeric-only consumers (project version, target VERSION/SOVERSION, libname suffix) keep reading `CYTNX_VERSION`. With no environment variable set, `CYTNX_VERSION_FULL` and `CYTNX_VERSION` are identical, so non-release builds are unaffected and `pytests/version_test.py` continues to compare `cytnx.__version__` against the numeric version.cmake values without change. Also append `CYTNX_VERSION_TAG` to the cibuildwheel Linux `environment-pass` list so the variable, when set on the host runner, is forwarded into the manylinux container that performs the actual wheel compilation. On macOS the variable is inherited from the runner environment directly and needs no allow-list entry. The variable itself is never set by a normal contributor or by any existing CI job in this commit; nothing downstream consumes it yet. A subsequent commit adds the release tooling that produces the suffix. Co-Authored-By: Claude <noreply@anthropic.com>
Add `tools/prepare_nightly_release.py`, a single-shot script intended
to run in CI before cibuildwheel on every push to `master`. It
- reuses the regex declared in
`[tool.scikit-build.metadata.version]` of pyproject.toml as the
sole parser for version.cmake, so the two never disagree on what
"the version" is;
- derives a PEP 440 dev version of the form
MAJOR.MINOR.PATCH.devYYYYMMDDHHMM (UTC), giving each push a
monotonically increasing, deterministic identifier;
- rewrites pyproject.toml in place to a static `version = "..."`
on the `cytnx` project, removing the `dynamic = ["version"]`
entry from `[project]` and the now-redundant
`[tool.scikit-build.metadata.version]` table; and
- appends `CYTNX_VERSION_TAG=.devYYYYMMDDHHMM` to `$GITHUB_ENV`
when running under GitHub Actions, so the surrounding CI job can
forward the suffix into the cibuildwheel build via the
`CYTNX_VERSION_TAG` hook in CMakeLists.txt, keeping the wheel
filename's version string and `cytnx.__version__` in lockstep.
The rewrite uses `tomlkit` (declared in the `release-tools`
dependency-group) for round-trip formatting preservation, so the
comments and section ordering of pyproject.toml survive the stamping
intact.
The script is idempotent only against a clean checkout; CI is
expected to run it once on a fresh tree before cibuildwheel.
No workflow invokes the script yet; the nightly publishing workflow
is added in a follow-up commit so that this commit can be reviewed
on its own.
Co-Authored-By: Claude <noreply@anthropic.com>
The workflow previously ran on every pull request and every push to
master to upload a non-version-suffixed wheel set to TestPyPI for
smoke-testing, and re-uploaded tagged releases to TestPyPI rather
than PyPI. The tag-push wheels never reached production PyPI.
This commit narrows the workflow to the role its name implies and
makes it the canonical production release pipeline:
- Triggers are reduced to `push` of `v*` tags and manual
`workflow_dispatch`. The `pull_request` trigger and the
`push` trigger on `master` are removed; the PR-validation and
per-PR ccache warm-up jobs run in dedicated workflows
(build_test.yml, ccache_seed.yml).
- The PR-only "Merge with latest target branch" step and the
pull_request-keyed ccache cache step are dropped together with the
PR trigger; both were unreachable.
- The publish step uploads to production PyPI (no
`repository-url` override). Because the workflow itself only
triggers on `v*` tag pushes and `workflow_dispatch`, the publish
job needs no additional `if:` guard.
- Job name renamed from `ReleaseTestPyPI` (`ReleaseWheel-TestPyPI`)
to `ReleasePyPI` to reflect the destination.
- Drop `defaults.run.shell: bash -el {0}`; the workflow does not
rely on login-shell `.bashrc` initialisation, so the implicit
`bash` shell is sufficient and avoids the extra interactive shell
setup cost per step.
- Pin every third-party action to a full commit SHA with the
upstream tag retained as a trailing comment, so an upstream tag
re-point cannot silently change what runs in a release job that
has PyPI publish permissions.
- Add a `ccache-wheel-${OS}-` restore-keys fallback so that the
first build of a new ref does not start with a cold ccache.
The wheel build matrix itself is unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
Add `release_pypi_nightly.yml`, which builds and publishes a cytnx
wheel set to production PyPI on every push to `master` (i.e. every
merged pull request) and on manual workflow_dispatch.
The pipeline follows the numpy / scipy convention of using one PyPI
project name for both stable and dev channels, distinguished by the
PEP 440 version string:
- tagged releases publish `cytnx X.Y.Z` (handled by
release_pypi.yml);
- nightlies publish `cytnx X.Y.Z.devYYYYMMDDHHMM`.
`pip install cytnx` continues to resolve to the latest stable
release; `pip install --pre cytnx` resolves to the most recent
nightly. No separate PyPI project, trusted-publisher entry, or
install command is needed.
Workflow shape:
1. Install the `release-tools` PEP 735 dependency-group from
pyproject.toml via `pip install --group release-tools`. Doing so
keeps the section name in pyproject.toml as the actual source of
truth for the workflow's tooling deps without invoking
scikit-build-core to compile cytnx on the host before cibuildwheel
does so under its own isolated environment, which
`pip install .[release-tools]` would have forced. The
runner-bundled pip is upgraded first because `--group` requires
pip 25.1+.
2. Run `tools/prepare_nightly_release.py` to rewrite pyproject.toml
(static dev version, `dynamic = ["version"]` removed,
`[tool.scikit-build.metadata.version]` removed) and to append
`CYTNX_VERSION_TAG=.devYYYYMMDDHHMM` to `$GITHUB_ENV`. This same
tag is consumed by the `CYTNX_VERSION_TAG` env hook in
CMakeLists.txt so the wheel filename and `cytnx.__version__`
agree.
3. Build wheels with cibuildwheel on the same matrix as
release_pypi.yml (ubuntu-24.04, ubuntu-24.04-arm, macos-14,
macos-15-intel). The ccache key prefix `ccache-wheel-${OS}-` is
shared with release_pypi.yml so a cache primed by either pipeline
can prime the other.
4. Forward `CYTNX_VERSION_TAG` into the cibuildwheel build via the
step's `env:` block; the Linux manylinux container additionally
needs the variable on the `environment-pass` list in
pyproject.toml (added earlier in this branch).
5. Upload the wheels to PyPI via OIDC trusted publishing using the
same SHA-pinned action versions as release_pypi.yml.
The `prepare_nightly_release.py` rewrite is destructive to the
checkout, so it must run on a fresh runner working tree; the
workflow is structured accordingly.
Co-Authored-By: Claude <noreply@anthropic.com>
1c0abab to
d91029c
Compare
|
I plan to follow SPEC 4 to release the nightly build. This is adopted by numpy, scipy and some scientific Python packages. I am requesting access of Scientific Python Nightly Wheels to release the nightly build on scientific-python/upload-nightly-action#167. We will not merge this PR until we get access. |
Summary
Replace the TestPyPI-on-PR/tag flow with two distinct release pipelines that share one PyPI project name, following the numpy / scipy convention:
release_pypi.ymlis restricted tov*tag pushes and manualworkflow_dispatch, and publishes the resulting wheels to production PyPI ascytnx X.Y.Z.release_pypi_nightly.ymltriggers on every push tomaster(PR merges) and on manualworkflow_dispatch, and publishes wheels to the samecytnxPyPI project but version-stampedMAJOR.MINOR.PATCH.devYYYYMMDDHHMM(PEP 440, UTC).pip install cytnxcontinues to resolve to the latest stable release (PEP 440 excludes pre-releases by default).pip install --pre cytnxresolves to the most recent nightly. One PyPI project, one trusted-publisher entry, two channels distinguished by the version string — same pattern as numpy, scipy, cython, etc.Commits
build: refactor optional-deps; add release-tools group— collapsedevextras down tocytnx[test] + cytnx[coverage](single source of truth per leaf group) and add a newrelease-toolsextras group containingtomlkit, consumed only by the release pipeline.build: add CYTNX_VERSION_TAG env hook for dev-version suffixes— introduceCYTNX_VERSION_FULLinCMakeLists.txtthat initialises to the numericCYTNX_VERSIONand is extended with$ENV{CYTNX_VERSION_TAG}when that variable is set. Used only for theCYTNX_VERSIONcompile definition (i.e.cytnx.__version__); numeric-only consumers (project(VERSION ...), target SOVERSION, libname) are untouched. Also addsCYTNX_VERSION_TAGto the cibuildwheel Linuxenvironment-passso the variable crosses into the manylinux container. No-op when the env var is unset.build: add nightly-release pyproject stamping helper—tools/prepare_nightly_release.pyusestomlkitto rewritepyproject.tomlin place (dynamic = ["version"]removed, staticversion = "X.Y.Z.devYYYYMMDDHHMM",[tool.scikit-build.metadata.version]removed) and appendsCYTNX_VERSION_TAG=.devYYYYMMDDHHMMto$GITHUB_ENV. Reuses the regex from[tool.scikit-build.metadata.version].regexto readversion.cmake, so the regex lives in exactly one place. Stand-alone; not wired up yet.ci: switch release_pypi.yml from TestPyPI to production PyPI— triggers narrowed tov*tags +workflow_dispatch. Drops thepull_requesttrigger and the PR-merge / PR-keyed ccache steps that went with it. Job renamedReleaseTestPyPI→ReleasePyPI. Publish destination is production PyPI (norepository-urloverride). Drops the login-shelldefaultsblock. Every third-party action is pinned to a full commit SHA with the upstream tag retained as a trailing comment.ci: add nightly cytnx dev-release pipeline— newrelease_pypi_nightly.yml. Triggers on push tomaster+workflow_dispatch. Installstomlkit, runs the prep script, builds wheels via cibuildwheel on the same 4-OS matrix as the stable workflow, and publishes to PyPI as acytnxdev release via the same trusted-publisher entry. ForwardsCYTNX_VERSION_TAGinto the cibuildwheel build via the step'senv:block. Shares theccache-wheel-${OS}-cache prefix withrelease_pypi.ymlso the two pipelines warm each other's caches.Why a separate workflow file?
Different triggers, different version-stamping behavior, and a broken nightly should never block a real release (or vice versa). The shared C++ build logic lives in
pyproject.toml([tool.cibuildwheel.*]) andtools/cibuildwheel_*, so the workflow YAML duplication is small.Version stamping — why timestamp instead of incremental
A monotonic timestamp (
devYYYYMMDDHHMM) requires no external state. An incrementaldevNwould need either querying PyPI for the last.devNor storing a counter, both of which race under parallel merges and break on repo moves. The minute-resolution stamp handles multiple merges per day; PyPI rejects duplicate filenames, but two merges within the same minute is extremely unlikely (and a re-run picks up a new minute).One-time setup required before merge (manual on PyPI)
These workflows use trusted publishing — no API tokens are stored in the repo. The following must be configured on PyPI before either workflow can publish:
cytnxproject — add a trusted publisher with repositoryCytnx-dev/Cytnx, workflowrelease_pypi.yml, jobReleasePyPI, no environment.cytnxproject — add a second trusted publisher entry with repositoryCytnx-dev/Cytnx, workflowrelease_pypi_nightly.yml, jobPublishNightlyPyPI, no environment.Until both are configured, the
pypa/gh-action-pypi-publishstep will fail; this is intentional and safe.Test plan
cytnxproject.workflow_dispatchofrelease_pypi_nightly.ymlfrom this branch (before merge) — verify the wheel filenames look likecytnx-1.0.0.devYYYYMMDDHHMM-*and the upload succeeds.workflow_dispatchofrelease_pypi.yml— verify it builds and publishescytnx X.Y.Zto production PyPI.release_pypi_nightly.ymlruns and acytnx X.Y.Z.devNrelease appears on PyPI.vX.Y.Z) and confirmrelease_pypi.ymlruns andcytnx X.Y.Zlands on PyPI.pip install cytnxresolves to the tagged stable;pip install --pre cytnxresolves to the latest nightly.