278 lines
7.9 KiB
Python
Executable File
278 lines
7.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import functools
|
|
import re
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from re import Match, Pattern
|
|
from typing import Callable
|
|
|
|
SUCCESS_CHAR = "\u2714"
|
|
FAIL_CHAR = "\u2716"
|
|
|
|
CVAT_VERSION_PATTERN = re.compile(
|
|
r"VERSION\s*=\s*\((\d+),\s*(\d*),\s*(\d+),\s*[\',\"](\w+)[\',\"],\s*(\d+)\)"
|
|
)
|
|
|
|
REPO_ROOT_DIR = Path(__file__).resolve().parents[1]
|
|
|
|
CVAT_INIT_PY_REL_PATH = "cvat/__init__.py"
|
|
CVAT_INIT_PY_PATH = REPO_ROOT_DIR / CVAT_INIT_PY_REL_PATH
|
|
|
|
|
|
@dataclass()
|
|
class Version:
|
|
major: int = 0
|
|
minor: int = 0
|
|
patch: int = 0
|
|
prerelease: str = ""
|
|
prerelease_number: int = 0
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.major}.{self.minor}.{self.patch}-{self.prerelease}.{self.prerelease_number}"
|
|
|
|
def cvat_repr(self):
|
|
return f'({self.major}, {self.minor}, {self.patch}, "{self.prerelease}", {self.prerelease_number})'
|
|
|
|
def compose_repr(self):
|
|
if self.prerelease != "final":
|
|
return "dev"
|
|
return f"v{self.major}.{self.minor}.{self.patch}"
|
|
|
|
def increment_prerelease_number(self) -> None:
|
|
self.prerelease_number += 1
|
|
|
|
def increment_prerelease(self) -> None:
|
|
flow = ("alpha", "beta", "rc", "final")
|
|
idx = flow.index(self.prerelease)
|
|
if idx == len(flow) - 1:
|
|
raise ValueError(f"Cannot increment current '{self.prerelease}' prerelease version")
|
|
|
|
self.prerelease = flow[idx + 1]
|
|
self._set_default_prerelease_number()
|
|
|
|
def set_prerelease(self, value: str) -> None:
|
|
values = ("alpha", "beta", "rc", "final")
|
|
if value not in values:
|
|
raise ValueError(f"{value} is a wrong, must be one of {values}")
|
|
|
|
self.prerelease = value
|
|
self._set_default_prerelease_number()
|
|
|
|
def increment_patch(self) -> None:
|
|
self.patch += 1
|
|
self._set_default_prerelease()
|
|
|
|
def increment_minor(self) -> None:
|
|
self.minor += 1
|
|
self._set_default_patch()
|
|
|
|
def increment_major(self) -> None:
|
|
self.major += 1
|
|
self._set_default_minor()
|
|
|
|
def set(self, v: str) -> None:
|
|
self.major, self.minor, self.patch = map(int, v.split("."))
|
|
self.prerelease = "final"
|
|
self.prerelease_number = 0
|
|
|
|
def _set_default_prerelease_number(self) -> None:
|
|
self.prerelease_number = 0
|
|
|
|
def _set_default_prerelease(self) -> None:
|
|
self.prerelease = "alpha"
|
|
self._set_default_prerelease_number()
|
|
|
|
def _set_default_patch(self) -> None:
|
|
self.patch = 0
|
|
self._set_default_prerelease()
|
|
|
|
def _set_default_minor(self) -> None:
|
|
self.minor = 0
|
|
self._set_default_patch()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ReplacementRule:
|
|
rel_path: str
|
|
pattern: Pattern[str]
|
|
replacement: Callable[[Version, Match[str]], str]
|
|
|
|
def apply(self, new_version: Version, *, verify_only: bool) -> bool:
|
|
path = REPO_ROOT_DIR / self.rel_path
|
|
text = path.read_text()
|
|
|
|
new_text, num_replacements = self.pattern.subn(
|
|
functools.partial(self.replacement, new_version), text
|
|
)
|
|
|
|
if not num_replacements:
|
|
print(f"{FAIL_CHAR} {self.rel_path}: failed to match version pattern.")
|
|
return False
|
|
|
|
if text == new_text:
|
|
if verify_only:
|
|
print(f"{SUCCESS_CHAR} {self.rel_path}: verified.")
|
|
else:
|
|
print(f"{SUCCESS_CHAR} {self.rel_path}: no need to update.")
|
|
else:
|
|
if verify_only:
|
|
print(f"{FAIL_CHAR} {self.rel_path}: verification failed.")
|
|
return False
|
|
else:
|
|
path.write_text(new_text)
|
|
print(f"{SUCCESS_CHAR} {self.rel_path}: updated.")
|
|
|
|
return True
|
|
|
|
|
|
REPLACEMENT_RULES = [
|
|
ReplacementRule(
|
|
CVAT_INIT_PY_REL_PATH, CVAT_VERSION_PATTERN, lambda v, m: f"VERSION = {v.cvat_repr()}"
|
|
),
|
|
ReplacementRule(
|
|
"docker-compose.yml",
|
|
re.compile(r"(\$\{CVAT_VERSION:-)([\w.]+)(\})"),
|
|
lambda v, m: m[1] + v.compose_repr() + m[3],
|
|
),
|
|
ReplacementRule(
|
|
"helm-chart/values.yaml",
|
|
re.compile(r"(^ image: cvat/(?:ui|server)\n tag: )([\w.]+)", re.M),
|
|
lambda v, m: m[1] + v.compose_repr(),
|
|
),
|
|
ReplacementRule(
|
|
"cvat-sdk/gen/generate.sh",
|
|
re.compile(r'^VERSION="[\d.]+"$', re.M),
|
|
lambda v, m: f'VERSION="{v.major}.{v.minor}.{v.patch}"',
|
|
),
|
|
ReplacementRule(
|
|
"cvat/schema.yml",
|
|
re.compile(r"^ version: [\d.]+$", re.M),
|
|
lambda v, m: f" version: {v.major}.{v.minor}.{v.patch}",
|
|
),
|
|
ReplacementRule(
|
|
"cvat-cli/src/cvat_cli/version.py",
|
|
re.compile(r'^VERSION = "[\d.]+"$', re.M),
|
|
lambda v, m: f'VERSION = "{v.major}.{v.minor}.{v.patch}"',
|
|
),
|
|
ReplacementRule(
|
|
"cvat-cli/requirements/base.txt",
|
|
re.compile(r"^cvat-sdk==[\d.]+$", re.M),
|
|
lambda v, m: f"cvat-sdk=={v.major}.{v.minor}.{v.patch}",
|
|
),
|
|
ReplacementRule(
|
|
"cvat-ui/package.json",
|
|
re.compile(r'^ "version": "[\d.]+",$', re.M),
|
|
lambda v, m: f' "version": "{v.major}.{v.minor}.{v.patch}",',
|
|
),
|
|
]
|
|
|
|
|
|
def get_current_version() -> Version:
|
|
version_text = CVAT_INIT_PY_PATH.read_text()
|
|
|
|
match = re.search(CVAT_VERSION_PATTERN, version_text)
|
|
if not match:
|
|
raise RuntimeError(f"Failed to find version in {CVAT_INIT_PY_PATH}")
|
|
|
|
return Version(int(match[1]), int(match[2]), int(match[3]), match[4], int(match[5]))
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Bump CVAT version")
|
|
|
|
action_group = parser.add_mutually_exclusive_group(required=True)
|
|
|
|
action_group.add_argument(
|
|
"--major", action="store_true", help="Increment the existing major version by 1"
|
|
)
|
|
action_group.add_argument(
|
|
"--minor", action="store_true", help="Increment the existing minor version by 1"
|
|
)
|
|
action_group.add_argument(
|
|
"--patch", action="store_true", help="Increment the existing patch version by 1"
|
|
)
|
|
action_group.add_argument(
|
|
"--prerelease",
|
|
nargs="?",
|
|
const="increment",
|
|
help="""Increment prerelease version alpha->beta->rc->final,
|
|
Also it's possible to pass value explicitly""",
|
|
)
|
|
action_group.add_argument(
|
|
"--prerelease_number", action="store_true", help="Increment prerelease number by 1"
|
|
)
|
|
|
|
action_group.add_argument(
|
|
"--current", "--show-current", action="store_true", help="Display current version"
|
|
)
|
|
action_group.add_argument(
|
|
"--verify-current",
|
|
action="store_true",
|
|
help="Check that all version numbers are consistent",
|
|
)
|
|
|
|
action_group.add_argument(
|
|
"--set", metavar="X.Y.Z", help="Set the version to the specified version"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
version = get_current_version()
|
|
verify_only = False
|
|
|
|
if args.current:
|
|
print(version)
|
|
return
|
|
|
|
elif args.verify_current:
|
|
verify_only = True
|
|
|
|
elif args.prerelease_number:
|
|
version.increment_prerelease_number()
|
|
|
|
elif args.prerelease:
|
|
if args.prerelease == "increment":
|
|
version.increment_prerelease()
|
|
else:
|
|
version.set_prerelease(args.prerelease)
|
|
|
|
elif args.patch:
|
|
version.increment_patch()
|
|
|
|
elif args.minor:
|
|
version.increment_minor()
|
|
|
|
elif args.major:
|
|
version.increment_major()
|
|
|
|
elif args.set is not None:
|
|
version.set(args.set)
|
|
|
|
else:
|
|
assert False, "Unreachable code"
|
|
|
|
if verify_only:
|
|
print(f"Verifying that version is {version}...")
|
|
else:
|
|
print(f"Bumping version to {version}...")
|
|
print()
|
|
|
|
success = True
|
|
|
|
for rule in REPLACEMENT_RULES:
|
|
if not rule.apply(version, verify_only=verify_only):
|
|
success = False
|
|
|
|
if not success:
|
|
if verify_only:
|
|
sys.exit("\nFailed to verify one or more files!")
|
|
else:
|
|
sys.exit("\nFailed to update one or more files!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|