feat(release): bump the root crate in Cargo.toml and Cargo.lock
A repo whose Cargo.toml has a [package] section now gets its version set to the release version, and the matching local [[package]] entry in Cargo.lock (same name, no source line) follows in the same release commit, so cargo build --locked keeps passing. Workspaces without [package] and repos without Cargo.toml release as before; a Cargo.lock without the local entry aborts the release before any file is written. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
168d59e1b2
commit
d823a42edc
4 files changed
+194
No files matched your search
@@ -10,6 +10,7 @@ Keep this package stable so existing imports continue to work, e.g.:
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .cargo import update_cargo_version
|
||||||
from .changelog_md import update_changelog
|
from .changelog_md import update_changelog
|
||||||
from .debian import _get_debian_author, update_debian_changelog
|
from .debian import _get_debian_author, update_debian_changelog
|
||||||
from .editor import _open_editor_for_changelog
|
from .editor import _open_editor_for_changelog
|
||||||
@@ -22,6 +23,7 @@ from .rpm_spec import update_spec_version
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"_get_debian_author",
|
"_get_debian_author",
|
||||||
"_open_editor_for_changelog",
|
"_open_editor_for_changelog",
|
||||||
|
"update_cargo_version",
|
||||||
"update_changelog",
|
"update_changelog",
|
||||||
"update_debian_changelog",
|
"update_debian_changelog",
|
||||||
"update_flake_version",
|
"update_flake_version",
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def _read(path: str) -> str:
|
||||||
|
with open(path, encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def _write(path: str, content: str) -> None:
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def update_cargo_version(
|
||||||
|
repo_root: str, new_version: str, preview: bool = False
|
||||||
|
) -> list[str]:
|
||||||
|
"""
|
||||||
|
Set the root crate's version in Cargo.toml and its entry in Cargo.lock.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
repo_root: repository root that may hold Cargo.toml and Cargo.lock.
|
||||||
|
new_version: release version without the leading "v".
|
||||||
|
preview: only print what would change.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The paths that were (or in preview would be) changed.
|
||||||
|
"""
|
||||||
|
toml_path = os.path.join(repo_root, "Cargo.toml")
|
||||||
|
if not os.path.exists(toml_path):
|
||||||
|
print("[INFO] No Cargo.toml found. Skipping Cargo version update.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
toml = _read(toml_path)
|
||||||
|
block = re.search(r"(?ms)^\[package\]\s*$.*?(?=^\s*\[|\Z)", toml)
|
||||||
|
if not block:
|
||||||
|
print("[INFO] Cargo.toml has no [package] section. Skipping.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
name = re.search(r'(?m)^\s*name\s*=\s*"([^"]+)"', block.group(0))
|
||||||
|
if not name:
|
||||||
|
raise RuntimeError("Missing name key in [package] section of Cargo.toml")
|
||||||
|
new_block, count = re.subn(
|
||||||
|
r'(?m)^(\s*version\s*=\s*")[^"]+(")',
|
||||||
|
lambda m: f"{m.group(1)}{new_version}{m.group(2)}",
|
||||||
|
block.group(0),
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
if count == 0:
|
||||||
|
raise RuntimeError("Missing version key in [package] section of Cargo.toml")
|
||||||
|
changes = {toml_path: toml[: block.start()] + new_block + toml[block.end() :]}
|
||||||
|
|
||||||
|
lock_path = os.path.join(repo_root, "Cargo.lock")
|
||||||
|
if os.path.exists(lock_path):
|
||||||
|
lock, count = re.subn(
|
||||||
|
rf'(?m)^(\[\[package\]\]\nname = "{re.escape(name.group(1))}"\n'
|
||||||
|
r'version = ")[^"]+("\n)(?!source = )',
|
||||||
|
lambda m: f"{m.group(1)}{new_version}{m.group(2)}",
|
||||||
|
_read(lock_path),
|
||||||
|
)
|
||||||
|
if count != 1:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Expected one local {name.group(1)!r} package in Cargo.lock, "
|
||||||
|
f"found {count}"
|
||||||
|
)
|
||||||
|
changes[lock_path] = lock
|
||||||
|
|
||||||
|
for path, content in changes.items():
|
||||||
|
if preview:
|
||||||
|
print(f"[PREVIEW] Would update {path} version to {new_version}")
|
||||||
|
else:
|
||||||
|
_write(path, content)
|
||||||
|
print(f"Updated {path} version to {new_version}")
|
||||||
|
return list(changes)
|
||||||
@@ -10,6 +10,7 @@ from pkgmgr.core.git.queries import get_current_branch
|
|||||||
from pkgmgr.core.repository.paths import resolve_repo_paths
|
from pkgmgr.core.repository.paths import resolve_repo_paths
|
||||||
|
|
||||||
from .files import (
|
from .files import (
|
||||||
|
update_cargo_version,
|
||||||
update_changelog,
|
update_changelog,
|
||||||
update_debian_changelog,
|
update_debian_changelog,
|
||||||
update_flake_version,
|
update_flake_version,
|
||||||
@@ -74,6 +75,8 @@ def _release_impl(
|
|||||||
|
|
||||||
update_flake_version(paths.flake_nix, new_ver_str, preview=preview)
|
update_flake_version(paths.flake_nix, new_ver_str, preview=preview)
|
||||||
|
|
||||||
|
cargo_files = update_cargo_version(repo_root, new_ver_str, preview=preview)
|
||||||
|
|
||||||
if paths.arch_pkgbuild:
|
if paths.arch_pkgbuild:
|
||||||
update_pkgbuild_version(paths.arch_pkgbuild, new_ver_str, preview=preview)
|
update_pkgbuild_version(paths.arch_pkgbuild, new_ver_str, preview=preview)
|
||||||
else:
|
else:
|
||||||
@@ -124,6 +127,7 @@ def _release_impl(
|
|||||||
paths.arch_pkgbuild,
|
paths.arch_pkgbuild,
|
||||||
paths.rpm_spec,
|
paths.rpm_spec,
|
||||||
paths.debian_changelog,
|
paths.debian_changelog,
|
||||||
|
*cargo_files,
|
||||||
]
|
]
|
||||||
existing_files = [
|
existing_files = [
|
||||||
p for p in files_to_add if isinstance(p, str) and p and os.path.exists(p)
|
p for p in files_to_add if isinstance(p, str) and p and os.path.exists(p)
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from pkgmgr.actions.release.files import update_cargo_version
|
||||||
|
|
||||||
|
CARGO_TOML = """[package]
|
||||||
|
name = "security-check"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0.0" }
|
||||||
|
"""
|
||||||
|
|
||||||
|
CARGO_LOCK = """version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-check"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-check"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpdateCargoVersion(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self._tmp = tempfile.TemporaryDirectory()
|
||||||
|
self.root = self._tmp.name
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self._tmp.cleanup()
|
||||||
|
|
||||||
|
def _write(self, name: str, content: str) -> str:
|
||||||
|
path = os.path.join(self.root, name)
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _read(self, name: str) -> str:
|
||||||
|
with open(os.path.join(self.root, name), encoding="utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def test_bumps_package_and_local_lock_entry_only(self) -> None:
|
||||||
|
toml = self._write("Cargo.toml", CARGO_TOML)
|
||||||
|
lock = self._write("Cargo.lock", CARGO_LOCK)
|
||||||
|
|
||||||
|
changed = update_cargo_version(self.root, "0.4.0")
|
||||||
|
|
||||||
|
self.assertEqual(changed, [toml, lock])
|
||||||
|
self.assertEqual(
|
||||||
|
self._read("Cargo.toml"),
|
||||||
|
CARGO_TOML.replace('version = "0.1.0"', 'version = "0.4.0"'),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self._read("Cargo.lock"),
|
||||||
|
CARGO_LOCK.replace(
|
||||||
|
'name = "security-check"\nversion = "0.1.0"\ndependencies',
|
||||||
|
'name = "security-check"\nversion = "0.4.0"\ndependencies',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_without_cargo_toml_changes_nothing(self) -> None:
|
||||||
|
self.assertEqual(update_cargo_version(self.root, "0.4.0"), [])
|
||||||
|
self.assertEqual(os.listdir(self.root), [])
|
||||||
|
|
||||||
|
def test_workspace_without_package_is_untouched(self) -> None:
|
||||||
|
workspace = '[workspace]\nmembers = ["a"]\n'
|
||||||
|
self._write("Cargo.toml", workspace)
|
||||||
|
|
||||||
|
self.assertEqual(update_cargo_version(self.root, "0.4.0"), [])
|
||||||
|
self.assertEqual(self._read("Cargo.toml"), workspace)
|
||||||
|
|
||||||
|
def test_without_lock_bumps_only_toml(self) -> None:
|
||||||
|
toml = self._write("Cargo.toml", CARGO_TOML)
|
||||||
|
|
||||||
|
self.assertEqual(update_cargo_version(self.root, "0.4.0"), [toml])
|
||||||
|
|
||||||
|
def test_preview_writes_nothing(self) -> None:
|
||||||
|
self._write("Cargo.toml", CARGO_TOML)
|
||||||
|
self._write("Cargo.lock", CARGO_LOCK)
|
||||||
|
|
||||||
|
update_cargo_version(self.root, "0.4.0", preview=True)
|
||||||
|
|
||||||
|
self.assertEqual(self._read("Cargo.toml"), CARGO_TOML)
|
||||||
|
self.assertEqual(self._read("Cargo.lock"), CARGO_LOCK)
|
||||||
|
|
||||||
|
def test_lock_without_local_entry_fails_before_writing(self) -> None:
|
||||||
|
self._write("Cargo.toml", CARGO_TOML)
|
||||||
|
self._write("Cargo.lock", 'version = 4\n\n[[package]]\nname = "serde"\n')
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
update_cargo_version(self.root, "0.4.0")
|
||||||
|
self.assertEqual(self._read("Cargo.toml"), CARGO_TOML)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in new issue
Block a user