Skip to content

Commit f1ead58

Browse files
committed
chore(common): version bump script
1 parent 4401d89 commit f1ead58

File tree

24 files changed

+1934
-1963
lines changed

24 files changed

+1934
-1963
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: versions-check
2+
3+
on:
4+
release:
5+
types:
6+
- published
7+
8+
permissions: {}
9+
10+
jobs:
11+
check-versions:
12+
name: versions-check/check-versions
13+
runs-on: ubuntu-latest
14+
permissions:
15+
actions: 'read' # Required to read workflow run information
16+
contents: 'read' # Required to checkout repository code
17+
packages: 'read' # Required to read GitHub packages/container registry
18+
steps:
19+
- name: Checkout Project
20+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
21+
with:
22+
token: ${{ secrets.BLOCKCHAIN_ACTIONS_TOKEN }}
23+
persist-credentials: 'false'
24+
25+
- name: Check FHEVM sub-projects versions
26+
run: |
27+
python3 ci/versioning.py check ${{ github.ref_name }}

ci/versioning.py

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import os
5+
import re
6+
import subprocess
7+
from argparse import ArgumentParser
8+
from pathlib import Path
9+
10+
FHEVM_ROOT_DIR = Path(os.path.dirname(__file__)).parent
11+
COPRO_DIR = FHEVM_ROOT_DIR.joinpath("coprocessor")
12+
GW_CONTRACTS_DIR = FHEVM_ROOT_DIR.joinpath("gateway-contracts")
13+
HOST_CONTRACTS_DIR = FHEVM_ROOT_DIR.joinpath("host-contracts")
14+
KMS_CONNECTOR_DIR = FHEVM_ROOT_DIR.joinpath("kms-connector")
15+
LIB_SOLIDITY_DIR = FHEVM_ROOT_DIR.joinpath("library-solidity")
16+
RUST_SDK_DIR = FHEVM_ROOT_DIR.joinpath("sdk").joinpath("rust-sdk")
17+
TEST_SUITE_DIR = FHEVM_ROOT_DIR.joinpath("test-suite")
18+
19+
FHEVM_SUBPROJECTS_VERSION_FILES = [
20+
COPRO_DIR.joinpath("fhevm-engine").joinpath("Cargo.toml"),
21+
GW_CONTRACTS_DIR.joinpath("package.json"),
22+
GW_CONTRACTS_DIR.joinpath("rust_bindings").joinpath("Cargo.toml"),
23+
HOST_CONTRACTS_DIR.joinpath("package.json"),
24+
KMS_CONNECTOR_DIR.joinpath("Cargo.toml"),
25+
LIB_SOLIDITY_DIR.joinpath("package.json"),
26+
RUST_SDK_DIR.joinpath("Cargo.toml"),
27+
TEST_SUITE_DIR.joinpath("gateway-stress").joinpath("Cargo.toml"),
28+
TEST_SUITE_DIR.joinpath("e2e").joinpath("package.json"),
29+
]
30+
31+
32+
def init_cli() -> ArgumentParser:
33+
"""Inits the CLI of the tool."""
34+
parser = ArgumentParser(
35+
description=(
36+
"A tool to check or update the versions within the FHEVM monorepo."
37+
)
38+
)
39+
subparsers = parser.add_subparsers(dest="command", help="Subcommands")
40+
41+
check_subparser = subparsers.add_parser(
42+
"check",
43+
help=(
44+
"Check if all projects' versions within the FHEVM monorepo matches a given version."
45+
),
46+
)
47+
check_subparser.add_argument(
48+
"version", type=str, help="The version that all FHEVM sub-projects should have."
49+
)
50+
51+
update_subparser = subparsers.add_parser(
52+
"update", help="Update the projects' versions within the FHEVM monorepo."
53+
)
54+
update_subparser.add_argument(
55+
"version", type=str, help="The version to set for all FHEVM sub-projects."
56+
)
57+
58+
return parser
59+
60+
61+
def main():
62+
cli = init_cli()
63+
args = cli.parse_args()
64+
65+
if args.command not in ["check", "update"]:
66+
return cli.print_help()
67+
68+
version = args.version.strip("v")
69+
if args.command == "check":
70+
check_semver_string(version, False)
71+
check_projects_versions(version)
72+
elif args.command == "update":
73+
check_semver_string(version, True)
74+
update_projects_versions(version)
75+
76+
77+
def check_projects_versions(fhevm_version: str):
78+
"""
79+
Checks that the version of all the projects of the monorepo matches the FHEVM version.
80+
"""
81+
check_results = [
82+
check_project_version(version_file, fhevm_version)
83+
for version_file in FHEVM_SUBPROJECTS_VERSION_FILES
84+
]
85+
if all(check_results):
86+
log_success("All versions are up-to-date!")
87+
else:
88+
log_info(
89+
f"Run `./ci/versioning.py update {fhevm_version}` to update all FHEVM sub-projects versions."
90+
)
91+
exit(1)
92+
93+
94+
def check_project_version(project_version_file: Path, fhevm_version: str) -> bool:
95+
"""
96+
Checks that the version of a project of the monorepo matches the FHEVM version.
97+
"""
98+
log_info(
99+
f"Checking that {project_version_file} version matches the FHEVM version..."
100+
)
101+
if project_version_file.name == "Cargo.toml":
102+
project_version = get_cargo_toml_version(project_version_file)
103+
elif project_version_file.name == "package.json":
104+
project_version = get_package_json_version(project_version_file)
105+
else:
106+
log_error(f"Unsupported version file: {project_version_file}!")
107+
return False
108+
109+
if project_version == fhevm_version:
110+
log_success(
111+
f"{project_version_file}'s version matches with the FHEVM version: {fhevm_version}!\n"
112+
)
113+
return True
114+
115+
log_error(
116+
f"ERROR: {project_version_file} version does not match FHEVM version!\n"
117+
f"FHEVM version: {fhevm_version}\n"
118+
f"{project_version_file} version: {project_version}\n"
119+
)
120+
return False
121+
122+
123+
def get_cargo_toml_version(cargo_toml_path: Path) -> str | None:
124+
"""Gets the version within a given Cargo.toml file."""
125+
with open(cargo_toml_path, "r") as cargo_toml_fd:
126+
cargo_toml_content = cargo_toml_fd.read()
127+
128+
# Find the version in the Cargo.toml
129+
# Here, we want to find the version in the [package] section to avoid catching versions
130+
# from dependencies. The `re.DOTALL` flag is used to allow the dot to match newlines.
131+
# There is only one captured group: the version found within the quotes
132+
matches = re.search(
133+
r'\[.*package\].*?version\s*=\s*"([^"]+)"',
134+
cargo_toml_content,
135+
flags=re.DOTALL,
136+
)
137+
138+
if not matches:
139+
log_error(f"Could not find version in {cargo_toml_path}")
140+
return None
141+
142+
# Extract the version from the matches: the first (and only) captured group from the regex.
143+
cargo_toml_version = matches.group(1)
144+
return cargo_toml_version
145+
146+
147+
def get_package_json_version(package_json_path: Path) -> str | None:
148+
"""Gets the version within a given package.json file."""
149+
with open(package_json_path, "r") as package_json_fd:
150+
return json.load(package_json_fd)["version"]
151+
152+
153+
def update_projects_versions(fhevm_version: str):
154+
"""Updates the version of all the monorepo projects to `fhevm_version`."""
155+
update_results = [
156+
update_project_version(version_file, fhevm_version)
157+
for version_file in FHEVM_SUBPROJECTS_VERSION_FILES
158+
]
159+
if all(update_results):
160+
log_success("All versions were successfully updated!")
161+
else:
162+
exit(1)
163+
164+
165+
def update_project_version(project_version_file: Path, fhevm_version: str) -> bool:
166+
"""Updates the version in the project version file."""
167+
log_info(f"Updating the {project_version_file}'s version...")
168+
169+
if project_version_file.name == "Cargo.toml":
170+
update_cargo_toml_version(project_version_file, fhevm_version)
171+
# Update version in lockfile
172+
subprocess.run(
173+
["cargo", "generate-lockfile", "--offline"],
174+
capture_output=True,
175+
cwd=project_version_file.parent,
176+
)
177+
elif project_version_file.name == "package.json":
178+
update_package_json_version(project_version_file, fhevm_version)
179+
lock_file = project_version_file.parent.joinpath("package-lock.json")
180+
if lock_file.exists():
181+
update_package_json_version(lock_file, fhevm_version)
182+
else:
183+
log_error(f"Unsupported version file: {project_version_file}!")
184+
return False
185+
186+
log_success(
187+
f"The {project_version_file}'s version has been successfully updated to {fhevm_version}!\n"
188+
)
189+
return True
190+
191+
192+
def update_cargo_toml_version(cargo_toml_path: Path, fhevm_version: str):
193+
"""Updates the version in the Cargo.toml file."""
194+
with open(cargo_toml_path, "r") as cargo_toml_fd:
195+
cargo_toml_content = cargo_toml_fd.read()
196+
197+
# Replace the version in the Cargo.toml
198+
# Similar to the check_version function, we use a regex to find the version in the [package]
199+
# section to avoid changing the version of any dependency. The `count=1` argument ensures that
200+
# only the first occurrence is replaced as we only expect one version. The `re.DOTALL` flag is
201+
# used to allow the dot to match newlines. There are two captured groups:
202+
# - The first one is the [package] section up until the first quote of the version.
203+
# - The second one is the ending quote of the version.
204+
# We then only replace the version by inserting it between both captured groups. This is to
205+
# make sure we do not alter the original format of the Cargo.toml.
206+
cargo_toml_content = re.sub(
207+
r'(\[.*package\].*?version\s*=\s*")[^"]+(")',
208+
lambda m: m.group(1) + fhevm_version + m.group(2),
209+
cargo_toml_content,
210+
count=1,
211+
flags=re.DOTALL,
212+
)
213+
214+
with open(cargo_toml_path, "w") as cargo_toml_fd:
215+
cargo_toml_fd.write(cargo_toml_content)
216+
217+
218+
def update_package_json_version(package_json_path: Path, fhevm_version: str):
219+
"""Updates the version in the package.json file."""
220+
with open(package_json_path, "r") as package_json_fd:
221+
package_json_content = json.load(package_json_fd)
222+
package_json_content["version"] = fhevm_version
223+
224+
with open(package_json_path, "w") as package_json_fd:
225+
json.dump(package_json_content, package_json_fd, indent=2)
226+
227+
228+
def check_semver_string(string: str, strict: bool):
229+
"""Checks the given string is a valid semver expression.
230+
231+
If `strict` is set and the string is not a valid semver expression, it exits the program.
232+
Else, it just raises a warning.
233+
"""
234+
# Official semver regex: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
235+
match = re.match(
236+
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$",
237+
string,
238+
)
239+
240+
if match:
241+
log_success(f"{string} is a valid semver expression\n")
242+
elif not match and strict:
243+
log_error(f"{string} is not a valid semver expression!")
244+
exit(1)
245+
else:
246+
log_warning(f"{string} is not a valid semver expression!\n")
247+
248+
249+
BRED = "\033[91m\033[1m"
250+
BGREEN = "\033[92m\033[1m"
251+
BYELLOW = "\033[93m\033[1m"
252+
BBLUE = "\033[94m\033[1m"
253+
NC = "\033[0m"
254+
255+
256+
def log_info(msg: str):
257+
print(f"{BBLUE}[*]{NC} {msg}")
258+
259+
260+
def log_success(msg: str):
261+
print(f"{BGREEN}[+]{NC} {msg}")
262+
263+
264+
def log_error(msg: str):
265+
print(f"{BRED}[-]{NC} {msg}")
266+
267+
268+
def log_warning(msg: str):
269+
print(f"{BYELLOW}[!]{NC} {msg}")
270+
271+
272+
if __name__ == "__main__":
273+
main()

0 commit comments

Comments
 (0)