188 lines
4.9 KiB
Python
188 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Update or verify ferro-ta version strings across release files.
|
|
|
|
Usage
|
|
-----
|
|
python3 scripts/bump_version.py 1.0.3
|
|
python3 scripts/bump_version.py --check
|
|
python3 scripts/bump_version.py --show
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VersionCarrier:
|
|
label: str
|
|
path: Path
|
|
pattern: str
|
|
replacement: str
|
|
|
|
def read(self) -> str:
|
|
text = self.path.read_text(encoding="utf-8")
|
|
match = re.search(self.pattern, text, flags=re.MULTILINE)
|
|
if not match:
|
|
raise ValueError(f"Could not find version for {self.label} in {self.path}")
|
|
return match.group(2)
|
|
|
|
def write(self, version: str) -> bool:
|
|
text = self.path.read_text(encoding="utf-8")
|
|
updated, count = re.subn(
|
|
self.pattern,
|
|
rf"\g<1>{version}\g<3>",
|
|
text,
|
|
count=1,
|
|
flags=re.MULTILINE,
|
|
)
|
|
if count != 1:
|
|
raise ValueError(f"Could not update {self.label} in {self.path}")
|
|
changed = updated != text
|
|
if changed:
|
|
self.path.write_text(updated, encoding="utf-8")
|
|
return changed
|
|
|
|
|
|
CARRIERS = [
|
|
VersionCarrier(
|
|
"cargo_root",
|
|
ROOT / "Cargo.toml",
|
|
r'(?m)^(version = ")([^"]+)(")$',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"cargo_core_dep",
|
|
ROOT / "Cargo.toml",
|
|
r'(ferro_ta_core = \{ path = "crates/ferro_ta_core", version = ")([^"]+)(" \})',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"cargo_core_crate",
|
|
ROOT / "crates" / "ferro_ta_core" / "Cargo.toml",
|
|
r'(?m)^(version = ")([^"]+)(")$',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"cargo_core_readme",
|
|
ROOT / "crates" / "ferro_ta_core" / "README.md",
|
|
r'(ferro_ta_core = ")([^"]+)(")',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"pyproject",
|
|
ROOT / "pyproject.toml",
|
|
r'(?m)^(version = ")([^"]+)(")$',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"wasm_cargo",
|
|
ROOT / "wasm" / "Cargo.toml",
|
|
r'(?m)^(version = ")([^"]+)(")$',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"wasm_package",
|
|
ROOT / "wasm" / "package.json",
|
|
r'("version": ")([^"]+)(")',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"conda",
|
|
ROOT / "conda" / "meta.yaml",
|
|
r'({% set version = ")([^"]+)(" %})',
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"docs_changelog",
|
|
ROOT / "docs" / "changelog.rst",
|
|
r"(These docs track package version ``)([^`]+)(``\.)",
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
VersionCarrier(
|
|
"docs_support_matrix",
|
|
ROOT / "docs" / "support_matrix.rst",
|
|
r"(These docs track package version ``)([^`]+)(``\.)",
|
|
r"\g<1>{version}\g<3>",
|
|
),
|
|
]
|
|
|
|
|
|
def _read_versions() -> dict[str, str]:
|
|
return {carrier.label: carrier.read() for carrier in CARRIERS}
|
|
|
|
|
|
def _print_versions(versions: dict[str, str]) -> None:
|
|
for label, version in versions.items():
|
|
print(f"{label:20} {version}")
|
|
|
|
|
|
def _check_versions() -> int:
|
|
versions = _read_versions()
|
|
unique = sorted(set(versions.values()))
|
|
_print_versions(versions)
|
|
if len(unique) != 1:
|
|
print()
|
|
print(f"ERROR: version mismatch detected: {', '.join(unique)}")
|
|
return 1
|
|
print()
|
|
print(f"OK: all tracked versions match {unique[0]}")
|
|
return 0
|
|
|
|
|
|
def _set_version(version: str) -> int:
|
|
if not SEMVER_RE.match(version):
|
|
print(f"ERROR: expected MAJOR.MINOR.PATCH, got {version!r}")
|
|
return 1
|
|
|
|
changed_paths: list[Path] = []
|
|
for carrier in CARRIERS:
|
|
if carrier.write(version):
|
|
changed_paths.append(carrier.path)
|
|
|
|
if changed_paths:
|
|
print(f"Updated version to {version}:")
|
|
for path in sorted(set(changed_paths)):
|
|
print(f" - {path.relative_to(ROOT)}")
|
|
else:
|
|
print(f"No changes needed. All tracked files already use {version}.")
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("version", nargs="?", help="New version to write")
|
|
parser.add_argument(
|
|
"--check",
|
|
action="store_true",
|
|
help="Fail if tracked version strings do not match",
|
|
)
|
|
parser.add_argument(
|
|
"--show",
|
|
action="store_true",
|
|
help="Print tracked version strings without modifying files",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.check:
|
|
return _check_versions()
|
|
if args.show:
|
|
_print_versions(_read_versions())
|
|
return 0
|
|
if args.version:
|
|
return _set_version(args.version)
|
|
|
|
parser.print_help()
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|