Skip to content

Commit d6b707a

Browse files
committed
add release automation scripts
1 parent 51bc89b commit d6b707a

10 files changed

+1120
-0
lines changed

scripts/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/venv

scripts/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Release Automation Scripts
2+
3+
This directory contains scripts to automate the risc0-ethereum release process as described in [RELEASE.md](../RELEASE.md).
4+
5+
## Quick Start
6+
7+
### Prerequisites
8+
9+
- Python 3.7+
10+
- Git
11+
- Bash
12+
13+
### Setup
14+
15+
1. Set up the Python environment:
16+
```bash
17+
scripts/setup-venv.sh
18+
```
19+
20+
2. Run a release preparation (dry-run first!):
21+
```bash
22+
# Prepare release 2.1.0 (dry run)
23+
scripts/prepare-release.sh 2.1.0 --dry-run
24+
25+
# Actually run it
26+
scripts/prepare-release.sh 2.1.0
27+
```
28+
29+
3. Prepare next development version:
30+
```bash
31+
# Prepare next dev version 2.2.0-alpha.1 (dry run)
32+
scripts/prepare-next-version.sh 2.2.0 --dry-run
33+
34+
# Actually run it
35+
scripts/prepare-next-version.sh 2.2.0
36+
```
37+
38+
## Main Orchestration Scripts
39+
40+
### `prepare-release.sh`
41+
Automates the complete release branch preparation process.
42+
43+
**Usage:**
44+
```bash
45+
scripts/prepare-release.sh <release_version> [options]
46+
47+
Arguments:
48+
release_version Version to release (e.g., 2.1.0)
49+
50+
Options:
51+
--risc0-version RISC Zero monorepo version to use (default: 2.0)
52+
--dry-run Show changes without applying them
53+
--help Show help message
54+
55+
Examples:
56+
scripts/prepare-release.sh 2.1.0
57+
scripts/prepare-release.sh 2.1.0 --risc0-version 2.0 --dry-run
58+
```
59+
60+
**What it does:**
61+
1. Creates release branch (e.g., `release-2.1`)
62+
2. Updates all Cargo.toml versions to release version
63+
3. Updates contract version strings
64+
4. Updates branch references from `main` to release branch
65+
5. Converts risc0 git dependencies to version dependencies
66+
6. Updates .gitignore to include Cargo.lock files
67+
7. Removes main branch warning from README.md
68+
8. Updates CHANGELOG.md to mark current version as released
69+
70+
### `prepare-next-version.sh`
71+
Prepares the main branch for the next development cycle.
72+
73+
**Usage:**
74+
```bash
75+
scripts/prepare-next-version.sh <next_version> [options]
76+
77+
Arguments:
78+
next_version Next development version (e.g., 2.2.0)
79+
Script automatically appends '-alpha.1'
80+
81+
Options:
82+
--dry-run Show changes without applying them
83+
--help Show help message
84+
85+
Examples:
86+
scripts/prepare-next-version.sh 2.2.0
87+
scripts/prepare-next-version.sh 2.2.0 --dry-run
88+
```
89+
90+
**What it does:**
91+
1. Updates all Cargo.toml versions to next alpha version
92+
2. Updates contract version strings to next alpha version
93+
3. Adds new "Unreleased" section to CHANGELOG.md

