scx/version-tool.py

289 lines
8.1 KiB
Python
Raw Normal View History

#!/usr/bin/python
import argparse
import json
import os
import re
import subprocess
2024-09-07 18:16:22 +01:00
import sys
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)
return ver
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:
if line.endswith('Cargo.toml'):
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):
workspace_re = r'(^\s*)(\[\s*workspace\s*\])(.*$)'
name_re = r'(^\s*name\s*=\s*")(.*)(".*$)'
ver_re = r'(^\s*version\s*=\s*")(.*)(".*$)'
line = line.rstrip()
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
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)
return ver
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))
if ver:
rust_vers[name] = ver
# 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()