To make updating large attribute sets faster, the update scripts are now run in parallel. Please note the following changes in semantics: - The string passed to updateScript needs to be a path to an executable file. - The updateScript can also be a list: the tail elements will then be passed to the head as command line arguments.
		
			
				
	
	
		
			80 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			80 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import argparse
 | 
						|
import concurrent.futures
 | 
						|
import json
 | 
						|
import os
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
 | 
						|
updates = {}
 | 
						|
 | 
						|
def eprint(*args, **kwargs):
 | 
						|
    print(*args, file=sys.stderr, **kwargs)
 | 
						|
 | 
						|
def run_update_script(package):
 | 
						|
    eprint(f" - {package['name']}: UPDATING ...")
 | 
						|
 | 
						|
    subprocess.run(package['updateScript'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True)
 | 
						|
 | 
						|
 | 
						|
def main(max_workers, keep_going, packages):
 | 
						|
    with open(sys.argv[1]) as f:
 | 
						|
        packages = json.load(f)
 | 
						|
 | 
						|
    eprint()
 | 
						|
    eprint('Going to be running update for following packages:')
 | 
						|
    for package in packages:
 | 
						|
        eprint(f" - {package['name']}")
 | 
						|
    eprint()
 | 
						|
 | 
						|
    confirm = input('Press Enter key to continue...')
 | 
						|
    if confirm == '':
 | 
						|
        eprint()
 | 
						|
        eprint('Running update for:')
 | 
						|
 | 
						|
        with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor:
 | 
						|
            for package in packages:
 | 
						|
                updates[executor.submit(run_update_script, package)] = package
 | 
						|
 | 
						|
            for future in concurrent.futures.as_completed(updates):
 | 
						|
                package = updates[future]
 | 
						|
 | 
						|
                try:
 | 
						|
                    future.result()
 | 
						|
                    eprint(f" - {package['name']}: DONE.")
 | 
						|
                except subprocess.CalledProcessError as e:
 | 
						|
                    eprint(f" - {package['name']}: ERROR")
 | 
						|
                    eprint()
 | 
						|
                    eprint(f"--- SHOWING ERROR LOG FOR {package['name']} ----------------------")
 | 
						|
                    eprint()
 | 
						|
                    eprint(e.stdout.decode('utf-8'))
 | 
						|
                    with open(f"{package['pname']}.log", 'wb') as f:
 | 
						|
                        f.write(e.stdout)
 | 
						|
                    eprint()
 | 
						|
                    eprint(f"--- SHOWING ERROR LOG FOR {package['name']} ----------------------")
 | 
						|
 | 
						|
                    if not keep_going:
 | 
						|
                        sys.exit(1)
 | 
						|
 | 
						|
        eprint()
 | 
						|
        eprint('Packages updated!')
 | 
						|
        sys.exit()
 | 
						|
    else:
 | 
						|
        eprint('Aborting!')
 | 
						|
        sys.exit(130)
 | 
						|
 | 
						|
parser = argparse.ArgumentParser(description='Update packages')
 | 
						|
parser.add_argument('--max-workers', '-j', dest='max_workers', type=int, help='Number of updates to run concurrently', nargs='?', default=4)
 | 
						|
parser.add_argument('--keep-going', '-k', dest='keep_going', action='store_true', help='Do not stop after first failure')
 | 
						|
parser.add_argument('packages', help='JSON file containing the list of package names and their update scripts')
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    args = parser.parse_args()
 | 
						|
 | 
						|
    try:
 | 
						|
        main(args.max_workers, args.keep_going, args.packages)
 | 
						|
    except (KeyboardInterrupt, SystemExit) as e:
 | 
						|
        for update in updates:
 | 
						|
            update.cancel()
 | 
						|
 | 
						|
        sys.exit(e.code if isinstance(e, SystemExit) else 130)
 |