scripts/bump-cargo-versions.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Cargo version bump script for risc0-ethereum release automation.
4+
5+
This script updates versions across all workspace Cargo.toml files.
6+
Supports both release mode (x.y.z) and next development mode (x.y+1.0-alpha.1).
7+
"""
8+
9+
import argparse
10+
import re
11+
import sys
12+
import toml
13+
from pathlib import Path
14+
from typing import List, Tuple, Optional
15+
16+
17+
def parse_version(version_str: str) -> Tuple[int, int, int, Optional[str]]:
18+
"""Parse semantic version string into components."""
19+
pattern = r'^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$'
20+
match = re.match(pattern, version_str)
21+
if not match:
22+
raise ValueError(f"Invalid version format: {version_str}")
23+
24+
major, minor, patch, prerelease = match.groups()
25+
return int(major), int(minor), int(patch), prerelease
26+
27+
28+
def format_version(major: int, minor: int, patch: int, prerelease: Optional[str] = None) -> str:
29+
"""Format version components back to string."""
30+
version = f"{major}.{minor}.{patch}"
31+
if prerelease:
32+
version += f"-{prerelease}"
33+
return version
34+
35+
36+
def get_next_release_version(current_version: str) -> str:
37+
"""Convert alpha version to release version (remove prerelease suffix)."""
38+
major, minor, patch, prerelease = parse_version(current_version)
39+
if prerelease and 'alpha' in prerelease:
40+
# Remove alpha suffix for release
41+
return format_version(major, minor, patch)
42+
else:
43+
# Already a release version, just return as-is
44+
return current_version
45+
46+
47+
def get_next_dev_version(current_version: str) -> str:
48+
"""Get next development version (increment minor, set patch to 0, add alpha.1)."""
49+
major, minor, patch, prerelease = parse_version(current_version)
50+
# For next dev version, increment minor and reset patch
51+
return format_version(major, minor + 1, 0, "alpha.1")
52+
53+
54+
def find_cargo_toml_files() -> List[Path]:
55+
"""Find all Cargo.toml files that need version updates."""
56+
root = Path('.')
57+
58+
# Use the grep command from RELEASE.md to find files
59+
import subprocess
60+
result = subprocess.run([
61+
'grep', '-rl', '^version = "', '--include=Cargo.toml',
62+
'--exclude-dir=./lib', '--exclude-dir=./examples',
63+
'--exclude-dir=./crates/ffi/guests', '--exclude-dir=./target', '.'
64+
], capture_output=True, text=True, cwd=root)
65+
66+
if result.returncode != 0:
67+
return []
68+
69+
files = [Path(f.strip()) for f in result.stdout.strip().split('\n') if f.strip()]
70+
return [f for f in files if f.exists()]
71+
72+
73+
def update_cargo_toml(file_path: Path, new_version: str, dry_run: bool = False) -> bool:
74+
"""Update version in a Cargo.toml file."""
75+
if not file_path.exists():
76+
print(f"Warning: {file_path} does not exist")
77+
return False
78+
79+
try:
80+
content = file_path.read_text()
81+
data = toml.loads(content)
82+
83+
# Check if this file has a version field
84+
if 'package' in data and 'version' in data['package']:
85+
old_version = data['package']['version']
86+
if old_version != new_version:
87+
print(f" {file_path}: {old_version} -> {new_version}")
88+
if not dry_run:
89+
data['package']['version'] = new_version
90+
file_path.write_text(toml.dumps(data))
91+
return True
92+
93+
# Check workspace version
94+
if 'workspace' in data and 'package' in data['workspace'] and 'version' in data['workspace']['package']:
95+
old_version = data['workspace']['package']['version']
96+
if old_version != new_version:
97+
print(f" {file_path} (workspace): {old_version} -> {new_version}")
98+
if not dry_run:
99+
data['workspace']['package']['version'] = new_version
100+
file_path.write_text(toml.dumps(data))
101+
return True
102+
103+
except Exception as e:
104+
print(f"Error updating {file_path}: {e}")
105+
return False
106+
107+
return False
108+
109+
110+
def main():
111+
parser = argparse.ArgumentParser(description='Bump Cargo.toml versions for risc0-ethereum release')
112+
parser.add_argument('version', help='Target version (e.g., 2.1.0 or auto)')
113+
parser.add_argument('--mode', choices=['release', 'next-dev'], required=True,
114+
help='release: prepare for release, next-dev: prepare for next development cycle')
115+
parser.add_argument('--dry-run', action='store_true', help='Show changes without applying them')
116+
117+
args = parser.parse_args()
118+
119+
# Read current version from workspace Cargo.toml
120+
workspace_cargo = Path('Cargo.toml')
121+
if not workspace_cargo.exists():
122+
print("Error: Cargo.toml not found in current directory")
123+
sys.exit(1)
124+
125+
try:
126+
workspace_data = toml.loads(workspace_cargo.read_text())
127+
current_version = workspace_data['workspace']['package']['version']
128+
print(f"Current version: {current_version}")
129+
except Exception as e:
130+
print(f"Error reading workspace Cargo.toml: {e}")
131+
sys.exit(1)
132+
133+
# Determine target version
134+
if args.version == 'auto':
135+
if args.mode == 'release':
136+
target_version = get_next_release_version(current_version)
137+
else: # next-dev
138+
target_version = get_next_dev_version(current_version)
139+
else:
140+
target_version = args.version
141+
142+
print(f"Target version: {target_version}")
143+
144+
if args.dry_run:
145+
print("\n=== DRY RUN MODE - No changes will be made ===")
146+
147+
# Validate target version format
148+
try:
149+
parse_version(target_version)
150+
except ValueError as e:
151+
print(f"Error: {e}")
152+
sys.exit(1)
153+
154+
changes_made = False
155+
156+
# Update Cargo.toml files
157+
print(f"\nUpdating Cargo.toml files:")
158+
cargo_files = find_cargo_toml_files()
159+
if not cargo_files:
160+
print("No Cargo.toml files found")
161+
else:
162+
for file_path in cargo_files:
163+
if update_cargo_toml(file_path, target_version, args.dry_run):
164+
changes_made = True
165+
166+
if not changes_made:
167+
print("\nNo changes needed - versions are already up to date")
168+
elif args.dry_run:
169+
print(f"\nDry run complete. Run without --dry-run to apply changes.")
170+
else:
171+
print(f"\nCargo version bump complete: {current_version} -> {target_version}")
172+
173+
174+
if __name__ == '__main__':
175+
main()

scripts/bump-contract-versions.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Contract version bump script for risc0-ethereum release automation.
4+
5+
This script updates version strings in Solidity contract files.
6+
"""
7+
8+
import argparse
9+
import re
10+
import sys
11+
from pathlib import Path
12+
from typing import List
13+
14+
15+
def find_contract_files() -> List[Path]:
16+
"""Find contract files that contain version strings."""
17+
return [
18+
Path('contracts/src/groth16/RiscZeroGroth16Verifier.sol'),
19+
Path('contracts/src/RiscZeroSetVerifier.sol')
20+
]
21+
22+
23+
def update_contract_version(file_path: Path, new_version: str, dry_run: bool = False) -> bool:
24+
"""Update version string in a Solidity contract file."""
25+
if not file_path.exists():
26+
print(f"Warning: {file_path} does not exist")
27+
return False
28+
29+
try:
30+
content = file_path.read_text()
31+
32+
# Pattern to match version strings in contracts
33+
pattern = r'string public constant VERSION = "([^"]+)";'
34+
35+
changes_made = False
36+
def replace_version(match):
37+
nonlocal changes_made
38+
old_version = match.group(1)
39+
if old_version != new_version:
40+
print(f" {file_path}: {old_version} -> {new_version}")
41+
changes_made = True
42+
return f'string public constant VERSION = "{new_version}";'
43+
return match.group(0)
44+
45+
new_content = re.sub(pattern, replace_version, content)
46+
47+
if changes_made and not dry_run:
48+
file_path.write_text(new_content)
49+
50+
return changes_made
51+
52+
except Exception as e:
53+
print(f"Error updating {file_path}: {e}")
54+
return False
55+
56+
57+
def main():
58+
parser = argparse.ArgumentParser(description='Bump contract version strings for risc0-ethereum release')
59+
parser.add_argument('version', help='Target version (e.g., 2.1.0)')
60+
parser.add_argument('--dry-run', action='store_true', help='Show changes without applying them')
61+
62+
args = parser.parse_args()
63+
64+
if args.dry_run:
65+
print("=== DRY RUN MODE - No changes will be made ===")
66+
67+
print(f"Target version: {args.version}")
68+
69+
changes_made = False
70+
71+
# Update contract files
72+
print(f"\nUpdating contract version strings:")
73+
contract_files = find_contract_files()
74+
for file_path in contract_files:
75+
if update_contract_version(file_path, args.version, args.dry_run):
76+
changes_made = True
77+
78+
if not changes_made:
79+
print("\nNo changes needed - contract versions are already up to date")
80+
elif args.dry_run:
81+
print(f"\nDry run complete. Run without --dry-run to apply changes.")
82+
else:
83+
print(f"\nContract version bump complete -> {args.version}")
84+
85+
86+
if __name__ == '__main__':
87+
main()

0 commit comments

Comments
 (0)