Path: blob/master/Tools/autotest/test_build_options.py
9692 views
#!/usr/bin/env python312"""3Contains functions used to test the ArduPilot build_options.py structures45To extract feature sizes:67cat >> /tmp/extra-hwdef.dat <<EOF8undef AP_BARO_MS5611_ENABLED9define AP_BARO_MS5611_ENABLED 110EOF1112nice 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 # noqa13grep 'sabling.*saves' /tmp/tbo-out1415- 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 # noqa1617AP_FLAKE8_CLEAN18"""1920import fnmatch21import optparse22import os23import pathlib24import re25import sys2627from pysim import util2829sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..', 'scripts'))30import extract_features # noqa313233class TestBuildOptionsResult(object):34'''object to return results from a comparison'''3536def __init__(self, feature, vehicle, bytes_delta):37self.feature = feature38self.vehicle = vehicle39self.bytes_delta = bytes_delta404142class TestBuildOptions(object):43def __init__(self,44match_glob=None,45do_step_disable_all=True,46do_step_disable_none=False,47do_step_disable_defaults=True,48do_step_disable_in_turn=True,49do_step_enable_in_turn=True,50build_targets=None,51board="CubeOrange", # DevEBoxH7v2 also works52extra_hwdef=None,53emit_disable_all_defines=None,54resume=False,55):56self.extra_hwdef = extra_hwdef57self.sizes_nothing_disabled = None58self.match_glob = match_glob59self.do_step_disable_all = do_step_disable_all60self.do_step_disable_none = do_step_disable_none61self.do_step_run_with_defaults = do_step_disable_defaults62self.do_step_disable_in_turn = do_step_disable_in_turn63self.do_step_enable_in_turn = do_step_enable_in_turn64self.build_targets = build_targets65if self.build_targets is None:66self.build_targets = self.all_targets()67self._board = board68self.emit_disable_all_defines = emit_disable_all_defines69self.resume = resume70self.results = {}7172self.enable_in_turn_results = {}73self.sizes_everything_disabled = None7475def must_have_defines_for_board(self, board):76'''return a set of defines which must always be enabled'''77must_have_defines = {78"CubeOrange": frozenset([79'AP_BARO_MS5611_ENABLED',80'AP_BARO_MS5607_ENABLED',81'AP_COMPASS_LSM303D_ENABLED',82'AP_COMPASS_AK8963_ENABLED',83'AP_COMPASS_AK09916_ENABLED',84'AP_COMPASS_ICM20948_ENABLED',85]),86"CubeBlack": frozenset([87'AP_BARO_MS5611_ENABLED',88'AP_BARO_MS5607_ENABLED',89'AP_COMPASS_LSM303D_ENABLED',90'AP_COMPASS_AK8963_ENABLED',91'AP_COMPASS_AK09916_ENABLED',92'AP_COMPASS_ICM20948_ENABLED',93]),94"Pixhawk6X-GenericVehicle": frozenset([95"AP_BARO_BMP388_ENABLED",96"AP_BARO_ICP201XX_ENABLED",97]),98}99return must_have_defines.get(board, frozenset([]))100101def must_have_defines(self):102return self.must_have_defines_for_board(self._board)103104@staticmethod105def all_targets():106return ['copter', 'plane', 'rover', 'antennatracker', 'sub', 'blimp']107108def progress(self, message):109print("###### %s" % message, file=sys.stderr)110111# swiped from app.py:112def get_build_options_from_ardupilot_tree(self):113'''return a list of build options'''114import importlib.util115spec = importlib.util.spec_from_file_location(116"build_options.py",117os.path.join(os.path.dirname(os.path.realpath(__file__)),118'..', 'scripts', 'build_options.py'))119mod = importlib.util.module_from_spec(spec)120spec.loader.exec_module(mod)121return mod.BUILD_OPTIONS122123def write_defines_to_file(self, defines, filepath):124self.write_defines_to_Path(defines, pathlib.Path(filepath))125126def write_defines_to_Path(self, defines, Path):127lines = []128lines.extend(["undef %s\n" % (a, ) for (a, b) in defines.items()])129lines.extend(["define %s %s\n" % (a, b) for (a, b) in defines.items()])130content = "".join(lines)131Path.write_text(content)132133def get_disable_defines(self, feature, options):134'''returns a hash of (name, value) defines to turn feature off -135recursively gets dependencies'''136ret = {137feature.define: 0,138}139added_one = True140while added_one:141added_one = False142for option in options:143if option.define in ret:144continue145if option.dependency is None:146continue147for dep in option.dependency.split(','):148f = self.get_option_by_label(dep, options)149if f.define not in ret:150continue151152# print("%s requires %s" % (option.define, f.define), file=sys.stderr)153added_one = True154ret[option.define] = 0155break156return ret157158def update_get_enable_defines_for_feature(self, ret, feature, options):159'''recursive function to turn on required feature and what it depends160on'''161ret[feature.define] = 1162if feature.dependency is None:163return164for depname in feature.dependency.split(','):165dep = None166for f in options:167if f.label == depname:168dep = f169if dep is None:170raise ValueError("Invalid dep (%s) for feature (%s)" %171(depname, feature.label))172self.update_get_enable_defines_for_feature(ret, dep, options)173174def get_enable_defines(self, feature, options):175'''returns a hash of (name, value) defines to turn all features *but* feature (and whatever it depends on) on'''176ret = self.get_disable_all_defines()177self.update_get_enable_defines_for_feature(ret, feature, options)178for define in self.must_have_defines_for_board(self._board):179ret[define] = 1180return ret181182def test_disable_feature(self, feature, options):183defines = self.get_disable_defines(feature, options)184185if len(defines.keys()) > 1:186self.progress("Disabling %s disables (%s)" % (187feature.define,188",".join(defines.keys())))189190self.test_compile_with_defines(defines)191192self.assert_feature_not_in_code(defines, feature)193194def assert_feature_not_in_code(self, defines, feature):195# if the feature is truly disabled then extract_features.py196# should say so:197for target in self.build_targets:198path = self.target_to_elf_path(target)199extractor = extract_features.ExtractFeatures(path)200(compiled_in_feature_defines, not_compiled_in_feature_defines) = extractor.extract()201for define in defines:202# the following defines are known not to work on some203# or all vehicles:204feature_define_whitelist = set([205'AP_RANGEFINDER_ENABLED', # only at vehicle level ATM206'HAL_PERIPH_SUPPORT_LONG_CAN_PRINTF', # no symbol207'AP_PROXIMITY_HEXSOONRADAR_ENABLED', # this shares symbols with AP_PROXIMITY_MR72_ENABLED208'AP_PROXIMITY_MR72_ENABLED', # this shares symbols with AP_PROXIMITY_HEXSOONRADAR_ENABLED209'AP_RANGEFINDER_NRA24_CAN_ENABLED',210'AP_RANGEFINDER_HEXSOONRADAR_ENABLED',211])212if define in compiled_in_feature_defines:213error = f"feature gated by {define} still compiled into ({target}); extract_features.py bug?"214if define in feature_define_whitelist:215print("warn: " + error)216else:217raise ValueError(error)218219def test_enable_feature(self, feature, options):220defines = self.get_enable_defines(feature, options)221222enabled = list(filter(lambda x : bool(defines[x]), defines.keys()))223224if len(enabled) > 1:225self.progress("Enabling %s enables (%s)" % (226feature.define,227",".join(enabled)))228229self.test_compile_with_defines(defines)230231self.assert_feature_in_code(defines, feature)232233def define_is_whitelisted_for_feature_in_code(self, target, define):234'''returns true if we can not expect the define to be extracted from235the binary'''236# the following defines are known not to work on some237# or all vehicles:238feature_define_whitelist = set([239'AC_POLYFENCE_CIRCLE_INT_SUPPORT_ENABLED', # no symbol240'AP_RANGEFINDER_ENABLED', # only at vehicle level ATM241'HAL_PERIPH_SUPPORT_LONG_CAN_PRINTF', # no symbol242'AP_DRONECAN_VOLZ_FEEDBACK_ENABLED', # broken, no subscriber243# Baro drivers either come in because you have244# external-probing enabled or you have them specified in245# your hwdef. If you're not probing and its not in your246# hwdef then the code will be elided as unreachable247'AP_BARO_ICM20789_ENABLED',248'AP_BARO_ICP101XX_ENABLED',249'AP_BARO_ICP201XX_ENABLED',250'AP_BARO_BMP085_ENABLED',251'AP_BARO_BMP280_ENABLED',252'AP_BARO_BMP388_ENABLED',253'AP_BARO_BMP581_ENABLED',254'AP_BARO_DPS280_ENABLED',255'AP_BARO_FBM320_ENABLED',256'AP_BARO_KELLERLD_ENABLED',257'AP_BARO_LPS2XH_ENABLED',258'AP_BARO_MS5607_ENABLED',259'AP_BARO_MS5611_ENABLED',260'AP_BARO_MS5637_ENABLED',261'AP_BARO_MS5837_ENABLED',262'AP_BARO_SPL06_ENABLED',263'AP_CAMERA_SEND_FOV_STATUS_ENABLED', # elided unless AP_CAMERA_SEND_FOV_STATUS_ENABLED264'AP_COMPASS_LSM9DS1_ENABLED', # must be in hwdef, not probed265'AP_COMPASS_MAG3110_ENABLED', # must be in hwdef, not probed266'AP_COMPASS_MMC5XX3_ENABLED', # must be in hwdef, not probed267'AP_MAVLINK_AUTOPILOT_VERSION_REQUEST_ENABLED', # completely elided268'AP_MAVLINK_MSG_RELAY_STATUS_ENABLED', # no symbol available269'AP_MAVLINK_MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES_ENABLED', # no symbol available270'HAL_MSP_SENSORS_ENABLED', # no symbol available271'AP_OSD_LINK_STATS_EXTENSIONS_ENABLED', # FIXME: need a new define/feature272'HAL_OSD_SIDEBAR_ENABLE', # FIXME: need a new define/feature273'HAL_PLUSCODE_ENABLE', # FIXME: need a new define/feature274'AP_SERIALMANAGER_REGISTER_ENABLED', # completely elided without a caller275'AP_OPTICALFLOW_ONBOARD_ENABLED', # only instantiated on Linux276'HAL_WITH_FRSKY_TELEM_BIDIRECTIONAL', # entirely elided if no user277'AP_PLANE_BLACKBOX_LOGGING', # entirely elided if no user278'AP_COMPASS_AK8963_ENABLED', # probed on a board-by-board basis, not on CubeOrange for example279'AP_COMPASS_LSM303D_ENABLED', # probed on a board-by-board basis, not on CubeOrange for example280'AP_BARO_THST_COMP_ENABLED', # compiler is optimising this symbol away281])282if target.lower() != "copter":283feature_define_whitelist.add('MODE_ZIGZAG_ENABLED')284feature_define_whitelist.add('MODE_SYSTEMID_ENABLED')285feature_define_whitelist.add('MODE_SPORT_ENABLED')286feature_define_whitelist.add('MODE_FOLLOW_ENABLED')287feature_define_whitelist.add('MODE_TURTLE_ENABLED')288feature_define_whitelist.add('MODE_GUIDED_NOGPS_ENABLED')289feature_define_whitelist.add('MODE_FLOWHOLD_ENABLED')290feature_define_whitelist.add('MODE_FLIP_ENABLED')291feature_define_whitelist.add('MODE_BRAKE_ENABLED')292feature_define_whitelist.add('AP_TEMPCALIBRATION_ENABLED')293feature_define_whitelist.add('AC_PAYLOAD_PLACE_ENABLED')294feature_define_whitelist.add('AP_AVOIDANCE_ENABLED')295feature_define_whitelist.add('AP_WINCH_ENABLED')296feature_define_whitelist.add('AP_WINCH_DAIWA_ENABLED')297feature_define_whitelist.add('AP_WINCH_PWM_ENABLED')298feature_define_whitelist.add(r'AP_MOTORS_FRAME_.*_ENABLED')299feature_define_whitelist.add('AP_MOTORS_TRI_ENABLED')300feature_define_whitelist.add('AP_COPTER_ADVANCED_FAILSAFE_ENABLED')301feature_define_whitelist.add('AP_INERTIALSENSOR_FAST_SAMPLE_WINDOW_ENABLED')302feature_define_whitelist.add('AP_COPTER_AHRS_AUTO_TRIM_ENABLED')303feature_define_whitelist.add('AP_RC_TRANSMITTER_TUNING_ENABLED')304305if target.lower() in ['antennatracker', 'blimp', 'sub', 'plane', 'copter']:306# plane has a dependency for AP_Follow which is not307# declared in build_options.py; we don't compile follow308# support for Follow into Plane unless scripting is also309# enabled. Copter manages to elide everything is310# MODE_FOLLOW isn't enabled.311feature_define_whitelist.add('AP_FOLLOW_ENABLED')312313if target.lower() != "plane":314# only on Plane:315feature_define_whitelist.add('AP_ICENGINE_ENABLED')316feature_define_whitelist.add('AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED')317feature_define_whitelist.add('AP_MAVLINK_MAV_CMD_SET_HAGL_ENABLED')318feature_define_whitelist.add('AP_ADVANCEDFAILSAFE_ENABLED')319feature_define_whitelist.add('AP_TUNING_ENABLED')320feature_define_whitelist.add('HAL_LANDING_DEEPSTALL_ENABLED')321feature_define_whitelist.add('HAL_SOARING_ENABLED')322feature_define_whitelist.add('AP_PLANE_BLACKBOX_LOGGING')323feature_define_whitelist.add('QAUTOTUNE_ENABLED')324feature_define_whitelist.add('AP_PLANE_OFFBOARD_GUIDED_SLEW_ENABLED')325feature_define_whitelist.add('HAL_QUADPLANE_ENABLED')326feature_define_whitelist.add('AP_BATTERY_WATT_MAX_ENABLED')327feature_define_whitelist.add('MODE_AUTOLAND_ENABLED')328feature_define_whitelist.add('AP_PLANE_GLIDER_PULLUP_ENABLED')329feature_define_whitelist.add('AP_QUICKTUNE_ENABLED')330feature_define_whitelist.add('AP_PLANE_SYSTEMID_ENABLED')331332if target.lower() not in ["plane", "copter"]:333feature_define_whitelist.add('HAL_ADSB_ENABLED')334feature_define_whitelist.add('AP_LANDINGGEAR_ENABLED')335# only Plane and Copter instantiate Parachute336feature_define_whitelist.add('HAL_PARACHUTE_ENABLED')337# only Plane and Copter have AP_Motors:338feature_define_whitelist.add(r'AP_MOTORS_TRI_ENABLED')339# other vehicles do not instantiate ADSB:340feature_define_whitelist.add('AP_ADSB_AVOIDANCE_ENABLED')341# only Plane and Copter instantiate the Motors library,342# required for these bindings:343feature_define_whitelist.add('AP_SCRIPTING_BINDING_MOTORS_ENABLED')344345if target.lower() not in ["rover", "copter"]:346# only Plane and Copter instantiate Beacon347feature_define_whitelist.add('AP_BEACON_ENABLED')348349if target.lower() != "rover":350# only on Rover:351feature_define_whitelist.add('HAL_TORQEEDO_ENABLED')352feature_define_whitelist.add('AP_ROVER_ADVANCED_FAILSAFE_ENABLED')353feature_define_whitelist.add('AP_ROVER_AUTO_ARM_ONCE_ENABLED')354if target.lower() != "sub":355# only on Sub:356feature_define_whitelist.add('AP_BARO_KELLERLD_ENABLED')357if target.lower() not in frozenset(["rover", "sub"]):358# only Rover and Sub get nmea airspeed359feature_define_whitelist.add('AP_AIRSPEED_NMEA_ENABLED')360if target.lower() not in frozenset(["copter", "rover"]):361feature_define_whitelist.add('HAL_SPRAYER_ENABLED')362feature_define_whitelist.add('HAL_PROXIMITY_ENABLED')363feature_define_whitelist.add('AP_PROXIMITY_.*_ENABLED')364feature_define_whitelist.add('AP_OAPATHPLANNER_ENABLED')365366if target.lower() in ["blimp", "antennatracker"]:367# no airspeed on blimp/tracker368feature_define_whitelist.add(r'AP_AIRSPEED_.*_ENABLED')369feature_define_whitelist.add(r'HAL_MOUNT_ENABLED')370feature_define_whitelist.add(r'AP_MOUNT_.*_ENABLED')371feature_define_whitelist.add(r'HAL_MOUNT_.*_ENABLED')372feature_define_whitelist.add(r'HAL_SOLO_GIMBAL_ENABLED')373feature_define_whitelist.add(r'AP_OPTICALFLOW_ENABLED')374feature_define_whitelist.add(r'AP_OPTICALFLOW_.*_ENABLED')375feature_define_whitelist.add(r'HAL_MSP_OPTICALFLOW_ENABLED')376# missing calls to fence.check():377feature_define_whitelist.add(r'AP_FENCE_ENABLED')378# RPM not instantiated on Blimp or Rover:379feature_define_whitelist.add(r'AP_RPM_ENABLED')380feature_define_whitelist.add(r'AP_RPM_.*_ENABLED')381# rangefinder init is not called:382feature_define_whitelist.add(r'HAL_MSP_RANGEFINDER_ENABLED')383# these guys don't instantiate anything which uses sd-card storage:384feature_define_whitelist.add(r'AP_SDCARD_STORAGE_ENABLED')385feature_define_whitelist.add(r'AP_RANGEFINDER_ENABLED')386feature_define_whitelist.add(r'AP_RANGEFINDER_.*_ENABLED')387388if target.lower() in ["blimp", "antennatracker", "sub"]:389# no OSD on Sub/blimp/tracker390feature_define_whitelist.add(r'OSD_ENABLED')391feature_define_whitelist.add(r'OSD_PARAM_ENABLED')392# AP_OSD is not instantiated, , so no MSP backend:393feature_define_whitelist.add(r'HAL_WITH_MSP_DISPLAYPORT')394feature_define_whitelist.add(r'AP_MSP_INAV_FONTS_ENABLED')395# camera instantiated in specific vehicles:396feature_define_whitelist.add(r'AP_CAMERA_ENABLED')397feature_define_whitelist.add(r'AP_CAMERA_.*_ENABLED')398# button update is not called in these vehicles399feature_define_whitelist.add(r'HAL_BUTTON_ENABLED')400# precland not instantiated on these vehicles401feature_define_whitelist.add(r'AC_PRECLAND_ENABLED')402feature_define_whitelist.add(r'AC_PRECLAND_.*_ENABLED')403# RSSI is not initialised - probably should be for some404feature_define_whitelist.add(r'AP_RSSI_ENABLED')405406if target.lower() in ["antennatracker", "sub"]:407# missing the init call to the relay library:408feature_define_whitelist.add(r'AP_RELAY_ENABLED')409feature_define_whitelist.add(r'AP_RC_CHANNEL_AUX_FUNCTION_STRINGS_ENABLED')410411if target.lower() in {"antennatracker", "blimp", "rover"}:412# these don't instantiate terrain413feature_define_whitelist.add('EK3_FEATURE_OPTFLOW_SRTM')414415if target.lower() not in ["AP_Periph"]:416feature_define_whitelist.add(r'AP_PERIPH_.*')417418for some_re in feature_define_whitelist:419if re.match(some_re, define):420return True421422def assert_feature_in_code(self, defines, feature):423# if the feature is truly disabled then extract_features.py424# should say so:425for target in self.build_targets:426path = self.target_to_elf_path(target)427extractor = extract_features.ExtractFeatures(path)428(compiled_in_feature_defines, not_compiled_in_feature_defines) = extractor.extract()429for define in defines:430if not defines[define]:431continue432if define in compiled_in_feature_defines:433continue434error = f"feature gated by {define} not compiled into ({target}); extract_features.py bug?"435if self.define_is_whitelisted_for_feature_in_code(target, define):436print("warn: " + error)437continue438raise ValueError(error)439440def board(self):441'''returns board to build for'''442return self._board443444def test_compile_with_defines(self, defines):445extra_hwdef_filepath = "/tmp/extra.hwdef"446self.write_defines_to_file(defines, extra_hwdef_filepath)447if self.extra_hwdef is not None:448content = open(self.extra_hwdef, "r").read()449with open(extra_hwdef_filepath, "a") as f:450f.write(content)451util.waf_configure(452self.board(),453extra_hwdef=extra_hwdef_filepath,454)455for t in self.build_targets:456try:457util.run_cmd([util.relwaf(), t])458except Exception:459print("Failed to build (%s) with things disabled" %460(t,))461raise462463def target_to_path(self, target, extension=None):464'''given a build target (e.g. copter), return expected path to .bin465file for that target'''466target_to_binpath = {467"copter": "arducopter",468"plane": "arduplane",469"rover": "ardurover",470"antennatracker": "antennatracker",471"sub": "ardusub",472"blimp": "blimp",473}474filename = target_to_binpath[target]475if extension is not None:476filename += "." + extension477return os.path.join("build", self.board(), "bin", filename)478479def target_to_bin_path(self, target):480'''given a build target (e.g. copter), return expected path to .bin481file for that target'''482return self.target_to_path(target, 'bin')483484def target_to_elf_path(self, target):485'''given a build target (e.g. copter), return expected path to .elf486file for that target'''487return self.target_to_path(target)488489def find_build_sizes(self):490'''returns a hash with size of all build targets'''491ret = {}492for target in self.build_targets:493path = self.target_to_bin_path(target)494ret[target] = os.path.getsize(path)495return ret496497def csv_for_results(self, results):498'''return a string with csv for results'''499features = sorted(results.keys())500all_vehicles = set()501for feature in features:502all_vehicles.update(list(results[feature].keys()))503sorted_all_vehicles = sorted(list(all_vehicles))504ret = ""505ret += ",".join(["Feature"] + sorted_all_vehicles) + "\n"506for feature in features:507line = [feature]508feature_results = results[feature]509for vehicle in sorted_all_vehicles:510bytes_delta = ""511if vehicle in feature_results:512result = feature_results[vehicle]513bytes_delta = result.bytes_delta514line.append(str(bytes_delta))515ret += ",".join(line) + "\n"516return ret517518def disable_in_turn_check_sizes(self, feature, sizes_nothing_disabled):519if not self.do_step_disable_none:520self.progress("disable-none skipped, size comparison not available")521return522current_sizes = self.find_build_sizes()523for (build, new_size) in current_sizes.items():524old_size = sizes_nothing_disabled[build]525self.progress("Disabling %s(%s) on %s saves %u bytes" %526(feature.label, feature.define, build, old_size - new_size))527if feature.define not in self.results:528self.results[feature.define] = {}529self.results[feature.define][build] = TestBuildOptionsResult(feature.define, build, old_size - new_size)530with open("/tmp/some.csv", "w") as f:531f.write(self.csv_for_results(self.results))532533def run_disable_in_turn(self):534progress_file = pathlib.Path("/tmp/run-disable-in-turn-progress")535resume_number = self.resume_number_from_progress_Path(progress_file)536options = self.get_build_options_from_ardupilot_tree()537count = 1538for feature in sorted(options, key=lambda x : x.define):539if resume_number is not None:540if count < resume_number:541count += 1542continue543if self.match_glob is not None:544if not fnmatch.fnmatch(feature.define, self.match_glob):545continue546with open(progress_file, "w") as f:547f.write(f"{count}/{len(options)} {feature.define}\n")548# if feature.define < "WINCH_ENABLED":549# count += 1550# continue551if feature.define in self.must_have_defines_for_board(self._board):552self.progress("Feature %s(%s) (%u/%u) is a MUST-HAVE" %553(feature.label, feature.define, count, len(options)))554count += 1555continue556self.progress("Disabling feature %s(%s) (%u/%u)" %557(feature.label, feature.define, count, len(options)))558self.test_disable_feature(feature, options)559count += 1560self.disable_in_turn_check_sizes(feature, self.sizes_nothing_disabled)561562def enable_in_turn_check_sizes(self, feature, sizes_everything_disabled):563if not self.do_step_disable_all:564self.progress("disable-none skipped, size comparison not available")565return566current_sizes = self.find_build_sizes()567for (build, new_size) in current_sizes.items():568old_size = sizes_everything_disabled[build]569self.progress("Enabling %s(%s) on %s costs %u bytes" %570(feature.label, feature.define, build, old_size - new_size))571if feature.define not in self.enable_in_turn_results:572self.enable_in_turn_results[feature.define] = {}573self.enable_in_turn_results[feature.define][build] = TestBuildOptionsResult(feature.define, build, old_size - new_size) # noqa574with open("/tmp/enable-in-turn.csv", "w") as f:575f.write(self.csv_for_results(self.enable_in_turn_results))576577def resume_number_from_progress_Path(self, progress_file):578if not self.resume:579return None580try:581content = progress_file.read_text().rstrip()582m = re.match(r"(\d+)/\d+ \w+", content)583if m is None:584raise ValueError(f"{progress_file} not matched")585return int(m.group(1))586except FileNotFoundError:587pass588return None589590def run_enable_in_turn(self):591progress_file = pathlib.Path("/tmp/run-enable-in-turn-progress")592resume_number = self.resume_number_from_progress_Path(progress_file)593options = self.get_build_options_from_ardupilot_tree()594count = 1595blacklisted_defines = {596'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:E501597'AP_NETWORKING_CAPTURE_ENABLED': "can't enable this without one of native-ethernet or PPP backends, don't want either in our deps!", # noqa:E501598'AP_NETWORKING_ENABLED': "can't enable this without one of native-ethernet or PPP backends, don't want either in our deps!", # noqa:E501599}600for feature in options:601if resume_number is not None:602if count < resume_number:603count += 1604continue605if feature.define in blacklisted_defines:606continue607if self.match_glob is not None:608if not fnmatch.fnmatch(feature.define, self.match_glob):609continue610self.progress("Enabling feature %s(%s) (%u/%u)" %611(feature.label, feature.define, count, len(options)))612with open(progress_file, "w") as f:613f.write(f"{count}/{len(options)} {feature.define}\n")614self.test_enable_feature(feature, options)615count += 1616self.enable_in_turn_check_sizes(feature, self.sizes_everything_disabled)617618def get_option_by_label(self, label, options):619for x in options:620if x.label == label:621return x622raise ValueError("No such option (%s)" % label)623624def get_disable_all_defines(self):625'''returns a hash of defines which turns all features off'''626options = self.get_build_options_from_ardupilot_tree()627defines = {}628for feature in options:629if self.match_glob is not None:630if not fnmatch.fnmatch(feature.define, self.match_glob):631continue632defines[feature.define] = 0633for define in self.must_have_defines_for_board(self._board):634defines[define] = 1635636return defines637638def run_disable_all(self):639defines = self.get_disable_all_defines()640self.test_compile_with_defines(defines)641self.sizes_everything_disabled = self.find_build_sizes()642643def run_disable_none(self):644self.test_compile_with_defines({})645self.sizes_nothing_disabled = self.find_build_sizes()646647def run_with_defaults(self):648options = self.get_build_options_from_ardupilot_tree()649defines = {}650for feature in options:651defines[feature.define] = feature.default652self.test_compile_with_defines(defines)653654def check_deps_consistency(self):655# self.progress("Checking deps consistency")656options = self.get_build_options_from_ardupilot_tree()657for feature in options:658self.get_disable_defines(feature, options)659660def check_duplicate_labels(self):661'''check that we do not have multiple features with same labels'''662options = self.get_build_options_from_ardupilot_tree()663seen_labels = {}664for feature in options:665if seen_labels.get(feature.label, None) is not None:666raise ValueError("Duplicate entries found for label '%s'" % feature.label)667seen_labels[feature.label] = True668669def do_emit_disable_all_defines(self):670defines = tbo.get_disable_all_defines()671for f in self.must_have_defines():672defines[f] = 1673tbo.write_defines_to_Path(defines, pathlib.Path("/dev/stdout"))674sys.exit(0)675676def run(self):677self.check_deps_consistency()678self.check_duplicate_labels()679680if self.emit_disable_all_defines:681self.do_emit_disable_all_defines()682sys.exit(0)683684if self.do_step_run_with_defaults:685self.progress("Running run-with-defaults step")686self.run_with_defaults()687if self.do_step_disable_all:688self.progress("Running disable-all step")689self.run_disable_all()690if self.do_step_disable_none:691self.progress("Running disable-none step")692self.run_disable_none()693if self.do_step_disable_in_turn:694self.progress("Running disable-in-turn step")695self.run_disable_in_turn()696if self.do_step_enable_in_turn:697self.progress("Running enable-in-turn step")698self.run_enable_in_turn()699700701if __name__ == '__main__':702703parser = optparse.OptionParser()704parser.add_option("--define-match-glob",705type='string',706default=None,707help='feature define must match this glob to be tested')708parser.add_option("--no-run-with-defaults",709action='store_true',710help='Do not run the run-with-defaults step')711parser.add_option("--no-disable-all",712action='store_true',713help='Do not run the disable-all step')714parser.add_option("--no-disable-none",715action='store_true',716help='Do not run the disable-none step')717parser.add_option("--no-disable-in-turn",718action='store_true',719help='Do not run the disable-in-turn step')720parser.add_option("--no-enable-in-turn",721action='store_true',722help='Do not run the enable-in-turn step')723parser.add_option("--build-targets",724type='choice',725choices=TestBuildOptions.all_targets(),726action='append',727help='vehicle targets to build')728parser.add_option("--extra-hwdef",729type='string',730default=None,731help="file containing extra hwdef information")732parser.add_option("--board",733type='string',734default="CubeOrange",735help='board to build for')736parser.add_option("--emit-disable-all-defines",737action='store_true',738help='emit defines used for disabling all features then exit')739parser.add_option("--resume",740action='store_true',741help='resume from previous progress file')742743opts, args = parser.parse_args()744745tbo = TestBuildOptions(746match_glob=opts.define_match_glob,747do_step_disable_all=not opts.no_disable_all,748do_step_disable_none=not opts.no_disable_none,749do_step_disable_defaults=not opts.no_run_with_defaults,750do_step_disable_in_turn=not opts.no_disable_in_turn,751do_step_enable_in_turn=not opts.no_enable_in_turn,752build_targets=opts.build_targets,753board=opts.board,754extra_hwdef=opts.extra_hwdef,755emit_disable_all_defines=opts.emit_disable_all_defines,756resume=opts.resume,757)758759tbo.run()760761762