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"\n Updating 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 ("\n No changes needed - versions are already up to date" )
168
+ elif args .dry_run :
169
+ print (f"\n Dry run complete. Run without --dry-run to apply changes." )
170
+ else :
171
+ print (f"\n Cargo version bump complete: { current_version } -> { target_version } " )
172
+
173
+
174
+ if __name__ == '__main__' :
175
+ main ()
0 commit comments