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 .cargo import update_cargo_version
|
||||
from .changelog_md import update_changelog
|
||||
from .debian import _get_debian_author, update_debian_changelog
|
||||
from .editor import _open_editor_for_changelog
|
||||
@@ -22,6 +23,7 @@ from .rpm_spec import update_spec_version
|
||||
__all__ = [
|
||||
"_get_debian_author",
|
||||
"_open_editor_for_changelog",
|
||||
"update_cargo_version",
|
||||
"update_changelog",
|
||||
"update_debian_changelog",
|
||||
"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 .files import (
|
||||
update_cargo_version,
|
||||
update_changelog,
|
||||
update_debian_changelog,
|
||||
update_flake_version,
|
||||
@@ -74,6 +75,8 @@ def _release_impl(
|
||||
|
||||
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:
|
||||
update_pkgbuild_version(paths.arch_pkgbuild, new_ver_str, preview=preview)
|
||||
else:
|
||||
@@ -124,6 +127,7 @@ def _release_impl(
|
||||
paths.arch_pkgbuild,
|
||||
paths.rpm_spec,
|
||||
paths.debian_changelog,
|
||||
*cargo_files,
|
||||
]
|
||||
existing_files = [
|
||||
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