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/autotest/test_build_options.py
Views: 1798
1
#!/usr/bin/env python3
2
3
"""
4
Contains functions used to test the ArduPilot build_options.py structures
5
6
To extract feature sizes:
7
8
cat >> /tmp/extra-hwdef.dat <<EOF
9
undef AP_BARO_MS56XX_ENABLED
10
define AP_BARO_MS56XX_ENABLED 1
11
EOF
12
13
nice time ./Tools/autotest/test_build_options.py --board=CubeOrange --extra-hwdef=/tmp/extra-hwdef.dat --no-run-with-defaults --no-disable-all --no-enable-in-turn | tee /tmp/tbo-out # noqa
14
grep 'sabling.*saves' /tmp/tbo-out
15
16
- note that a lot of the time explicitly disabling features will make the binary larger as the ROMFS includes the generated hwdef.h which will have the extra define in it # noqa
17
18
AP_FLAKE8_CLEAN
19
"""
20
21
from __future__ import print_function
22
23
import fnmatch
24
import optparse
25
import os
26
import pathlib
27
import re
28
import sys
29
30
from pysim import util
31
32
sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
33
import extract_features # noqa
34
35
36
class TestBuildOptionsResult(object):
37
'''object to return results from a comparison'''
38
39
def __init__(self, feature, vehicle, bytes_delta):
40
self.feature = feature
41
self.vehicle = vehicle
42
self.bytes_delta = bytes_delta
43
44
45
class TestBuildOptions(object):
46
def __init__(self,
47
match_glob=None,
48
do_step_disable_all=True,
49
do_step_disable_none=False,
50
do_step_disable_defaults=True,
51
do_step_disable_in_turn=True,
52
do_step_enable_in_turn=True,
53
build_targets=None,
54
board="CubeOrange", # DevEBoxH7v2 also works
55
extra_hwdef=None,
56
emit_disable_all_defines=None,
57
resume=False,
58
):
59
self.extra_hwdef = extra_hwdef
60
self.sizes_nothing_disabled = None
61
self.match_glob = match_glob
62
self.do_step_disable_all = do_step_disable_all
63
self.do_step_disable_none = do_step_disable_none
64
self.do_step_run_with_defaults = do_step_disable_defaults
65
self.do_step_disable_in_turn = do_step_disable_in_turn
66
self.do_step_enable_in_turn = do_step_enable_in_turn
67
self.build_targets = build_targets
68
if self.build_targets is None:
69
self.build_targets = self.all_targets()
70
self._board = board
71
self.emit_disable_all_defines = emit_disable_all_defines
72
self.resume = resume
73
self.results = {}
74
75
self.enable_in_turn_results = {}
76
self.sizes_everything_disabled = None
77
78
def must_have_defines_for_board(self, board):
79
'''return a set of defines which must always be enabled'''
80
must_have_defines = {
81
"CubeOrange": frozenset([
82
'AP_BARO_MS56XX_ENABLED',
83
'AP_COMPASS_LSM303D_ENABLED',
84
'AP_COMPASS_AK8963_ENABLED',
85
'AP_COMPASS_AK09916_ENABLED',
86
'AP_COMPASS_ICM20948_ENABLED',
87
]),
88
"CubeBlack": frozenset([
89
'AP_BARO_MS56XX_ENABLED',
90
'AP_COMPASS_LSM303D_ENABLED',
91
'AP_COMPASS_AK8963_ENABLED',
92
'AP_COMPASS_AK09916_ENABLED',
93
'AP_COMPASS_ICM20948_ENABLED',
94
]),
95
"Pixhawk6X-GenericVehicle": frozenset([
96
"AP_BARO_BMP388_ENABLED",
97
"AP_BARO_ICP201XX_ENABLED",
98
]),
99
}
100
return must_have_defines.get(board, frozenset([]))
101
102
def must_have_defines(self):
103
return self.must_have_defines_for_board(self._board)
104
105
@staticmethod
106
def all_targets():
107
return ['copter', 'plane', 'rover', 'antennatracker', 'sub', 'blimp']
108
109
def progress(self, message):
110
print("###### %s" % message, file=sys.stderr)
111
112
# swiped from app.py:
113
def get_build_options_from_ardupilot_tree(self):
114
'''return a list of build options'''
115
import importlib.util
116
spec = importlib.util.spec_from_file_location(
117
"build_options.py",
118
os.path.join(os.path.dirname(os.path.realpath(__file__)),
119
'..', 'scripts', 'build_options.py'))
120
mod = importlib.util.module_from_spec(spec)
121
spec.loader.exec_module(mod)
122
return mod.BUILD_OPTIONS
123
124
def write_defines_to_file(self, defines, filepath):
125
self.write_defines_to_Path(defines, pathlib.Path(filepath))
126
127
def write_defines_to_Path(self, defines, Path):
128
lines = []
129
lines.extend(["undef %s\n" % (a, ) for (a, b) in defines.items()])
130
lines.extend(["define %s %s\n" % (a, b) for (a, b) in defines.items()])
131
content = "".join(lines)
132
Path.write_text(content)
133
134
def get_disable_defines(self, feature, options):
135
'''returns a hash of (name, value) defines to turn feature off -
136
recursively gets dependencies'''
137
ret = {
138
feature.define: 0,
139
}
140
added_one = True
141
while added_one:
142
added_one = False
143
for option in options:
144
if option.define in ret:
145
continue
146
if option.dependency is None:
147
continue
148
for dep in option.dependency.split(','):
149
f = self.get_option_by_label(dep, options)
150
if f.define not in ret:
151
continue
152
153
# print("%s requires %s" % (option.define, f.define), file=sys.stderr)
154
added_one = True
155
ret[option.define] = 0
156
break
157
return ret
158
159
def update_get_enable_defines_for_feature(self, ret, feature, options):
160
'''recursive function to turn on required feature and what it depends
161
on'''
162
ret[feature.define] = 1
163
if feature.dependency is None:
164
return
165
for depname in feature.dependency.split(','):
166
dep = None
167
for f in options:
168
if f.label == depname:
169
dep = f
170
if dep is None:
171
raise ValueError("Invalid dep (%s) for feature (%s)" %
172
(depname, feature.label))
173
self.update_get_enable_defines_for_feature(ret, dep, options)
174
175
def get_enable_defines(self, feature, options):
176
'''returns a hash of (name, value) defines to turn all features *but* feature (and whatever it depends on) on'''
177
ret = self.get_disable_all_defines()
178
self.update_get_enable_defines_for_feature(ret, feature, options)
179
for define in self.must_have_defines_for_board(self._board):
180
ret[define] = 1
181
return ret
182
183
def test_disable_feature(self, feature, options):
184
defines = self.get_disable_defines(feature, options)
185
186
if len(defines.keys()) > 1:
187
self.progress("Disabling %s disables (%s)" % (
188
feature.define,
189
",".join(defines.keys())))
190
191
self.test_compile_with_defines(defines)
192
193
self.assert_feature_not_in_code(defines, feature)
194
195
def assert_feature_not_in_code(self, defines, feature):
196
# if the feature is truly disabled then extract_features.py
197
# should say so:
198
for target in self.build_targets:
199
path = self.target_to_elf_path(target)
200
extracter = extract_features.ExtractFeatures(path)
201
(compiled_in_feature_defines, not_compiled_in_feature_defines) = extracter.extract()
202
for define in defines:
203
# the following defines are known not to work on some
204
# or all vehicles:
205
feature_define_whitelist = set([
206
'AP_RANGEFINDER_ENABLED', # only at vehicle level ATM
207
'HAL_PERIPH_SUPPORT_LONG_CAN_PRINTF', # no symbol
208
])
209
if define in compiled_in_feature_defines:
210
error = f"feature gated by {define} still compiled into ({target}); extract_features.py bug?"
211
if define in feature_define_whitelist:
212
print("warn: " + error)
213
else:
214
raise ValueError(error)
215
216
def test_enable_feature(self, feature, options):
217
defines = self.get_enable_defines(feature, options)
218
219
enabled = list(filter(lambda x : bool(defines[x]), defines.keys()))
220
221
if len(enabled) > 1:
222
self.progress("Enabling %s enables (%s)" % (
223
feature.define,
224
",".join(enabled)))
225
226
self.test_compile_with_defines(defines)
227
228
self.assert_feature_in_code(defines, feature)
229
230
def define_is_whitelisted_for_feature_in_code(self, target, define):
231
'''returns true if we can not expect the define to be extracted from
232
the binary'''
233
# the following defines are known not to work on some
234
# or all vehicles:
235
feature_define_whitelist = set([
236
'AP_RANGEFINDER_ENABLED', # only at vehicle level ATM
237
'HAL_PERIPH_SUPPORT_LONG_CAN_PRINTF', # no symbol
238
'AP_DRONECAN_VOLZ_FEEDBACK_ENABLED', # broken, no subscriber
239
# Baro drivers either come in because you have
240
# external-probing enabled or you have them specified in
241
# your hwdef. If you're not probing and its not in your
242
# hwdef then the code will be elided as unreachable
243
'AP_BARO_ICM20789_ENABLED',
244
'AP_BARO_ICP101XX_ENABLED',
245
'AP_BARO_ICP201XX_ENABLED',
246
'AP_BARO_BMP085_ENABLED',
247
'AP_BARO_BMP280_ENABLED',
248
'AP_BARO_BMP388_ENABLED',
249
'AP_BARO_BMP581_ENABLED',
250
'AP_BARO_DPS280_ENABLED',
251
'AP_BARO_FBM320_ENABLED',
252
'AP_BARO_KELLERLD_ENABLED',
253
'AP_BARO_LPS2XH_ENABLED',
254
'AP_BARO_MS56XX_ENABLED',
255
'AP_BARO_SPL06_ENABLED',
256
'AP_CAMERA_SEND_FOV_STATUS_ENABLED', # elided unless AP_CAMERA_SEND_FOV_STATUS_ENABLED
257
'AP_COMPASS_LSM9DS1_ENABLED', # must be in hwdef, not probed
258
'AP_COMPASS_MAG3110_ENABLED', # must be in hwdef, not probed
259
'AP_COMPASS_MMC5XX3_ENABLED', # must be in hwdef, not probed
260
'AP_MAVLINK_AUTOPILOT_VERSION_REQUEST_ENABLED', # completely elided
261
'AP_MAVLINK_MSG_RELAY_STATUS_ENABLED', # no symbol available
262
'AP_MAVLINK_MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES_ENABLED', # no symbol available
263
'HAL_MSP_SENSORS_ENABLED', # no symbol available
264
'AP_OSD_LINK_STATS_EXTENSIONS_ENABLED', # FIXME: need a new define/feature
265
'HAL_OSD_SIDEBAR_ENABLE', # FIXME: need a new define/feature
266
'HAL_PLUSCODE_ENABLE', # FIXME: need a new define/feature
267
'AP_SERIALMANAGER_REGISTER_ENABLED', # completely elided without a caller
268
'AP_OPTICALFLOW_ONBOARD_ENABLED', # only instantiated on Linux
269
'HAL_WITH_FRSKY_TELEM_BIDIRECTIONAL', # entirely elided if no user
270
'AP_PLANE_BLACKBOX_LOGGING', # entirely elided if no user
271
'AP_COMPASS_AK8963_ENABLED', # probed on a board-by-board basis, not on CubeOrange for example
272
'AP_COMPASS_LSM303D_ENABLED', # probed on a board-by-board basis, not on CubeOrange for example
273
])
274
if target.lower() != "copter":
275
feature_define_whitelist.add('MODE_ZIGZAG_ENABLED')
276
feature_define_whitelist.add('MODE_SYSTEMID_ENABLED')
277
feature_define_whitelist.add('MODE_SPORT_ENABLED')
278
feature_define_whitelist.add('MODE_FOLLOW_ENABLED')
279
feature_define_whitelist.add('MODE_TURTLE_ENABLED')
280
feature_define_whitelist.add('MODE_GUIDED_NOGPS_ENABLED')
281
feature_define_whitelist.add('MODE_FLOWHOLD_ENABLED')
282
feature_define_whitelist.add('MODE_FLIP_ENABLED')
283
feature_define_whitelist.add('MODE_BRAKE_ENABLED')
284
feature_define_whitelist.add('AP_TEMPCALIBRATION_ENABLED')
285
feature_define_whitelist.add('AC_PAYLOAD_PLACE_ENABLED')
286
feature_define_whitelist.add('AP_AVOIDANCE_ENABLED')
287
feature_define_whitelist.add('AP_WINCH_ENABLED')
288
feature_define_whitelist.add('AP_WINCH_DAIWA_ENABLED')
289
feature_define_whitelist.add('AP_WINCH_PWM_ENABLED')
290
feature_define_whitelist.add(r'AP_MOTORS_FRAME_.*_ENABLED')
291
feature_define_whitelist.add('AP_COPTER_ADVANCED_FAILSAFE_ENABLED')
292
feature_define_whitelist.add('AP_INERTIALSENSOR_FAST_SAMPLE_WINDOW_ENABLED')
293
294
if target.lower() != "plane":
295
# only on Plane:
296
feature_define_whitelist.add('AP_ICENGINE_ENABLED')
297
feature_define_whitelist.add('AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED')
298
feature_define_whitelist.add('AP_MAVLINK_MAV_CMD_SET_HAGL_ENABLED')
299
feature_define_whitelist.add('AP_ADVANCEDFAILSAFE_ENABLED')
300
feature_define_whitelist.add('AP_TUNING_ENABLED')
301
feature_define_whitelist.add('HAL_LANDING_DEEPSTALL_ENABLED')
302
feature_define_whitelist.add('HAL_SOARING_ENABLED')
303
feature_define_whitelist.add('AP_PLANE_BLACKBOX_LOGGING')
304
feature_define_whitelist.add('QAUTOTUNE_ENABLED')
305
feature_define_whitelist.add('AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED')
306
feature_define_whitelist.add('HAL_QUADPLANE_ENABLED')
307
feature_define_whitelist.add('AP_BATTERY_WATT_MAX_ENABLED')
308
feature_define_whitelist.add('MODE_AUTOLAND_ENABLED')
309
feature_define_whitelist.add('AP_PLANE_GLIDER_PULLUP_ENABLED')
310
feature_define_whitelist.add('AP_QUICKTUNE_ENABLED')
311
312
if target.lower() not in ["plane", "copter"]:
313
feature_define_whitelist.add('HAL_ADSB_ENABLED')
314
feature_define_whitelist.add('AP_LANDINGGEAR_ENABLED')
315
# only Plane and Copter instantiate Parachute
316
feature_define_whitelist.add('HAL_PARACHUTE_ENABLED')
317
# only Plane and Copter have AP_Motors:
318
319
if target.lower() not in ["rover", "copter"]:
320
# only Plane and Copter instantiate Beacon
321
feature_define_whitelist.add('AP_BEACON_ENABLED')
322
323
if target.lower() != "rover":
324
# only on Rover:
325
feature_define_whitelist.add('HAL_TORQEEDO_ENABLED')
326
feature_define_whitelist.add('AP_ROVER_ADVANCED_FAILSAFE_ENABLED')
327
if target.lower() != "sub":
328
# only on Sub:
329
feature_define_whitelist.add('AP_BARO_KELLERLD_ENABLED')
330
if target.lower() not in frozenset(["rover", "sub"]):
331
# only Rover and Sub get nmea airspeed
332
feature_define_whitelist.add('AP_AIRSPEED_NMEA_ENABLED')
333
if target.lower() not in frozenset(["copter", "rover"]):
334
feature_define_whitelist.add('HAL_SPRAYER_ENABLED')
335
feature_define_whitelist.add('HAL_PROXIMITY_ENABLED')
336
feature_define_whitelist.add('AP_PROXIMITY_.*_ENABLED')
337
feature_define_whitelist.add('AP_OAPATHPLANNER_ENABLED')
338
339
if target.lower() in ["blimp", "antennatracker"]:
340
# no airspeed on blimp/tracker
341
feature_define_whitelist.add(r'AP_AIRSPEED_.*_ENABLED')
342
feature_define_whitelist.add(r'HAL_MOUNT_ENABLED')
343
feature_define_whitelist.add(r'AP_MOUNT_.*_ENABLED')
344
feature_define_whitelist.add(r'HAL_MOUNT_.*_ENABLED')
345
feature_define_whitelist.add(r'HAL_SOLO_GIMBAL_ENABLED')
346
feature_define_whitelist.add(r'AP_OPTICALFLOW_ENABLED')
347
feature_define_whitelist.add(r'AP_OPTICALFLOW_.*_ENABLED')
348
feature_define_whitelist.add(r'HAL_MSP_OPTICALFLOW_ENABLED')
349
# missing calls to fence.check():
350
feature_define_whitelist.add(r'AP_FENCE_ENABLED')
351
# RPM not instantiated on Blimp or Rover:
352
feature_define_whitelist.add(r'AP_RPM_ENABLED')
353
feature_define_whitelist.add(r'AP_RPM_.*_ENABLED')
354
# rangefinder init is not called:
355
feature_define_whitelist.add(r'HAL_MSP_RANGEFINDER_ENABLED')
356
# these guys don't instantiate anything which uses sd-card storage:
357
feature_define_whitelist.add(r'AP_SDCARD_STORAGE_ENABLED')
358
feature_define_whitelist.add(r'AP_RANGEFINDER_ENABLED')
359
feature_define_whitelist.add(r'AP_RANGEFINDER_.*_ENABLED')
360
361
if target.lower() in ["blimp", "antennatracker", "sub"]:
362
# no OSD on Sub/blimp/tracker
363
feature_define_whitelist.add(r'OSD_ENABLED')
364
feature_define_whitelist.add(r'OSD_PARAM_ENABLED')
365
# AP_OSD is not instantiated, , so no MSP backend:
366
feature_define_whitelist.add(r'HAL_WITH_MSP_DISPLAYPORT')
367
# camera instantiated in specific vehicles:
368
feature_define_whitelist.add(r'AP_CAMERA_ENABLED')
369
feature_define_whitelist.add(r'AP_CAMERA_.*_ENABLED')
370
# button update is not called in these vehicles
371
feature_define_whitelist.add(r'HAL_BUTTON_ENABLED')
372
# precland not instantiated on these vehicles
373
feature_define_whitelist.add(r'AC_PRECLAND_ENABLED')
374
feature_define_whitelist.add(r'AC_PRECLAND_.*_ENABLED')
375
# RSSI is not initialised - probably should be for some
376
feature_define_whitelist.add(r'AP_RSSI_ENABLED')
377
378
if target.lower() in ["antennatracker", "sub"]:
379
# missing the init call to the relay library:
380
feature_define_whitelist.add(r'AP_RELAY_ENABLED')
381
feature_define_whitelist.add(r'AP_RC_CHANNEL_AUX_FUNCTION_STRINGS_ENABLED')
382
383
for some_re in feature_define_whitelist:
384
if re.match(some_re, define):
385
return True
386
387
def assert_feature_in_code(self, defines, feature):
388
# if the feature is truly disabled then extract_features.py
389
# should say so:
390
for target in self.build_targets:
391
path = self.target_to_elf_path(target)
392
extracter = extract_features.ExtractFeatures(path)
393
(compiled_in_feature_defines, not_compiled_in_feature_defines) = extracter.extract()
394
for define in defines:
395
if not defines[define]:
396
continue
397
if define in compiled_in_feature_defines:
398
continue
399
error = f"feature gated by {define} not compiled into ({target}); extract_features.py bug?"
400
if self.define_is_whitelisted_for_feature_in_code(target, define):
401
print("warn: " + error)
402
continue
403
raise ValueError(error)
404
405
def board(self):
406
'''returns board to build for'''
407
return self._board
408
409
def test_compile_with_defines(self, defines):
410
extra_hwdef_filepath = "/tmp/extra.hwdef"
411
self.write_defines_to_file(defines, extra_hwdef_filepath)
412
if self.extra_hwdef is not None:
413
content = open(self.extra_hwdef, "r").read()
414
with open(extra_hwdef_filepath, "a") as f:
415
f.write(content)
416
util.waf_configure(
417
self.board(),
418
extra_hwdef=extra_hwdef_filepath,
419
)
420
for t in self.build_targets:
421
try:
422
util.run_cmd([util.relwaf(), t])
423
except Exception:
424
print("Failed to build (%s) with things disabled" %
425
(t,))
426
raise
427
428
def target_to_path(self, target, extension=None):
429
'''given a build target (e.g. copter), return expected path to .bin
430
file for that target'''
431
target_to_binpath = {
432
"copter": "arducopter",
433
"plane": "arduplane",
434
"rover": "ardurover",
435
"antennatracker": "antennatracker",
436
"sub": "ardusub",
437
"blimp": "blimp",
438
}
439
filename = target_to_binpath[target]
440
if extension is not None:
441
filename += "." + extension
442
return os.path.join("build", self.board(), "bin", filename)
443
444
def target_to_bin_path(self, target):
445
'''given a build target (e.g. copter), return expected path to .bin
446
file for that target'''
447
return self.target_to_path(target, 'bin')
448
449
def target_to_elf_path(self, target):
450
'''given a build target (e.g. copter), return expected path to .elf
451
file for that target'''
452
return self.target_to_path(target)
453
454
def find_build_sizes(self):
455
'''returns a hash with size of all build targets'''
456
ret = {}
457
for target in self.build_targets:
458
path = self.target_to_bin_path(target)
459
ret[target] = os.path.getsize(path)
460
return ret
461
462
def csv_for_results(self, results):
463
'''return a string with csv for results'''
464
features = sorted(results.keys())
465
all_vehicles = set()
466
for feature in features:
467
all_vehicles.update(list(results[feature].keys()))
468
sorted_all_vehicles = sorted(list(all_vehicles))
469
ret = ""
470
ret += ",".join(["Feature"] + sorted_all_vehicles) + "\n"
471
for feature in features:
472
line = [feature]
473
feature_results = results[feature]
474
for vehicle in sorted_all_vehicles:
475
bytes_delta = ""
476
if vehicle in feature_results:
477
result = feature_results[vehicle]
478
bytes_delta = result.bytes_delta
479
line.append(str(bytes_delta))
480
ret += ",".join(line) + "\n"
481
return ret
482
483
def disable_in_turn_check_sizes(self, feature, sizes_nothing_disabled):
484
if not self.do_step_disable_none:
485
self.progress("disable-none skipped, size comparison not available")
486
return
487
current_sizes = self.find_build_sizes()
488
for (build, new_size) in current_sizes.items():
489
old_size = sizes_nothing_disabled[build]
490
self.progress("Disabling %s(%s) on %s saves %u bytes" %
491
(feature.label, feature.define, build, old_size - new_size))
492
if feature.define not in self.results:
493
self.results[feature.define] = {}
494
self.results[feature.define][build] = TestBuildOptionsResult(feature.define, build, old_size - new_size)
495
with open("/tmp/some.csv", "w") as f:
496
f.write(self.csv_for_results(self.results))
497
498
def run_disable_in_turn(self):
499
progress_file = pathlib.Path("/tmp/run-disable-in-turn-progress")
500
resume_number = self.resume_number_from_progress_Path(progress_file)
501
options = self.get_build_options_from_ardupilot_tree()
502
count = 1
503
for feature in sorted(options, key=lambda x : x.define):
504
if resume_number is not None:
505
if count < resume_number:
506
count += 1
507
continue
508
if self.match_glob is not None:
509
if not fnmatch.fnmatch(feature.define, self.match_glob):
510
continue
511
with open(progress_file, "w") as f:
512
f.write(f"{count}/{len(options)} {feature.define}\n")
513
# if feature.define < "WINCH_ENABLED":
514
# count += 1
515
# continue
516
if feature.define in self.must_have_defines_for_board(self._board):
517
self.progress("Feature %s(%s) (%u/%u) is a MUST-HAVE" %
518
(feature.label, feature.define, count, len(options)))
519
count += 1
520
continue
521
self.progress("Disabling feature %s(%s) (%u/%u)" %
522
(feature.label, feature.define, count, len(options)))
523
self.test_disable_feature(feature, options)
524
count += 1
525
self.disable_in_turn_check_sizes(feature, self.sizes_nothing_disabled)
526
527
def enable_in_turn_check_sizes(self, feature, sizes_everything_disabled):
528
if not self.do_step_disable_all:
529
self.progress("disable-none skipped, size comparison not available")
530
return
531
current_sizes = self.find_build_sizes()
532
for (build, new_size) in current_sizes.items():
533
old_size = sizes_everything_disabled[build]
534
self.progress("Enabling %s(%s) on %s costs %u bytes" %
535
(feature.label, feature.define, build, old_size - new_size))
536
if feature.define not in self.enable_in_turn_results:
537
self.enable_in_turn_results[feature.define] = {}
538
self.enable_in_turn_results[feature.define][build] = TestBuildOptionsResult(feature.define, build, old_size - new_size) # noqa
539
with open("/tmp/enable-in-turn.csv", "w") as f:
540
f.write(self.csv_for_results(self.enable_in_turn_results))
541
542
def resume_number_from_progress_Path(self, progress_file):
543
if not self.resume:
544
return None
545
try:
546
content = progress_file.read_text().rstrip()
547
m = re.match(r"(\d+)/\d+ \w+", content)
548
if m is None:
549
raise ValueError(f"{progress_file} not matched")
550
return int(m.group(1))
551
except FileNotFoundError:
552
pass
553
return None
554
555
def run_enable_in_turn(self):
556
progress_file = pathlib.Path("/tmp/run-enable-in-turn-progress")
557
resume_number = self.resume_number_from_progress_Path(progress_file)
558
options = self.get_build_options_from_ardupilot_tree()
559
count = 1
560
for feature in options:
561
if resume_number is not None:
562
if count < resume_number:
563
count += 1
564
continue
565
if self.match_glob is not None:
566
if not fnmatch.fnmatch(feature.define, self.match_glob):
567
continue
568
self.progress("Enabling feature %s(%s) (%u/%u)" %
569
(feature.label, feature.define, count, len(options)))
570
with open(progress_file, "w") as f:
571
f.write(f"{count}/{len(options)} {feature.define}\n")
572
self.test_enable_feature(feature, options)
573
count += 1
574
self.enable_in_turn_check_sizes(feature, self.sizes_everything_disabled)
575
576
def get_option_by_label(self, label, options):
577
for x in options:
578
if x.label == label:
579
return x
580
raise ValueError("No such option (%s)" % label)
581
582
def get_disable_all_defines(self):
583
'''returns a hash of defines which turns all features off'''
584
options = self.get_build_options_from_ardupilot_tree()
585
defines = {}
586
for feature in options:
587
if self.match_glob is not None:
588
if not fnmatch.fnmatch(feature.define, self.match_glob):
589
continue
590
defines[feature.define] = 0
591
for define in self.must_have_defines_for_board(self._board):
592
defines[define] = 1
593
594
return defines
595
596
def run_disable_all(self):
597
defines = self.get_disable_all_defines()
598
self.test_compile_with_defines(defines)
599
self.sizes_everything_disabled = self.find_build_sizes()
600
601
def run_disable_none(self):
602
self.test_compile_with_defines({})
603
self.sizes_nothing_disabled = self.find_build_sizes()
604
605
def run_with_defaults(self):
606
options = self.get_build_options_from_ardupilot_tree()
607
defines = {}
608
for feature in options:
609
defines[feature.define] = feature.default
610
self.test_compile_with_defines(defines)
611
612
def check_deps_consistency(self):
613
# self.progress("Checking deps consistency")
614
options = self.get_build_options_from_ardupilot_tree()
615
for feature in options:
616
self.get_disable_defines(feature, options)
617
618
def check_duplicate_labels(self):
619
'''check that we do not have multiple features with same labels'''
620
options = self.get_build_options_from_ardupilot_tree()
621
seen_labels = {}
622
for feature in options:
623
if seen_labels.get(feature.label, None) is not None:
624
raise ValueError("Duplicate entries found for label '%s'" % feature.label)
625
seen_labels[feature.label] = True
626
627
def do_emit_disable_all_defines(self):
628
defines = tbo.get_disable_all_defines()
629
for f in self.must_have_defines():
630
defines[f] = 1
631
tbo.write_defines_to_Path(defines, pathlib.Path("/dev/stdout"))
632
sys.exit(0)
633
634
def run(self):
635
self.check_deps_consistency()
636
self.check_duplicate_labels()
637
638
if self.emit_disable_all_defines:
639
self.do_emit_disable_all_defines()
640
sys.exit(0)
641
642
if self.do_step_run_with_defaults:
643
self.progress("Running run-with-defaults step")
644
self.run_with_defaults()
645
if self.do_step_disable_all:
646
self.progress("Running disable-all step")
647
self.run_disable_all()
648
if self.do_step_disable_none:
649
self.progress("Running disable-none step")
650
self.run_disable_none()
651
if self.do_step_disable_in_turn:
652
self.progress("Running disable-in-turn step")
653
self.run_disable_in_turn()
654
if self.do_step_enable_in_turn:
655
self.progress("Running enable-in-turn step")
656
self.run_enable_in_turn()
657
658
659
if __name__ == '__main__':
660
661
parser = optparse.OptionParser()
662
parser.add_option("--define-match-glob",
663
type='string',
664
default=None,
665
help='feature define must match this glob to be tested')
666
parser.add_option("--no-run-with-defaults",
667
action='store_true',
668
help='Do not run the run-with-defaults step')
669
parser.add_option("--no-disable-all",
670
action='store_true',
671
help='Do not run the disable-all step')
672
parser.add_option("--no-disable-none",
673
action='store_true',
674
help='Do not run the disable-none step')
675
parser.add_option("--no-disable-in-turn",
676
action='store_true',
677
help='Do not run the disable-in-turn step')
678
parser.add_option("--no-enable-in-turn",
679
action='store_true',
680
help='Do not run the enable-in-turn step')
681
parser.add_option("--build-targets",
682
type='choice',
683
choices=TestBuildOptions.all_targets(),
684
action='append',
685
help='vehicle targets to build')
686
parser.add_option("--extra-hwdef",
687
type='string',
688
default=None,
689
help="file containing extra hwdef information")
690
parser.add_option("--board",
691
type='string',
692
default="CubeOrange",
693
help='board to build for')
694
parser.add_option("--emit-disable-all-defines",
695
action='store_true',
696
help='emit defines used for disabling all features then exit')
697
parser.add_option("--resume",
698
action='store_true',
699
help='resume from previous progress file')
700
701
opts, args = parser.parse_args()
702
703
tbo = TestBuildOptions(
704
match_glob=opts.define_match_glob,
705
do_step_disable_all=not opts.no_disable_all,
706
do_step_disable_none=not opts.no_disable_none,
707
do_step_disable_defaults=not opts.no_run_with_defaults,
708
do_step_disable_in_turn=not opts.no_disable_in_turn,
709
do_step_enable_in_turn=not opts.no_enable_in_turn,
710
build_targets=opts.build_targets,
711
board=opts.board,
712
extra_hwdef=opts.extra_hwdef,
713
emit_disable_all_defines=opts.emit_disable_all_defines,
714
resume=opts.resume,
715
)
716
717
tbo.run()
718
719