Skip to content
Merged
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
53 changes: 1 addition & 52 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -245,58 +245,7 @@ set_python_project_versions() {
local python_executable=""
version="$(semver_to_pep440 "$1")"
python_executable="$(uv_python_executable)"

"$python_executable" - "$version" <<'PY'
from pathlib import Path
import re
import sys
import tomllib

version = sys.argv[1]
project_paths = (
Path("pyproject.toml"),
*sorted(Path("adapters").glob("**/pyproject.toml")),
)
pin_pattern = re.compile(r'(nemo-fabric-[a-z0-9-]+\s*==\s*)([^"\s,;]+)')

for path in project_paths:
text = path.read_text()
updated, count = re.subn(
r'^version\s*=\s*"[^"]+"$',
f'version = "{version}"',
text,
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise SystemExit(f"Failed to find exactly one project version in {path}")
updated = pin_pattern.sub(rf"\g<1>{version}", updated)
if updated != text:
path.write_text(updated)
print(f"{path} version and internal pins updated to {version}")
else:
print(f"{path} already set to {version}")

runtime_path = Path("python/pyproject.toml")
runtime = tomllib.loads(runtime_path.read_text())
project = runtime.get("project", {})
if "version" in project or "version" not in project.get("dynamic", []):
raise SystemExit(
"python/pyproject.toml must keep a dynamic version derived from Cargo.toml"
)

mismatched_pins = []
for path in project_paths:
for match in pin_pattern.finditer(path.read_text()):
if match.group(2) != version:
mismatched_pins.append(f"{path}: {match.group(0)}")
if mismatched_pins:
raise SystemExit(
"Internal Python dependency pins are not synchronized: "
+ ", ".join(mismatched_pins)
)
print("python/pyproject.toml continues to derive its version from Cargo.toml")
PY
"$python_executable" scripts/ci/set_python_project_versions.py "$version"
}

set_project_version() {
Expand Down
70 changes: 70 additions & 0 deletions scripts/ci/set_python_project_versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import re
import sys
import tomllib
from pathlib import Path


PROJECT_VERSION_PATTERN = re.compile(r'^version\s*=\s*"[^"]+"$', flags=re.MULTILINE)
INTERNAL_PIN_PATTERN = re.compile(
r"(?P<prefix>nemo-fabric-[a-z0-9-]+(?:\[[^\]]+\])?\s*==\s*)"
r'(?P<version>[^"\s,;]+)'
)


def set_python_project_versions(root: Path, version: str) -> None:
project_paths = (
root / "pyproject.toml",
*sorted((root / "adapters").glob("**/pyproject.toml")),
)

for path in project_paths:
text = path.read_text(encoding="utf-8")
updated, count = PROJECT_VERSION_PATTERN.subn(
f'version = "{version}"',
text,
count=1,
)
if count != 1:
raise SystemExit(f"Failed to find exactly one project version in {path}")
Comment thread
AnuradhaKaruppiah marked this conversation as resolved.
updated = INTERNAL_PIN_PATTERN.sub(
lambda match: f"{match.group('prefix')}{version}",
updated,
)
if updated != text:
path.write_text(updated, encoding="utf-8")
print(
f"{path.relative_to(root)} version and internal pins updated to {version}"
)
else:
print(f"{path.relative_to(root)} already set to {version}")

runtime_path = root / "python" / "pyproject.toml"
runtime = tomllib.loads(runtime_path.read_text(encoding="utf-8"))
project = runtime.get("project", {})
if "version" in project or "version" not in project.get("dynamic", []):
raise SystemExit(
"python/pyproject.toml must keep a dynamic version derived from Cargo.toml"
)
Comment thread
AnuradhaKaruppiah marked this conversation as resolved.

mismatched_pins = []
for path in project_paths:
for match in INTERNAL_PIN_PATTERN.finditer(path.read_text(encoding="utf-8")):
if match.group("version") != version:
mismatched_pins.append(f"{path.relative_to(root)}: {match.group(0)}")
if mismatched_pins:
raise SystemExit(
"Internal Python dependency pins are not synchronized: "
+ ", ".join(mismatched_pins)
)
print("python/pyproject.toml continues to derive its version from Cargo.toml")


if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit("Usage: set_python_project_versions.py <version>")
set_python_project_versions(Path.cwd(), sys.argv[1])
Comment thread
AnuradhaKaruppiah marked this conversation as resolved.
87 changes: 87 additions & 0 deletions tests/scripts/test_set_python_project_versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import sys
import tomllib
from pathlib import Path


CI_SCRIPTS = Path(__file__).resolve().parents[2] / "scripts" / "ci"
sys.path.insert(0, str(CI_SCRIPTS))

import set_python_project_versions # noqa: E402


def test_set_python_project_versions_updates_internal_pins_with_extras(
tmp_path: Path,
):
(tmp_path / "adapters" / "claude").mkdir(parents=True)
(tmp_path / "python").mkdir()
(tmp_path / "pyproject.toml").write_text(
"""\
[project]
name = "nemo-fabric"
version = "0.2.0"
dependencies = [
"nemo-fabric-runtime == 0.2.0",
]

[project.optional-dependencies]
claude = [
"nemo-fabric-adapters-claude[harness] == 0.2.0",
]
hermes-agent = [
"nemo-fabric-adapters-hermes[harness] == 0.2.0; python_version < '3.14'",
]
""",
encoding="utf-8",
)
(tmp_path / "adapters" / "claude" / "pyproject.toml").write_text(
"""\
[project]
name = "nemo-fabric-adapters-claude"
version = "0.2.0"
dependencies = [
"nemo-fabric-adapters-common == 0.2.0",
]
""",
encoding="utf-8",
)
runtime_path = tmp_path / "python" / "pyproject.toml"
runtime_path.write_text(
"""\
[project]
name = "nemo-fabric-runtime"
dynamic = ["version"]
""",
encoding="utf-8",
)

set_python_project_versions.set_python_project_versions(tmp_path, "0.2.0rc5")

root_project = tomllib.loads(
(tmp_path / "pyproject.toml").read_text(encoding="utf-8")
)["project"]
adapter_project = tomllib.loads(
(tmp_path / "adapters" / "claude" / "pyproject.toml").read_text(
encoding="utf-8"
)
)["project"]

assert root_project["version"] == "0.2.0rc5"
assert root_project["dependencies"] == ["nemo-fabric-runtime == 0.2.0rc5"]
assert root_project["optional-dependencies"]["claude"] == [
"nemo-fabric-adapters-claude[harness] == 0.2.0rc5"
]
assert root_project["optional-dependencies"]["hermes-agent"] == [
"nemo-fabric-adapters-hermes[harness] == 0.2.0rc5; python_version < '3.14'"
]
assert adapter_project["version"] == "0.2.0rc5"
assert adapter_project["dependencies"] == [
"nemo-fabric-adapters-common == 0.2.0rc5"
]
assert tomllib.loads(runtime_path.read_text(encoding="utf-8"))["project"][
"dynamic"
] == ["version"]
Comment thread
AnuradhaKaruppiah marked this conversation as resolved.
Loading