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/ap_library.py
Views: 1798
1
# Copyright (C) 2016 Intel Corporation. All rights reserved.
2
#
3
# This file is free software: you can redistribute it and/or modify it
4
# under the terms of the GNU General Public License as published by the
5
# Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# This file is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
# See the GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License along
14
# with this program. If not, see <http://www.gnu.org/licenses/>.
15
"""
16
Waf tool for Ardupilot libraries. The function bld.ap_library() creates the
17
necessary task generators for creating the objects of a library for a vehicle.
18
That includes the common objects, which are shared among vehicles. That
19
function is used by bld.ap_stlib() and shouldn't need to be called otherwise.
20
21
The environment variable AP_LIBRARIES_OBJECTS_KW is a dictionary of keyword
22
arguments to be passed to bld.objects() when during the creation of the task
23
generators. You can use it to pass extra arguments to that function (although
24
some of them will be rewritten, see the implementation for details).
25
26
This tool also checks if the headers used by the source files don't use
27
vehicle-related headers and fails the build if they do.
28
"""
29
import os
30
import re
31
32
from waflib import Errors, Task, Utils, Logs
33
from waflib.Configure import conf
34
from waflib.TaskGen import after_method, before_method, feature
35
from waflib.Tools import c_preproc
36
37
import ardupilotwaf as ap
38
39
UTILITY_SOURCE_EXTS = ['utility/' + glob for glob in ap.SOURCE_EXTS]
40
41
def _common_tgen_name(library):
42
return 'objs/%s' % library
43
44
def _vehicle_tgen_name(library, vehicle):
45
return 'objs/%s/%s' % (library, vehicle)
46
47
_vehicle_indexes = {}
48
def _vehicle_index(vehicle):
49
""" Used for the objects taskgens idx parameter """
50
if vehicle not in _vehicle_indexes:
51
_vehicle_indexes[vehicle] = len(_vehicle_indexes) + 1
52
return _vehicle_indexes[vehicle]
53
54
# note that AP_NavEKF3_core.h is needed for AP_NavEKF3_feature.h
55
_vehicle_macros = ['APM_BUILD_DIRECTORY', 'AP_BUILD_TARGET_NAME',
56
'APM_BUILD_TYPE', 'APM_BUILD_COPTER_OR_HELI',
57
'AP_NavEKF3_core.h', 'lua_generated_bindings.h',
58
'AP_InertialSensor_rate_config.h']
59
_macros_re = re.compile(r'\b(%s)\b' % '|'.join(_vehicle_macros))
60
61
# some cpp files are not available at the time we run this check so need to be
62
# unilaterally added
63
_vehicle_cpp_need_macros = ['lua_generated_bindings.cpp']
64
_macros_cpp_re = re.compile(r'\b(%s)\b' % '|'.join(_vehicle_cpp_need_macros))
65
66
def _remove_comments(s):
67
return c_preproc.re_cpp.sub(c_preproc.repl, s)
68
69
_depends_on_vehicle_cache = {}
70
def _depends_on_vehicle(bld, source_node):
71
path = source_node.srcpath()
72
73
if not bld.env.BUILDROOT:
74
bld.env.BUILDROOT = bld.bldnode.make_node('').abspath()
75
76
if _macros_cpp_re.search(path) is not None:
77
_depends_on_vehicle_cache[path] = True
78
79
if path not in _depends_on_vehicle_cache:
80
try:
81
s = _remove_comments(source_node.read())
82
_depends_on_vehicle_cache[path] = _macros_re.search(s) is not None
83
except Exception:
84
return False
85
86
return _depends_on_vehicle_cache[path]
87
88
@conf
89
def ap_library(bld, library, vehicle):
90
try:
91
common_tg = bld.get_tgen_by_name(_common_tgen_name(library))
92
except Errors.WafError:
93
common_tg = None
94
95
try:
96
vehicle_tg = bld.get_tgen_by_name(_vehicle_tgen_name(library, vehicle))
97
except Errors.WafError:
98
vehicle_tg = None
99
100
if common_tg and vehicle_tg:
101
return
102
103
if library.find('*') != -1:
104
# allow for wildcard patterns, used for submodules without direct waf support
105
library_dir = bld.srcnode.find_dir('.')
106
wildcard = library
107
else:
108
library_dir = bld.srcnode.find_dir('libraries/%s' % library)
109
wildcard = ap.SOURCE_EXTS + UTILITY_SOURCE_EXTS
110
111
if not library_dir:
112
bld.fatal('ap_library: %s not found' % library)
113
114
src = library_dir.ant_glob(wildcard)
115
116
# allow for dynamically generated sources in a library that inherit the
117
# dependencies and includes
118
if library in bld.env.AP_LIB_EXTRA_SOURCES:
119
for s in bld.env.AP_LIB_EXTRA_SOURCES[library]:
120
src.append(bld.bldnode.find_or_declare(os.path.join('libraries', library, s)))
121
122
if not common_tg:
123
kw = dict(bld.env.AP_LIBRARIES_OBJECTS_KW)
124
kw['features'] = kw.get('features', []) + ['ap_library_object']
125
kw.update(
126
name=_common_tgen_name(library),
127
source=[s for s in src if not _depends_on_vehicle(bld, s)],
128
idx=0,
129
)
130
bld.objects(**kw)
131
132
if not vehicle_tg:
133
source = [s for s in src if _depends_on_vehicle(bld, s)]
134
135
if not source:
136
return
137
138
kw = dict(bld.env.AP_LIBRARIES_OBJECTS_KW)
139
kw['features'] = kw.get('features', []) + ['ap_library_object']
140
kw.update(
141
name=_vehicle_tgen_name(library, vehicle),
142
source=source,
143
defines=ap.get_legacy_defines(vehicle, bld),
144
idx=_vehicle_index(vehicle),
145
)
146
bld.objects(**kw)
147
148
@before_method('process_use')
149
@feature('cxxstlib')
150
def process_ap_libraries(self):
151
self.use = Utils.to_list(getattr(self, 'use', []))
152
libraries = Utils.to_list(getattr(self, 'ap_libraries', []))
153
vehicle = getattr(self, 'ap_vehicle', None)
154
155
for l in libraries:
156
self.use.append(_common_tgen_name(l))
157
if vehicle:
158
self.use.append(_vehicle_tgen_name(l, vehicle))
159
160
@before_method('process_source')
161
@feature('cxxstlib')
162
def dynamic_post(self):
163
if not getattr(self, 'dynamic_source', None):
164
return
165
self.source = Utils.to_list(self.source)
166
self.source.extend(self.bld.bldnode.ant_glob(self.dynamic_source))
167
168
class ap_library_check_headers(Task.Task):
169
color = 'PINK'
170
before = 'cxx c'
171
dispatched_headers = set()
172
whitelist = (
173
'libraries/AP_Vehicle/AP_Vehicle_Type.h',
174
'libraries/AP_Common/AP_FWVersionDefine.h',
175
'libraries/AP_Scripting/lua_generated_bindings.h',
176
'libraries/AP_NavEKF3/AP_NavEKF3_feature.h',
177
'libraries/AP_LandingGear/AP_LandingGear_config.h',
178
'libraries/AP_InertialSensor/AP_InertialSensor_rate_config.h',
179
)
180
whitelist = tuple(os.path.join(*p.split('/')) for p in whitelist)
181
182
def run(self):
183
for n in self.headers:
184
s = _remove_comments(n.read())
185
if _macros_re.search(s):
186
raise Errors.WafError('%s: library header uses vehicle-dependent macros' % n.srcpath())
187
188
def uid(self):
189
try:
190
return self._uid
191
except AttributeError:
192
self._uid = 'check_headers-%s' % self.compiled_task.uid()
193
return self._uid
194
195
def signature(self):
196
bld = self.generator.bld
197
# force scan() to be called
198
bld.imp_sigs[self.uid()] = None
199
s = super(ap_library_check_headers, self).signature()
200
bld.ap_persistent_task_sigs[self.uid()] = s
201
return s
202
203
def scan(self):
204
r = []
205
self.headers = []
206
207
srcnode_path = self.generator.bld.srcnode.abspath()
208
209
# force dependency scan, if necessary
210
self.compiled_task.signature()
211
if not self.compiled_task.uid() in self.generator.bld.node_deps:
212
return r, []
213
for n in self.generator.bld.node_deps[self.compiled_task.uid()]:
214
# using common Node methods doesn't work here
215
p = n.abspath()
216
if not p.startswith(srcnode_path):
217
continue
218
rel_p = os.path.relpath(p, srcnode_path)
219
if rel_p in self.whitelist:
220
continue
221
222
# check if the path ends with something in the white list
223
# this is required for white listing files in 'build/' (for scripting generated bindings)
224
found = False
225
for m in self.whitelist:
226
if rel_p.endswith(m):
227
found = True
228
break
229
230
if found:
231
continue
232
233
r.append(n)
234
if n not in self.dispatched_headers:
235
self.headers.append(n)
236
self.dispatched_headers.add(n)
237
238
return r, []
239
240
def __str__(self):
241
return str(self.compiled_task)
242
243
def keyword(self):
244
return 'Checking included headers'
245
246
def custom_flags_check(tgen):
247
'''
248
check for tasks marked as having custom cpp or c flags
249
a library can do this by setting AP_LIB_EXTRA_CXXFLAGS and AP_LIB_EXTRA_CFLAGS
250
251
For example add this is the configure section of the library, using AP_DDS as an example:
252
253
cfg.env.AP_LIB_EXTRA_CXXFLAGS['AP_DDS'] = ['-DSOME_CXX_FLAG']
254
cfg.env.AP_LIB_EXTRA_CFLAGS['AP_DDS'] = ['-DSOME_C_FLAG']
255
'''
256
if not tgen.name.startswith("objs/"):
257
return
258
libname = tgen.name[5:]
259
if libname in tgen.env.AP_LIB_EXTRA_CXXFLAGS:
260
tgen.env.CXXFLAGS.extend(tgen.env.AP_LIB_EXTRA_CXXFLAGS[libname])
261
if libname in tgen.env.AP_LIB_EXTRA_CFLAGS:
262
tgen.env.CFLAGS.extend(tgen.env.AP_LIB_EXTRA_CFLAGS[libname])
263
264
265
def double_precision_check(tasks):
266
'''check for tasks marked as double precision'''
267
268
for t in tasks:
269
if len(t.inputs) == 1:
270
# get a list of tasks we need to change to be double precision
271
double_tasks = []
272
for library in t.env.DOUBLE_PRECISION_SOURCES.keys():
273
for s in t.env.DOUBLE_PRECISION_SOURCES[library]:
274
double_tasks.append([library, s])
275
276
src = str(t.inputs[0]).split('/')[-2:]
277
double_library = t.env.DOUBLE_PRECISION_LIBRARIES.get(src[0],False)
278
279
if double_library or src in double_tasks:
280
t.env.CXXFLAGS = ap.set_double_precision_flags(t.env.CXXFLAGS)
281
282
283
def gsoap_library_check(bld, tasks):
284
'''check for tasks marked as gSOAP library source'''
285
286
for t in tasks:
287
if len(t.inputs) == 1:
288
gsoap_tasks = []
289
for s in t.env.AP_LIB_EXTRA_SOURCES["AP_ONVIF"]:
290
gsoap_tasks.append(bld.bldnode.find_or_declare(os.path.join('libraries', "AP_ONVIF", s)))
291
292
if t.inputs[0] in gsoap_tasks:
293
t.env.CXXFLAGS += [
294
'-Wno-shadow',
295
]
296
if 'clang++' not in t.env.COMPILER_CXX:
297
t.env.CXXFLAGS += [
298
'-Wno-suggest-override',
299
]
300
301
302
@feature('ap_library_object')
303
@after_method('process_source')
304
def ap_library_register_for_check(self):
305
if not hasattr(self, 'compiled_tasks'):
306
return
307
308
custom_flags_check(self)
309
double_precision_check(self.compiled_tasks)
310
if self.env.ENABLE_ONVIF:
311
gsoap_library_check(self.bld, self.compiled_tasks)
312
313
if not self.env.ENABLE_HEADER_CHECKS:
314
return
315
316
for t in self.compiled_tasks:
317
tsk = self.create_task('ap_library_check_headers')
318
tsk.compiled_task = t
319
320
def write_compilation_database(bld):
321
"""
322
Write the compilation database as JSON
323
"""
324
database_file = bld.bldnode.find_or_declare('compile_commands.json')
325
# don't remove the file at clean
326
327
Logs.info('Build commands will be stored in %s', database_file.path_from(bld.path))
328
try:
329
root = database_file.read_json()
330
except IOError:
331
root = []
332
compile_cmd_db = dict((x['file'], x) for x in root)
333
for task in bld.compilation_database_tasks:
334
try:
335
cmd = task.last_cmd
336
except AttributeError:
337
continue
338
f_node = task.inputs[0]
339
filename = f_node.path_from(task.get_cwd())
340
entry = {
341
"directory": task.get_cwd().abspath(),
342
"arguments": cmd,
343
"file": filename,
344
}
345
compile_cmd_db[filename] = entry
346
root = list(compile_cmd_db.values())
347
database_file.write_json(root)
348
349
def target_list_changed(bld, targets):
350
"""
351
Check if the list of targets has changed recorded in target_list file
352
"""
353
# target_list file is in the root build directory
354
target_list_file = bld.bldnode.find_or_declare('target_list')
355
try:
356
with open(target_list_file.abspath(), 'r') as f:
357
old_targets = f.read().strip().split(',')
358
except IOError:
359
Logs.info('No target_list file found, creating')
360
old_targets = []
361
if old_targets != targets:
362
with open(target_list_file.abspath(), 'w') as f:
363
f.write(','.join(targets))
364
return True
365
return False
366
367
@conf
368
def remove_target_list(cfg):
369
target_list_file = cfg.bldnode.make_node(cfg.options.board + '/target_list')
370
try:
371
Logs.info('Removing target_list file %s', target_list_file.abspath())
372
os.remove(target_list_file.abspath())
373
except OSError:
374
pass
375
376
@feature('cxxprogram', 'cxxstlib')
377
@after_method('propagate_uselib_vars')
378
def dry_run_compilation_database(self):
379
if not hasattr(self, 'bld'):
380
return
381
bld = self.bld
382
bld.compilation_database_tasks = []
383
targets = bld.targets.split(',')
384
use = self.use
385
if isinstance(use, str):
386
use = [use]
387
# if targets have not changed and neither has configuration,
388
# we can skip compilation database generation
389
if not target_list_changed(bld, targets + use):
390
Logs.info('Targets have not changed, skipping compilation database compile_commands.json generation')
391
return
392
Logs.info('Generating compile_commands.json')
393
# we need only to generate last_cmd, so override
394
# exec_command temporarily
395
def exec_command(bld, *k, **kw):
396
return 0
397
398
for g in bld.groups:
399
for tg in g:
400
# we only care to list targets and library objects
401
if not hasattr(tg, 'name'):
402
continue
403
if (tg.name not in targets) and (tg.name not in self.use):
404
continue
405
try:
406
f = tg.post
407
except AttributeError:
408
pass
409
else:
410
f()
411
412
if isinstance(tg, Task.Task):
413
lst = [tg]
414
else:
415
lst = tg.tasks
416
for tsk in lst:
417
if tsk.__class__.__name__ == "swig":
418
tsk.runnable_status()
419
if hasattr(tsk, 'more_tasks'):
420
lst.extend(tsk.more_tasks)
421
# Not all dynamic tasks can be processed, in some cases
422
# one may have to call the method "run()" like this:
423
# elif tsk.__class__.__name__ == 'src2c':
424
# tsk.run()
425
# if hasattr(tsk, 'more_tasks'):
426
# lst.extend(tsk.more_tasks)
427
428
tup = tuple(y for y in [Task.classes.get(x) for x in ('c', 'cxx')] if y)
429
if isinstance(tsk, tup):
430
bld.compilation_database_tasks.append(tsk)
431
tsk.nocache = True
432
old_exec = tsk.exec_command
433
tsk.exec_command = exec_command
434
tsk.run()
435
tsk.exec_command = old_exec
436
437
write_compilation_database(bld)
438
439
def configure(cfg):
440
cfg.env.AP_LIBRARIES_OBJECTS_KW = dict()
441
cfg.env.AP_LIB_EXTRA_SOURCES = dict()
442
cfg.env.AP_LIB_EXTRA_CXXFLAGS = dict()
443
cfg.env.AP_LIB_EXTRA_CFLAGS = dict()
444
cfg.env.DOUBLE_PRECISION_SOURCES = dict()
445
cfg.env.DOUBLE_PRECISION_LIBRARIES = dict()
446
447