From 67ada4aed4f8e33a3016dbb185632eaf48f14c76 Mon Sep 17 00:00:00 2001 From: Thibault Pelletier Date: Tue, 10 Nov 2026 15:17:19 +0100 Subject: [PATCH] fix(ci): add slicer-core version to generated wheel - Add slicer-core version to generated wheel version name - Add build dependencies in the SDK requirements - Update VTK version to 9.6.2 --- .github/scripts/pin_slicer_core.py | 99 +++++++++++++++++++ .github/workflows/build-wheels.yml | 16 ++- .github/workflows/ci.yml | 2 + .github/workflows/release.yml | 2 + LayerDM/pyproject.toml | 2 +- LayerDMSDK/pyproject.toml | 8 +- .../packages/build_module/pyproject.toml | 2 +- LayerDMSDK/tests/test_build_module.py | 21 +++- 8 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 .github/scripts/pin_slicer_core.py diff --git a/.github/scripts/pin_slicer_core.py b/.github/scripts/pin_slicer_core.py new file mode 100644 index 0000000..96d2be0 --- /dev/null +++ b/.github/scripts/pin_slicer_core.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Pin the slicer-core version a wheel is built against. + +""" + +from __future__ import annotations + +import argparse +import re +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +PACKAGES = ("LayerDM", "LayerDMSDK") +VERSION_TAG = "refs/tags/v[0-9]*" + + +def library_version() -> str: + tag = subprocess.run( + [ + "git", + "for-each-ref", + "--sort=-v:refname", + "--count=1", + "--format=%(refname:strip=2)", + VERSION_TAG, + ], + cwd=ROOT, + check=True, + capture_output=True, + text=True, + ).stdout.strip() + if not tag: + raise SystemExit( + f"error: no v.. tag found in {ROOT} " + "(shallow clone without tags? use fetch-depth: 0)" + ) + return tag.removeprefix("v") + + +def runtime_requirement(slicer_core: str) -> str: + """slicer-core requirement covering every build revision of the same ABI. + + 5.11.0.6 -> "slicer-core==5.11.0.*" (matches 5.11.0.0 ... 5.11.0.255) + """ + abi = ".".join(slicer_core.split(".")[:3]) + return f"slicer-core=={abi}.*" + + +def pin(package: str, slicer_core: str, version: str) -> None: + path = ROOT / package / "pyproject.toml" + text = path.read_text(encoding="utf-8") + runtime = runtime_requirement(slicer_core) + + text, build_pins = re.subn( + r"slicer-core-sdk==[^\"\n]+", f"slicer-core-sdk=={slicer_core}", text + ) + text, runtime_pins = re.subn( + r"\"slicer-core==[^\"\n]+\"", f'"{runtime}"', text + ) + text, dynamic = re.subn( + r"^dynamic = \[\"version\"\]$", f'version = "{version}"', text, flags=re.MULTILINE + ) + text = re.sub(r"^metadata\.version\.provider[^\n]*\n", "", text, flags=re.MULTILINE) + + expected_runtime = 1 if package == "LayerDM" else 0 + if (runtime_pins, dynamic) != (expected_runtime, 1) or build_pins < 1: + raise SystemExit( + f"error: unexpected content in {path} " + f"(slicer-core-sdk pins: {build_pins}, " + f"slicer-core dependencies: {runtime_pins} (expected {expected_runtime}), " + f"dynamic version: {dynamic})" + ) + + path.write_text(text, encoding="utf-8") + print(f"{path.relative_to(ROOT)}: version={version} build={slicer_core} runtime={runtime}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--slicer-core", + required=True, + help="slicer-core version to build against, e.g. 5.11.0.6", + ) + args = parser.parse_args() + + if not re.fullmatch(r"\d+(\.\d+)+", args.slicer_core): + raise SystemExit(f"error: not a valid slicer-core version: {args.slicer_core}") + + version = f"{library_version()}.{args.slicer_core}" + for package in PACKAGES: + pin(package, args.slicer_core, version) + print(f"wheel version: {version}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 7077347..a1748a9 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -19,6 +19,10 @@ on: description: "Wheel platform tag to apply after building." required: true type: string + slicer-core-version: + description: "slicer-core version to build against, appended to the package version (e.g. 5.11.0.6)." + required: true + type: string jobs: build-test: @@ -46,12 +50,18 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-tags: true # tags are used to determine wheel version + # Full history for slicer-core version pin and CI run + fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }} + # Rewrites the slicer-core pins of both pyprojects and freezes the wheel + # version to .. + - name: Pin slicer-core version + run: python .github/scripts/pin_slicer_core.py --slicer-core "${{ inputs.slicer-core-version }}" + # Required to use MSVC with Ninja - uses: ilammy/msvc-dev-cmd@v1.13.0 @@ -75,13 +85,13 @@ jobs: - name: Upload runtime wheels uses: actions/upload-artifact@v4 with: - name: slicer_layer_dm-${{ inputs.build-target }} + name: slicer_layer_dm-${{ inputs.slicer-core-version }}-${{ inputs.build-target }} path: | wheelhouse/slicer_layer_dm-*.whl - name: Upload SDK wheels uses: actions/upload-artifact@v4 with: - name: slicer_layer_dm_sdk-${{ inputs.build-target }} + name: slicer_layer_dm_sdk-${{ inputs.slicer-core-version }}-${{ inputs.build-target }} path: | wheelhouse/slicer_layer_dm_sdk-*.whl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c365c94..a062b89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] py-abi: ["cp310"] + slicer-core: ["5.12.4.0", "5.13.31053.0"] include: # Python version to install on host - py-abi: cp310 @@ -31,3 +32,4 @@ jobs: build-target: ${{ matrix.py-abi }}-${{ matrix.build-target }} python-version: ${{ matrix.python-version }} platform-tag: ${{ matrix.platform-tag }} + slicer-core-version: ${{ matrix.slicer-core }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a7d4a8..6008ec2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] py-abi: ["cp310", "cp311", "cp312", "cp313"] + slicer-core: ["5.12.4.0", "5.13.31053.0"] include: # Python version to install on host - py-abi: cp310 @@ -35,6 +36,7 @@ jobs: build-target: ${{ matrix.py-abi }}-${{ matrix.build-target }} python-version: ${{ matrix.python-version }} platform-tag: ${{ matrix.platform-tag }} + slicer-core-version: ${{ matrix.slicer-core }} upload-pypi: name: Push runtime wheel on PyPi diff --git a/LayerDM/pyproject.toml b/LayerDM/pyproject.toml index 26dcfac..c696b9d 100644 --- a/LayerDM/pyproject.toml +++ b/LayerDM/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "scikit-build-core", "slicer-core-sdk==5.11.0.*", - "vtk-sdk==9.6.0", + "vtk-sdk==9.6.2", "vtk-sdk-python-wheel-helper" ] build-backend = "scikit_build_core.build" diff --git a/LayerDMSDK/pyproject.toml b/LayerDMSDK/pyproject.toml index 4224337..8a06a03 100644 --- a/LayerDMSDK/pyproject.toml +++ b/LayerDMSDK/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "scikit-build-core", "slicer-core-sdk==5.11.0.*", - "vtk-sdk==9.6.0", + "vtk-sdk==9.6.2", "vtk-sdk-python-wheel-helper" ] build-backend = "scikit_build_core.build" @@ -24,6 +24,12 @@ authors = [ { name = "Alexy Pellegrini", email = "alexy.pellegrini@kitware.com" } ] keywords = ["Python", "VTK", "ITK", "3DSlicer", "Slicer", "Medical"] +dependencies = [ + "slicer-core-sdk==5.11.0.*", + "vtk-sdk==9.6.2", + "vtk-sdk-python-wheel-helper", + "scikit-build-core", +] classifiers = [ "License :: OSI Approved :: BSD License", diff --git a/LayerDMSDK/tests/packages/build_module/pyproject.toml b/LayerDMSDK/tests/packages/build_module/pyproject.toml index 0cc8ee1..6c00870 100644 --- a/LayerDMSDK/tests/packages/build_module/pyproject.toml +++ b/LayerDMSDK/tests/packages/build_module/pyproject.toml @@ -3,7 +3,7 @@ requires = [ "scikit-build-core", "vtk-sdk-python-wheel-helper", "vtk-sdk==9.6.0", - "slicer-core-sdk==5.11.0.*", + "slicer-core-sdk", "slicer-layer-dm-sdk" ] build-backend = "scikit_build_core.build" diff --git a/LayerDMSDK/tests/test_build_module.py b/LayerDMSDK/tests/test_build_module.py index 66ff1ac..c9c5832 100644 --- a/LayerDMSDK/tests/test_build_module.py +++ b/LayerDMSDK/tests/test_build_module.py @@ -5,16 +5,32 @@ hence it is part of the SDK test suite and not of the LayerDM unittests. """ +import re from pathlib import Path from .venv import VEnv +def slicer_pins(runtime_dir: Path) -> tuple[str, str]: + """Return the (slicer-core, slicer-core-sdk) requirements the runtime declares. + + """ + pyproject = (runtime_dir / "pyproject.toml").read_text(encoding="utf-8") + sdk = re.search(r'"slicer-core-sdk==([^"]+)"', pyproject) + core = re.search(r'"slicer-core==([^"]+)"', pyproject) + if sdk is None or core is None: + raise AssertionError(f"no slicer-core pins found in {(runtime_dir / 'pyproject.toml')}") + return f"slicer-core=={core.group(1)}", f"slicer-core-sdk=={sdk.group(1)}" + + def test_build_module(virtualenv: VEnv, curdir: Path, tmp_path: Path): # To enable current project to be used we have to cut pypi index # But doing so would cause issues with dependencies, so we use the # pip wheel command to download all needed wheels from pypi and # put them in a temporary index. + runtime_dir = (curdir.parent.parent / "LayerDM").resolve() + slicer_core, slicer_core_sdk = slicer_pins(runtime_dir) + virtualenv.module("pip", "wheel", "scikit-build-core", "ninja", @@ -22,13 +38,12 @@ def test_build_module(virtualenv: VEnv, curdir: Path, tmp_path: Path): "setuptools-scm", "vtk-sdk-python-wheel-helper", "vtk-sdk==9.6.0", - "slicer-core==5.11.0.*", - "slicer-core-sdk==5.11.0.*", + slicer_core, + slicer_core_sdk, "--extra-index-url", "https://vtk.org/files/wheel-sdks", "--wheel-dir", tmp_path.as_posix() ) - runtime_dir = (curdir.parent.parent / "LayerDM").resolve() virtualenv.module("pip", "wheel", runtime_dir.as_posix(), "--find-links", tmp_path.as_posix(), "--no-index",