CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/ardupilotwaf/ardupilotwaf.py
Views: 1798
1
# encoding: utf-8
2
3
from __future__ import print_function
4
from waflib import Build, ConfigSet, Configure, Context, Errors, Logs, Options, Utils, Task
5
from waflib.Configure import conf
6
from waflib.Scripting import run_command
7
from waflib.TaskGen import before_method, after_method, feature
8
import os.path, os
9
from pathlib import Path
10
from collections import OrderedDict
11
import subprocess
12
13
import ap_persistent
14
15
SOURCE_EXTS = [
16
'*.S',
17
'*.c',
18
'*.cpp',
19
]
20
21
COMMON_VEHICLE_DEPENDENT_CAN_LIBRARIES = [
22
'AP_CANManager',
23
'AP_KDECAN',
24
'AP_PiccoloCAN',
25
'AP_PiccoloCAN/piccolo_protocol',
26
]
27
28
COMMON_VEHICLE_DEPENDENT_LIBRARIES = [
29
'AP_Airspeed',
30
'AP_AccelCal',
31
'AP_ADC',
32
'AP_AHRS',
33
'AP_Airspeed',
34
'AP_Baro',
35
'AP_BattMonitor',
36
'AP_BoardConfig',
37
'AP_Camera',
38
'AP_Common',
39
'AP_Compass',
40
'AP_Declination',
41
'AP_GPS',
42
'AP_GSOF',
43
'AP_HAL',
44
'AP_HAL_Empty',
45
'AP_InertialSensor',
46
'AP_Math',
47
'AP_Mission',
48
'AP_DAL',
49
'AP_NavEKF',
50
'AP_NavEKF2',
51
'AP_NavEKF3',
52
'AP_Notify',
53
'AP_OpticalFlow',
54
'AP_Param',
55
'AP_Rally',
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
]
134
135
def get_legacy_defines(sketch_name, bld):
136
# If we are building heli, we adjust the build directory define so that
137
# we do not need to actually split heli and copter directories
138
if bld.cmd == 'heli' or 'heli' in bld.targets:
139
return [
140
'APM_BUILD_DIRECTORY=APM_BUILD_Heli',
141
'AP_BUILD_TARGET_NAME="' + sketch_name + '"',
142
]
143
144
return [
145
'APM_BUILD_DIRECTORY=APM_BUILD_' + sketch_name,
146
'AP_BUILD_TARGET_NAME="' + sketch_name + '"',
147
]
148
149
def set_double_precision_flags(flags):
150
# set up flags for double precision files:
151
# * remove all single precision constant flags
152
# * set define to allow double precision math in AP headers
153
154
flags = flags[:] # copy the list to avoid affecting other builds
155
156
# remove GCC and clang single precision constant flags
157
for opt in ('-fsingle-precision-constant', '-cl-single-precision-constant'):
158
while True: # might have multiple copies from different sources
159
try:
160
flags.remove(opt)
161
except ValueError:
162
break
163
flags.append("-DALLOW_DOUBLE_MATH_FUNCTIONS")
164
165
return flags
166
167
IGNORED_AP_LIBRARIES = [
168
'doc',
169
'AP_Scripting', # this gets explicitly included when it is needed and should otherwise never be globbed in
170
'AP_DDS',
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):
503
ctx.env['AP_VERSION_ITEMS'] += [(k, '"{}"'.format(os.environ.get(k, v)))]
504
505
@conf
506
def ap_version_append_int(ctx, k, v):
507
ctx.env['AP_VERSION_ITEMS'] += [(k, '{}'.format(os.environ.get(k, v)))]
508
509
@conf
510
def write_version_header(ctx, tgt):
511
with open(tgt, 'w') as f:
512
print(
513
'''// auto-generated header, do not edit
514
515
#pragma once
516
517
#ifndef FORCE_VERSION_H_INCLUDE
518
#error ap_version.h should never be included directly. You probably want to include AP_Common/AP_FWVersion.h
519
#endif
520
''', file=f)
521
522
for k, v in ctx.env['AP_VERSION_ITEMS']:
523
print('#define {} {}'.format(k, v), file=f)
524
525
@conf
526
def ap_find_benchmarks(bld, use=[]):
527
if not bld.env.HAS_GBENCHMARK:
528
return
529
530
includes = [bld.srcnode.abspath() + '/benchmarks/']
531
to_remove = '-Werror=suggest-override'
532
if to_remove in bld.env.CXXFLAGS:
533
need_remove = True
534
else:
535
need_remove = False
536
if need_remove:
537
while to_remove in bld.env.CXXFLAGS:
538
bld.env.CXXFLAGS.remove(to_remove)
539
540
for f in bld.path.ant_glob(incl='*.cpp'):
541
ap_program(
542
bld,
543
features=['gbenchmark'],
544
includes=includes,
545
source=[f],
546
use=use,
547
vehicle_binary=False,
548
program_name=f.change_ext('').name,
549
program_groups='benchmarks',
550
use_legacy_defines=False,
551
)
552
553
def test_summary(bld):
554
from io import BytesIO
555
import sys
556
557
if not hasattr(bld, 'utest_results'):
558
Logs.info('check: no test run')
559
return
560
561
fails = []
562
563
for filename, exit_code, out, err in bld.utest_results:
564
Logs.pprint('GREEN' if exit_code == 0 else 'YELLOW',
565
' %s' % filename,
566
'returned %d' % exit_code)
567
568
if exit_code != 0:
569
fails.append(filename)
570
elif not bld.options.check_verbose:
571
continue
572
573
if len(out):
574
buf = BytesIO(out)
575
for line in buf:
576
print(" OUT: %s" % line.decode(), end='', file=sys.stderr)
577
print()
578
579
if len(err):
580
buf = BytesIO(err)
581
for line in buf:
582
print(" ERR: %s" % line.decode(), end='', file=sys.stderr)
583
print()
584
585
if not fails:
586
Logs.info('check: All %u tests passed!' % len(bld.utest_results))
587
return
588
589
Logs.error('check: %u of %u tests failed' %
590
(len(fails), len(bld.utest_results)))
591
592
for filename in fails:
593
Logs.error(' %s' % filename)
594
595
bld.fatal('check: some tests failed')
596
597
_build_commands = {}
598
599
def _process_build_command(bld):
600
if bld.cmd not in _build_commands:
601
return
602
603
params = _build_commands[bld.cmd]
604
605
targets = params['targets']
606
if targets:
607
if bld.targets:
608
bld.targets += ',' + targets
609
else:
610
bld.targets = targets
611
612
program_group_list = Utils.to_list(params['program_group_list'])
613
bld.options.program_group.extend(program_group_list)
614
615
def build_command(name,
616
targets=None,
617
program_group_list=[],
618
doc='build shortcut'):
619
_build_commands[name] = dict(
620
targets=targets,
621
program_group_list=program_group_list,
622
)
623
624
class context_class(Build.BuildContext):
625
cmd = name
626
context_class.__doc__ = doc
627
628
def _select_programs_from_group(bld):
629
groups = bld.options.program_group
630
if not groups:
631
if bld.targets:
632
groups = []
633
else:
634
groups = ['bin']
635
636
possible_groups = list(_grouped_programs.keys())
637
possible_groups.remove('bin') # Remove `bin` so as not to duplicate all items in bin
638
if 'all' in groups:
639
groups = possible_groups
640
641
for group in groups:
642
if group not in _grouped_programs:
643
bld.fatal(f'Group {group} not found, possible groups: {possible_groups}')
644
645
target_names = _grouped_programs[group].keys()
646
647
for name in target_names:
648
if bld.targets:
649
bld.targets += ',' + name
650
else:
651
bld.targets = name
652
653
def options(opt):
654
opt.ap_groups = {
655
'configure': opt.add_option_group('Ardupilot configure options'),
656
'linux': opt.add_option_group('Linux boards configure options'),
657
'build': opt.add_option_group('Ardupilot build options'),
658
'check': opt.add_option_group('Ardupilot check options'),
659
'clean': opt.add_option_group('Ardupilot clean options'),
660
}
661
662
g = opt.ap_groups['build']
663
664
g.add_option('--program-group',
665
action='append',
666
default=[],
667
help='''Select all programs that go in <PROGRAM_GROUP>/ for the build.
668
Example: `waf --program-group examples` builds all examples. The
669
special group "all" selects all programs.
670
''')
671
672
g.add_option('--upload',
673
action='store_true',
674
help='''Upload applicable targets to a connected device. Not all
675
platforms may support this. Example: `waf copter --upload` means "build
676
arducopter and upload it to my board".
677
''')
678
679
g.add_option('--upload-port',
680
action='store',
681
dest='upload_port',
682
default=None,
683
help='''Specify the port to be used with the --upload option. For example a port of /dev/ttyS10 indicates that serial port 10 shuld be used.
684
''')
685
686
g.add_option('--upload-blueos',
687
action='store',
688
dest='upload_blueos',
689
default=None,
690
help='''Automatically upload to a BlueOS device. The argument is the url for the device. http://blueos.local for example.
691
''')
692
693
g.add_option('--upload-force',
694
action='store_true',
695
help='''Override board type check and continue loading. Same as using uploader.py --force.
696
''')
697
698
g.add_option('--define',
699
action='append',
700
help='Add C++ define to build.')
701
702
g = opt.ap_groups['check']
703
704
g.add_option('--check-verbose',
705
action='store_true',
706
help='Output all test programs.')
707
708
g = opt.ap_groups['clean']
709
710
g.add_option('--clean-all-sigs',
711
action='store_true',
712
help='''Clean signatures for all tasks. By default, tasks that scan for
713
implicit dependencies (like the compilation tasks) keep the dependency
714
information across clean commands, so that that information is changed
715
only when really necessary. Also, some tasks that don't really produce
716
files persist their signature. This option avoids that behavior when
717
cleaning the build.
718
''')
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