Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/autotest/test_build_options.py
9692 views
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_MS5611_ENABLED
10
define AP_BARO_MS5611_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
import fnmatch
22
import optparse
23
import os
24
import pathlib
25
import re
26
import sys
27
28
from pysim import util
29
30
sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
31
import extract_features # noqa
32
33
34
class TestBuildOptionsResult(object):
35
'''object to return results from a comparison'''
36
37
def __init__(self, feature, vehicle, bytes_delta):
38
self.feature = feature
39
self.vehicle = vehicle
40
self.bytes_delta = bytes_delta
41
42
43
class TestBuildOptions(object):
44
def __init__(self,
45
match_glob=None,
46
do_step_disable_all=True,
47
do_step_disable_none=False,
48
do_step_disable_defaults=True,
49
do_step_disable_in_turn=True,
50
do_step_enable_in_turn=True,
51
build_targets=None,
52
board="CubeOrange", # DevEBoxH7v2 also works
53
extra_hwdef=None,
54
emit_disable_all_defines=None,
55
resume=False,
56
):
57
self.extra_hwdef = extra_hwdef
58
self.sizes_nothing_disabled = None
59
self.match_glob = match_glob
60
self.do_step_disable_all = do_step_disable_all
61
self.do_step_disable_none = do_step_disable_none
62
self.do_step_run_with_defaults = do_step_disable_defaults
63
self.do_step_disable_in_turn = do_step_disable_in_turn
64
self.do_step_enable_in_turn = do_step_enable_in_turn
65
self.build_targets = build_targets
66
if self.build_targets is None:
67
self.build_targets = self.all_targets()
68
self._board = board
69
self.emit_disable_all_defines = emit_disable_all_defines
70
self.resume = resume
71
self.results = {}
72
73
self.enable_in_turn_results = {}
74
self.sizes_everything_disabled = None
75
76
def must_have_defines_for_board(self, board):
77
'''return a set of defines which must always be enabled'''
78
must_have_defines = {
79
"CubeOrange": frozenset([
80
'AP_BARO_MS5611_ENABLED',
81
'AP_BARO_MS5607_ENABLED',
82
'AP_COMPASS_LSM303D_ENABLED',
83
'AP_COMPASS_AK8963_ENABLED',
84
'AP_COMPASS_AK09916_ENABLED',
85
'AP_COMPASS_ICM20948_ENABLED',
86
]),
87
"CubeBlack": frozenset([
88
'AP_BARO_MS5611_ENABLED',
89
'AP_BARO_MS5607_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
extractor = extract_features.ExtractFeatures(path)
201
(compiled_in_feature_defines, not_compiled_in_feature_defines) = extractor.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
'AP_PROXIMITY_HEXSOONRADAR_ENABLED', # this shares symbols with AP_PROXIMITY_MR72_ENABLED
209
'AP_PROXIMITY_MR72_ENABLED', # this shares symbols with AP_PROXIMITY_HEXSOONRADAR_ENABLED
210
'AP_RANGEFINDER_NRA24_CAN_ENABLED',
211
'AP_RANGEFINDER_HEXSOONRADAR_ENABLED',
212
])
213
if define in compiled_in_feature_defines:
214
error = f"feature gated by {define} still compiled into ({target}); extract_features.py bug?"
215
if define in feature_define_whitelist:
216
print("warn: " + error)
217
else:
218
raise ValueError(error)
219
220
def test_enable_feature(self, feature, options):
221
defines = self.get_enable_defines(feature, options)
222
223
enabled = list(filter(lambda x : bool(defines[x]), defines.keys()))
224
225
if len(enabled) > 1:
226
self.progress("Enabling %s enables (%s)" % (
227
feature.define,
228
",".join(enabled)))
229
230
self.test_compile_with_defines(defines)
231
232
self.assert_feature_in_code(defines, feature)
233
234
def define_is_whitelisted_for_feature_in_code(self, target, define):
235
'''returns true if we can not expect the define to be extracted from
236
the binary'''
237
# the following defines are known not to work on some
238
# or all vehicles:
239
feature_define_whitelist = set([
240
'AC_POLYFENCE_CIRCLE_INT_SUPPORT_ENABLED', # no symbol
241
'AP_RANGEFINDER_ENABLED', # only at vehicle level ATM
242
'HAL_PERIPH_SUPPORT_LONG_CAN_PRINTF', # no symbol
243
'AP_DRONECAN_VOLZ_FEEDBACK_ENABLED', # broken, no subscriber
244
# Baro drivers either come in because you have
245
# external-probing enabled or you have them specified in
246
# your hwdef. If you're not probing and its not in your
247
# hwdef then the code will be elided as unreachable
248
'AP_BARO_ICM20789_ENABLED',
249
'AP_BARO_ICP101XX_ENABLED',
250
'AP_BARO_ICP201XX_ENABLED',
251
'AP_BARO_BMP085_ENABLED',
252
'AP_BARO_BMP280_ENABLED',
253
'AP_BARO_BMP388_ENABLED',
254
'AP_BARO_BMP581_ENABLED',
255
'AP_BARO_DPS280_ENABLED',
256
'AP_BARO_FBM320_ENABLED',
257
'AP_BARO_KELLERLD_ENABLED',
258
'AP_BARO_LPS2XH_ENABLED',
259
'AP_BARO_MS5607_ENABLED',
260
'AP_BARO_MS5611_ENABLED',
261
'AP_BARO_MS5637_ENABLED',
262
'AP_BARO_MS5837_ENABLED',
263
'AP_BARO_SPL06_ENABLED',
264
'AP_CAMERA_SEND_FOV_STATUS_ENABLED', # elided unless AP_CAMERA_SEND_FOV_STATUS_ENABLED
265
'AP_COMPASS_LSM9DS1_ENABLED', # must be in hwdef, not probed
266
'AP_COMPASS_MAG3110_ENABLED', # must be in hwdef, not probed
267
'AP_COMPASS_MMC5XX3_ENABLED', # must be in hwdef, not probed
268
'AP_MAVLINK_AUTOPILOT_VERSION_REQUEST_ENABLED', # completely elided
269
'AP_MAVLINK_MSG_RELAY_STATUS_ENABLED', # no symbol available
270
'AP_MAVLINK_MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES_ENABLED', # no symbol available
271
'HAL_MSP_SENSORS_ENABLED', # no symbol available
272
'AP_OSD_LINK_STATS_EXTENSIONS_ENABLED', # FIXME: need a new define/feature
273
'HAL_OSD_SIDEBAR_ENABLE', # FIXME: need a new define/feature
274
'HAL_PLUSCODE_ENABLE', # FIXME: need a new define/feature
275
'AP_SERIALMANAGER_REGISTER_ENABLED', # completely elided without a caller
276
'AP_OPTICALFLOW_ONBOARD_ENABLED', # only instantiated on Linux
277
'HAL_WITH_FRSKY_TELEM_BIDIRECTIONAL', # entirely elided if no user
278
'AP_PLANE_BLACKBOX_LOGGING', # entirely elided if no user
279
'AP_COMPASS_AK8963_ENABLED', # probed on a board-by-board basis, not on CubeOrange for example
280
'AP_COMPASS_LSM303D_ENABLED', # probed on a board-by-board basis, not on CubeOrange for example
281
'AP_BARO_THST_COMP_ENABLED', # compiler is optimising this symbol away
282
])
283
if target.lower() != "copter":
284
feature_define_whitelist.add('MODE_ZIGZAG_ENABLED')
285
feature_define_whitelist.add('MODE_SYSTEMID_ENABLED')
286
feature_define_whitelist.add('MODE_SPORT_ENABLED')
287
feature_define_whitelist.add('MODE_FOLLOW_ENABLED')
288
feature_define_whitelist.add('MODE_TURTLE_ENABLED')
289
feature_define_whitelist.add('MODE_GUIDED_NOGPS_ENABLED')
290
feature_define_whitelist.add('MODE_FLOWHOLD_ENABLED')
291
feature_define_whitelist.add('MODE_FLIP_ENABLED')
292
feature_define_whitelist.add('MODE_BRAKE_ENABLED')
293
feature_define_whitelist.add('AP_TEMPCALIBRATION_ENABLED')
294
feature_define_whitelist.add('AC_PAYLOAD_PLACE_ENABLED')
295
feature_define_whitelist.add('AP_AVOIDANCE_ENABLED')
296
feature_define_whitelist.add('AP_WINCH_ENABLED')
297
feature_define_whitelist.add('AP_WINCH_DAIWA_ENABLED')
298
feature_define_whitelist.add('AP_WINCH_PWM_ENABLED')
299
feature_define_whitelist.add(r'AP_MOTORS_FRAME_.*_ENABLED')
300
feature_define_whitelist.add('AP_MOTORS_TRI_ENABLED')
301
feature_define_whitelist.add('AP_COPTER_ADVANCED_FAILSAFE_ENABLED')
302
feature_define_whitelist.add('AP_INERTIALSENSOR_FAST_SAMPLE_WINDOW_ENABLED')
303
feature_define_whitelist.add('AP_COPTER_AHRS_AUTO_TRIM_ENABLED')
304
feature_define_whitelist.add('AP_RC_TRANSMITTER_TUNING_ENABLED')
305
306
if target.lower() in ['antennatracker', 'blimp', 'sub', 'plane', 'copter']:
307
# plane has a dependency for AP_Follow which is not
308
# declared in build_options.py; we don't compile follow
309
# support for Follow into Plane unless scripting is also
310
# enabled. Copter manages to elide everything is
311
# MODE_FOLLOW isn't enabled.
312
feature_define_whitelist.add('AP_FOLLOW_ENABLED')
313
314
if target.lower() != "plane":
315
# only on Plane:
316
feature_define_whitelist.add('AP_ICENGINE_ENABLED')
317
feature_define_whitelist.add('AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED')
318
feature_define_whitelist.add('AP_MAVLINK_MAV_CMD_SET_HAGL_ENABLED')
319
feature_define_whitelist.add('AP_ADVANCEDFAILSAFE_ENABLED')
320
feature_define_whitelist.add('AP_TUNING_ENABLED')
321
feature_define_whitelist.add('HAL_LANDING_DEEPSTALL_ENABLED')
322
feature_define_whitelist.add('HAL_SOARING_ENABLED')
323
feature_define_whitelist.add('AP_PLANE_BLACKBOX_LOGGING')
324
feature_define_whitelist.add('QAUTOTUNE_ENABLED')
325
feature_define_whitelist.add('AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED')
326
feature_define_whitelist.add('HAL_QUADPLANE_ENABLED')
327
feature_define_whitelist.add('AP_BATTERY_WATT_MAX_ENABLED')
328
feature_define_whitelist.add('MODE_AUTOLAND_ENABLED')
329
feature_define_whitelist.add('AP_PLANE_GLIDER_PULLUP_ENABLED')
330
feature_define_whitelist.add('AP_QUICKTUNE_ENABLED')
331
feature_define_whitelist.add('AP_PLANE_SYSTEMID_ENABLED')
332
333
if target.lower() not in ["plane", "copter"]:
334
feature_define_whitelist.add('HAL_ADSB_ENABLED')
335
feature_define_whitelist.add('AP_LANDINGGEAR_ENABLED')
336
# only Plane and Copter instantiate Parachute
337
feature_define_whitelist.add('HAL_PARACHUTE_ENABLED')
338
# only Plane and Copter have AP_Motors:
339
feature_define_whitelist.add(r'AP_MOTORS_TRI_ENABLED')
340
# other vehicles do not instantiate ADSB:
341
feature_define_whitelist.add('AP_ADSB_AVOIDANCE_ENABLED')
342
# only Plane and Copter instantiate the Motors library,
343
# required for these bindings:
344
feature_define_whitelist.add('AP_SCRIPTING_BINDING_MOTORS_ENABLED')
345
346
if target.lower() not in ["rover", "copter"]:
347
# only Plane and Copter instantiate Beacon
348
feature_define_whitelist.add('AP_BEACON_ENABLED')
349
350
if target.lower() != "rover":
351
# only on Rover:
352
feature_define_whitelist.add('HAL_TORQEEDO_ENABLED')
353
feature_define_whitelist.add('AP_ROVER_ADVANCED_FAILSAFE_ENABLED')
354
feature_define_whitelist.add('AP_ROVER_AUTO_ARM_ONCE_ENABLED')
355
if target.lower() != "sub":
356
# only on Sub:
357
feature_define_whitelist.add('AP_BARO_KELLERLD_ENABLED')
358
if target.lower() not in frozenset(["rover", "sub"]):
359
# only Rover and Sub get nmea airspeed
360
feature_define_whitelist.add('AP_AIRSPEED_NMEA_ENABLED')
361
if target.lower() not in frozenset(["copter", "rover"]):
362
feature_define_whitelist.add('HAL_SPRAYER_ENABLED')
363
feature_define_whitelist.add('HAL_PROXIMITY_ENABLED')
364
feature_define_whitelist.add('AP_PROXIMITY_.*_ENABLED')
365
feature_define_whitelist.add('AP_OAPATHPLANNER_ENABLED')
366
367
if target.lower() in ["blimp", "antennatracker"]:
368
# no airspeed on blimp/tracker
369
feature_define_whitelist.add(r'AP_AIRSPEED_.*_ENABLED')
370
feature_define_whitelist.add(r'HAL_MOUNT_ENABLED')
371
feature_define_whitelist.add(r'AP_MOUNT_.*_ENABLED')
372
feature_define_whitelist.add(r'HAL_MOUNT_.*_ENABLED')
373
feature_define_whitelist.add(r'HAL_SOLO_GIMBAL_ENABLED')
374
feature_define_whitelist.add(r'AP_OPTICALFLOW_ENABLED')
375
feature_define_whitelist.add(r'AP_OPTICALFLOW_.*_ENABLED')
376
feature_define_whitelist.add(r'HAL_MSP_OPTICALFLOW_ENABLED')
377
# missing calls to fence.check():
378
feature_define_whitelist.add(r'AP_FENCE_ENABLED')
379
# RPM not instantiated on Blimp or Rover:
380
feature_define_whitelist.add(r'AP_RPM_ENABLED')
381
feature_define_whitelist.add(r'AP_RPM_.*_ENABLED')
382
# rangefinder init is not called:
383
feature_define_whitelist.add(r'HAL_MSP_RANGEFINDER_ENABLED')
384
# these guys don't instantiate anything which uses sd-card storage:
385
feature_define_whitelist.add(r'AP_SDCARD_STORAGE_ENABLED')
386
feature_define_whitelist.add(r'AP_RANGEFINDER_ENABLED')
387
feature_define_whitelist.add(r'AP_RANGEFINDER_.*_ENABLED')
388
389
if target.lower() in ["blimp", "antennatracker", "sub"]:
390
# no OSD on Sub/blimp/tracker
391
feature_define_whitelist.add(r'OSD_ENABLED')
392
feature_define_whitelist.add(r'OSD_PARAM_ENABLED')
393
# AP_OSD is not instantiated, , so no MSP backend:
394
feature_define_whitelist.add(r'HAL_WITH_MSP_DISPLAYPORT')
395
feature_define_whitelist.add(r'AP_MSP_INAV_FONTS_ENABLED')
396
# camera instantiated in specific vehicles:
397
feature_define_whitelist.add(r'AP_CAMERA_ENABLED')
398
feature_define_whitelist.add(r'AP_CAMERA_.*_ENABLED')
399
# button update is not called in these vehicles
400
feature_define_whitelist.add(r'HAL_BUTTON_ENABLED')
401
# precland not instantiated on these vehicles
402
feature_define_whitelist.add(r'AC_PRECLAND_ENABLED')
403
feature_define_whitelist.add(r'AC_PRECLAND_.*_ENABLED')
404
# RSSI is not initialised - probably should be for some
405
feature_define_whitelist.add(r'AP_RSSI_ENABLED')
406
407
if target.lower() in ["antennatracker", "sub"]:
408
# missing the init call to the relay library:
409
feature_define_whitelist.add(r'AP_RELAY_ENABLED')
410
feature_define_whitelist.add(r'AP_RC_CHANNEL_AUX_FUNCTION_STRINGS_ENABLED')
411
412
if target.lower() in {"antennatracker", "blimp", "rover"}:
413
# these don't instantiate terrain
414
feature_define_whitelist.add('EK3_FEATURE_OPTFLOW_SRTM')
415
416
if target.lower() not in ["AP_Periph"]:
417
feature_define_whitelist.add(r'AP_PERIPH_.*')
418
419
for some_re in feature_define_whitelist:
420
if re.match(some_re, define):
421
return True
422
423
def assert_feature_in_code(self, defines, feature):
424
# if the feature is truly disabled then extract_features.py
425
# should say so:
426
for target in self.build_targets:
427
path = self.target_to_elf_path(target)
428
extractor = extract_features.ExtractFeatures(path)
429
(compiled_in_feature_defines, not_compiled_in_feature_defines) = extractor.extract()
430
for define in defines:
431
if not defines[define]:
432
continue
433
if define in compiled_in_feature_defines:
434
continue
435
error = f"feature gated by {define} not compiled into ({target}); extract_features.py bug?"
436
if self.define_is_whitelisted_for_feature_in_code(target, define):
437
print("warn: " + error)
438
continue
439
raise ValueError(error)
440
441
def board(self):
442
'''returns board to build for'''
443
return self._board
444
445
def test_compile_with_defines(self, defines):
446
extra_hwdef_filepath = "/tmp/extra.hwdef"
447
self.write_defines_to_file(defines, extra_hwdef_filepath)
448
if self.extra_hwdef is not None:
449
content = open(self.extra_hwdef, "r").read()
450
with open(extra_hwdef_filepath, "a") as f:
451
f.write(content)
452
util.waf_configure(
453
self.board(),
454
extra_hwdef=extra_hwdef_filepath,
455
)
456
for t in self.build_targets:
457
try:
458
util.run_cmd([util.relwaf(), t])
459
except Exception:
460
print("Failed to build (%s) with things disabled" %
461
(t,))
462
raise
463
464
def target_to_path(self, target, extension=None):
465
'''given a build target (e.g. copter), return expected path to .bin
466
file for that target'''
467
target_to_binpath = {
468
"copter": "arducopter",
469
"plane": "arduplane",
470
"rover": "ardurover",
471
"antennatracker": "antennatracker",
472
"sub": "ardusub",
473
"blimp": "blimp",
474
}
475
filename = target_to_binpath[target]
476
if extension is not None:
477
filename += "." + extension
478
return os.path.join("build", self.board(), "bin", filename)
479
480
def target_to_bin_path(self, target):
481
'''given a build target (e.g. copter), return expected path to .bin
482
file for that target'''
483
return self.target_to_path(target, 'bin')
484
485
def target_to_elf_path(self, target):
486
'''given a build target (e.g. copter), return expected path to .elf
487
file for that target'''
488
return self.target_to_path(target)
489
490
def find_build_sizes(self):
491
'''returns a hash with size of all build targets'''
492
ret = {}
493
for target in self.build_targets:
494
path = self.target_to_bin_path(target)
495
ret[target] = os.path.getsize(path)
496
return ret
497
498
def csv_for_results(self, results):
499
'''return a string with csv for results'''
500
features = sorted(results.keys())
501
all_vehicles = set()
502
for feature in features:
503
all_vehicles.update(list(results[feature].keys()))
504
sorted_all_vehicles = sorted(list(all_vehicles))
505
ret = ""
506
ret += ",".join(["Feature"] + sorted_all_vehicles) + "\n"
507
for feature in features:
508
line = [feature]
509
feature_results = results[feature]
510
for vehicle in sorted_all_vehicles:
511
bytes_delta = ""
512
if vehicle in feature_results:
513
result = feature_results[vehicle]
514
bytes_delta = result.bytes_delta
515
line.append(str(bytes_delta))
516
ret += ",".join(line) + "\n"
517
return ret
518
519
def disable_in_turn_check_sizes(self, feature, sizes_nothing_disabled):
520
if not self.do_step_disable_none:
521
self.progress("disable-none skipped, size comparison not available")
522
return
523
current_sizes = self.find_build_sizes()
524
for (build, new_size) in current_sizes.items():
525
old_size = sizes_nothing_disabled[build]
526
self.progress("Disabling %s(%s) on %s saves %u bytes" %
527
(feature.label, feature.define, build, old_size - new_size))
528
if feature.define not in self.results:
529
self.results[feature.define] = {}
530
self.results[feature.define][build] = TestBuildOptionsResult(feature.define, build, old_size - new_size)
531
with open("/tmp/some.csv", "w") as f:
532
f.write(self.csv_for_results(self.results))
533
534
def run_disable_in_turn(self):
535
progress_file = pathlib.Path("/tmp/run-disable-in-turn-progress")
536
resume_number = self.resume_number_from_progress_Path(progress_file)
537
options = self.get_build_options_from_ardupilot_tree()
538
count = 1
539
for feature in sorted(options, key=lambda x : x.define):
540
if resume_number is not None:
541
if count < resume_number:
542
count += 1
543
continue
544
if self.match_glob is not None:
545
if not fnmatch.fnmatch(feature.define, self.match_glob):
546
continue
547
with open(progress_file, "w") as f:
548
f.write(f"{count}/{len(options)} {feature.define}\n")
549
# if feature.define < "WINCH_ENABLED":
550
# count += 1
551
# continue
552
if feature.define in self.must_have_defines_for_board(self._board):
553
self.progress("Feature %s(%s) (%u/%u) is a MUST-HAVE" %
554
(feature.label, feature.define, count, len(options)))
555
count += 1
556
continue
557
self.progress("Disabling feature %s(%s) (%u/%u)" %
558
(feature.label, feature.define, count, len(options)))
559
self.test_disable_feature(feature, options)
560
count += 1
561
self.disable_in_turn_check_sizes(feature, self.sizes_nothing_disabled)
562
563
def enable_in_turn_check_sizes(self, feature, sizes_everything_disabled):
564
if not self.do_step_disable_all:
565
self.progress("disable-none skipped, size comparison not available")
566
return
567
current_sizes = self.find_build_sizes()
568
for (build, new_size) in current_sizes.items():
569
old_size = sizes_everything_disabled[build]
570
self.progress("Enabling %s(%s) on %s costs %u bytes" %
571
(feature.label, feature.define, build, old_size - new_size))
572
if feature.define not in self.enable_in_turn_results:
573
self.enable_in_turn_results[feature.define] = {}
574
self.enable_in_turn_results[feature.define][build] = TestBuildOptionsResult(feature.define, build, old_size - new_size) # noqa
575
with open("/tmp/enable-in-turn.csv", "w") as f:
576
f.write(self.csv_for_results(self.enable_in_turn_results))
577
578
def resume_number_from_progress_Path(self, progress_file):
579
if not self.resume:
580
return None
581
try:
582
content = progress_file.read_text().rstrip()
583
m = re.match(r"(\d+)/\d+ \w+", content)
584
if m is None:
585
raise ValueError(f"{progress_file} not matched")
586
return int(m.group(1))
587
except FileNotFoundError:
588
pass
589
return None
590
591
def run_enable_in_turn(self):
592
progress_file = pathlib.Path("/tmp/run-enable-in-turn-progress")
593
resume_number = self.resume_number_from_progress_Path(progress_file)
594
options = self.get_build_options_from_ardupilot_tree()
595
count = 1
596
blacklisted_defines = {
597
'AP_NETWORKING_CAN_MCAST_ENABLED': "can't enable this without one of native-ethernet or PPP backends, don't want either in our deps!", # noqa:E501
598
'AP_NETWORKING_CAPTURE_ENABLED': "can't enable this without one of native-ethernet or PPP backends, don't want either in our deps!", # noqa:E501
599
'AP_NETWORKING_ENABLED': "can't enable this without one of native-ethernet or PPP backends, don't want either in our deps!", # noqa:E501
600
}
601
for feature in options:
602
if resume_number is not None:
603
if count < resume_number:
604
count += 1
605
continue
606
if feature.define in blacklisted_defines:
607
continue
608
if self.match_glob is not None:
609
if not fnmatch.fnmatch(feature.define, self.match_glob):
610
continue
611
self.progress("Enabling feature %s(%s) (%u/%u)" %
612
(feature.label, feature.define, count, len(options)))
613
with open(progress_file, "w") as f:
614
f.write(f"{count}/{len(options)} {feature.define}\n")
615
self.test_enable_feature(feature, options)
616
count += 1
617
self.enable_in_turn_check_sizes(feature, self.sizes_everything_disabled)
618
619
def get_option_by_label(self, label, options):
620
for x in options:
621
if x.label == label:
622
return x
623
raise ValueError("No such option (%s)" % label)
624
625
def get_disable_all_defines(self):
626
'''returns a hash of defines which turns all features off'''
627
options = self.get_build_options_from_ardupilot_tree()
628
defines = {}
629
for feature in options:
630
if self.match_glob is not None:
631
if not fnmatch.fnmatch(feature.define, self.match_glob):
632
continue
633
defines[feature.define] = 0
634
for define in self.must_have_defines_for_board(self._board):
635
defines[define] = 1
636
637
return defines
638
639
def run_disable_all(self):
640
defines = self.get_disable_all_defines()
641
self.test_compile_with_defines(defines)
642
self.sizes_everything_disabled = self.find_build_sizes()
643
644
def run_disable_none(self):
645
self.test_compile_with_defines({})
646
self.sizes_nothing_disabled = self.find_build_sizes()
647
648
def run_with_defaults(self):
649
options = self.get_build_options_from_ardupilot_tree()
650
defines = {}
651
for feature in options:
652
defines[feature.define] = feature.default
653
self.test_compile_with_defines(defines)
654
655
def check_deps_consistency(self):
656
# self.progress("Checking deps consistency")
657
options = self.get_build_options_from_ardupilot_tree()
658
for feature in options:
659
self.get_disable_defines(feature, options)
660
661
def check_duplicate_labels(self):
662
'''check that we do not have multiple features with same labels'''
663
options = self.get_build_options_from_ardupilot_tree()
664
seen_labels = {}
665
for feature in options:
666
if seen_labels.get(feature.label, None) is not None:
667
raise ValueError("Duplicate entries found for label '%s'" % feature.label)
668
seen_labels[feature.label] = True
669
670
def do_emit_disable_all_defines(self):
671
defines = tbo.get_disable_all_defines()
672
for f in self.must_have_defines():
673
defines[f] = 1
674
tbo.write_defines_to_Path(defines, pathlib.Path("/dev/stdout"))
675
sys.exit(0)
676
677
def run(self):
678
self.check_deps_consistency()
679
self.check_duplicate_labels()
680
681
if self.emit_disable_all_defines:
682
self.do_emit_disable_all_defines()
683
sys.exit(0)
684
685
if self.do_step_run_with_defaults:
686
self.progress("Running run-with-defaults step")
687
self.run_with_defaults()
688
if self.do_step_disable_all:
689
self.progress("Running disable-all step")
690
self.run_disable_all()
691
if self.do_step_disable_none:
692
self.progress("Running disable-none step")
693
self.run_disable_none()
694
if self.do_step_disable_in_turn:
695
self.progress("Running disable-in-turn step")
696
self.run_disable_in_turn()
697
if self.do_step_enable_in_turn:
698
self.progress("Running enable-in-turn step")
699
self.run_enable_in_turn()
700
701
702
if __name__ == '__main__':
703
704
parser = optparse.OptionParser()
705
parser.add_option("--define-match-glob",
706
type='string',
707
default=None,
708
help='feature define must match this glob to be tested')
709
parser.add_option("--no-run-with-defaults",
710
action='store_true',
711
help='Do not run the run-with-defaults step')
712
parser.add_option("--no-disable-all",
713
action='store_true',
714
help='Do not run the disable-all step')
715
parser.add_option("--no-disable-none",
716
action='store_true',
717
help='Do not run the disable-none step')
718
parser.add_option("--no-disable-in-turn",
719
action='store_true',
720
help='Do not run the disable-in-turn step')
721
parser.add_option("--no-enable-in-turn",
722
action='store_true',
723
help='Do not run the enable-in-turn step')
724
parser.add_option("--build-targets",
725
type='choice',
726
choices=TestBuildOptions.all_targets(),
727
action='append',
728
help='vehicle targets to build')
729
parser.add_option("--extra-hwdef",
730
type='string',
731
default=None,
732
help="file containing extra hwdef information")
733
parser.add_option("--board",
734
type='string',
735
default="CubeOrange",
736
help='board to build for')
737
parser.add_option("--emit-disable-all-defines",
738
action='store_true',
739
help='emit defines used for disabling all features then exit')
740
parser.add_option("--resume",
741
action='store_true',
742
help='resume from previous progress file')
743
744
opts, args = parser.parse_args()
745
746
tbo = TestBuildOptions(
747
match_glob=opts.define_match_glob,
748
do_step_disable_all=not opts.no_disable_all,
749
do_step_disable_none=not opts.no_disable_none,
750
do_step_disable_defaults=not opts.no_run_with_defaults,
751
do_step_disable_in_turn=not opts.no_disable_in_turn,
752
do_step_enable_in_turn=not opts.no_enable_in_turn,
753
build_targets=opts.build_targets,
754
board=opts.board,
755
extra_hwdef=opts.extra_hwdef,
756
emit_disable_all_defines=opts.emit_disable_all_defines,
757
resume=opts.resume,
758
)
759
760
tbo.run()
761
762