Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/ardupilotwaf/build_summary.py
9751 views
1
# encoding: utf-8
2
3
# Copyright (C) 2016 Intel Corporation. All rights reserved.
4
#
5
# This file is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by the
7
# Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This file is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
# See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along
16
# with this program. If not, see <http://www.gnu.org/licenses/>.
17
18
# flake8: noqa
19
20
'''
21
Waf tool for printing build summary. To be used, this must be loaded in the
22
options(), configure() and build() functions.
23
24
This tool expects toolchain tool to be already loaded.
25
26
The environment variable BUILD_SUMMARY_HEADER can be used to change the default
27
header for the targets' summary table.
28
29
Extra information can be printed by creating assigning a function to
30
bld.extra_build_summary. That function must receive bld as the first argument
31
and this module as the second one.
32
33
If one target's task generator (tg) doesn't have a link_task or places the ELF
34
file at a place different from link_task.outputs[0], then
35
tg.build_summary['binary'] should be set as the Node object or a path relative
36
to bld.bldnode for the binary file. Otherwise, size information won't be
37
printed for that target.
38
'''
39
import sys
40
41
from waflib import Context, Logs, Node
42
from waflib.Configure import conf
43
from waflib.TaskGen import before_method, feature
44
45
MAX_TARGETS = 20
46
47
header_text = {
48
'target': 'Target',
49
'binary_path': 'Binary',
50
'size_text': 'Text (B)',
51
'size_data': 'Data (B)',
52
'size_bss': 'BSS (B)',
53
'size_total': 'Total Flash Used (B)',
54
'size_free_flash': 'Free Flash (B)',
55
'ext_flash_used': 'External Flash Used (B)',
56
}
57
58
def text(label, text=''):
59
text = text.strip()
60
if text:
61
Logs.info('%s%s%s%s%s' % (
62
Logs.colors.NORMAL,
63
Logs.colors.BOLD,
64
label,
65
Logs.colors.NORMAL,
66
text))
67
else:
68
Logs.info('%s%s%s' % (
69
Logs.colors.NORMAL,
70
Logs.colors.BOLD,
71
label
72
))
73
74
def print_table(summary_data_list, header):
75
max_widths = []
76
table = [[] for _ in range(len(summary_data_list))]
77
78
header_row = []
79
for h in header:
80
txt = header_text.get(h, h)
81
header_row.append(txt)
82
max_width = len(txt)
83
for i, row_data in enumerate(summary_data_list):
84
data = row_data.get(h, '-')
85
86
# Output if a piece of reporting data is not applicable, example: free_flash in SITL
87
if data is None:
88
data = "Not Applicable"
89
90
txt = str(data)
91
table[i].append(txt)
92
93
w = len(txt)
94
if w > max_width:
95
max_width = w
96
max_widths.append(max_width)
97
98
sep = ' '
99
fmts = ['{:<%d}' % w for w in max_widths]
100
header_row = sep.join(fmts).format(*header_row)
101
text(header_row)
102
103
line = ('-' * len(sep)).join('-' * w for w in max_widths)
104
print(line)
105
106
for row in table:
107
fmts = []
108
for j, v in enumerate(row):
109
w = max_widths[j]
110
try:
111
float(v)
112
except ValueError:
113
fmts.append('{:<%d}' % w)
114
else:
115
fmts.append('{:>%d}' % w)
116
row = sep.join(fmts).format(*row)
117
print(row)
118
119
def _build_summary(bld):
120
Logs.info('')
121
text('BUILD SUMMARY')
122
text('Build directory: ', bld.bldnode.abspath())
123
124
targets_suppressed = False
125
if bld.targets == '*':
126
taskgens = bld.get_all_task_gen()
127
if len(taskgens) > MAX_TARGETS and not bld.options.summary_all:
128
targets_suppressed = True
129
taskgens = taskgens[:MAX_TARGETS]
130
else:
131
targets = bld.targets.split(',')
132
if len(targets) > MAX_TARGETS and not bld.options.summary_all:
133
targets_suppressed = True
134
targets = targets[:MAX_TARGETS]
135
taskgens = [bld.get_tgen_by_name(t) for t in targets]
136
137
nodes = []
138
filtered_taskgens = []
139
for tg in taskgens:
140
if not hasattr(tg, 'build_summary'):
141
tg.init_summary_data()
142
143
n = tg.build_summary.get('binary', None)
144
if not n:
145
t = getattr(tg, 'link_task', None)
146
if not t:
147
continue
148
n = t.outputs[0]
149
tg.build_summary['binary'] = str(n)
150
151
nodes.append(n)
152
filtered_taskgens.append(tg)
153
taskgens = filtered_taskgens
154
155
if nodes:
156
l = bld.size_summary(nodes)
157
for i, data in enumerate(l):
158
taskgens[i].build_summary.update(data)
159
160
summary_data_list = [tg.build_summary for tg in taskgens]
161
print_table(summary_data_list, bld.env.BUILD_SUMMARY_HEADER)
162
163
if targets_suppressed:
164
Logs.info('')
165
Logs.pprint(
166
'NORMAL',
167
'\033[0;31;1mNote: Some targets were suppressed. Use --summary-all if you want information of all targets.',
168
)
169
170
if hasattr(bld, 'extra_build_summary'):
171
bld.extra_build_summary(bld, sys.modules[__name__])
172
173
# totals=True means relying on -t flag to give us a "(TOTALS)" output
174
def _parse_size_output(s, s_all, totals=False):
175
176
# Get the size of .crash_log and .heap to remove them from .bss reporting as
177
# binutils size includes in bss the size of any section that has the ALLOC
178
# flag but neither CODE nor HAS_CONTENTS (as reported by objdump -h). also
179
# get external flash size if applicable.
180
crash_log_size = None
181
heap_size = 0
182
ext_flash_used = 0
183
if s_all is not None:
184
lines = s_all.splitlines()[1:]
185
for line in lines:
186
if ".crash_log" in line:
187
row = line.strip().split()
188
crash_log_size = int(row[1])
189
if ".heap" in line:
190
row = line.strip().split()
191
heap_size = int(row[1])
192
if ".extflash" in line:
193
row = line.strip().split()
194
if int(row[1]) > 0:
195
ext_flash_used = int(row[1])
196
197
import re
198
pattern = re.compile("^.*TOTALS.*$")
199
lines = s.splitlines()[1:]
200
l = []
201
for line in lines:
202
if pattern.match(line) or totals is False:
203
row = line.strip().split()
204
205
# check if crash_log wasn't found
206
# this will be the case for none arm boards: sitl, linux, etc.
207
if crash_log_size is None:
208
size_bss = int(row[2])
209
size_free_flash = None
210
else:
211
# BSS: remove the portion occupied by crash_log as the command `size binary.elf`
212
# reports BSS with crash_log included
213
size_bss = int(row[2]) - crash_log_size
214
size_free_flash = crash_log_size
215
size_bss -= heap_size # remove also-included default heap section size from bss
216
217
l.append(dict(
218
size_text=int(row[0]),
219
size_data=int(row[1]),
220
size_bss=size_bss,
221
# Total Flash Cost = Data + Text
222
size_total=int(row[0]) + int(row[1]) - ext_flash_used,
223
size_free_flash=size_free_flash,
224
ext_flash_used= ext_flash_used if ext_flash_used else None,
225
))
226
return l
227
228
@conf
229
def size_summary(bld, nodes):
230
l = []
231
for n in nodes:
232
path = n
233
if isinstance(n, Node.Node):
234
path = n.path_from(bld.bldnode)
235
l.append(dict(binary_path=path))
236
237
for d in l:
238
if bld.env.SIZE:
239
if bld.env.get_flat('SIZE').endswith("xtensa-esp32-elf-size"):
240
cmd = [bld.env.get_flat('SIZE')] + ["-t"] + [d['binary_path']]
241
else:
242
cmd = [bld.env.get_flat('SIZE')] + [d['binary_path']]
243
244
if bld.env.get_flat('SIZE').endswith("arm-none-eabi-size"):
245
cmd2 = [bld.env.get_flat('SIZE')] + ["-A"] + [d['binary_path']]
246
out2 = bld.cmd_and_log(cmd2,
247
cwd=bld.bldnode.abspath(),
248
quiet=Context.BOTH,
249
)
250
else:
251
out2 = None
252
253
out = bld.cmd_and_log(
254
cmd,
255
cwd=bld.bldnode.abspath(),
256
quiet=Context.BOTH,
257
)
258
if bld.env.get_flat('SIZE').endswith("xtensa-esp32-elf-size"):
259
parsed = _parse_size_output(out, out2, True)
260
else:
261
parsed = _parse_size_output(out, out2, False)
262
for i, data in enumerate(parsed):
263
try:
264
d.update(data)
265
except:
266
print("build summary debug: "+str(i)+"->"+str(data))
267
268
return l
269
270
@conf
271
def build_summary_post_fun(bld):
272
if not bld.env.AP_PROGRAM_AS_STLIB:
273
bld.add_post_fun(_build_summary)
274
275
@feature('cprogram', 'cxxprogram')
276
@before_method('process_rule')
277
def init_summary_data(self):
278
self.build_summary = dict(target=self.name)
279
280
def options(opt):
281
g = opt.ap_groups['build']
282
283
g.add_option('--summary-all',
284
action='store_true',
285
help='''Print build summary for all targets. By default, only
286
information about the first %d targets will be printed.
287
''' % MAX_TARGETS)
288
289
def configure(cfg):
290
size_name = 'size'
291
292
if cfg.env.TOOLCHAIN != 'native':
293
size_name = cfg.env.TOOLCHAIN + '-' + size_name
294
295
cfg.find_program(size_name, var='SIZE', mandatory=False)
296
297
if not cfg.env.BUILD_SUMMARY_HEADER:
298
cfg.env.BUILD_SUMMARY_HEADER = [
299
'target',
300
'size_text',
301
'size_data',
302
'size_bss',
303
'size_total',
304
'size_free_flash',
305
'ext_flash_used',
306
]
307
308