From 0b98302a98d0d6fa73ad689746aa7f13b3bab3f6 Mon Sep 17 00:00:00 2001 From: Ivana Date: Sun, 31 May 2026 18:13:29 +0000 Subject: [PATCH 1/5] build: refactor optional-deps; add release-tools dependency-group 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 --- pyproject.toml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 45de6d9dd..362054caa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,9 +44,8 @@ coverage = [ # both Python and C++ coverage. Build-time deps (scikit-build-core, # pybind11) come from `[build-system].requires` automatically. dev = [ - "pytest", - "pytest-cov", - "gcovr", + "cytnx[test]", + "cytnx[coverage]", ] docs = [ "sphinx>=7.4.7", @@ -56,6 +55,17 @@ docs = [ "furo>=2024.8.6", ] +[dependency-groups] +# Build / release helpers (e.g. tools/prepare_nightly_release.py, +# which rewrites this file to stamp a nightly version). Declared as a +# PEP 735 dependency-group rather than under +# `[project.optional-dependencies]` because these tools 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 scikit-build-core to compile the project, +# which `pip install .[release-tools]` would have forced. +release-tools = ["tomlkit"] + [project.urls] Documentation = "https://cytnx-dev.github.io/Cytnx/" Repository = "https://github.com/Cytnx-dev/Cytnx.git" From be17ffdb1a920c13a081ebee7626a09e3d0df668 Mon Sep 17 00:00:00 2001 From: Ivana Date: Sun, 31 May 2026 18:13:42 +0000 Subject: [PATCH 2/5] build: add CYTNX_VERSION_TAG env hook for dev-version suffixes `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 --- CMakeLists.txt | 16 ++++++++++++++-- pyproject.toml | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40baa8243..4df530b45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,9 +63,21 @@ include(version.cmake) set(CYTNX_VERSION ${CYTNX_VERSION_MAJOR}.${CYTNX_VERSION_MINOR}.${CYTNX_VERSION_PATCH} ) +# `CYTNX_VERSION_FULL` carries an optional PEP 440-style suffix +# (e.g. `.dev202605311220`) supplied via the `CYTNX_VERSION_TAG` +# environment variable. The nightly release pipeline sets this from +# tools/prepare_nightly_release.py so that the wheel filename +# (cytnx_nightly-X.Y.Z.devN-*.whl) and the runtime `cytnx.__version__` +# agree. `CYTNX_VERSION` itself stays strictly numeric because +# `project(VERSION ...)` and `set_target_properties(VERSION ...)` +# require MAJOR.MINOR.PATCH. +set(CYTNX_VERSION_FULL "${CYTNX_VERSION}") +if(DEFINED ENV{CYTNX_VERSION_TAG} AND NOT "$ENV{CYTNX_VERSION_TAG}" STREQUAL "") + string(APPEND CYTNX_VERSION_FULL "$ENV{CYTNX_VERSION_TAG}") +endif() set(CYTNX_VARIANT_INFO "") -message(STATUS " Version: ${CYTNX_VERSION}") +message(STATUS " Version: ${CYTNX_VERSION_FULL}") # create a file that contain all the link flags: FILE(WRITE "${CMAKE_BINARY_DIR}/linkflags.tmp" "" "") @@ -392,7 +404,7 @@ IF(BUILD_PYTHON) pybind/ncon_py.cpp ) target_link_libraries(pycytnx PUBLIC cytnx) - target_compile_definitions(pycytnx PRIVATE CYTNX_VERSION="${CYTNX_VERSION}") + target_compile_definitions(pycytnx PRIVATE CYTNX_VERSION="${CYTNX_VERSION_FULL}") # On macOS, Python extensions should NOT link to libpython # Use -undefined dynamic_lookup to resolve symbols from the running interpreter diff --git a/pyproject.toml b/pyproject.toml index 362054caa..7ad52e015 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,7 +103,7 @@ before-build = "python ./tools/cibuildwheel_before_build.py" [tool.cibuildwheel.linux] before-all = "bash ./tools/cibuildwheel_before_all.sh" environment = { CMAKE_INCLUDE_PATH = "/usr/include/openblas", CCACHE_DEBUG = "true", CCACHE_DEBUGLEVEL = "1", CCACHE_LOGFILE = "/tmp/cytnx-ccache.log" } -environment-pass = ["CMAKE_C_COMPILER_LAUNCHER", "CMAKE_CXX_COMPILER_LAUNCHER", "CMAKE_CUDA_COMPILER_LAUNCHER", "CCACHE_COMPILERCHECK", "CCACHE_MAXSIZE", "CCACHE_DIR", "CCACHE_BASEDIR", "CCACHE_DEBUG", "CCACHE_DEBUGLEVEL", "CCACHE_LOGFILE"] +environment-pass = ["CMAKE_C_COMPILER_LAUNCHER", "CMAKE_CXX_COMPILER_LAUNCHER", "CMAKE_CUDA_COMPILER_LAUNCHER", "CCACHE_COMPILERCHECK", "CCACHE_MAXSIZE", "CCACHE_DIR", "CCACHE_BASEDIR", "CCACHE_DEBUG", "CCACHE_DEBUGLEVEL", "CCACHE_LOGFILE", "CYTNX_VERSION_TAG"] [tool.cibuildwheel.macos] before-all = "bash ./tools/cibuildwheel_before_all_macos.sh" From e31cc1e0e965c093ecd200f981ed07e6989ae9fd Mon Sep 17 00:00:00 2001 From: Ivana Date: Sun, 31 May 2026 18:13:52 +0000 Subject: [PATCH 3/5] build: add nightly-release pyproject stamping helper 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 --- tools/prepare_nightly_release.py | 108 +++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tools/prepare_nightly_release.py diff --git a/tools/prepare_nightly_release.py b/tools/prepare_nightly_release.py new file mode 100644 index 000000000..6f357648c --- /dev/null +++ b/tools/prepare_nightly_release.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Stamp pyproject.toml and emit the build version tag for a nightly release. + +The script + + 1. reads `MAJOR.MINOR.PATCH` from version.cmake using the same regex + that the `[tool.scikit-build.metadata.version]` block of + pyproject.toml uses, so there is only one source of truth for how + the version is parsed; + 2. derives a PEP 440 dev version of the form + `MAJOR.MINOR.PATCH.devYYYYMMDDHHMM` (UTC stamp); + 3. rewrites pyproject.toml in place so cibuildwheel produces wheels + for the `cytnx` PyPI project with that static dev version + (`pip install --pre cytnx` will pick up the nightly; `pip install + cytnx` continues to install the latest stable, mirroring the + numpy / scipy convention); and + 4. appends `CYTNX_VERSION_TAG=.devYYYYMMDDHHMM` to `$GITHUB_ENV` so + the surrounding CI job can forward the same tag into CMake. The + C++ compile definition `CYTNX_VERSION` (which becomes + `cytnx.__version__`) is built from the numeric CMake version plus + this tag, keeping `cytnx.__version__` aligned with the wheel + filename. + +This is intended to run inside a fresh CI checkout before cibuildwheel. +It mutates the working tree and is not idempotent. + +Requires `tomlkit` (declared in pyproject.toml's `release-tools` +optional-dependencies group) so the rewrite preserves comments and +formatting on round-trip. +""" + +import datetime +import os +import pathlib +import re +import sys + +import tomlkit + +REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent +PYPROJECT = REPO_ROOT / "pyproject.toml" +VERSION_CMAKE = REPO_ROOT / "version.cmake" + + +def load_version_regex(doc: tomlkit.TOMLDocument) -> re.Pattern[str]: + pattern = doc["tool"]["scikit-build"]["metadata"]["version"]["regex"] + return re.compile(str(pattern).strip()) + + +def read_base_version(version_re: re.Pattern[str]) -> str: + text = VERSION_CMAKE.read_text() + match = version_re.search(text) + if not match: + sys.exit(f"could not parse MAJOR/MINOR/PATCH from {VERSION_CMAKE}") + return f"{match['major']}.{match['minor']}.{match['patch']}" + + +def build_dev_tag() -> str: + stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d%H%M") + return f".dev{stamp}" + + +def rewrite_pyproject(doc: tomlkit.TOMLDocument, version: str) -> None: + project = doc["project"] + + dynamic = list(project.get("dynamic", [])) + if "version" not in dynamic: + sys.exit('expected "version" in [project].dynamic in pyproject.toml') + dynamic.remove("version") + if dynamic: + project["dynamic"] = dynamic + else: + del project["dynamic"] + project["version"] = version + + skb_metadata = doc["tool"]["scikit-build"]["metadata"] + if "version" not in skb_metadata: + sys.exit("expected [tool.scikit-build.metadata.version] in pyproject.toml") + del skb_metadata["version"] + + PYPROJECT.write_text(tomlkit.dumps(doc)) + + +def emit_github_env(tag: str) -> None: + github_env = os.environ.get("GITHUB_ENV") + if not github_env: + # Outside Actions; print so the operator can pass it manually. + print(f"CYTNX_VERSION_TAG={tag}") + return + with open(github_env, "a", encoding="utf-8") as f: + f.write(f"CYTNX_VERSION_TAG={tag}\n") + + +def main() -> None: + doc = tomlkit.parse(PYPROJECT.read_text()) + version_re = load_version_regex(doc) + base = read_base_version(version_re) + tag = build_dev_tag() + version = f"{base}{tag}" + + rewrite_pyproject(doc, version) + emit_github_env(tag) + + print(f"stamped pyproject.toml: cytnx=={version}") + + +if __name__ == "__main__": + main() From f1ca89b807abf6bafec91ddded5a0a64589f0061 Mon Sep 17 00:00:00 2001 From: Ivana Date: Sun, 31 May 2026 18:14:04 +0000 Subject: [PATCH 4/5] ci: switch release_pypi.yml from TestPyPI to production PyPI 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 --- .github/workflows/release_pypi.yml | 60 +++++++++--------------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml index fdcb8749b..5f2db2311 100644 --- a/.github/workflows/release_pypi.yml +++ b/.github/workflows/release_pypi.yml @@ -1,10 +1,13 @@ name: Release to PyPI +# Official `cytnx` release pipeline. Triggers only on `v*` tag pushes +# and manual workflow_dispatch; the resulting wheels are published to +# production PyPI in both cases. Nightly builds against master are +# handled by release_pypi_nightly.yml and publish under a separate +# nightly project. + on: - pull_request: push: - branches: - - master tags: - "v*" workflow_dispatch: @@ -20,58 +23,32 @@ jobs: # newer images because Homebrew dylib minos can force a higher wheel # deployment target and break delocate compatibility for lower targets. os: [ubuntu-24.04, ubuntu-24.04-arm, macos-14, macos-15-intel] - defaults: - run: - shell: bash -el {0} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 submodules: recursive - # When re-running a PR job, GitHub Actions uses the merge commit created at - # the time of the original run, not a fresh merge with the latest target branch. - # This step ensures we always test against the most recent target branch. - - name: Merge with latest target branch (pull_request only) - if: github.event_name == 'pull_request' - run: | - git fetch origin ${{ github.event.pull_request.base.ref }} - git merge --no-edit origin/${{ github.event.pull_request.base.ref }} - # Refresh submodules in case the merge moved any gitlinks (e.g. cmake_modules/morse_cmake). - git submodule update --init --recursive - - - name: Cache Ccache Directory (pull_request) - if: ${{ github.event_name == 'pull_request' }} - uses: actions/cache@v4 - with: - path: | - ~/.ccache - key: ccache-wheel-${{ runner.os }}-${{ github.event.pull_request.head.ref }}-${{ github.sha }} - restore-keys: | - ccache-wheel-${{ runner.os }}-${{ github.event.pull_request.head.ref }}- - ccache-wheel-${{ runner.os }}-${{ github.event.pull_request.base.ref }}- - - - name: Cache Ccache Directory (push) - if: ${{ github.event_name == 'push' }} - uses: actions/cache@v4 + - name: Cache Ccache Directory + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.ccache key: ccache-wheel-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }} restore-keys: | ccache-wheel-${{ runner.os }}-${{ github.ref_name }}- + ccache-wheel-${{ runner.os }}- - name: Set Ccache Directory run: | - echo "Start building---------------------------------" # HOST_CCACHE_DIR is consumed only by the docker bind-mount source # below; the in-container CCACHE_DIR is set in the Build Wheels # step's env: block (and forwarded via environment-pass). echo "HOST_CCACHE_DIR=${HOME}/.ccache" >> "$GITHUB_ENV" - name: Build Wheels - uses: pypa/cibuildwheel@v3.3.0 + uses: pypa/cibuildwheel@63fd63b352a9a8bdcc24791c9dbee952ee9a8abc # v3.3.0 env: CMAKE_C_COMPILER_LAUNCHER: ccache CMAKE_CXX_COMPILER_LAUNCHER: ccache @@ -94,28 +71,25 @@ jobs: CIBW_TEST_COMMAND: "python {project}/tools/validate_ccache_stats.py" - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} path: ./wheelhouse/*.whl - ReleaseTestPyPI: - name: ReleaseWheel-TestPyPI + ReleasePyPI: + name: ReleasePyPI needs: BuildWheel - if: ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) }} runs-on: ubuntu-24.04 permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: pattern: cibw-wheels-* path: dist merge-multiple: true - - name: Publish package distributions to TestPyPI (pull_request and tag push) - if: ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) }} - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Publish package distributions to PyPI (tag push or manual dispatch) + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 with: - repository-url: https://test.pypi.org/legacy/ packages-dir: dist From d91029ca0a8a7544d90b707c8f831fe858fcacd4 Mon Sep 17 00:00:00 2001 From: Ivana Date: Sun, 31 May 2026 18:14:20 +0000 Subject: [PATCH 5/5] ci: add nightly cytnx dev-release pipeline 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 --- .github/workflows/release_pypi_nightly.yml | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 .github/workflows/release_pypi_nightly.yml diff --git a/.github/workflows/release_pypi_nightly.yml b/.github/workflows/release_pypi_nightly.yml new file mode 100644 index 000000000..7151a4f3c --- /dev/null +++ b/.github/workflows/release_pypi_nightly.yml @@ -0,0 +1,112 @@ +name: Nightly Release to PyPI + +# Builds and publishes a `cytnx` dev wheel set to PyPI on every push to +# master (i.e. every PR merge), so that `pip install --pre cytnx` +# tracks the latest master while `pip install cytnx` keeps resolving +# to the most recent tagged release. Wheels share the official `cytnx` +# PyPI project name but use a PEP 440 dev version +# (MAJOR.MINOR.PATCH.devYYYYMMDDHHMM) stamped into pyproject.toml by +# tools/prepare_nightly_release.py at build time. + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + BuildNightlyWheel: + name: BuildNightlyWheel-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, ubuntu-24.04-arm, macos-14, macos-15-intel] + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 + submodules: recursive + + - name: Install nightly stamping helper + # Installs the `release-tools` PEP 735 dependency-group from + # pyproject.toml (currently just `tomlkit`). Using `--group` + # rather than `pip install .[release-tools]` keeps the source + # of truth for build-pipeline deps in pyproject.toml without + # triggering scikit-build-core to compile cytnx itself before + # cibuildwheel does so under its own isolated environment. + # `--group` requires pip 25.1+; the runner-bundled pip is + # upgraded first to make the requirement portable across + # runner image versions. + run: | + python -m pip install --upgrade pip + pip install --group release-tools + + - name: Stamp pyproject.toml for nightly release + # Rewrites pyproject.toml to set a static + # MAJOR.MINOR.PATCH.devYYYYMMDDHHMM version and exports + # CYTNX_VERSION_TAG to $GITHUB_ENV so subsequent steps (and the + # cibuildwheel build) can append the same suffix to + # cytnx.__version__ via CMake. + run: python tools/prepare_nightly_release.py + + - name: Cache Ccache Directory + # Share the ccache prefix with release_pypi.yml: nightlies and + # tagged releases build the same C++ sources, so a cache primed + # by either feeds the other. + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + ~/.ccache + key: ccache-wheel-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ccache-wheel-${{ runner.os }}-${{ github.ref_name }}- + ccache-wheel-${{ runner.os }}- + + - name: Set Ccache Directory + run: | + # HOST_CCACHE_DIR is consumed only by the docker bind-mount source + # below; the in-container CCACHE_DIR is set in the Build Wheels + # step's env: block (and forwarded via environment-pass). + echo "HOST_CCACHE_DIR=${HOME}/.ccache" >> "$GITHUB_ENV" + + - name: Build Wheels + uses: pypa/cibuildwheel@63fd63b352a9a8bdcc24791c9dbee952ee9a8abc # v3.3.0 + env: + CMAKE_C_COMPILER_LAUNCHER: ccache + CMAKE_CXX_COMPILER_LAUNCHER: ccache + CMAKE_CUDA_COMPILER_LAUNCHER: ccache + CCACHE_COMPILERCHECK: content + CCACHE_MAXSIZE: 1G + CCACHE_DIR: ${{ runner.os == 'Linux' && '/host_ccache' || env.HOST_CCACHE_DIR }} + CCACHE_BASEDIR: ${{ runner.os == 'Linux' && '/project/build' || github.workspace }} + CIBW_CONTAINER_ENGINE: "docker; create_args: --volume ${{ env.HOST_CCACHE_DIR }}:/host_ccache" + CIBW_TEST_COMMAND: "python {project}/tools/validate_ccache_stats.py" + # CYTNX_VERSION_TAG was exported to $GITHUB_ENV by the Stamp + # step above; on Linux it is forwarded into the manylinux + # container via [tool.cibuildwheel.linux].environment-pass. + CYTNX_VERSION_TAG: ${{ env.CYTNX_VERSION_TAG }} + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: cibw-wheels-nightly-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + PublishNightlyPyPI: + name: PublishNightlyPyPI + needs: BuildNightlyWheel + runs-on: ubuntu-24.04 + permissions: + id-token: write + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: cibw-wheels-nightly-* + path: dist + merge-multiple: true + + - name: Publish package distributions to PyPI (cytnx dev release) + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 + with: + packages-dir: dist