From 1d36f53bd383044e5964d3c57d5ba2c157479ac5 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Fri, 17 Jul 2020 14:14:26 +0200 Subject: [PATCH] Nice UI --- tools/snap/build_remote.py | 124 ++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 44 deletions(-) diff --git a/tools/snap/build_remote.py b/tools/snap/build_remote.py index d0ec58685..929d32f03 100755 --- a/tools/snap/build_remote.py +++ b/tools/snap/build_remote.py @@ -1,18 +1,22 @@ #!/usr/bin/env python3 import argparse +import curses import glob -import multiprocessing +import datetime +from multiprocessing import Pool, Process, Manager import re import subprocess import sys -from os.path import join, realpath, dirname, basename, exists +import time +from os.path import join, realpath, dirname, basename CERTBOT_DIR = dirname(dirname(dirname(realpath(__file__)))) PLUGINS = [basename(path) for path in glob.glob(join(CERTBOT_DIR, 'certbot-dns-*'))] -def _build_snap(target, archs): +def _build_snap(target, archs, status): + status[target] = {} if target == 'certbot': workspace = CERTBOT_DIR else: @@ -22,55 +26,81 @@ def _build_snap(target, archs): '| grep -v python-augeas > "{1}/snap-constraints.txt"').format(sys.executable, workspace), shell=True, cwd=CERTBOT_DIR) + process = subprocess.Popen([ + 'snapcraft', 'remote-build', '--launchpad-accept-public-upload', '--recover', '--build-on', ','.join(archs) + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, cwd=workspace) + + line = process.stdout.readline() + while line: + _extract_state(target, line, status) + line = process.stdout.readline() + + return {target: workspace} + + +def _extract_state(project, output, status): + match = re.match(r'^.*arch=(\w+)\s+state=([\w ]+).*$', output) + if match: + arch = match.group(1) + state_str = match.group(2) + state = status[project] + if state_str == 'Successfully built': + state[arch] = 'S' + elif state_str == 'Failed to build': + state[arch] = 'F' + elif state_str == 'Uploading build': + state[arch] = 'U' + elif state_str == 'Currently building': + state[arch] = 'B' + elif state_str == 'Needs building': + state[arch] = 'W' + + status[project] = state + + +def _dump_status(status): + stdscr = curses.initscr() + curses.noecho() + curses.cbreak() + try: - subprocess.check_call([ - 'snapcraft', 'remote-build', '--launchpad-accept-public-upload', - '--build-on', ','.join(archs) - ], universal_newlines=True, cwd=workspace) - except subprocess.CalledProcessError as e: - # Will be handled after - pass + while True: + stdscr.addstr(0, 0, 'Build status at {0}'.format(datetime.datetime.now())) + stdscr.addstr(1, 0, 'W = wait, B = building, U = uploading, F = fail, S = success') + stdscr.addstr(2, 0, ' project amd64 arm64 armhf ') + stdscr.addstr(3, 0, '---------------------------+-------+-------+-------') + idx = 4 + for project, states in status.items(): + stdscr.addstr(idx, 0, ' {0} | {1} | {2} | {3} '.format( + project + ' ' * (25 - len(project)), states.get('arm64', 'W'), + states.get('arm64', 'W'), states.get('armhf', 'W'))) + idx = idx + 1 - status = {} - - for arch in archs: - status[arch] = None - build_file = join(workspace, '{0}_{1}.txt'.format(target, arch)) - if exists(build_file): - with open(build_file) as file_h: - build_output = file_h.read() - - if not re.search(r'Snapped {0}_.*_{1}\.snap'.format(target, arch), build_output): - status[arch] = build_output - - return {target: status} + stdscr.refresh() + time.sleep(1) + finally: + curses.echo() + curses.nocbreak() + curses.endwin() -def _dump_results(targets, archs, results): +def _dump_results(targets, archs, status, workspaces): failures = False for target in targets: for arch in archs: - build_output = results[target][arch] - if build_output: + result = status[target][arch] + + if result == 'F': failures = True + + with open(join(workspaces[target], '{0}_{1}.txt'.format(target, arch))) as file_h: + build_output = file_h.read() + print('Output for failed build target={0} arch={1}'.format(target, arch)) print('-------------------------------------------') print(build_output) print('-------------------------------------------') - print('Build summary') - print('=============') - targets = list(targets) - targets.sort() - for target in targets: - print('Builds for target={0}: {1}'.format( - target, - ', '.join([ - 'arch={0} ({1})'.format(arch, 'success' if not results[target][arch] else 'failure') - for arch in archs - ]) - )) - return failures @@ -93,15 +123,21 @@ def main(): targets.remove('DNS_PLUGINS') targets.update(PLUGINS) - pool = multiprocessing.Pool(processes=len(targets)) - async_results = [pool.apply_async(_build_snap, (target, archs)) for target in targets] + status = Manager().dict() - results = {} + state_process = Process(target=_dump_status, args=(status,)) + state_process.start() + pool = Pool(processes=len(targets)) + async_results = [pool.apply_async(_build_snap, (target, archs, status)) for target in targets] + + workspaces = {} for async_result in async_results: - results.update(async_result.get()) + workspaces.update(async_result.get()) - failures = _dump_results(targets, archs, results) + state_process.terminate() + + failures = _dump_results(targets, archs, status, workspaces) return 1 if failures else 0