CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

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