fix(tools): bump-version: support X.Y.Z.W, regen error_codes.json
What changed, and why it matters
This is a routine maintenance update to an internal developer tool that bumps software version numbers. It adds support for four-part version numbers, fixes a Python path, and makes the script regenerate an error-codes file after updating the production-test project. There is nothing in the change that affects how the Trezor device protects keys, handles transactions, or communicates with users.
No security action needed; treat as normal tooling cleanup.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors tools/bump-version.py to support VERSION_BUILD/VERSION_TWEAK (fourth version component), consolidates two near-duplicate file-editing helpers into bump_re, fixes the relative path used to call translations/cli.py, and adds a call to regenerate error_codes.json for the prodtest project. All changes are confined to a build/release helper script; no firmware runtime code, cryptography, or protocol handling is modified.
Changed components
tools/bump-version.pyInspect captured patch +56 / −48
diff --git a/tools/bump-version.py b/tools/bump-version.py
index cf82a46e..467e1416 100755
--- a/tools/bump-version.py
+++ b/tools/bump-version.py
@@ -8,20 +8,26 @@ from typing import Any
import click
-VERSION_RE = re.compile(r"^(\d+)[.](\d+)[.](\d+)$")
+VERSION_RE = re.compile(r"^(\d+)[.](\d+)[.](\d+)(?:[.](\d+))?$")
HEADER_LINE_RE = re.compile(r"^#define ([A-Z_]+) \S+$")
VERSION_FILE_LINE_RE = re.compile(r"^([A-Z_]+) = \S+$")
+SHORT = {
+ "core": Path("embed/projects/firmware"),
+ "legacy": Path("firmware"),
+}
+NO_BUILD = ["python", "legacy/*"]
-def bump_header(filename: Path, **kwargs: Any) -> None:
+
+def bump_re(pattern: re.Pattern, fmt: str, filename: Path, **kwargs: Any) -> None:
result_lines = []
with open(filename, "r+") as fh:
for line in fh:
- m = HEADER_LINE_RE.match(line)
- if m is not None and m[1] in kwargs:
+ m = pattern.match(line)
+ if m is not None and kwargs.get(m[1]) is not None:
symbol = m[1]
- result_lines.append(f"#define {symbol} {kwargs[symbol]}\n")
+ result_lines.append(fmt.format(symbol=symbol, value=kwargs[symbol]))
else:
result_lines.append(line)
@@ -31,32 +37,18 @@ def bump_header(filename: Path, **kwargs: Any) -> None:
fh.write(line)
-def bump_version_file(filename: Path, **kwargs: Any) -> None:
- result_lines = []
+def bump_header(filename: Path, **kwargs: Any) -> None:
+ return bump_re(HEADER_LINE_RE, "#define {symbol} {value}\n", filename, **kwargs)
- with open(filename, "r+") as fh:
- for line in fh:
- m = VERSION_FILE_LINE_RE.match(line)
- if m is not None and m[1] in kwargs:
- symbol = m[1]
- result_lines.append(f"{symbol} = {kwargs[symbol]}\n")
- else:
- result_lines.append(line)
- fh.seek(0)
- fh.truncate(0)
- for line in result_lines:
- fh.write(line)
+def bump_version_file(filename: Path, **kwargs: Any) -> None:
+ return bump_re(VERSION_FILE_LINE_RE, "{symbol} = {value}\n", filename, **kwargs)
def bump_python(subdir: Path, new_version: str) -> None:
subprocess.check_call(["uv", "version", new_version], cwd=subdir)
-def hex_lit(version: Any) -> str:
- return rf'"\x{int(version):02X}"'
-
-
@click.command()
@click.argument(
"project",
@@ -67,24 +59,45 @@ def hex_lit(version: Any) -> str:
type=str,
)
def cli(project: str | Path, version: str) -> None:
- """Bump version for given project (core, python, legacy/firmware,
- legacy/bootloader, core/embed/projects/prodtest, nordic/trezor/trezor-ble).
+ """\b
+ PROJECT must be a directory like:
+ - core/embed/projects/firmware (shortcut: core)
+ - core/embed/projects/bootloader
+ - core/embed/projects/prodtest
+ - python
+ - nordic/trezor/trezor-ble
+ - legacy/firmware (shortcut: legacy)
+ - legacy/bootloader
+ \b
+ VERSION must be formatted like:
+ - MAJOR.MINOR.PATCH (build is set to 0 if applicable)
+ - MAJOR.MINOR.PATCH.BUILD (unsupported for legacy, python)
"""
project = Path(project)
+ if project.name in SHORT:
+ project = project / SHORT[project.name]
- m = VERSION_RE.match(version)
- if m is None:
- raise click.ClickException("Version must be MAJOR.MINOR.PATCH")
+ if (m := VERSION_RE.match(version)) is None:
+ raise click.ClickException(
+ "Version must be MAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH.BUILD"
+ )
+ major, minor, patch, build = m.groups()
- major, minor, patch = m.group(1, 2, 3)
+ if any(project.match(pat) for pat in NO_BUILD):
+ if build is not None:
+ raise click.ClickException(
+ f"Version must be MAJOR.MINOR.PATCH for {project}."
+ )
+ elif build is None:
+ build = 0
- parts = project.parts
if (project / "VERSION").is_file():
bump_version_file(
project / "VERSION",
VERSION_MAJOR=major,
VERSION_MINOR=minor,
PATCHLEVEL=patch,
+ VERSION_TWEAK=build,
)
elif (project / "version.h").is_file():
bump_header(
@@ -92,25 +105,20 @@ def cli(project: str | Path, version: str) -> None:
VERSION_MAJOR=major,
VERSION_MINOR=minor,
VERSION_PATCH=patch,
+ VERSION_BUILD=build,
)
- elif parts[-1] == "core":
- bump_header(
- project / "embed" / "projects" / "firmware" / "version.h",
- VERSION_MAJOR=major,
- VERSION_MINOR=minor,
- VERSION_PATCH=patch,
- )
- # also bump language JSONs
- subprocess.run(["python", project / "translations" / "cli.py", "gen"])
- elif parts[-1] == "legacy":
- bump_header(
- project / "firmware" / "version.h",
- VERSION_MAJOR=major,
- VERSION_MINOR=minor,
- VERSION_PATCH=patch,
- )
- elif parts[-1] == "python":
- bump_python(project / "python", f"{major}.{minor}.{patch}")
+ if project.match("core/embed/projects/firmware"):
+ # also bump language JSONs
+ subprocess.check_call(
+ ["python", project.parents[2] / "translations" / "cli.py", "gen"]
+ )
+ if project.match("core/embed/projects/prodtest"):
+ # refresh error_codes.json
+ subprocess.check_call(
+ ["python", project.parents[2] / "tools" / "prodtest_error_codes.py"]
+ )
+ elif project.name == "python":
+ bump_python(project, f"{major}.{minor}.{patch}")
else:
raise click.ClickException(f"Unknown project {project}.")
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.