Path: blob/master/Tools/ardupilotwaf/ardupilotwaf.py
9872 views
# encoding: utf-812# flake8: noqa34from waflib import Build, ConfigSet, Configure, Context, Errors, Logs, Options, Utils, Task5from waflib.Configure import conf6from waflib.Scripting import run_command7from waflib.TaskGen import before_method, after_method, feature8import os.path, os9from pathlib import Path10from collections import OrderedDict11import subprocess1213SOURCE_EXTS = [14'*.S',15'*.c',16'*.cpp',17]1819COMMON_VEHICLE_DEPENDENT_CAN_LIBRARIES = [20'AP_CANManager',21'AP_KDECAN',22'AP_PiccoloCAN',23'AP_PiccoloCAN/piccolo_protocol',24]2526COMMON_VEHICLE_DEPENDENT_LIBRARIES = [27'AP_AccelCal',28'AP_ADC',29'AP_AHRS',30'AP_Airspeed',31'AP_Baro',32'AP_BattMonitor',33'AP_BoardConfig',34'AP_Camera',35'AP_Common',36'AP_Compass',37'AP_Declination',38'AP_GPS',39'AP_GSOF',40'AP_HAL',41'AP_HAL_Empty',42'AP_DDS',43'AP_InertialSensor',44'AP_Math',45'AP_Mission',46'AP_DAL',47'AP_NavEKF',48'AP_NavEKF2',49'AP_NavEKF3',50'AP_Notify',51'AP_OpticalFlow',52'AP_Param',53'AP_Rally',54'AP_LightWareSerial',55'AP_RangeFinder',56'AP_Scheduler',57'AP_SerialManager',58'AP_Terrain',59'AP_Vehicle',60'AP_InternalError',61'AP_Logger',62'Filter',63'GCS_MAVLink',64'RC_Channel',65'SRV_Channel',66'StorageManager',67'AP_Tuning',68'AP_RPM',69'AP_RSSI',70'AP_Mount',71'AP_Module',72'AP_Button',73'AP_ICEngine',74'AP_Networking',75'AP_Frsky_Telem',76'AP_IBus_Telem',77'AP_FlashStorage',78'AP_Relay',79'AP_ServoRelayEvents',80'AP_Volz_Protocol',81'AP_SBusOut',82'AP_IOMCU',83'AP_Parachute',84'AP_RAMTRON',85'AP_RCProtocol',86'AP_Radio',87'AP_TempCalibration',88'AP_VisualOdom',89'AP_BLHeli',90'AP_ROMFS',91'AP_Proximity',92'AP_Gripper',93'AP_RTC',94'AC_Sprayer',95'AC_Fence',96'AC_Avoidance',97'AP_LandingGear',98'AP_RobotisServo',99'AP_NMEA_Output',100'AP_OSD',101'AP_Filesystem',102'AP_ADSB',103'AP_ADSB/sagetech-sdk',104'AC_PID',105'AP_SerialLED',106'AP_EFI',107'AP_Hott_Telem',108'AP_ESC_Telem',109'AP_Servo_Telem',110'AP_Stats',111'AP_GyroFFT',112'AP_RCTelemetry',113'AP_Generator',114'AP_MSP',115'AP_OLC',116'AP_WheelEncoder',117'AP_ExternalAHRS',118'AP_VideoTX',119'AP_FETtecOneWire',120'AP_TemperatureSensor',121'AP_Torqeedo',122'AP_CustomRotations',123'AP_AIS',124'AP_OpenDroneID',125'AP_CheckFirmware',126'AP_ExternalControl',127'AP_JSON',128'AP_Beacon',129'AP_Arming',130'AP_RCMapper',131'AP_MultiHeap',132'AP_Follow',133]134135def get_legacy_defines(sketch_name, bld):136# If we are building heli, we adjust the build directory define so that137# we do not need to actually split heli and copter directories138if bld.cmd == 'heli' or 'heli' in bld.targets:139return [140'APM_BUILD_DIRECTORY=APM_BUILD_Heli',141'AP_BUILD_TARGET_NAME="' + sketch_name + '"',142]143144return [145'APM_BUILD_DIRECTORY=APM_BUILD_' + sketch_name,146'AP_BUILD_TARGET_NAME="' + sketch_name + '"',147]148149def set_double_precision_flags(flags):150# set up flags for double precision files:151# * remove all single precision constant flags152# * set define to allow double precision math in AP headers153154flags = flags[:] # copy the list to avoid affecting other builds155156# remove GCC and clang single precision constant flags157for opt in ('-fsingle-precision-constant', '-cl-single-precision-constant'):158while True: # might have multiple copies from different sources159try:160flags.remove(opt)161except ValueError:162break163flags.append("-DAP_MATH_ALLOW_DOUBLE_FUNCTIONS=1")164165return flags166167IGNORED_AP_LIBRARIES = [168'doc',169'AP_Scripting', # this gets explicitly included when it is needed and should otherwise never be globbed in170]171172173def ap_autoconfigure(execute_method):174"""175Decorator that enables context commands to run *configure* as needed.176"""177def execute(self):178"""179Wraps :py:func:`waflib.Context.Context.execute` on the context class180"""181if 'tools/' in self.targets:182raise Errors.WafError('\"tools\" name has been replaced with \"tool\" for build please use that!')183if not Configure.autoconfig:184return execute_method(self)185186# Disable autoconfig so waf's version doesn't run (and don't end up on loop of bad configure)187Configure.autoconfig = False188189if self.variant == '':190raise Errors.WafError('The project is badly configured: run "waf configure" again!')191192env = ConfigSet.ConfigSet()193do_config = False194195try:196p = os.path.join(Context.out_dir, Build.CACHE_DIR, self.variant + Build.CACHE_SUFFIX)197env.load(p)198except EnvironmentError:199raise Errors.WafError('The project is not configured for board {0}: run "waf configure --board {0} [...]" first!'.format(self.variant))200201lock_env = ConfigSet.ConfigSet()202203try:204lock_env.load(os.path.join(Context.top_dir, Options.lockfile))205except EnvironmentError:206Logs.warn('Configuring the project')207do_config = True208else:209if lock_env.run_dir != Context.run_dir:210do_config = True211else:212h = 0213214for f in env.CONFIGURE_FILES:215try:216h = Utils.h_list((h, Utils.readf(f, 'rb')))217except EnvironmentError:218do_config = True219break220else:221do_config = h != env.CONFIGURE_HASH222223if do_config:224cmd = lock_env.config_cmd or 'configure'225tmp = Options.options.__dict__226227if env.OPTIONS and sorted(env.OPTIONS.keys()) == sorted(tmp.keys()):228Options.options.__dict__ = env.OPTIONS229else:230raise Errors.WafError('The project configure options have changed: run "waf configure" again!')231232try:233run_command(cmd)234finally:235Options.options.__dict__ = tmp236237run_command(self.cmd)238else:239return execute_method(self)240241return execute242243def ap_configure_post_recurse():244post_recurse_orig = Configure.ConfigurationContext.post_recurse245246def post_recurse(self, node):247post_recurse_orig(self, node)248249self.all_envs[self.variant].CONFIGURE_FILES = self.files250self.all_envs[self.variant].CONFIGURE_HASH = self.hash251252return post_recurse253254@conf255def ap_get_all_libraries(bld):256if bld.env.BOOTLOADER:257# we don't need the full set of libraries for the bootloader build258return ['AP_HAL']259libraries = []260for lib_node in bld.srcnode.ant_glob('libraries/*', dir=True, src=False):261name = lib_node.name262if name in IGNORED_AP_LIBRARIES:263continue264if name.startswith('AP_HAL'):265continue266if name == 'SITL':267continue268libraries.append(name)269libraries.extend(['AP_HAL', 'AP_HAL_Empty'])270libraries.append('AP_PiccoloCAN/piccolo_protocol')271return libraries272273@conf274def ap_common_vehicle_libraries(bld):275libraries = COMMON_VEHICLE_DEPENDENT_LIBRARIES276277if bld.env.with_can or bld.env.HAL_NUM_CAN_IFACES:278libraries.extend(COMMON_VEHICLE_DEPENDENT_CAN_LIBRARIES)279280return libraries281282_grouped_programs = {}283284285class upload_fw_blueos(Task.Task):286def run(self):287# this is rarely used, so we import requests here to avoid the overhead288import requests289binary_path = self.inputs[0].abspath()290# check if .apj file exists for chibios builds291if Path(binary_path + ".apj").exists():292binary_path = binary_path + ".apj"293bld = self.generator.bld294board = bld.bldnode.name.capitalize()295print(f"Uploading {binary_path} to BlueOS at {bld.options.upload_blueos} for board {board}")296url = f'{bld.options.upload_blueos}/ardupilot-manager/v1.0/install_firmware_from_file?board_name={board}'297files = {298'binary': open(binary_path, 'rb')299}300response = requests.post(url, files=files, verify=False)301if response.status_code != 200:302raise Errors.WafError(f"Failed to upload firmware to BlueOS: {response.status_code}: {response.text}")303print("Upload complete")304305def keyword(self):306return "Uploading to BlueOS"307308class check_elf_symbols(Task.Task):309color='CYAN'310always_run = True311def keyword(self):312return "checking symbols"313314def run(self):315'''316check for disallowed symbols in elf file, such as C++ exceptions317'''318elfpath = self.inputs[0].abspath()319320if not self.env.CHECK_SYMBOLS:321# checking symbols disabled on this build322return323324if not self.env.vehicle_binary or self.env.SIM_ENABLED:325# we only want to check symbols for vehicle binaries, allowing examples326# to use C++ exceptions. We also allow them in simulator builds327return328329# we use string find on these symbols, so this catches all types of throw330# calls this should catch all uses of exceptions unless the compiler331# manages to inline them332blacklist = ['std::__throw',333'operator new[](unsigned int)',334'operator new[](unsigned long)',335'operator new(unsigned int)',336'operator new(unsigned long)']337338nmout = subprocess.getoutput("%s -C %s" % (self.env.get_flat('NM'), elfpath))339for b in blacklist:340if nmout.find(b) != -1:341raise Errors.WafError("Disallowed symbol in %s: %s" % (elfpath, b))342343344@feature('post_link')345@after_method('process_source')346def post_link(self):347'''348setup tasks to run after link stage349'''350self.link_task.always_run = True351352link_output = self.link_task.outputs[0]353354check_elf_task = self.create_task('check_elf_symbols', src=link_output)355check_elf_task.set_run_after(self.link_task)356if self.bld.options.upload_blueos and self.env["BOARD_CLASS"] == "LINUX":357_upload_task = self.create_task('upload_fw_blueos', src=link_output)358_upload_task.set_run_after(self.link_task)359360@conf361def ap_program(bld,362program_groups='bin',363program_dir=None,364use_legacy_defines=True,365program_name=None,366vehicle_binary=True,367**kw):368if 'target' in kw:369bld.fatal('Do not pass target for program')370if 'defines' not in kw:371kw['defines'] = []372if 'source' not in kw:373kw['source'] = bld.path.ant_glob(SOURCE_EXTS)374375if not program_name:376program_name = bld.path.name377378if use_legacy_defines:379kw['defines'].extend(get_legacy_defines(bld.path.name, bld))380381kw['features'] = kw.get('features', []) + bld.env.AP_PROGRAM_FEATURES + ['post_link']382383program_groups = Utils.to_list(program_groups)384385if not program_dir:386program_dir = program_groups[0]387388name = os.path.join(program_dir, program_name)389390tg_constructor = bld.program391if bld.env.AP_PROGRAM_AS_STLIB:392tg_constructor = bld.stlib393else:394if bld.env.STATIC_LINKING:395kw['features'].append('static_linking')396397398tg = tg_constructor(399target='#%s' % name,400name=name,401program_name=program_name,402program_dir=program_dir,403**kw404)405406tg.env.vehicle_binary = vehicle_binary407408if 'use' in kw and bld.env.STATIC_LINKING:409# ensure we link against vehicle library410tg.env.STLIB += [kw['use']]411412for group in program_groups:413_grouped_programs.setdefault(group, {}).update({tg.name : tg})414415return tg416417418@conf419def ap_example(bld, **kw):420kw['program_groups'] = 'examples'421ap_program(bld, use_legacy_defines=False, vehicle_binary=False, **kw)422423def unique_list(items):424'''remove duplicate elements from a list while maintaining ordering'''425return list(OrderedDict.fromkeys(items))426427@conf428def ap_stlib(bld, **kw):429if 'name' not in kw:430bld.fatal('Missing name for ap_stlib')431if 'ap_vehicle' not in kw:432bld.fatal('Missing ap_vehicle for ap_stlib')433if 'ap_libraries' not in kw:434bld.fatal('Missing ap_libraries for ap_stlib')435436kw['ap_libraries'] = unique_list(kw['ap_libraries'] + bld.env.AP_LIBRARIES)437for l in kw['ap_libraries']:438bld.ap_library(l, kw['ap_vehicle'])439440if 'dynamic_source' not in kw:441kw['dynamic_source'] = 'modules/DroneCAN/libcanard/dsdlc_generated/src/**.c'442443kw['features'] = kw.get('features', []) + ['cxx', 'cxxstlib']444kw['target'] = kw['name']445kw['source'] = []446447bld.stlib(**kw)448449_created_program_dirs = set()450@feature('cxxstlib', 'cxxprogram')451@before_method('process_rule')452def ap_create_program_dir(self):453if not hasattr(self, 'program_dir'):454return455if self.program_dir in _created_program_dirs:456return457self.bld.bldnode.make_node(self.program_dir).mkdir()458_created_program_dirs.add(self.program_dir)459460@feature('cxxstlib')461@before_method('process_rule')462def ap_stlib_target(self):463if self.target.startswith('#'):464self.target = self.target[1:]465self.target = '#%s' % os.path.join('lib', self.target)466467@conf468def ap_find_tests(bld, use=[], DOUBLE_PRECISION_SOURCES=[]):469if not bld.env.HAS_GTEST:470return471472features = []473if bld.cmd == 'check':474features.append('test')475476use = Utils.to_list(use)477use.append('GTEST')478479includes = [bld.srcnode.abspath() + '/tests/']480481for f in bld.path.ant_glob(incl='*.cpp'):482t = ap_program(483bld,484features=features,485includes=includes,486source=[f],487use=use,488program_name=f.change_ext('').name,489program_groups='tests',490use_legacy_defines=False,491vehicle_binary=False,492cxxflags=['-Wno-undef'],493)494filename = os.path.basename(f.abspath())495if filename in DOUBLE_PRECISION_SOURCES:496t.env.CXXFLAGS = set_double_precision_flags(t.env.CXXFLAGS)497498_versions = []499500@conf501def ap_version_append_str(ctx, k, v, consistent_v=None):502if ctx.env.CONSISTENT_BUILDS and consistent_v is not None:503v = consistent_v # override with consistent value504else:505v = os.environ.get(k, v) # use v unless defined in environment506507ctx.env['AP_VERSION_ITEMS'] += [(k, f'"{v}"')]508509@conf510def ap_version_append_int(ctx, k, v, consistent_v=None):511if ctx.env.CONSISTENT_BUILDS and consistent_v is not None:512v = consistent_v # override with consistent value513else:514v = os.environ.get(k, v) # use v unless defined in environment515516ctx.env['AP_VERSION_ITEMS'] += [(k, f'{v}')]517518@conf519def write_version_header(ctx, tgt):520with open(tgt, 'w') as f:521print(522'''// auto-generated header, do not edit523524#pragma once525526#ifndef FORCE_VERSION_H_INCLUDE527#error ap_version.h should never be included directly. You probably want to include AP_Common/AP_FWVersion.h528#endif529''', file=f)530531for k, v in ctx.env['AP_VERSION_ITEMS']:532print('#define {} {}'.format(k, v), file=f)533534@conf535def ap_find_benchmarks(bld, use=[]):536if not bld.env.HAS_GBENCHMARK:537return538539includes = [bld.srcnode.abspath() + '/benchmarks/']540to_remove = '-Werror=suggest-override'541if to_remove in bld.env.CXXFLAGS:542need_remove = True543else:544need_remove = False545if need_remove:546while to_remove in bld.env.CXXFLAGS:547bld.env.CXXFLAGS.remove(to_remove)548549for f in bld.path.ant_glob(incl='*.cpp'):550ap_program(551bld,552features=['gbenchmark'],553includes=includes,554source=[f],555use=use,556vehicle_binary=False,557program_name=f.change_ext('').name,558program_groups='benchmarks',559use_legacy_defines=False,560)561562def test_summary(bld):563from io import BytesIO564import sys565566if not hasattr(bld, 'utest_results'):567Logs.info('check: no test run')568return569570fails = []571572for filename, exit_code, out, err in bld.utest_results:573Logs.pprint('GREEN' if exit_code == 0 else 'YELLOW',574' %s' % filename,575'returned %d' % exit_code)576577if exit_code != 0:578fails.append(filename)579elif not bld.options.check_verbose:580continue581582if len(out):583buf = BytesIO(out)584for line in buf:585print(" OUT: %s" % line.decode(), end='', file=sys.stderr)586print()587588if len(err):589buf = BytesIO(err)590for line in buf:591print(" ERR: %s" % line.decode(), end='', file=sys.stderr)592print()593594if not fails:595Logs.info('check: All %u tests passed!' % len(bld.utest_results))596return597598Logs.error('check: %u of %u tests failed' %599(len(fails), len(bld.utest_results)))600601for filename in fails:602Logs.error(' %s' % filename)603604bld.fatal('check: some tests failed')605606_build_commands = {}607608def _process_build_command(bld):609if bld.cmd not in _build_commands:610return611612params = _build_commands[bld.cmd]613614targets = params['targets']615if targets:616if bld.targets:617bld.targets += ',' + targets618else:619bld.targets = targets620621program_group_list = Utils.to_list(params['program_group_list'])622bld.options.program_group.extend(program_group_list)623624def build_command(name,625targets=None,626program_group_list=[],627doc='build shortcut'):628_build_commands[name] = dict(629targets=targets,630program_group_list=program_group_list,631)632633class context_class(Build.BuildContext):634cmd = name635context_class.__doc__ = doc636637def _select_programs_from_group(bld):638groups = bld.options.program_group639if not groups:640if bld.targets:641groups = []642else:643groups = ['bin']644645possible_groups = list(_grouped_programs.keys())646possible_groups.remove('bin') # Remove `bin` so as not to duplicate all items in bin647if 'all' in groups:648groups = possible_groups649650for group in groups:651if group not in _grouped_programs:652bld.fatal(f'Group {group} not found, possible groups: {possible_groups}')653654target_names = _grouped_programs[group].keys()655656for name in target_names:657if bld.targets:658bld.targets += ',' + name659else:660bld.targets = name661662def options(opt):663opt.ap_groups = {664'configure': opt.add_option_group('Ardupilot configure options'),665'linux': opt.add_option_group('Linux boards configure options'),666'build': opt.add_option_group('Ardupilot build options'),667'check': opt.add_option_group('Ardupilot check options'),668'clean': opt.add_option_group('Ardupilot clean options'),669}670671g = opt.ap_groups['build']672673g.add_option('--program-group',674action='append',675default=[],676help='''Select all programs that go in <PROGRAM_GROUP>/ for the build.677Example: `waf --program-group examples` builds all examples. The678special group "all" selects all programs.679''')680681g.add_option('--upload',682action='store_true',683help='''Upload applicable targets to a connected device. Not all684platforms may support this. Example: `waf copter --upload` means "build685arducopter and upload it to my board".686''')687688g.add_option('--upload-port',689action='store',690dest='upload_port',691default=None,692help='''Specify the port to be used with the --upload option. For example a port of /dev/ttyS10 indicates that serial port 10 should be used.693''')694695g.add_option('--upload-blueos',696action='store',697dest='upload_blueos',698default=None,699help='''Automatically upload to a BlueOS device. The argument is the url for the device. http://blueos.local for example.700''')701702g.add_option('--upload-force',703action='store_true',704help='''Override board type check and continue loading. Same as using uploader.py --force.705''')706707g.add_option('--define',708action='append',709help='Add C++ define to build.')710711g = opt.ap_groups['check']712713g.add_option('--check-verbose',714action='store_true',715help='Output all test programs.')716717g = opt.ap_groups['clean']718719g.add_option('--asan',720action='store_true',721help='''Build using the macOS clang Address Sanitizer. In order to run with722Address Sanitizer support llvm-symbolizer is required to be on the PATH.723This option is only supported on macOS versions of clang.724''')725726g.add_option('--ubsan',727action='store_true',728help='''Build using the gcc undefined behaviour sanitizer''')729730g.add_option('--ubsan-abort',731action='store_true',732help='''Build using the gcc undefined behaviour sanitizer and abort on error''')733734def build(bld):735bld.add_pre_fun(_process_build_command)736bld.add_pre_fun(_select_programs_from_group)737738739