2024-08-09 00:20:13 +01:00
#!/usr/bin/python
import argparse
import json
import os
import re
import subprocess
2024-09-07 18:16:22 +01:00
import sys
2024-08-09 00:20:13 +01:00
verbose = False
def warn ( line ) :
print ( ' [WARN] ' + line , file = sys . stderr )
def err ( line ) :
raise Exception ( line )
def dbg ( line ) :
if verbose :
print ( ' [DBG] ' + line , file = sys . stderr )
def underline ( string ) :
return f ' \033 [4m { string } \033 [0m '
def do_meson_ver ( new_ver ) :
path = ' meson.build '
with open ( path , ' r ' ) as f :
lines = f . readlines ( )
ver_lineno = - 1
for lineno , line in enumerate ( lines ) :
ver_re = r " (^.*version: \ s* ' )([0-9.]*)( ' .*$) "
m = re . match ( ver_re , line . rstrip ( ) )
if m :
ver_lineno = lineno
pre = m . group ( 1 )
ver = m . group ( 2 )
post = m . group ( 3 )
dbg ( f ' [ { path } : { lineno + 1 } ] { pre } { underline ( ver ) } { post } ' )
break
if ver_lineno < 0 :
err ( f ' [ { path } ] Failed to find verion ' )
if new_ver is None or ver == new_ver :
return ver
print ( f ' [ { path } : { ver_lineno + 1 } ] Updating from { ver } to { new_ver } ' )
lines [ ver_lineno ] = f ' { pre } { new_ver } { post } \n '
with open ( path , ' w ' ) as f :
f . writelines ( lines )
2024-08-09 00:58:16 +01:00
return ver
2024-08-09 00:20:13 +01:00
def get_rust_paths ( ) :
result = subprocess . run ( [ ' git ' , ' ls-files ' ] , stdout = subprocess . PIPE )
lines = result . stdout . decode ( ' utf-8 ' ) . splitlines ( )
paths = [ ]
for line in lines :
2024-10-02 19:32:22 +01:00
# ignore root Cargo.toml
if line . endswith ( ' Cargo.toml ' ) and ' / ' in line :
2024-08-09 00:20:13 +01:00
paths . append ( line )
return paths
def cargo_path_to_crate ( path ) :
return path . split ( ' / ' ) [ - 2 ]
def do_rust_ver ( path , new_ver ) :
with open ( path , ' r ' ) as f :
lines = f . readlines ( )
name_lineno = - 1
ver_lineno = - 1
name = None
ver = None
for lineno , line in enumerate ( lines ) :
2024-08-25 18:00:19 +01:00
workspace_re = r ' (^ \ s*)( \ [ \ s*workspace \ s* \ ])(.*$) '
2024-08-09 00:20:13 +01:00
name_re = r ' (^ \ s*name \ s*= \ s* " )(.*)( " .*$) '
ver_re = r ' (^ \ s*version \ s*= \ s* " )(.*)( " .*$) '
line = line . rstrip ( )
2024-08-25 18:00:19 +01:00
m = re . match ( workspace_re , line )
if m :
dbg ( f ' [ { path } : { lineno } ] SKIP: { m . group ( 1 ) } { underline ( m . group ( 2 ) ) } { m . group ( 3 ) } ' )
return None
2024-08-09 00:20:13 +01:00
m = re . match ( name_re , line )
if m :
name_lineno = lineno
name = m . group ( 2 )
dbg ( f ' [ { path } : { lineno + 1 } ] { m . group ( 1 ) } { underline ( name ) } { m . group ( 3 ) } ' )
else :
m = re . match ( ver_re , line )
if m :
ver_lineno = lineno
pre = m . group ( 1 )
ver = m . group ( 2 )
post = m . group ( 3 )
dbg ( f ' [ { path } : { lineno + 1 } ] { pre } { underline ( ver ) } { post } ' )
if name_lineno > = 0 and ver_lineno > = 0 :
break
if name_lineno < 0 or ver_lineno < 0 :
err ( f ' [ { path } ] Failed to find name or version ' )
if name != cargo_path_to_crate ( path ) :
warn ( f ' [ { path } : { name_lineno } ] name \" { name } \" does not match the path ' )
if new_ver is None or ver == new_ver :
return ver
print ( f ' [ { path } : { ver_lineno + 1 } ] Updating from { ver } to { new_ver } ' )
lines [ ver_lineno ] = f ' { pre } { new_ver } { post } \n '
with open ( path , ' w ' ) as f :
f . writelines ( lines )
2024-08-09 00:58:16 +01:00
return ver
2024-08-09 00:20:13 +01:00
def do_rust_deps ( path , deps , new_deps ) :
with open ( path , ' r ' ) as f :
lines = f . readlines ( )
in_dep = None
block_depth = 0
crate = None
need_write = False
for lineno , line in enumerate ( lines ) :
line = line . rstrip ( )
# determine whether in a dependencies section
sect_re = r ' ^ \ s* \ [([^ \ [ \ ]]*)] \ s*$ '
m = re . match ( sect_re , line )
if m :
if block_depth != 0 :
err ( f ' [ { path } : { lineno + 1 } ] Unbalanced block_depth { block_depth } ' ) ;
sect = m . group ( 1 ) . strip ( )
if sect . endswith ( ' dependencies ' ) :
dbg ( f ' [ { path } { lineno + 1 } ] [ { sect } ] ' )
in_dep = sect
else :
in_dep = None
continue
if not in_dep :
continue
# strip and store comment
body = line
comment_re = r ' (^.*)(#.*$) '
comment = " "
m = re . match ( comment_re , body )
if m :
body = m . group ( 1 )
comment = m . group ( 2 )
if len ( body . strip ( ) ) == 0 :
continue
# determine the current crate
if block_depth == 0 :
crate_re = r ' ^ \ s*([^= \ s]*) \ s*=.*$ '
m = re . match ( crate_re , body )
if m :
crate = m . group ( 1 )
crate_on_line = True
else :
warn ( f ' [ { path } : { lineno + 1 } ] Failed to find crate name ' )
crate = None
else :
crate_on_line = False
# do dumb nesting depth tracking
block_depth + = body . count ( ' { ' ) - body . count ( ' } ' )
block_depth + = body . count ( ' [ ' ) - body . count ( ' ] ' )
if crate is None :
continue
# determine the crate version
ver = None
if crate_on_line :
ver_re = r ' (^[^=].*= \ s* " )([^ " ]*)( " \ s*$) '
m = re . match ( ver_re , body )
if m :
pre = m . group ( 1 )
ver = m . group ( 2 )
post = m . group ( 3 )
if ver is None :
ver_re = r ' (^.*version \ s*= \ s* " )([^ " ]*)( " .*$) '
m = re . match ( ver_re , body )
if m :
pre = m . group ( 1 )
ver = m . group ( 2 )
post = m . group ( 3 )
if ver is None :
if block_depth == 0 :
warn ( f ' { path } : { lineno + 1 } no version ' )
continue
dbg ( f ' [ { path } : { lineno + 1 } ] { crate } : { pre } { underline ( ver ) } { post } ' )
# check whether the version matches
if crate in deps :
if deps [ crate ] != ver :
warn ( f ' [ { path } : { lineno + 1 } ] crate " { crate } " { ver } mismatches existing { deps [ crate ] } ' )
else :
deps [ crate ] = ver
if crate in new_deps :
new_ver = new_deps [ crate ]
if ver != new_ver :
print ( f ' [ { path } : { lineno + 1 } ] Updating dep { crate } = " { ver } " -> " { new_ver } " ' )
lines [ lineno ] = f ' { pre } { new_ver } { post } { comment } \n '
need_write = True
crate = None
if block_depth != 0 :
err ( f ' [ { path } : { lineno + 1 } ] Unbalanced block_depth { block_depth } ' ) ;
if need_write :
with open ( path , ' w ' ) as f :
f . writelines ( lines )
def main ( ) :
parser = argparse . ArgumentParser ( prog = ' version-tool.py ' ,
description = ' Check and update versions. " version-tool.py > vers.json " to generate the template. Apply the edited version with " version-too.py -u vers.json " ' )
parser . add_argument ( ' -u ' , ' --update ' , metavar = ' JSON ' , type = str ,
help = ' Update versions from the specified json file ' )
parser . add_argument ( ' -v ' , ' --verbose ' , action = ' store_true ' )
args = parser . parse_args ( )
global verbose
verbose = args . verbose
vers_key = ' 00-versions '
rust_vers_key = ' 01-rust-versions '
rust_deps_key = ' 02-rust-deps '
vers = { }
rust_vers = { }
rust_deps = { }
new_vers = { }
new_rust_vers = { }
new_rust_deps = { }
if args . update :
with open ( args . update , ' r ' ) as f :
parsed = json . loads ( f . read ( ) )
new_vers = parsed [ vers_key ]
new_rust_vers = parsed [ rust_vers_key ]
new_rust_deps = parsed [ rust_deps_key ]
# package version
vers [ ' meson ' ] = do_meson_ver ( new_vers . get ( ' meson ' ) )
# rust crates implemented in the tree
rust_paths = get_rust_paths ( )
for path in rust_paths :
name = cargo_path_to_crate ( path )
ver = do_rust_ver ( path , new_rust_vers . get ( name ) )
2024-08-25 18:00:19 +01:00
if ver :
rust_vers [ name ] = ver
2024-08-09 00:20:13 +01:00
# crates implemented in the tree are included as deps by default
rust_deps . update ( rust_vers )
new_rust_deps . update ( new_rust_vers )
# rust dependencies
for path in rust_paths :
do_rust_deps ( path , rust_deps , new_rust_deps )
# if not updating, print out what's read
if args . update is None :
for crate in rust_vers :
rust_deps . pop ( crate )
manifest = { vers_key : vers ,
rust_vers_key : rust_vers ,
rust_deps_key : rust_deps }
print ( json . dumps ( manifest , sort_keys = True , indent = 4 ) )
main ( )