Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/scripts/pin_slicer_core.py
Original file line number Diff line number Diff line change
@@ -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<major>.<minor>.<patch> 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())
16 changes: 13 additions & 3 deletions .github/workflows/build-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 <library version>.<slicer-core version>.
- 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

Expand All @@ -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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }}
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion LayerDM/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 7 additions & 1 deletion LayerDMSDK/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion LayerDMSDK/tests/packages/build_module/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 18 additions & 3 deletions LayerDMSDK/tests/test_build_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,45 @@
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",
"cmake",
"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",
Expand Down
Loading