#!/usr/bin/python import argparse import os import re import subprocess import sys priority=['scx_stats', 'scx_stats_derive', 'scx_utils', 'scx_rustland_core'] skip=['scx_mitosis'] publish_args={'scx_rlfifo': ['--no-verify'], 'scx_rustland': ['--no-verify']} verbose = False 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 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 cargo_is_workspace(path): with open(path, 'r') as f: lines = f.readlines() for lineno, line in enumerate(lines): workspace_re = r'(^\s*)(\[\s*workspace\s*\])(.*$)' m = re.match(workspace_re, line) if m: dbg(f'[{path}:{lineno}] SKIP: {m.group(1)}{underline(m.group(2))}{m.group(3)}') return True return False def publish(path, extra_args, ignore_existing): directory = os.path.dirname(path) try: proc = subprocess.run(['cargo', 'publish'] + extra_args, cwd=directory, check=True, capture_output=True) except subprocess.CalledProcessError as e: stdout = e.stdout.decode('utf-8').splitlines() stderr = e.stderr.decode('utf-8').splitlines() okay = False if ignore_existing: already_re = r'(^.*)(crate.*already uploaded)(.*$)' m = re.match(already_re, stderr[-1]) if m: print(f'IGNORE: {m.group(1)}{underline(m.group(2))}{m.group(3)}') okay = True if verbose or not okay: for line in stdout: print(f'STDOUT: {line}') for line in stderr: print(f'STDERR: {line}') if not okay: raise e def main(): parser = argparse.ArgumentParser(prog='cargo-publish.py', description='Publish rust projects, use --dry to see what it\'s going to do') parser.add_argument('-s', '--start', help='skip crates which come before this crate in the publishing order') parser.add_argument('-i', '--ignore', action='store_true', help='ignore errors from already published crates') parser.add_argument('-d', '--dry', action='store_true') parser.add_argument('-v', '--verbose', action='store_true') args = parser.parse_args() global verbose verbose = args.verbose paths = get_rust_paths() crate_path_args = {} for path in paths: if cargo_is_workspace(path): continue crate = cargo_path_to_crate(path) pargs = [] if crate in publish_args: pargs = publish_args[crate] del publish_args[crate] crate_path_args[crate] = [path, pargs] if len(publish_args): err(f'publish_args contains non-existent crates {publish_args}') if args.start and args.start not in crate_path_args: err(f'--start specified non-existent crate {args.start}') for crate in skip: if crate not in crate_path_args: err(f'{crate} is in skip list but does not exist') del crate_path_args[crate] # Fill targets in publishing order targets = [] for pri in priority: if pri not in crate_path_args: err(f'cannot find cargo path for priority crate {pri}') path_args = crate_path_args[pri] targets.append([pri, [path_args[0], path_args[1]]]) del crate_path_args[pri] for crate, path_args in sorted(crate_path_args.items()): targets.append([crate, [path_args[0], path_args[1]]]) # Publish start_from = args.start for target in targets: if start_from and target[0] != start_from: continue start_from = None print(f'Publishing crate {target[0]} {" ".join(target[1][1])}') dbg(f'target: {target}') if not args.dry: publish(target[1][0], target[1][1], args.ignore) main()