diff --git a/Tools/ardupilotwaf/build_summary.py b/Tools/ardupilotwaf/build_summary.py new file mode 100644 index 0000000000..b5d9f7932e --- /dev/null +++ b/Tools/ardupilotwaf/build_summary.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# Copyright (C) 2016 Intel Corporation. All rights reserved. +# +# This file is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +''' +Waf tool for printing build summary. To be used, this must be loaded in the +options(), configure() and build() functions. + +This tool expects toolchain tool to be already loaded. + +The environment variable BUILD_SUMMARY_HEADER can be used to change the default +header for the targets' summary table. + +Extra information can be printed by creating assigning a function to +bld.extra_build_summary. That function must receive bld as the first argument +and this module as the second one. + +If one target's task generator (tg) doesn't have a link_task or places the ELF +file at a place different from link_task.outputs[0], then +tg.build_summary['binary'] should be set as the Node object or a path relative +to bld.bldnode for the binary file. Otherwise, size information won't be +printed for that target. +''' +import sys + +from waflib import Context, Logs, Node +from waflib.Configure import conf +from waflib.TaskGen import before_method, feature + +MAX_TARGETS = 20 + +header_text = { + 'target': 'Target', + 'binary_path': 'Binary', + 'size_text': 'Text', + 'size_data': 'Data', + 'size_bss': 'BSS', + 'size_total': 'Total', +} + +def text(label, text=''): + text = text.strip() + if text: + Logs.info('%s%s%s%s%s' % ( + Logs.colors.NORMAL, + Logs.colors.BOLD, + label, + Logs.colors.NORMAL, + text)) + else: + Logs.info('%s%s%s' % ( + Logs.colors.NORMAL, + Logs.colors.BOLD, + label + )) + +def print_table(summary_data_list, header): + max_widths = [] + table = [[] for _ in range(len(summary_data_list))] + + header_row = [] + for h in header: + txt = header_text.get(h, h) + header_row.append(txt) + max_width = len(txt) + for i, row_data in enumerate(summary_data_list): + txt = str(row_data.get(h, '-')) + table[i].append(txt) + + w = len(txt) + if w > max_width: + max_width = w + max_widths.append(max_width) + + sep = ' ' + fmts = ['{:<%d}' % w for w in max_widths] + header_row = sep.join(fmts).format(*header_row) + text(header_row) + + line = ('-' * len(sep)).join('-' * w for w in max_widths) + print(line) + + for row in table: + fmts = [] + for j, v in enumerate(row): + w = max_widths[j] + try: + float(v) + except ValueError: + fmts.append('{:<%d}' % w) + else: + fmts.append('{:>%d}' % w) + row = sep.join(fmts).format(*row) + print(row) + +def _build_summary(bld): + Logs.info('') + text('BUILD SUMMARY') + text('Build directory: ', bld.bldnode.abspath()) + + targets_suppressed = False + if bld.targets == '*': + taskgens = bld.get_all_task_gen() + if len(taskgens) > MAX_TARGETS and not bld.options.summary_all: + targets_suppressed = True + taskgens = taskgens[:MAX_TARGETS] + else: + targets = bld.targets.split(',') + if len(targets) > MAX_TARGETS and not bld.options.summary_all: + targets_suppressed = True + targets = targets[:MAX_TARGETS] + taskgens = [bld.get_tgen_by_name(t) for t in targets] + + nodes = [] + filtered_taskgens = [] + for tg in taskgens: + if not hasattr(tg, 'build_summary'): + tg.init_summary_data() + + n = tg.build_summary.get('binary', None) + if not n: + t = getattr(tg, 'link_task', None) + if not t: + continue + n = t.outputs[0] + tg.build_summary['binary'] = n + + nodes.append(n) + filtered_taskgens.append(tg) + taskgens = filtered_taskgens + + l = bld.size_summary(nodes) + for i, data in enumerate(l): + taskgens[i].build_summary.update(data) + + summary_data_list = [tg.build_summary for tg in taskgens] + print_table(summary_data_list, bld.env.BUILD_SUMMARY_HEADER) + + if targets_suppressed: + Logs.info('') + Logs.pprint( + 'NORMAL', + 'Note: Some targets were suppressed. Use --summary-all if you ' + + 'want information of all targets.', + ) + + if hasattr(bld, 'extra_build_summary'): + bld.extra_build_summary(bld, sys.modules[__name__]) + +def _parse_size_output(s): + lines = s.splitlines()[1:] + l = [] + for line in lines: + row = line.strip().split() + l.append(dict( + size_text=int(row[0]), + size_data=int(row[1]), + size_bss=int(row[2]), + size_total=int(row[3]), + )) + return l + +@conf +def size_summary(bld, nodes): + l = [] + for n in nodes: + path = n + if isinstance(n, Node.Node): + path = n.path_from(bld.bldnode) + l.append(dict(binary_path=path)) + + if bld.env.SIZE: + cmd = [bld.env.get_flat('SIZE')] + [d['binary_path'] for d in l] + out = bld.cmd_and_log( + cmd, + cwd=bld.bldnode.abspath(), + quiet=Context.BOTH, + ) + parsed = _parse_size_output(out) + for i, data in enumerate(parsed): + l[i].update(data) + + return l + +@feature('cprogram', 'cxxprogram') +@before_method('process_rule') +def init_summary_data(self): + self.build_summary = dict(target=self.name) + +def options(opt): + g = opt.ap_groups['build'] + + g.add_option('--summary-all', + action='store_true', + help=''' +Print build summary for all targets. By default, only information about the +first %d targets will be printed. +''' % MAX_TARGETS) + +def configure(cfg): + cfg.find_toolchain_program('size', mandatory=False) + + if not cfg.env.BUILD_SUMMARY_HEADER: + cfg.env.BUILD_SUMMARY_HEADER = [ + 'target', + 'size_text', + 'size_data', + 'size_bss', + 'size_total', + ] + +def build(bld): + bld.add_post_fun(_build_summary)