import sys
import tomllib
from pathlib import Path
def load_toml(path):
with open(path, "rb") as f:
return tomllib.load(f)
def workspace_inherited_version(root):
try:
v = load_toml(root / "Cargo.toml").get("workspace", {}).get("package", {}).get("version")
if isinstance(v, str):
return v
except FileNotFoundError:
pass
for manifest in root.rglob("Cargo.toml"):
if "target" in manifest.parts:
continue
try:
v = load_toml(manifest).get("workspace", {}).get("package", {}).get("version")
except Exception:
continue
if isinstance(v, str):
return v
return None
def manifest_versions(root):
ws_version = workspace_inherited_version(root)
result = {}
for manifest in root.rglob("Cargo.toml"):
if "target" in manifest.parts:
continue
try:
pkg = load_toml(manifest).get("package")
except Exception:
continue
if not pkg or not pkg.get("name"):
continue
version = pkg.get("version")
if isinstance(version, dict) and version.get("workspace") is True:
version = ws_version
elif not isinstance(version, str):
version = None
result.setdefault(pkg["name"], []).append((version, manifest.relative_to(root).as_posix()))
return result
def local_lock_versions(lock_path):
data = load_toml(lock_path)
local = {}
for pkg in data.get("package", []):
name = pkg.get("name")
if name and "source" not in pkg: local[name] = pkg.get("version")
return local
def main():
root = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path(__file__).resolve().parent.parent
lock_path = root / "Cargo.lock"
if not lock_path.is_file():
print(f"WARN: no Cargo.lock at {root} - nothing to check "
"(a publishable crate SHOULD commit Cargo.lock).")
return 0
local = local_lock_versions(lock_path)
manifests = manifest_versions(root)
mismatches = []
checked = []
for name, lock_version in sorted(local.items()):
entries = manifests.get(name)
if not entries:
mismatches.append(
f" {name}: present in Cargo.lock as a local package (version {lock_version}) "
"but no matching [package] Cargo.toml was found in the tree")
continue
paths = ", ".join(rel for (_, rel) in entries)
versions = {version for (version, _) in entries}
if None in versions:
mismatches.append(
f" {name} ({paths}): could not resolve a version from Cargo.toml "
f"(Cargo.lock={lock_version})")
elif len(versions) > 1:
found = ", ".join(sorted(v for v in versions if v is not None))
mismatches.append(
f" {name} ({paths}): AMBIGUOUS - multiple Cargo.toml declare this name at "
f"differing versions [{found}]; cannot verify Cargo.lock={lock_version}")
else:
toml_version = next(iter(versions))
if toml_version != lock_version:
mismatches.append(
f" {name} ({paths}): Cargo.toml={toml_version} but Cargo.lock={lock_version}")
else:
checked.append(f"{name}@{toml_version}")
if mismatches:
print("Cargo.lock local-package version does NOT match Cargo.toml:")
print("\n".join(mismatches))
print("\nFix: run `cargo build` (or `cargo update -p <name>`) to resync Cargo.lock, "
"then commit the updated Cargo.lock.")
return 1
if not checked:
print("WARN: Cargo.lock has no local (source-less) packages to check.")
return 0
print(f"OK: Cargo.lock matches Cargo.toml for {len(checked)} local package(s): "
+ ", ".join(checked))
return 0
if __name__ == "__main__":
sys.exit(main())