ardupilot/Tools/ardupilotwaf/ardupilotwaf.py

405 lines
10 KiB
Python

#!/usr/bin/env python
# encoding: utf-8
from __future__ import print_function
from waflib import Logs, Options, Utils
from waflib.Build import BuildContext
from waflib.Configure import conf
from waflib.TaskGen import before_method, feature
import os.path, os
from collections import OrderedDict
SOURCE_EXTS = [
'*.S',
'*.c',
'*.cpp',
]
UTILITY_SOURCE_EXTS = [ 'utility/' + glob for glob in SOURCE_EXTS ]
COMMON_VEHICLE_DEPENDENT_LIBRARIES = [
'AP_AccelCal',
'AP_ADC',
'AP_AHRS',
'AP_Airspeed',
'AP_Baro',
'AP_BattMonitor',
'AP_BoardConfig',
'AP_Buffer',
'AP_Common',
'AP_Compass',
'AP_Declination',
'AP_GPS',
'AP_HAL',
'AP_HAL_Empty',
'AP_InertialSensor',
'AP_Math',
'AP_Mission',
'AP_NavEKF',
'AP_NavEKF2',
'AP_Notify',
'AP_OpticalFlow',
'AP_Param',
'AP_Rally',
'AP_RangeFinder',
'AP_Scheduler',
'AP_SerialManager',
'AP_Terrain',
'AP_Vehicle',
'DataFlash',
'Filter',
'GCS_MAVLink',
'RC_Channel',
'StorageManager',
'AP_Tuning',
'AP_RPM',
'AP_RSSI',
'AP_Mount',
'AP_Module',
'AP_Button',
'AP_ICEngine',
]
def _get_legacy_defines(sketch_name):
return [
'APM_BUILD_DIRECTORY=APM_BUILD_' + sketch_name,
'SKETCH="' + sketch_name + '"',
'SKETCHNAME="' + sketch_name + '"',
]
IGNORED_AP_LIBRARIES = [
'doc',
'GCS_Console',
]
@conf
def ap_get_all_libraries(bld):
libraries = []
for lib_node in bld.srcnode.ant_glob('libraries/*', dir=True, src=False):
name = lib_node.name
if name in IGNORED_AP_LIBRARIES:
continue
if name.startswith('AP_HAL'):
continue
if name == 'SITL':
continue
libraries.append(name)
libraries.extend(['AP_HAL', 'AP_HAL_Empty'])
return libraries
@conf
def ap_common_vehicle_libraries(bld):
return COMMON_VEHICLE_DEPENDENT_LIBRARIES
_grouped_programs = {}
@conf
def ap_program(bld,
program_groups='bin',
program_dir=None,
use_legacy_defines=True,
program_name=None,
**kw):
if 'target' in kw:
bld.fatal('Do not pass target for program')
if 'defines' not in kw:
kw['defines'] = []
if 'source' not in kw:
kw['source'] = bld.path.ant_glob(SOURCE_EXTS)
if not program_name:
program_name = bld.path.name
if use_legacy_defines:
kw['defines'].extend(_get_legacy_defines(bld.path.name))
kw['cxxflags'] = kw.get('cxxflags', []) + ['-include', 'ap_config.h']
kw['features'] = kw.get('features', []) + bld.env.AP_PROGRAM_FEATURES
program_groups = Utils.to_list(program_groups)
if not program_dir:
program_dir = program_groups[0]
name = os.path.join(program_dir, program_name)
tg_constructor = bld.program
if bld.env.AP_PROGRAM_AS_STLIB:
tg_constructor = bld.stlib
else:
if bld.env.STATIC_LINKING:
kw['features'].append('static_linking')
tg = tg_constructor(
target='#%s' % name,
name=name,
program_name=program_name,
program_dir=program_dir,
**kw
)
for group in program_groups:
_grouped_programs.setdefault(group, []).append(tg)
@conf
def ap_example(bld, **kw):
kw['program_groups'] = 'examples'
ap_program(bld, use_legacy_defines=False, **kw)
# NOTE: Code in libraries/ is compiled multiple times. So ensure each
# compilation is independent by providing different index for each.
# The need for this should disappear when libraries change to be
# independent of vehicle type.
LAST_IDX = 0
def _get_next_idx():
global LAST_IDX
LAST_IDX += 1
return LAST_IDX
def unique_list(items):
'''remove duplicate elements from a list while maintaining ordering'''
return list(OrderedDict.fromkeys(items))
@conf
def ap_stlib(bld, **kw):
if 'name' not in kw:
bld.fatal('Missing name for ap_stlib')
if 'vehicle' not in kw:
bld.fatal('Missing vehicle for ap_stlib')
if 'libraries' not in kw:
bld.fatal('Missing libraries for ap_stlib')
sources = []
libraries = unique_list(kw['libraries'] + bld.env.AP_LIBRARIES)
for lib_name in libraries:
lib_node = bld.srcnode.find_dir('libraries/' + lib_name)
if lib_node is None:
bld.fatal('Could not find library ' + lib_name)
lib_sources = lib_node.ant_glob(SOURCE_EXTS + UTILITY_SOURCE_EXTS)
sources.extend(lib_sources)
kw['cxxflags'] = kw.get('cxxflags', []) + ['-include', 'ap_config.h']
kw['features'] = kw.get('features', []) + bld.env.AP_STLIB_FEATURES
kw['source'] = sources
kw['target'] = kw['name']
kw['defines'] = _get_legacy_defines(kw['vehicle'])
kw['idx'] = _get_next_idx()
bld.stlib(**kw)
_created_program_dirs = set()
@feature('cxxstlib', 'cxxprogram')
@before_method('process_rule')
def ap_create_program_dir(self):
if not hasattr(self, 'program_dir'):
return
if self.program_dir in _created_program_dirs:
return
self.bld.bldnode.make_node(self.program_dir).mkdir()
_created_program_dirs.add(self.program_dir)
@feature('cxxstlib')
@before_method('process_rule')
def ap_stlib_target(self):
if self.target.startswith('#'):
self.target = self.target[1:]
self.target = '#%s' % os.path.join('lib', self.target)
@conf
def ap_find_tests(bld, use=[]):
if not bld.env.HAS_GTEST:
return
features = []
if bld.cmd == 'check':
features.append('test')
use = Utils.to_list(use)
use.append('GTEST')
includes = [bld.srcnode.abspath() + '/tests/']
for f in bld.path.ant_glob(incl='*.cpp'):
ap_program(
bld,
features=features,
includes=includes,
source=[f],
use=use,
program_name=f.change_ext('').name,
program_groups='tests',
use_legacy_defines=False,
cxxflags=['-Wno-undef'],
)
_versions = []
@conf
def ap_version_append_str(ctx, k, v):
ctx.env['AP_VERSION_ITEMS'] += [(k, '"{}"'.format(os.environ.get(k, v)))]
@conf
def write_version_header(ctx, tgt):
with open(tgt, 'w') as f:
print('#pragma once\n', file=f)
for k, v in ctx.env['AP_VERSION_ITEMS']:
print('#define {} {}'.format(k, v), file=f)
@conf
def ap_find_benchmarks(bld, use=[]):
if not bld.env.HAS_GBENCHMARK:
return
includes = [bld.srcnode.abspath() + '/benchmarks/']
for f in bld.path.ant_glob(incl='*.cpp'):
ap_program(
bld,
features=['gbenchmark'],
includes=includes,
source=[f],
use=use,
program_name=f.change_ext('').name,
program_groups='benchmarks',
use_legacy_defines=False,
)
def test_summary(bld):
from io import BytesIO
import sys
if not hasattr(bld, 'utest_results'):
Logs.info('check: no test run')
return
fails = []
for filename, exit_code, out, err in bld.utest_results:
Logs.pprint('GREEN' if exit_code == 0 else 'YELLOW',
' %s' % filename,
'returned %d' % exit_code)
if exit_code != 0:
fails.append(filename)
elif not bld.options.check_verbose:
continue
if len(out):
buf = BytesIO(out)
for line in buf:
print(" OUT: %s" % line.decode(), end='', file=sys.stderr)
print()
if len(err):
buf = BytesIO(err)
for line in buf:
print(" ERR: %s" % line.decode(), end='', file=sys.stderr)
print()
if not fails:
Logs.info('check: All %u tests passed!' % len(bld.utest_results))
return
Logs.error('check: %u of %u tests failed' %
(len(fails), len(bld.utest_results)))
for filename in fails:
Logs.error(' %s' % filename)
bld.fatal('check: some tests failed')
_build_commands = {}
def _process_build_command(bld):
if bld.cmd not in _build_commands:
return
params = _build_commands[bld.cmd]
targets = params['targets']
if targets:
if bld.targets:
bld.targets += ',' + targets
else:
bld.targets = targets
program_group_list = Utils.to_list(params['program_group_list'])
bld.options.program_group.extend(program_group_list)
def build_command(name,
targets=None,
program_group_list=[],
doc='build shortcut'):
_build_commands[name] = dict(
targets=targets,
program_group_list=program_group_list,
)
class context_class(BuildContext):
cmd = name
context_class.__doc__ = doc
def _select_programs_from_group(bld):
groups = bld.options.program_group
if not groups:
if bld.targets:
groups = []
else:
groups = ['bin']
if 'all' in groups:
groups = _grouped_programs.keys()
for group in groups:
if group not in _grouped_programs:
bld.fatal('Group %s not found' % group)
tg = _grouped_programs[group][0]
if bld.targets:
bld.targets += ',' + tg.name
else:
bld.targets = tg.name
for tg in _grouped_programs[group][1:]:
bld.targets += ',' + tg.name
def options(opt):
g = opt.ap_groups['build']
g.add_option('--program-group',
action='append',
default=[],
help='''
Select all programs that go in <PROGRAM_GROUP>/ for the build. Example: `waf
--program-group examples` builds all examples. The special group "all" selects
all programs.
''')
g.add_option('--upload',
action='store_true',
help='''
Upload applicable targets to a connected device. Not all platforms may support
this. Example: `waf copter --upload` means "build arducopter and upload it to
my board".
''')
g = opt.ap_groups['check']
g.add_option('--check-verbose',
action='store_true',
help='Output all test programs.')
def build(bld):
global LAST_IDX
# FIXME: This is done to prevent same task generators being created with
# different idx when build() is called multiple times (e.g. waf bin tests).
# Ideally, task generators should be created just once.
LAST_IDX = 0
bld.add_pre_fun(_process_build_command)
bld.add_pre_fun(_select_programs_from_group)