Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/ardupilotwaf/ardupilotwaf.py
9872 views
1
# encoding: utf-8
2
3
# flake8: noqa
4
5
from waflib import Build, ConfigSet, Configure, Context, Errors, Logs, Options, Utils, Task
6
from waflib.Configure import conf
7
from waflib.Scripting import run_command
8
from waflib.TaskGen import before_method, after_method, feature
9
import os.path, os
10
from pathlib import Path
11
from collections import OrderedDict
12
import subprocess
13
14
SOURCE_EXTS = [
15
'*.S',
16
'*.c',
17
'*.cpp',
18
]
19
20
COMMON_VEHICLE_DEPENDENT_CAN_LIBRARIES = [
21
'AP_CANManager',
22
'AP_KDECAN',
23
'AP_PiccoloCAN',
24
'AP_PiccoloCAN/piccolo_protocol',
25
]
26
27
COMMON_VEHICLE_DEPENDENT_LIBRARIES = [
28
'AP_AccelCal',
29
'AP_ADC',
30
'AP_AHRS',
31
'AP_Airspeed',
32
'AP_Baro',
33
'AP_BattMonitor',
34
'AP_BoardConfig',
35
'AP_Camera',
36
'AP_Common',
37
'AP_Compass',
38
'AP_Declination',
39
'AP_GPS',
40
'AP_GSOF',
41
'AP_HAL',
42
'AP_HAL_Empty',
43
'AP_DDS',
44
'AP_InertialSensor',
45
'AP_Math',
46
'AP_Mission',
47
'AP_DAL',
48
'AP_NavEKF',
49
'AP_NavEKF2',
50
'AP_NavEKF3',
51
'AP_Notify',
52
'AP_OpticalFlow',
53
'AP_Param',
54
'AP_Rally',
55
'AP_LightWareSerial',
56
'AP_RangeFinder',
57
'AP_Scheduler',
58
'AP_SerialManager',
59
'AP_Terrain',
60
'AP_Vehicle',
61
'AP_InternalError',
62
'AP_Logger',
63
'Filter',
64
'GCS_MAVLink',
65
'RC_Channel',
66
'SRV_Channel',
67
'StorageManager',
68
'AP_Tuning',
69
'AP_RPM',
70
'AP_RSSI',
71
'AP_Mount',
72
'AP_Module',
73
'AP_Button',
74
'AP_ICEngine',
75
'AP_Networking',
76
'AP_Frsky_Telem',
77
'AP_IBus_Telem',
78
'AP_FlashStorage',
79
'AP_Relay',
80
'AP_ServoRelayEvents',
81
'AP_Volz_Protocol',
82
'AP_SBusOut',
83
'AP_IOMCU',
84
'AP_Parachute',
85
'AP_RAMTRON',
86
'AP_RCProtocol',
87
'AP_Radio',
88
'AP_TempCalibration',
89
'AP_VisualOdom',
90
'AP_BLHeli',
91
'AP_ROMFS',
92
'AP_Proximity',
93
'AP_Gripper',
94
'AP_RTC',
95
'AC_Sprayer',
96
'AC_Fence',
97
'AC_Avoidance',
98
'AP_LandingGear',
99
'AP_RobotisServo',
100
'AP_NMEA_Output',
101
'AP_OSD',
102
'AP_Filesystem',
103
'AP_ADSB',
104
'AP_ADSB/sagetech-sdk',
105
'AC_PID',
106
'AP_SerialLED',
107
'AP_EFI',
108
'AP_Hott_Telem',
109
'AP_ESC_Telem',
110
'AP_Servo_Telem',
111
'AP_Stats',
112
'AP_GyroFFT',
113
'AP_RCTelemetry',
114
'AP_Generator',
115
'AP_MSP',
116
'AP_OLC',
117
'AP_WheelEncoder',
118
'AP_ExternalAHRS',
119
'AP_VideoTX',
120
'AP_FETtecOneWire',
121
'AP_TemperatureSensor',
122
'AP_Torqeedo',
123
'AP_CustomRotations',
124
'AP_AIS',
125
'AP_OpenDroneID',
126
'AP_CheckFirmware',
127
'AP_ExternalControl',
128
'AP_JSON',
129
'AP_Beacon',
130
'AP_Arming',
131
'AP_RCMapper',
132
'AP_MultiHeap',
133
'AP_Follow',
134
]
135
136
def get_legacy_defines(sketch_name, bld):
137
# If we are building heli, we adjust the build directory define so that
138
# we do not need to actually split heli and copter directories
139
if bld.cmd == 'heli' or 'heli' in bld.targets:
140
return [
141
'APM_BUILD_DIRECTORY=APM_BUILD_Heli',
142
'AP_BUILD_TARGET_NAME="' + sketch_name + '"',
143
]
144
145
return [
146
'APM_BUILD_DIRECTORY=APM_BUILD_' + sketch_name,
147
'AP_BUILD_TARGET_NAME="' + sketch_name + '"',
148
]
149
150
def set_double_precision_flags(flags):
151
# set up flags for double precision files:
152
# * remove all single precision constant flags
153
# * set define to allow double precision math in AP headers
154
155
flags = flags[:] # copy the list to avoid affecting other builds
156
157
# remove GCC and clang single precision constant flags
158
for opt in ('-fsingle-precision-constant', '-cl-single-precision-constant'):
159
while True: # might have multiple copies from different sources
160
try:
161
flags.remove(opt)
162
except ValueError:
163
break
164
flags.append("-DAP_MATH_ALLOW_DOUBLE_FUNCTIONS=1")
165
166
return flags
167
168
IGNORED_AP_LIBRARIES = [
169
'doc',
170
'AP_Scripting', # this gets explicitly included when it is needed and should otherwise never be globbed in
171
]
172
173
174
def ap_autoconfigure(execute_method):
175
"""
176
Decorator that enables context commands to run *configure* as needed.
177
"""
178
def execute(self):
179
"""
180
Wraps :py:func:`waflib.Context.Context.execute` on the context class
181
"""
182
if 'tools/' in self.targets:
183
raise Errors.WafError('\"tools\" name has been replaced with \"tool\" for build please use that!')
184
if not Configure.autoconfig:
185
return execute_method(self)
186
187
# Disable autoconfig so waf's version doesn't run (and don't end up on loop of bad configure)
188
Configure.autoconfig = False
189
190
if self.variant == '':
191
raise Errors.WafError('The project is badly configured: run "waf configure" again!')
192
193
env = ConfigSet.ConfigSet()
194
do_config = False
195
196
try:
197
p = os.path.join(Context.out_dir, Build.CACHE_DIR, self.variant + Build.CACHE_SUFFIX)
198
env.load(p)
199
except EnvironmentError:
200
raise Errors.WafError('The project is not configured for board {0}: run "waf configure --board {0} [...]" first!'.format(self.variant))
201
202
lock_env = ConfigSet.ConfigSet()
203
204
try:
205
lock_env.load(os.path.join(Context.top_dir, Options.lockfile))
206
except EnvironmentError:
207
Logs.warn('Configuring the project')
208
do_config = True
209
else:
210
if lock_env.run_dir != Context.run_dir:
211
do_config = True
212
else:
213
h = 0
214
215
for f in env.CONFIGURE_FILES:
216
try:
217
h = Utils.h_list((h, Utils.readf(f, 'rb')))
218
except EnvironmentError:
219
do_config = True
220
break
221
else:
222
do_config = h != env.CONFIGURE_HASH
223
224
if do_config:
225
cmd = lock_env.config_cmd or 'configure'
226
tmp = Options.options.__dict__
227
228
if env.OPTIONS and sorted(env.OPTIONS.keys()) == sorted(tmp.keys()):
229
Options.options.__dict__ = env.OPTIONS
230
else:
231
raise Errors.WafError('The project configure options have changed: run "waf configure" again!')
232
233
try:
234
run_command(cmd)
235
finally:
236
Options.options.__dict__ = tmp
237
238
run_command(self.cmd)
239
else:
240
return execute_method(self)
241
242
return execute
243
244
def ap_configure_post_recurse():
245
post_recurse_orig = Configure.ConfigurationContext.post_recurse
246
247
def post_recurse(self, node):
248
post_recurse_orig(self, node)
249
250
self.all_envs[self.variant].CONFIGURE_FILES = self.files
251
self.all_envs[self.variant].CONFIGURE_HASH = self.hash
252
253
return post_recurse
254
255
@conf
256
def ap_get_all_libraries(bld):
257
if bld.env.BOOTLOADER:
258
# we don't need the full set of libraries for the bootloader build
259
return ['AP_HAL']
260
libraries = []
261
for lib_node in bld.srcnode.ant_glob('libraries/*', dir=True, src=False):
262
name = lib_node.name
263
if name in IGNORED_AP_LIBRARIES:
264
continue
265
if name.startswith('AP_HAL'):
266
continue
267
if name == 'SITL':
268
continue
269
libraries.append(name)
270
libraries.extend(['AP_HAL', 'AP_HAL_Empty'])
271
libraries.append('AP_PiccoloCAN/piccolo_protocol')
272
return libraries
273
274
@conf
275
def ap_common_vehicle_libraries(bld):
276
libraries = COMMON_VEHICLE_DEPENDENT_LIBRARIES
277
278
if bld.env.with_can or bld.env.HAL_NUM_CAN_IFACES:
279
libraries.extend(COMMON_VEHICLE_DEPENDENT_CAN_LIBRARIES)
280
281
return libraries
282
283
_grouped_programs = {}
284
285
286
class upload_fw_blueos(Task.Task):
287
def run(self):
288
# this is rarely used, so we import requests here to avoid the overhead
289
import requests
290
binary_path = self.inputs[0].abspath()
291
# check if .apj file exists for chibios builds
292
if Path(binary_path + ".apj").exists():
293
binary_path = binary_path + ".apj"
294
bld = self.generator.bld
295
board = bld.bldnode.name.capitalize()
296
print(f"Uploading {binary_path} to BlueOS at {bld.options.upload_blueos} for board {board}")
297
url = f'{bld.options.upload_blueos}/ardupilot-manager/v1.0/install_firmware_from_file?board_name={board}'
298
files = {
299
'binary': open(binary_path, 'rb')
300
}
301
response = requests.post(url, files=files, verify=False)
302
if response.status_code != 200:
303
raise Errors.WafError(f"Failed to upload firmware to BlueOS: {response.status_code}: {response.text}")
304
print("Upload complete")
305
306
def keyword(self):
307
return "Uploading to BlueOS"
308
309
class check_elf_symbols(Task.Task):
310
color='CYAN'
311
always_run = True
312
def keyword(self):
313
return "checking symbols"
314
315
def run(self):
316
'''
317
check for disallowed symbols in elf file, such as C++ exceptions
318
'''
319
elfpath = self.inputs[0].abspath()
320
321
if not self.env.CHECK_SYMBOLS:
322
# checking symbols disabled on this build
323
return
324
325
if not self.env.vehicle_binary or self.env.SIM_ENABLED:
326
# we only want to check symbols for vehicle binaries, allowing examples
327
# to use C++ exceptions. We also allow them in simulator builds
328
return
329
330
# we use string find on these symbols, so this catches all types of throw
331
# calls this should catch all uses of exceptions unless the compiler
332
# manages to inline them
333
blacklist = ['std::__throw',
334
'operator new[](unsigned int)',
335
'operator new[](unsigned long)',
336
'operator new(unsigned int)',
337
'operator new(unsigned long)']
338
339
nmout = subprocess.getoutput("%s -C %s" % (self.env.get_flat('NM'), elfpath))
340
for b in blacklist:
341
if nmout.find(b) != -1:
342
raise Errors.WafError("Disallowed symbol in %s: %s" % (elfpath, b))
343
344
345
@feature('post_link')
346
@after_method('process_source')
347
def post_link(self):
348
'''
349
setup tasks to run after link stage
350
'''
351
self.link_task.always_run = True
352
353
link_output = self.link_task.outputs[0]
354
355
check_elf_task = self.create_task('check_elf_symbols', src=link_output)
356
check_elf_task.set_run_after(self.link_task)
357
if self.bld.options.upload_blueos and self.env["BOARD_CLASS"] == "LINUX":
358
_upload_task = self.create_task('upload_fw_blueos', src=link_output)
359
_upload_task.set_run_after(self.link_task)
360
361
@conf
362
def ap_program(bld,
363
program_groups='bin',
364
program_dir=None,
365
use_legacy_defines=True,
366
program_name=None,
367
vehicle_binary=True,
368
**kw):
369
if 'target' in kw:
370
bld.fatal('Do not pass target for program')
371
if 'defines' not in kw:
372
kw['defines'] = []
373
if 'source' not in kw:
374
kw['source'] = bld.path.ant_glob(SOURCE_EXTS)
375
376
if not program_name:
377
program_name = bld.path.name
378
379
if use_legacy_defines:
380
kw['defines'].extend(get_legacy_defines(bld.path.name, bld))
381
382
kw['features'] = kw.get('features', []) + bld.env.AP_PROGRAM_FEATURES + ['post_link']
383
384
program_groups = Utils.to_list(program_groups)
385
386
if not program_dir:
387
program_dir = program_groups[0]
388
389
name = os.path.join(program_dir, program_name)
390
391
tg_constructor = bld.program
392
if bld.env.AP_PROGRAM_AS_STLIB:
393
tg_constructor = bld.stlib
394
else:
395
if bld.env.STATIC_LINKING:
396
kw['features'].append('static_linking')
397
398
399
tg = tg_constructor(
400
target='#%s' % name,
401
name=name,
402
program_name=program_name,
403
program_dir=program_dir,
404
**kw
405
)
406
407
tg.env.vehicle_binary = vehicle_binary
408
409
if 'use' in kw and bld.env.STATIC_LINKING:
410
# ensure we link against vehicle library
411
tg.env.STLIB += [kw['use']]
412
413
for group in program_groups:
414
_grouped_programs.setdefault(group, {}).update({tg.name : tg})
415
416
return tg
417
418
419
@conf
420
def ap_example(bld, **kw):
421
kw['program_groups'] = 'examples'
422
ap_program(bld, use_legacy_defines=False, vehicle_binary=False, **kw)
423
424
def unique_list(items):
425
'''remove duplicate elements from a list while maintaining ordering'''
426
return list(OrderedDict.fromkeys(items))
427
428
@conf
429
def ap_stlib(bld, **kw):
430
if 'name' not in kw:
431
bld.fatal('Missing name for ap_stlib')
432
if 'ap_vehicle' not in kw:
433
bld.fatal('Missing ap_vehicle for ap_stlib')
434
if 'ap_libraries' not in kw:
435
bld.fatal('Missing ap_libraries for ap_stlib')
436
437
kw['ap_libraries'] = unique_list(kw['ap_libraries'] + bld.env.AP_LIBRARIES)
438
for l in kw['ap_libraries']:
439
bld.ap_library(l, kw['ap_vehicle'])
440
441
if 'dynamic_source' not in kw:
442
kw['dynamic_source'] = 'modules/DroneCAN/libcanard/dsdlc_generated/src/**.c'
443
444
kw['features'] = kw.get('features', []) + ['cxx', 'cxxstlib']
445
kw['target'] = kw['name']
446
kw['source'] = []
447
448
bld.stlib(**kw)
449
450
_created_program_dirs = set()
451
@feature('cxxstlib', 'cxxprogram')
452
@before_method('process_rule')
453
def ap_create_program_dir(self):
454
if not hasattr(self, 'program_dir'):
455
return
456
if self.program_dir in _created_program_dirs:
457
return
458
self.bld.bldnode.make_node(self.program_dir).mkdir()
459
_created_program_dirs.add(self.program_dir)
460
461
@feature('cxxstlib')
462
@before_method('process_rule')
463
def ap_stlib_target(self):
464
if self.target.startswith('#'):
465
self.target = self.target[1:]
466
self.target = '#%s' % os.path.join('lib', self.target)
467
468
@conf
469
def ap_find_tests(bld, use=[], DOUBLE_PRECISION_SOURCES=[]):
470
if not bld.env.HAS_GTEST:
471
return
472
473
features = []
474
if bld.cmd == 'check':
475
features.append('test')
476
477
use = Utils.to_list(use)
478
use.append('GTEST')
479
480
includes = [bld.srcnode.abspath() + '/tests/']
481
482
for f in bld.path.ant_glob(incl='*.cpp'):
483
t = ap_program(
484
bld,
485
features=features,
486
includes=includes,
487
source=[f],
488
use=use,
489
program_name=f.change_ext('').name,
490
program_groups='tests',
491
use_legacy_defines=False,
492
vehicle_binary=False,
493
cxxflags=['-Wno-undef'],
494
)
495
filename = os.path.basename(f.abspath())
496
if filename in DOUBLE_PRECISION_SOURCES:
497
t.env.CXXFLAGS = set_double_precision_flags(t.env.CXXFLAGS)
498
499
_versions = []
500
501
@conf
502
def ap_version_append_str(ctx, k, v, consistent_v=None):
503
if ctx.env.CONSISTENT_BUILDS and consistent_v is not None:
504
v = consistent_v # override with consistent value
505
else:
506
v = os.environ.get(k, v) # use v unless defined in environment
507
508
ctx.env['AP_VERSION_ITEMS'] += [(k, f'"{v}"')]
509
510
@conf
511
def ap_version_append_int(ctx, k, v, consistent_v=None):
512
if ctx.env.CONSISTENT_BUILDS and consistent_v is not None:
513
v = consistent_v # override with consistent value
514
else:
515
v = os.environ.get(k, v) # use v unless defined in environment
516
517
ctx.env['AP_VERSION_ITEMS'] += [(k, f'{v}')]
518
519
@conf
520
def write_version_header(ctx, tgt):
521
with open(tgt, 'w') as f:
522
print(
523
'''// auto-generated header, do not edit
524
525
#pragma once
526
527
#ifndef FORCE_VERSION_H_INCLUDE
528
#error ap_version.h should never be included directly. You probably want to include AP_Common/AP_FWVersion.h
529
#endif
530
''', file=f)
531
532
for k, v in ctx.env['AP_VERSION_ITEMS']:
533
print('#define {} {}'.format(k, v), file=f)
534
535
@conf
536
def ap_find_benchmarks(bld, use=[]):
537
if not bld.env.HAS_GBENCHMARK:
538
return
539
540
includes = [bld.srcnode.abspath() + '/benchmarks/']
541
to_remove = '-Werror=suggest-override'
542
if to_remove in bld.env.CXXFLAGS:
543
need_remove = True
544
else:
545
need_remove = False
546
if need_remove:
547
while to_remove in bld.env.CXXFLAGS:
548
bld.env.CXXFLAGS.remove(to_remove)
549
550
for f in bld.path.ant_glob(incl='*.cpp'):
551
ap_program(
552
bld,
553
features=['gbenchmark'],
554
includes=includes,
555
source=[f],
556
use=use,
557
vehicle_binary=False,
558
program_name=f.change_ext('').name,
559
program_groups='benchmarks',
560
use_legacy_defines=False,
561
)
562
563
def test_summary(bld):
564
from io import BytesIO
565
import sys
566
567
if not hasattr(bld, 'utest_results'):
568
Logs.info('check: no test run')
569
return
570
571
fails = []
572
573
for filename, exit_code, out, err in bld.utest_results:
574
Logs.pprint('GREEN' if exit_code == 0 else 'YELLOW',
575
' %s' % filename,
576
'returned %d' % exit_code)
577
578
if exit_code != 0:
579
fails.append(filename)
580
elif not bld.options.check_verbose:
581
continue
582
583
if len(out):
584
buf = BytesIO(out)
585
for line in buf:
586
print(" OUT: %s" % line.decode(), end='', file=sys.stderr)
587
print()
588
589
if len(err):
590
buf = BytesIO(err)
591
for line in buf:
592
print(" ERR: %s" % line.decode(), end='', file=sys.stderr)
593
print()
594
595
if not fails:
596
Logs.info('check: All %u tests passed!' % len(bld.utest_results))
597
return
598
599
Logs.error('check: %u of %u tests failed' %
600
(len(fails), len(bld.utest_results)))
601
602
for filename in fails:
603
Logs.error(' %s' % filename)
604
605
bld.fatal('check: some tests failed')
606
607
_build_commands = {}
608
609
def _process_build_command(bld):
610
if bld.cmd not in _build_commands:
611
return
612
613
params = _build_commands[bld.cmd]
614
615
targets = params['targets']
616
if targets:
617
if bld.targets:
618
bld.targets += ',' + targets
619
else:
620
bld.targets = targets
621
622
program_group_list = Utils.to_list(params['program_group_list'])
623
bld.options.program_group.extend(program_group_list)
624
625
def build_command(name,
626
targets=None,
627
program_group_list=[],
628
doc='build shortcut'):
629
_build_commands[name] = dict(
630
targets=targets,
631
program_group_list=program_group_list,
632
)
633
634
class context_class(Build.BuildContext):
635
cmd = name
636
context_class.__doc__ = doc
637
638
def _select_programs_from_group(bld):
639
groups = bld.options.program_group
640
if not groups:
641
if bld.targets:
642
groups = []
643
else:
644
groups = ['bin']
645
646
possible_groups = list(_grouped_programs.keys())
647
possible_groups.remove('bin') # Remove `bin` so as not to duplicate all items in bin
648
if 'all' in groups:
649
groups = possible_groups
650
651
for group in groups:
652
if group not in _grouped_programs:
653
bld.fatal(f'Group {group} not found, possible groups: {possible_groups}')
654
655
target_names = _grouped_programs[group].keys()
656
657
for name in target_names:
658
if bld.targets:
659
bld.targets += ',' + name
660
else:
661
bld.targets = name
662
663
def options(opt):
664
opt.ap_groups = {
665
'configure': opt.add_option_group('Ardupilot configure options'),
666
'linux': opt.add_option_group('Linux boards configure options'),
667
'build': opt.add_option_group('Ardupilot build options'),
668
'check': opt.add_option_group('Ardupilot check options'),
669
'clean': opt.add_option_group('Ardupilot clean options'),
670
}
671
672
g = opt.ap_groups['build']
673
674
g.add_option('--program-group',
675
action='append',
676
default=[],
677
help='''Select all programs that go in <PROGRAM_GROUP>/ for the build.
678
Example: `waf --program-group examples` builds all examples. The
679
special group "all" selects all programs.
680
''')
681
682
g.add_option('--upload',
683
action='store_true',
684
help='''Upload applicable targets to a connected device. Not all
685
platforms may support this. Example: `waf copter --upload` means "build
686
arducopter and upload it to my board".
687
''')
688
689
g.add_option('--upload-port',
690
action='store',
691
dest='upload_port',
692
default=None,
693
help='''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.
694
''')
695
696
g.add_option('--upload-blueos',
697
action='store',
698
dest='upload_blueos',
699
default=None,
700
help='''Automatically upload to a BlueOS device. The argument is the url for the device. http://blueos.local for example.
701
''')
702
703
g.add_option('--upload-force',
704
action='store_true',
705
help='''Override board type check and continue loading. Same as using uploader.py --force.
706
''')
707
708
g.add_option('--define',
709
action='append',
710
help='Add C++ define to build.')
711
712
g = opt.ap_groups['check']
713
714
g.add_option('--check-verbose',
715
action='store_true',
716
help='Output all test programs.')
717
718
g = opt.ap_groups['clean']
719
720
g.add_option('--asan',
721
action='store_true',
722
help='''Build using the macOS clang Address Sanitizer. In order to run with
723
Address Sanitizer support llvm-symbolizer is required to be on the PATH.
724
This option is only supported on macOS versions of clang.
725
''')
726
727
g.add_option('--ubsan',
728
action='store_true',
729
help='''Build using the gcc undefined behaviour sanitizer''')
730
731
g.add_option('--ubsan-abort',
732
action='store_true',
733
help='''Build using the gcc undefined behaviour sanitizer and abort on error''')
734
735
def build(bld):
736
bld.add_pre_fun(_process_build_command)
737
bld.add_pre_fun(_select_programs_from_group)
738
739