Skip to content

Commit 30fb343

Browse files
romanzvdovhanych
authored andcommitted
chore: reimplement Python script for copying artifacts
1 parent c6c5d82 commit 30fb343

4 files changed

Lines changed: 185 additions & 296 deletions

File tree

scripts/copy-firmware-jsons.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""
2+
Copies firmware binaries, translations and JSONs from a local copy of `trezor-suite-firmware-release`.
3+
4+
Also, renames the files according to the naming convention in this repo, trezor-<model>-<version>(-bitcoinonly)?.bin
5+
6+
Usage:
7+
8+
nix-shell
9+
python scripts/copy-firmware-jsons.py 2.9.1 -r ../trezor-suite-firmware-release/
10+
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import json
16+
import typing as t
17+
import subprocess
18+
import shutil
19+
import re
20+
from pathlib import Path
21+
22+
import click
23+
24+
ROOT = Path(__file__).parent.parent.resolve()
25+
26+
FW_DATA = ROOT / "firmware"
27+
28+
29+
def copy_single_file(model_dir: Path, pattern: str, dst: Path) -> None:
30+
paths = list(model_dir.glob(pattern))
31+
if len(paths) > 1:
32+
raise click.ClickException(
33+
f"{model_dir / pattern} matches {len(paths)} files", paths
34+
)
35+
36+
if paths:
37+
copy_file(src=paths[0], dst=dst)
38+
39+
40+
def copy_and_adapt_json(src: Path, dst: Path, new_url: Path) -> dict:
41+
if not src.exists():
42+
return
43+
44+
click.echo(f"\t{src}{dst}")
45+
data = json.load(src.open())
46+
47+
# overwrite "url" since the binary has been renamed
48+
data["url"] = str(new_url)
49+
50+
if (translations := data.get("translations")) is not None:
51+
# drop "signed/" subdirectory from translations' URLs
52+
data["translations"] = {
53+
key: re.sub("^firmware/signed/", "firmware/", value)
54+
for key, value in translations.items()
55+
}
56+
57+
json_write(data=data, path=dst)
58+
return data
59+
60+
61+
def update_releases_json(
62+
releases_json_path: Path, universal_json: dict | None, btconly_json: dict | None
63+
) -> None:
64+
"""
65+
Add a new entry to `releases.json`.
66+
67+
It is currently used by some CLI tools in https://github.com/trezor/trezor-firmware.
68+
Not used anymore by Trezor Suite.
69+
"""
70+
if universal_json is None and btconly_json is None:
71+
return
72+
73+
shared_keys = [
74+
"required",
75+
"version",
76+
"min_bridge_version",
77+
"min_bootloader_version",
78+
"min_firmware_version",
79+
"bootloader_version",
80+
"firmware_revision",
81+
]
82+
non_shared_keys = ["url", "fingerprint", "changelog"]
83+
84+
universal_shared = {k: universal_json[k] for k in shared_keys}
85+
btconly_shared = {k: btconly_json[k] for k in shared_keys}
86+
assert universal_shared == btconly_shared
87+
88+
new_item = dict(universal_shared)
89+
90+
# T1B1 doesn't have translations
91+
assert universal_json.get("translations") == btconly_json.get("translations")
92+
if (translations := universal_json.get("translations")) is not None:
93+
new_item["translations"] = list(translations) # keep only the keys
94+
95+
for suffix, items in [("", universal_json), ("_bitcoinonly", btconly_json)]:
96+
for k in non_shared_keys:
97+
value = items[k]
98+
if k == "url":
99+
# adapt URL prefix to the existing convention
100+
value = re.sub("^firmware/", "data/firmware/", value)
101+
new_item[k + suffix] = value
102+
103+
data = json.load(releases_json_path.open())
104+
105+
for i, item in enumerate(data):
106+
if item["version"] == new_item["version"]:
107+
data[i] = new_item
108+
break
109+
else:
110+
data.insert(0, new_item)
111+
112+
json_write(data, releases_json_path)
113+
114+
115+
def json_write(data: dict, path: Path, *extra_flags: str) -> None:
116+
json_str = json.dumps(data, ensure_ascii=False)
117+
prettier_cmd = ["prettier", "--print-width=100", "--parser=json"]
118+
pretty_json = subprocess.check_output(prettier_cmd, input=json_str, text=True)
119+
path.write_text(pretty_json)
120+
121+
122+
def copy_file(src: Path, dst: Path) -> None:
123+
click.echo(f"\t{src}{dst}")
124+
shutil.copy(src=src, dst=dst)
125+
126+
127+
@click.command()
128+
@click.argument("version")
129+
@click.option(
130+
"-r", "--release-repo", type=Path, default="../trezor-suite-firmware-release/"
131+
)
132+
def main(version: str, release_repo: Path):
133+
signed = release_repo / "firmware" / "signed"
134+
if not signed.exists():
135+
raise click.ClickException(f"{signed} is missing")
136+
137+
for model_src in sorted(signed.glob("t?[btw]?")):
138+
model_name = model_src.name.lower()
139+
140+
# copy signed binaries while matching naming convention
141+
click.secho(f"\n{model_name} binaries", bold=True)
142+
model_dst = FW_DATA / model_name
143+
universal_dst = model_dst / f"trezor-{model_name}-{version}.bin"
144+
copy_single_file(
145+
model_src,
146+
f"firmware-{model_name.upper()}-{version}-*-signed.bin",
147+
universal_dst,
148+
)
149+
btconly_dst = model_dst / f"trezor-{model_name}-{version}-bitcoinonly.bin"
150+
copy_single_file(
151+
model_src,
152+
f"firmware-{model_name.upper()}-btconly-{version}-*-signed.bin",
153+
btconly_dst,
154+
)
155+
156+
# copy signed translations (no renaming is needed) for this version
157+
click.secho(f"{model_name} translations", bold=True)
158+
translations_src = signed / "translations" / model_name
159+
translations_dst = FW_DATA / "translations" / model_name
160+
blob_pattern = f"translation-{model_name.upper()}-??-??-{version}.bin"
161+
for blob in sorted(translations_src.glob(blob_pattern)):
162+
copy_file(blob, translations_dst / blob.name)
163+
164+
# copy and adapt JSONs
165+
click.secho(f"{model_name} JSONs", bold=True)
166+
universal_json = copy_and_adapt_json(
167+
model_src / "universal" / f"{model_name}-{version}-universal.json",
168+
model_dst / "universal" / f"{model_name}-{version}-universal.json",
169+
new_url=universal_dst.relative_to(ROOT),
170+
)
171+
btconly_json = copy_and_adapt_json(
172+
model_src / "bitcoinonly" / f"{model_name}-{version}-bitcoinonly.json",
173+
model_dst / "bitcoinonly" / f"{model_name}-{version}-bitcoinonly.json",
174+
new_url=btconly_dst.relative_to(ROOT),
175+
)
176+
update_releases_json(model_dst / "releases.json", universal_json, btconly_json)
177+
178+
179+
if __name__ == "__main__":
180+
main()

scripts/pyproject.toml

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)