Path: blob/master/Tools/ardupilotwaf/build_summary.py
9751 views
# encoding: utf-812# Copyright (C) 2016 Intel Corporation. All rights reserved.3#4# This file is free software: you can redistribute it and/or modify it5# under the terms of the GNU General Public License as published by the6# Free Software Foundation, either version 3 of the License, or7# (at your option) any later version.8#9# This file is distributed in the hope that it will be useful, but10# WITHOUT ANY WARRANTY; without even the implied warranty of11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.12# See the GNU General Public License for more details.13#14# You should have received a copy of the GNU General Public License along15# with this program. If not, see <http://www.gnu.org/licenses/>.1617# flake8: noqa1819'''20Waf tool for printing build summary. To be used, this must be loaded in the21options(), configure() and build() functions.2223This tool expects toolchain tool to be already loaded.2425The environment variable BUILD_SUMMARY_HEADER can be used to change the default26header for the targets' summary table.2728Extra information can be printed by creating assigning a function to29bld.extra_build_summary. That function must receive bld as the first argument30and this module as the second one.3132If one target's task generator (tg) doesn't have a link_task or places the ELF33file at a place different from link_task.outputs[0], then34tg.build_summary['binary'] should be set as the Node object or a path relative35to bld.bldnode for the binary file. Otherwise, size information won't be36printed for that target.37'''38import sys3940from waflib import Context, Logs, Node41from waflib.Configure import conf42from waflib.TaskGen import before_method, feature4344MAX_TARGETS = 204546header_text = {47'target': 'Target',48'binary_path': 'Binary',49'size_text': 'Text (B)',50'size_data': 'Data (B)',51'size_bss': 'BSS (B)',52'size_total': 'Total Flash Used (B)',53'size_free_flash': 'Free Flash (B)',54'ext_flash_used': 'External Flash Used (B)',55}5657def text(label, text=''):58text = text.strip()59if text:60Logs.info('%s%s%s%s%s' % (61Logs.colors.NORMAL,62Logs.colors.BOLD,63label,64Logs.colors.NORMAL,65text))66else:67Logs.info('%s%s%s' % (68Logs.colors.NORMAL,69Logs.colors.BOLD,70label71))7273def print_table(summary_data_list, header):74max_widths = []75table = [[] for _ in range(len(summary_data_list))]7677header_row = []78for h in header:79txt = header_text.get(h, h)80header_row.append(txt)81max_width = len(txt)82for i, row_data in enumerate(summary_data_list):83data = row_data.get(h, '-')8485# Output if a piece of reporting data is not applicable, example: free_flash in SITL86if data is None:87data = "Not Applicable"8889txt = str(data)90table[i].append(txt)9192w = len(txt)93if w > max_width:94max_width = w95max_widths.append(max_width)9697sep = ' '98fmts = ['{:<%d}' % w for w in max_widths]99header_row = sep.join(fmts).format(*header_row)100text(header_row)101102line = ('-' * len(sep)).join('-' * w for w in max_widths)103print(line)104105for row in table:106fmts = []107for j, v in enumerate(row):108w = max_widths[j]109try:110float(v)111except ValueError:112fmts.append('{:<%d}' % w)113else:114fmts.append('{:>%d}' % w)115row = sep.join(fmts).format(*row)116print(row)117118def _build_summary(bld):119Logs.info('')120text('BUILD SUMMARY')121text('Build directory: ', bld.bldnode.abspath())122123targets_suppressed = False124if bld.targets == '*':125taskgens = bld.get_all_task_gen()126if len(taskgens) > MAX_TARGETS and not bld.options.summary_all:127targets_suppressed = True128taskgens = taskgens[:MAX_TARGETS]129else:130targets = bld.targets.split(',')131if len(targets) > MAX_TARGETS and not bld.options.summary_all:132targets_suppressed = True133targets = targets[:MAX_TARGETS]134taskgens = [bld.get_tgen_by_name(t) for t in targets]135136nodes = []137filtered_taskgens = []138for tg in taskgens:139if not hasattr(tg, 'build_summary'):140tg.init_summary_data()141142n = tg.build_summary.get('binary', None)143if not n:144t = getattr(tg, 'link_task', None)145if not t:146continue147n = t.outputs[0]148tg.build_summary['binary'] = str(n)149150nodes.append(n)151filtered_taskgens.append(tg)152taskgens = filtered_taskgens153154if nodes:155l = bld.size_summary(nodes)156for i, data in enumerate(l):157taskgens[i].build_summary.update(data)158159summary_data_list = [tg.build_summary for tg in taskgens]160print_table(summary_data_list, bld.env.BUILD_SUMMARY_HEADER)161162if targets_suppressed:163Logs.info('')164Logs.pprint(165'NORMAL',166'\033[0;31;1mNote: Some targets were suppressed. Use --summary-all if you want information of all targets.',167)168169if hasattr(bld, 'extra_build_summary'):170bld.extra_build_summary(bld, sys.modules[__name__])171172# totals=True means relying on -t flag to give us a "(TOTALS)" output173def _parse_size_output(s, s_all, totals=False):174175# Get the size of .crash_log and .heap to remove them from .bss reporting as176# binutils size includes in bss the size of any section that has the ALLOC177# flag but neither CODE nor HAS_CONTENTS (as reported by objdump -h). also178# get external flash size if applicable.179crash_log_size = None180heap_size = 0181ext_flash_used = 0182if s_all is not None:183lines = s_all.splitlines()[1:]184for line in lines:185if ".crash_log" in line:186row = line.strip().split()187crash_log_size = int(row[1])188if ".heap" in line:189row = line.strip().split()190heap_size = int(row[1])191if ".extflash" in line:192row = line.strip().split()193if int(row[1]) > 0:194ext_flash_used = int(row[1])195196import re197pattern = re.compile("^.*TOTALS.*$")198lines = s.splitlines()[1:]199l = []200for line in lines:201if pattern.match(line) or totals is False:202row = line.strip().split()203204# check if crash_log wasn't found205# this will be the case for none arm boards: sitl, linux, etc.206if crash_log_size is None:207size_bss = int(row[2])208size_free_flash = None209else:210# BSS: remove the portion occupied by crash_log as the command `size binary.elf`211# reports BSS with crash_log included212size_bss = int(row[2]) - crash_log_size213size_free_flash = crash_log_size214size_bss -= heap_size # remove also-included default heap section size from bss215216l.append(dict(217size_text=int(row[0]),218size_data=int(row[1]),219size_bss=size_bss,220# Total Flash Cost = Data + Text221size_total=int(row[0]) + int(row[1]) - ext_flash_used,222size_free_flash=size_free_flash,223ext_flash_used= ext_flash_used if ext_flash_used else None,224))225return l226227@conf228def size_summary(bld, nodes):229l = []230for n in nodes:231path = n232if isinstance(n, Node.Node):233path = n.path_from(bld.bldnode)234l.append(dict(binary_path=path))235236for d in l:237if bld.env.SIZE:238if bld.env.get_flat('SIZE').endswith("xtensa-esp32-elf-size"):239cmd = [bld.env.get_flat('SIZE')] + ["-t"] + [d['binary_path']]240else:241cmd = [bld.env.get_flat('SIZE')] + [d['binary_path']]242243if bld.env.get_flat('SIZE').endswith("arm-none-eabi-size"):244cmd2 = [bld.env.get_flat('SIZE')] + ["-A"] + [d['binary_path']]245out2 = bld.cmd_and_log(cmd2,246cwd=bld.bldnode.abspath(),247quiet=Context.BOTH,248)249else:250out2 = None251252out = bld.cmd_and_log(253cmd,254cwd=bld.bldnode.abspath(),255quiet=Context.BOTH,256)257if bld.env.get_flat('SIZE').endswith("xtensa-esp32-elf-size"):258parsed = _parse_size_output(out, out2, True)259else:260parsed = _parse_size_output(out, out2, False)261for i, data in enumerate(parsed):262try:263d.update(data)264except:265print("build summary debug: "+str(i)+"->"+str(data))266267return l268269@conf270def build_summary_post_fun(bld):271if not bld.env.AP_PROGRAM_AS_STLIB:272bld.add_post_fun(_build_summary)273274@feature('cprogram', 'cxxprogram')275@before_method('process_rule')276def init_summary_data(self):277self.build_summary = dict(target=self.name)278279def options(opt):280g = opt.ap_groups['build']281282g.add_option('--summary-all',283action='store_true',284help='''Print build summary for all targets. By default, only285information about the first %d targets will be printed.286''' % MAX_TARGETS)287288def configure(cfg):289size_name = 'size'290291if cfg.env.TOOLCHAIN != 'native':292size_name = cfg.env.TOOLCHAIN + '-' + size_name293294cfg.find_program(size_name, var='SIZE', mandatory=False)295296if not cfg.env.BUILD_SUMMARY_HEADER:297cfg.env.BUILD_SUMMARY_HEADER = [298'target',299'size_text',300'size_data',301'size_bss',302'size_total',303'size_free_flash',304'ext_flash_used',305]306307308