def format_interleaved_lines(self, data): if self.max_toplevel_jobs != 1: prefix = clr('[{}:{}] ').format(data['job_id'], data['stage_label']) else: prefix = '' template = '\r{}\r{}'.format(' ' * terminal_width(), prefix) suffix = clr('@|') return ''.join(template + line + suffix for line in data['data'].splitlines(True))
def format_interleaved_lines(self, data): if self.max_toplevel_jobs != 1: prefix = clr('[{}:{}] ').format( data['job_id'], data['stage_label']) else: prefix = '' template = '\r{}\r{}'.format(' ' * terminal_width(), prefix) suffix = clr('@|') return ''.join(template + l + suffix for l in data['data'].splitlines(True))
def format_interleaved_lines(self, data): if self.max_toplevel_jobs != 1: prefix = clr('[{}:{}] ').format(data['job_id'], data['stage_label']) else: prefix = '' # This is used to clear the status bar that is printed in the current line clear_line = '\r{}\r'.format(' ' * terminal_width()) suffix = clr('@|') lines = data['data'].splitlines() return clear_line + '\n'.join(prefix + line + suffix for line in lines)
def run(self): queued_jobs = [] active_jobs = [] completed_jobs = {} failed_jobs = [] warned_jobs = [] cumulative_times = dict() start_times = dict() active_stages = dict() start_time = self.pre_start_time or time.time() last_update_time = time.time() # If the status rate is too low, just disable it if self.active_status_rate < 1E-3: self.show_active_status = False else: update_duration = 1.0 / self.active_status_rate # Disable the wide log padding if the status is disabled if not self.show_active_status: disable_wide_log() while True: # Check if we should stop if not self.keep_running: wide_log( clr('[{}] An internal error occurred!').format(self.label)) return # Write a continuously-updated status line if self.show_active_status: # Try to get an event from the queue (non-blocking) try: event = self.event_queue.get(False) except Empty: # Determine if the status should be shown based on the desired # status rate elapsed_time = time.time() - last_update_time show_status_now = elapsed_time > update_duration if show_status_now: # Print live status (overwrites last line) status_line = clr( '[{} {} s] [{}/{} complete] [{}/{} jobs] [{} queued]' ).format( self.label, format_time_delta_short(time.time() - start_time), len(completed_jobs), len(self.jobs), job_server.running_jobs(), job_server.max_jobs(), len(queued_jobs) + len(active_jobs) - len(active_stages)) # Show failed jobs if len(failed_jobs) > 0: status_line += clr( ' [@!@{rf}{}@| @{rf}failed@|]').format( len(failed_jobs)) # Check load / mem if not job_server.load_ok(): status_line += clr(' [@!@{rf}High Load@|]') if not job_server.mem_ok(): status_line += clr(' [@!@{rf}Low Memory@|]') # Add active jobs if len(active_jobs) == 0: status_line += clr( ' @/@!@{kf}Waiting for jobs...@|') else: active_labels = [] for j, (s, t, p) in active_stages.items(): d = format_time_delta_short( cumulative_times[j] + time.time() - t) if p == '': active_labels.append( clr('[{}:{} - {}]').format(j, s, d)) else: active_labels.append( clr('[{}:{} ({}%) - {}]').format( j, s, p, d)) status_line += ' ' + ' '.join(active_labels) # Print the status line # wide_log(status_line) wide_log(status_line, rhs='', end='\r') sys.stdout.flush() # Store this update time last_update_time = time.time() else: time.sleep( max(0.0, min(update_duration - elapsed_time, 0.01))) # Only continue when no event was received continue else: # Try to get an event from the queue (blocking) try: event = self.event_queue.get(True) except Empty: break # A `None` event is a signal to terminate if event is None: break # Handle the received events eid = event.event_id if 'JOB_STATUS' == eid: queued_jobs = event.data['queued'] active_jobs = event.data['active'] completed_jobs = event.data['completed'] # Check if all jobs have finished in some way if all([ len(event.data[t]) == 0 for t in ['pending', 'queued', 'active'] ]): break elif 'STARTED_JOB' == eid: cumulative_times[event.data['job_id']] = 0.0 wide_log( clr('Starting >>> {:<{}}').format(event.data['job_id'], self.max_jid_length)) elif 'FINISHED_JOB' == eid: duration = format_time_delta( cumulative_times[event.data['job_id']]) if event.data['succeeded']: wide_log( clr('Finished <<< {:<{}} [ {} ]').format( event.data['job_id'], self.max_jid_length, duration)) else: failed_jobs.append(event.data['job_id']) wide_log( clr('Failed <<< {:<{}} [ {} ]').format( event.data['job_id'], self.max_jid_length, duration)) elif 'ABANDONED_JOB' == eid: # Create a human-readable reason string if 'DEP_FAILED' == event.data['reason']: direct = event.data['dep_job_id'] == event.data[ 'direct_dep_job_id'] if direct: reason = clr('Depends on failed job {}').format( event.data['dep_job_id']) else: reason = clr('Depends on failed job {} via {}').format( event.data['dep_job_id'], event.data['direct_dep_job_id']) elif 'PEER_FAILED' == event.data['reason']: reason = clr('Unrelated job failed') elif 'MISSING_DEPS' == event.data['reason']: reason = clr('Depends on unknown jobs: {}').format( ', '.join([ clr('@!{}@|').format(jid) for jid in event.data['dep_ids'] ])) wide_log( clr('Abandoned <<< {:<{}} [ {} ]').format( event.data['job_id'], self.max_jid_length, reason)) elif 'STARTED_STAGE' == eid: active_stages[event.data['job_id']] = [ event.data['stage_label'], event.time, '' ] start_times[event.data['job_id']] = event.time if self.show_stage_events: wide_log( clr('Starting >> {}:{}').format( event.data['job_id'], event.data['stage_label'])) elif 'STAGE_PROGRESS' == eid: active_stages[event.data['job_id']][2] = event.data['percent'] elif 'SUBPROCESS' == eid: if self.show_stage_events: wide_log( clr('Subprocess > {}:{} `{}`').format( event.data['job_id'], event.data['stage_label'], event.data['stage_repro'])) elif 'FINISHED_STAGE' == eid: # Get the stage duration duration = event.time - start_times[event.data['job_id']] cumulative_times[event.data['job_id']] += duration # This is no longer the active stage for this job del active_stages[event.data['job_id']] header_border = None header_border_file = sys.stdout header_title = None header_title_file = sys.stdout lines = [] footer_title = None footer_title_file = sys.stdout footer_border = None footer_border_file = sys.stdout # Generate headers / borders for output if event.data['succeeded']: footer_title = clr('Finished << {}:{}').format( event.data['job_id'], event.data['stage_label']) if len(event.data['stderr']) > 0: # Mark that this job warned about something if event.data['job_id'] not in warned_jobs: warned_jobs.append(event.data['job_id']) # Output contains warnings header_border = clr('@!@{yf}' + '_' * (terminal_width() - 1) + '@|') header_border_file = sys.stderr header_title = clr('Warnings << {}:{} {}').format( event.data['job_id'], event.data['stage_label'], event.data['logfile_filename']) header_title_file = sys.stderr footer_border = clr('@{yf}' + '.' * (terminal_width() - 1) + '@|') footer_border_file = sys.stderr else: # Normal output, no warnings header_title = clr('Output << {}:{} {}').format( event.data['job_id'], event.data['stage_label'], event.data['logfile_filename']) # Don't print footer title if not self.show_stage_events: footer_title = None else: # Output contains errors header_border = clr('@!@{rf}' + '_' * (terminal_width() - 1) + '@|') header_border_file = sys.stderr header_title = clr('Errors << {}:{} {}').format( event.data['job_id'], event.data['stage_label'], event.data['logfile_filename']) header_title_file = sys.stderr footer_border = clr('@{rf}' + '.' * (terminal_width() - 1) + '@|') footer_border_file = sys.stderr footer_title = clr( 'Failed << {}:{:<{}} [ Exited with code {} ]').format( event.data['job_id'], event.data['stage_label'], max( 0, self.max_jid_length - len(event.data['job_id'])), event.data['retcode']) footer_title_file = sys.stderr lines_target = sys.stdout if self.show_buffered_stdout: if len(event.data['interleaved']) > 0: lines = [ line for line in event.data['interleaved'].splitlines(True) if (self.show_compact_io is False or len(line.strip()) > 0) ] else: header_border = None header_title = None footer_border = None elif self.show_buffered_stderr: if len(event.data['stderr']) > 0: lines = [ line for line in event.data['stderr'].splitlines(True) if (self.show_compact_io is False or len(line.strip()) > 0) ] lines_target = sys.stderr else: header_border = None header_title = None footer_border = None if len(lines) > 0: if self.show_repro_cmd: if event.data['repro'] is not None: lines.append( clr('@!@{kf}{}@|\n').format( event.data['repro'])) # Print the output if header_border: wide_log(header_border, file=header_border_file) if header_title: wide_log(header_title, file=header_title_file) if len(lines) > 0: wide_log(''.join(lines), end='\r', file=lines_target) if footer_border: wide_log(footer_border, file=footer_border_file) if footer_title: wide_log(footer_title, file=footer_title_file) elif 'STDERR' == eid: if self.show_live_stderr and len(event.data['data']) > 0: wide_log(self.format_interleaved_lines(event.data), end='\r', file=sys.stderr) elif 'STDOUT' == eid: if self.show_live_stdout and len(event.data['data']) > 0: wide_log(self.format_interleaved_lines(event.data), end='\r') elif 'MESSAGE' == eid: wide_log(event.data['msg']) # Print the full summary if self.show_full_summary: self.print_exec_summary(completed_jobs, warned_jobs, failed_jobs) # Print a compact summary if self.show_summary or self.show_full_summary: self.print_compact_summary(completed_jobs, warned_jobs, failed_jobs) # Print final runtime wide_log( clr('[{}] Runtime: {} total.').format( self.label, format_time_delta(time.time() - start_time)))
def print_exec_summary(self, completed_jobs, warned_jobs, failed_jobs): """ Print verbose execution summary. """ # Calculate the longest jid max_jid_len = max([len(jid) for jid in self.available_jobs]) templates = { 'successful': clr(" [@!@{gf}Successful@|] @{cf}{jid:<%d}@|" % max_jid_len), 'warned': clr(" [ @!@{yf}Warned@|] @{cf}{jid:<%d}@|" % max_jid_len), 'failed': clr(" [ @!@{rf}Failed@|] @{cf}{jid:<%d}@|" % max_jid_len), 'ignored': clr(" [ @!@{kf}Ignored@|] @{cf}{jid:<%d}@|" % max_jid_len), 'abandoned': clr(" [ @!@{rf}Abandoned@|] @{cf}{jid:<%d}@|" % max_jid_len), } # Calculate the maximum _printed_ length for each template max_column_len = max([ len(remove_ansi_escape(t.format(jid=("?" * max_jid_len)))) for t in templates.values() ]) # Calculate the number of columns number_of_columns = int((terminal_width() / max_column_len) or 1) # Construct different categories of jobs (jid -> output template) successfuls = {} warneds = {} faileds = {} ignoreds = {} abandoneds = {} non_whitelisted = {} blacklisted = {} # Give each package an output template to use for jid in self.available_jobs: if jid in self.blacklisted_jobs: blacklisted[jid] = templates['ignored'] elif jid not in self.jobs: ignoreds[jid] = templates['ignored'] elif len(self.whitelisted_jobs ) > 0 and jid not in self.whitelisted_jobs: non_whitelisted[jid] = templates['ignored'] elif jid in completed_jobs: if jid in failed_jobs: faileds[jid] = templates['failed'] elif jid in warned_jobs: warneds[jid] = templates['warned'] else: successfuls[jid] = templates['successful'] else: abandoneds[jid] = templates['abandoned'] # Combine successfuls and ignoreds, sort by key if len(successfuls) + len(ignoreds) > 0: wide_log("") wide_log( clr("[{}] Successful {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns( sorted(itertools.chain(successfuls.items(), ignoreds.items())), number_of_columns) else: wide_log("") wide_log( clr("[{}] No {} succeeded.").format(self.label, self.jobs_label)) wide_log("") # Print out whitelisted jobs if len(non_whitelisted) > 0: wide_log("") wide_log( clr("[{}] Non-whitelisted {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(non_whitelisted.items()), number_of_columns) # Print out blacklisted jobs if len(blacklisted) > 0: wide_log("") wide_log( clr("[{}] Blacklisted {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(blacklisted.items()), number_of_columns) # Print out jobs that failed if len(faileds) > 0: wide_log("") wide_log( clr("[{}] Failed {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(faileds.items()), number_of_columns) # Print out jobs that were abandoned if len(abandoneds) > 0: wide_log("") wide_log( clr("[{}] Abandoned {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(abandoneds.items()), number_of_columns) wide_log("")
def run(self): queued_jobs = [] active_jobs = [] completed_jobs = {} failed_jobs = [] warned_jobs = [] cumulative_times = dict() start_times = dict() active_stages = dict() start_time = self.pre_start_time or time.time() last_update_time = time.time() # If the status rate is too low, just disable it if self.active_status_rate < 1E-3: self.show_active_status = False else: update_duration = 1.0 / self.active_status_rate # Disable the wide log padding if the status is disabled if not self.show_active_status: disable_wide_log() while True: # Check if we should stop if not self.keep_running: wide_log(clr('[{}] An internal error occurred!').format(self.label)) return # Write a continuously-updated status line if self.show_active_status: # Try to get an event from the queue (non-blocking) try: event = self.event_queue.get(False) except Empty: # Determine if the status should be shown based on the desired # status rate elapsed_time = time.time() - last_update_time show_status_now = elapsed_time > update_duration if show_status_now: # Print live status (overwrites last line) status_line = clr('[{} {} s] [{}/{} complete] [{}/{} jobs] [{} queued]').format( self.label, format_time_delta_short(time.time() - start_time), len(completed_jobs), len(self.jobs), job_server.running_jobs(), job_server.max_jobs(), len(queued_jobs) + len(active_jobs) - len(active_stages) ) # Show failed jobs if len(failed_jobs) > 0: status_line += clr(' [@!@{rf}{}@| @{rf}failed@|]').format(len(failed_jobs)) # Check load / mem if not job_server.load_ok(): status_line += clr(' [@!@{rf}High Load@|]') if not job_server.mem_ok(): status_line += clr(' [@!@{rf}Low Memory@|]') # Add active jobs if len(active_jobs) == 0: status_line += clr(' @/@!@{kf}Waiting for jobs...@|') else: active_labels = [] for j, (s, t, p) in active_stages.items(): d = format_time_delta_short(cumulative_times[j] + time.time() - t) if p == '': active_labels.append(clr('[{}:{} - {}]').format(j, s, d)) else: active_labels.append(clr('[{}:{} ({}%) - {}]').format(j, s, p, d)) status_line += ' ' + ' '.join(active_labels) # Print the status line # wide_log(status_line) wide_log(status_line, rhs='', end='\r') sys.stdout.flush() # Store this update time last_update_time = time.time() else: time.sleep(max(0.0, min(update_duration - elapsed_time, 0.01))) # Only continue when no event was received continue else: # Try to get an event from the queue (blocking) try: event = self.event_queue.get(True) except Empty: break # A `None` event is a signal to terminate if event is None: break # Handle the received events eid = event.event_id if 'JOB_STATUS' == eid: queued_jobs = event.data['queued'] active_jobs = event.data['active'] completed_jobs = event.data['completed'] # Check if all jobs have finished in some way if all([len(event.data[t]) == 0 for t in ['pending', 'queued', 'active']]): break elif 'STARTED_JOB' == eid: cumulative_times[event.data['job_id']] = 0.0 wide_log(clr('Starting >>> {:<{}}').format( event.data['job_id'], self.max_jid_length)) elif 'FINISHED_JOB' == eid: duration = format_time_delta(cumulative_times[event.data['job_id']]) if event.data['succeeded']: wide_log(clr('Finished <<< {:<{}} [ {} ]').format( event.data['job_id'], self.max_jid_length, duration)) else: failed_jobs.append(event.data['job_id']) wide_log(clr('Failed <<< {:<{}} [ {} ]').format( event.data['job_id'], self.max_jid_length, duration)) elif 'ABANDONED_JOB' == eid: # Create a human-readable reason string if 'DEP_FAILED' == event.data['reason']: direct = event.data['dep_job_id'] == event.data['direct_dep_job_id'] if direct: reason = clr('Depends on failed job {}').format(event.data['dep_job_id']) else: reason = clr('Depends on failed job {} via {}').format( event.data['dep_job_id'], event.data['direct_dep_job_id']) elif 'PEER_FAILED' == event.data['reason']: reason = clr('Unrelated job failed') elif 'MISSING_DEPS' == event.data['reason']: reason = clr('Depends on unknown jobs: {}').format( ', '.join([clr('@!{}@|').format(jid) for jid in event.data['dep_ids']])) wide_log(clr('Abandoned <<< {:<{}} [ {} ]').format( event.data['job_id'], self.max_jid_length, reason)) elif 'STARTED_STAGE' == eid: active_stages[event.data['job_id']] = [event.data['stage_label'], event.time, ''] start_times[event.data['job_id']] = event.time if self.show_stage_events: wide_log(clr('Starting >> {}:{}').format( event.data['job_id'], event.data['stage_label'])) elif 'STAGE_PROGRESS' == eid: active_stages[event.data['job_id']][2] = event.data['percent'] elif 'SUBPROCESS' == eid: if self.show_stage_events: wide_log(clr('Subprocess > {}:{} `{}`').format( event.data['job_id'], event.data['stage_label'], event.data['stage_repro'])) elif 'FINISHED_STAGE' == eid: # Get the stage duration duration = event.time - start_times[event.data['job_id']] cumulative_times[event.data['job_id']] += duration # This is no longer the active stage for this job del active_stages[event.data['job_id']] header_border = None header_border_file = sys.stdout header_title = None header_title_file = sys.stdout lines = [] footer_title = None footer_title_file = sys.stdout footer_border = None footer_border_file = sys.stdout # Generate headers / borders for output if event.data['succeeded']: footer_title = clr( 'Finished << {}:{}').format( event.data['job_id'], event.data['stage_label']) if len(event.data['stderr']) > 0: # Mark that this job warned about something if event.data['job_id'] not in warned_jobs: warned_jobs.append(event.data['job_id']) # Output contains warnings header_border = clr('@!@{yf}' + '_' * (terminal_width() - 1) + '@|') header_border_file = sys.stderr header_title = clr( 'Warnings << {}:{} {}').format( event.data['job_id'], event.data['stage_label'], event.data['logfile_filename']) header_title_file = sys.stderr footer_border = clr('@{yf}' + '.' * (terminal_width() - 1) + '@|') footer_border_file = sys.stderr else: # Normal output, no warnings header_title = clr( 'Output << {}:{} {}').format( event.data['job_id'], event.data['stage_label'], event.data['logfile_filename']) # Don't print footer title if not self.show_stage_events: footer_title = None else: # Output contains errors header_border = clr('@!@{rf}' + '_' * (terminal_width() - 1) + '@|') header_border_file = sys.stderr header_title = clr( 'Errors << {}:{} {}').format( event.data['job_id'], event.data['stage_label'], event.data['logfile_filename']) header_title_file = sys.stderr footer_border = clr('@{rf}' + '.' * (terminal_width() - 1) + '@|') footer_border_file = sys.stderr footer_title = clr( 'Failed << {}:{:<{}} [ Exited with code {} ]').format( event.data['job_id'], event.data['stage_label'], max(0, self.max_jid_length - len(event.data['job_id'])), event.data['retcode']) footer_title_file = sys.stderr lines_target = sys.stdout if self.show_buffered_stdout: if len(event.data['interleaved']) > 0: lines = [ l for l in event.data['interleaved'].splitlines(True) if (self.show_compact_io is False or len(l.strip()) > 0) ] else: header_border = None header_title = None footer_border = None elif self.show_buffered_stderr: if len(event.data['stderr']) > 0: lines = [ l for l in event.data['stderr'].splitlines(True) if (self.show_compact_io is False or len(l.strip()) > 0) ] lines_target = sys.stderr else: header_border = None header_title = None footer_border = None if len(lines) > 0: if self.show_repro_cmd: if event.data['repro'] is not None: lines.append(clr('@!@{kf}{}@|\n').format(event.data['repro'])) # Print the output if header_border: wide_log(header_border, file=header_border_file) if header_title: wide_log(header_title, file=header_title_file) if len(lines) > 0: wide_log(''.join(lines), end='\r', file=lines_target) if footer_border: wide_log(footer_border, file=footer_border_file) if footer_title: wide_log(footer_title, file=footer_title_file) elif 'STDERR' == eid: if self.show_live_stderr and len(event.data['data']) > 0: wide_log(self.format_interleaved_lines(event.data), end='\r', file=sys.stderr) elif 'STDOUT' == eid: if self.show_live_stdout and len(event.data['data']) > 0: wide_log(self.format_interleaved_lines(event.data), end='\r') elif 'MESSAGE' == eid: wide_log(event.data['msg']) # Print the full summary if self.show_full_summary: self.print_exec_summary(completed_jobs, warned_jobs, failed_jobs) # Print a compact summary if self.show_summary or self.show_full_summary: self.print_compact_summary(completed_jobs, warned_jobs, failed_jobs) # Print final runtime wide_log(clr('[{}] Runtime: {} total.').format( self.label, format_time_delta(time.time() - start_time)))
def print_exec_summary(self, completed_jobs, warned_jobs, failed_jobs): """ Print verbose execution summary. """ # Calculate the longest jid max_jid_len = max([len(jid) for jid in self.available_jobs]) templates = { 'successful': clr(" [@!@{gf}Successful@|] @{cf}{jid:<%d}@|" % max_jid_len), 'warned': clr(" [ @!@{yf}Warned@|] @{cf}{jid:<%d}@|" % max_jid_len), 'failed': clr(" [ @!@{rf}Failed@|] @{cf}{jid:<%d}@|" % max_jid_len), 'ignored': clr(" [ @!@{kf}Ignored@|] @{cf}{jid:<%d}@|" % max_jid_len), 'abandoned': clr(" [ @!@{rf}Abandoned@|] @{cf}{jid:<%d}@|" % max_jid_len), } # Calculate the maximum _printed_ length for each template max_column_len = max([ len(remove_ansi_escape(t.format(jid=("?" * max_jid_len)))) for t in templates.values() ]) # Calculate the number of columns number_of_columns = (terminal_width() / max_column_len) or 1 # Construct different categories of jobs (jid -> output template) successfuls = {} warneds = {} faileds = {} ignoreds = {} abandoneds = {} non_whitelisted = {} blacklisted = {} # Give each package an output template to use for jid in self.available_jobs: if jid in self.blacklisted_jobs: blacklisted[jid] = templates['ignored'] elif jid not in self.jobs: ignoreds[jid] = templates['ignored'] elif len(self.whitelisted_jobs) > 0 and jid not in self.whitelisted_jobs: non_whitelisted[jid] = templates['ignored'] elif jid in completed_jobs: if jid in failed_jobs: faileds[jid] = templates['failed'] elif jid in warned_jobs: warneds[jid] = templates['warned'] else: successfuls[jid] = templates['successful'] else: abandoneds[jid] = templates['abandoned'] # Combine successfuls and ignoreds, sort by key if len(successfuls) + len(ignoreds) > 0: wide_log("") wide_log(clr("[{}] Successful {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns( sorted(successfuls.items() + ignoreds.items()), number_of_columns) else: wide_log("") wide_log(clr("[{}] No {} succeeded.").format(self.label, self.jobs_label)) wide_log("") # Print out whitelisted jobs if len(non_whitelisted) > 0: wide_log("") wide_log(clr("[{}] Non-whitelisted {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(non_whitelisted.items()), number_of_columns) # Print out blacklisted jobs if len(blacklisted) > 0: wide_log("") wide_log(clr("[{}] Blacklisted {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(blacklisted.items()), number_of_columns) # Print out jobs that failed if len(faileds) > 0: wide_log("") wide_log(clr("[{}] Failed {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(faileds.items()), number_of_columns) # Print out jobs that were abandoned if len(abandoneds) > 0: wide_log("") wide_log(clr("[{}] Abandoned {}:").format(self.label, self.jobs_label)) wide_log("") print_items_in_columns(sorted(abandoneds.items()), number_of_columns) wide_log("")
def print_build_summary(context, packages_to_be_built, completed_packages, failed_packages): # Calculate the longest package name max_name_len = max([len(pkg.name) for _, pkg in context.packages]) def get_template(template_name, column_width): templates = { 'successful': " @!@{gf}Successful@| @{cf}{package:<" + str(column_width) + "}@|", 'failed': " @!@{rf}Failed@| @{cf}{package:<" + str(column_width) + "}@|", 'not_built': " @!@{kf}Not built@| @{cf}{package:<" + str(column_width) + "}@|", } return templates[template_name] # Setup templates for comparison successful_template = get_template('successful', max_name_len) failed_template = get_template('failed', max_name_len) not_built_template = get_template('not_built', max_name_len) # Calculate the maximum _printed_ length for each template faux_package_name = ("x" * max_name_len) templates = [ remove_ansi_escape(clr(successful_template).format(package=faux_package_name)), remove_ansi_escape(clr(failed_template).format(package=faux_package_name)), remove_ansi_escape(clr(not_built_template).format(package=faux_package_name)), ] # Calculate the longest column using the longest template max_column_len = max([len(template) for template in templates]) # Calculate the number of columns number_of_columns = (terminal_width() / max_column_len) or 1 successfuls = {} faileds = {} not_builts = {} non_whitelisted = {} blacklisted = {} for (_, pkg) in context.packages: if pkg.name in context.blacklist: blacklisted[pkg.name] = clr(not_built_template).format(package=pkg.name) elif len(context.whitelist) > 0 and pkg.name not in context.whitelist: non_whitelisted[pkg.name] = clr(not_built_template).format(package=pkg.name) elif pkg.name in completed_packages: successfuls[pkg.name] = clr(successful_template).format(package=pkg.name) else: if pkg.name in failed_packages: faileds[pkg.name] = clr(failed_template).format(package=pkg.name) else: not_builts[pkg.name] = clr(not_built_template).format(package=pkg.name) # Combine successfuls and not_builts, sort by key, only take values wide_log("") wide_log("Build summary:") combined = dict(successfuls) combined.update(not_builts) non_failed = [v for k, v in sorted(combined.items(), key=operator.itemgetter(0))] print_items_in_columns(non_failed, number_of_columns) # Print out whitelisted packages if len(non_whitelisted) > 0: wide_log("") wide_log("Non-Whitelisted Packages:") non_whitelisted_list = [v for k, v in sorted(non_whitelisted.items(), key=operator.itemgetter(0))] print_items_in_columns(non_whitelisted_list, number_of_columns) # Print out blacklisted packages if len(blacklisted) > 0: wide_log("") wide_log("Blacklisted Packages:") blacklisted_list = [v for k, v in sorted(blacklisted.items(), key=operator.itemgetter(0))] print_items_in_columns(blacklisted_list, number_of_columns) # Faileds only, sort by key, only take values failed = [v for k, v in sorted(faileds.items(), key=operator.itemgetter(0))] if len(failed) > 0: wide_log("") wide_log("Failed packages:") print_items_in_columns(failed, number_of_columns) else: wide_log("") wide_log("All packages built successfully.") wide_log("") wide_log(clr("[build] @!@{gf}Successfully@| built '@!@{cf}{0}@|' packages, " "@!@{rf}failed@| to build '@!@{cf}{1}@|' packages, " "and @!@{kf}did not try to build@| '@!@{cf}{2}@|' packages.").format( len(successfuls), len(faileds), len(not_builts) ))