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/cmake.py
Views: 1798
1
# encoding: utf-8
2
3
# Copyright (C) 2015-2016 Intel Corporation. All rights reserved.
4
#
5
# This file is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by the
7
# Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This file is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
# See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along
16
# with this program. If not, see <http://www.gnu.org/licenses/>.
17
18
"""
19
Waf tool for external builds with cmake. This tool defines the feature
20
'cmake_build', for building through the cmake interface.
21
22
You can use CMAKE_MIN_VERSION environment variable before loading this tool in
23
the configuration to set a minimum version required for cmake. Example::
24
25
def configure(cfg):
26
cfg.env.CMAKE_MIN_VERSION = '3.5.2'
27
cfg.load('cmake')
28
29
Usage example::
30
31
def build(bld):
32
# cmake configuration
33
foo = bld.cmake(
34
name='foo',
35
cmake_src='path/to/foosrc', # where is the source tree
36
cmake_bld='path/to/foobld', # where to generate the build system
37
cmake_vars=dict(
38
CMAKE_BUILD_TYPE='Release',
39
...
40
),
41
)
42
43
# cmake build for external target 'bar'
44
bld(
45
features='cmake_build',
46
cmake_config='foo', # this build depends on the cmake generation above defined
47
cmake_target='bar', # what to pass to option --target of cmake
48
)
49
50
# cmake build for target 'baz' (syntactic sugar)
51
foo.build('baz')
52
53
The keys of cmake_vars are sorted so that unnecessary execution is avoided. If
54
you want to ensure an order in which the variables are passed to cmake, use an
55
OrderedDict. Example::
56
57
def build(bld):
58
foo_vars = OrderedDict()
59
foo_vars['CMAKE_BUILD_TYPE'] = 'Release'
60
foo_vars['FOO'] = 'value_of_foo'
61
foo_vars['BAR'] = 'value_of_bar'
62
63
# cmake configuration
64
foo = bld.cmake(
65
cmake_vars=foo_vars,
66
...
67
)
68
69
There may be cases when you want to establish dependency between other tasks and
70
the external build system's products (headers and libraries, for example). In
71
that case, you can specify the specific files in the option 'target' of your
72
cmake_build task generator. Example::
73
74
def build(bld):
75
...
76
77
# declaring on target only what I'm interested in
78
foo.build('baz', target='path/to/foobld/include/baz.h')
79
80
# myprogram.c includes baz.h, so the dependency is (implicitly)
81
# established
82
bld.program(target='myprogram', source='myprogram.c')
83
84
# another example
85
foo.build('another', target='another.txt')
86
87
bld(
88
rule='${CP} ${SRC} ${TGT}',
89
source=bld.bldnode.find_or_declare('another.txt'),
90
target='another_copied.txt',
91
)
92
93
94
You can also establish the dependency directly on a task object::
95
96
@feature('myfeature')
97
def process_myfeature(self):
98
baz_taskgen = self.bld.get_tgen_by_name('baz')
99
baz_taskgen.post()
100
101
# every cmake_build taskgen stores its task in cmake_build_task
102
baz_task = baz_taskgen.cmake_build_task
103
104
tsk = self.create_task('mytask')
105
106
tsk.set_run_after(baz_task)
107
108
# tsk is run whenever baz_task changes its outputs, namely,
109
# path/to/foobld/include/baz.h
110
tsk.dep_nodes.extend(baz_task.outputs)
111
112
If your cmake build creates several files (that may be dependency for several
113
tasks), you can use the parameter cmake_output_patterns. It receives a pattern
114
or a list of patterns relative to the cmake build directory. After the build
115
task is run, the files that match those patterns are set as output of the cmake
116
build task, so that they get a signature. Example::
117
118
def build(bld):
119
...
120
121
foo.build('baz', cmake_output_patterns='include/*.h')
122
123
...
124
"""
125
126
from waflib import Context, Node, Task, Utils
127
from waflib.Configure import conf
128
from waflib.TaskGen import feature, taskgen_method
129
130
from collections import OrderedDict
131
import os
132
import re
133
import sys
134
135
class cmake_configure_task(Task.Task):
136
vars = ['CMAKE_BLD_DIR']
137
run_str = '${CMAKE} ${CMAKE_FLAGS} ${CMAKE_SRC_DIR} ${CMAKE_VARS} ${CMAKE_GENERATOR_OPTION}'
138
color = 'BLUE'
139
140
def exec_command(self, cmd, **kw):
141
kw['stdout'] = sys.stdout
142
return super(cmake_configure_task, self).exec_command(cmd, **kw)
143
144
def uid(self):
145
if not hasattr(self, 'uid_'):
146
m = Utils.md5()
147
def u(s):
148
m.update(s.encode('utf-8'))
149
u(self.__class__.__name__)
150
u(self.env.get_flat('CMAKE_SRC_DIR'))
151
u(self.env.get_flat('CMAKE_BLD_DIR'))
152
u(self.env.get_flat('CMAKE_VARS'))
153
u(self.env.get_flat('CMAKE_FLAGS'))
154
self.uid_ = m.digest()
155
156
return self.uid_
157
158
def __str__(self):
159
return self.cmake.name
160
161
def keyword(self):
162
return 'CMake Configure'
163
164
# Clean cmake configuration
165
cmake_configure_task._original_run = cmake_configure_task.run
166
def _cmake_configure_task_run(self):
167
cmakecache_path = self.outputs[0].abspath()
168
if os.path.exists(cmakecache_path):
169
os.remove(cmakecache_path)
170
self._original_run()
171
cmake_configure_task.run = _cmake_configure_task_run
172
173
class cmake_build_task(Task.Task):
174
run_str = '${CMAKE} --build ${CMAKE_BLD_DIR} --target ${CMAKE_TARGET}'
175
color = 'BLUE'
176
# the cmake-generated build system is responsible of managing its own
177
# dependencies
178
always_run = True
179
180
def exec_command(self, cmd, **kw):
181
kw['stdout'] = sys.stdout
182
return super(cmake_build_task, self).exec_command(cmd, **kw)
183
184
def uid(self):
185
if not hasattr(self, 'uid_'):
186
m = Utils.md5()
187
def u(s):
188
m.update(s.encode('utf-8'))
189
u(self.__class__.__name__)
190
u(self.env.get_flat('CMAKE_BLD_DIR'))
191
u(self.env.get_flat('CMAKE_TARGET'))
192
self.uid_ = m.digest()
193
194
return self.uid_
195
196
def __str__(self):
197
return '%s %s' % (self.cmake.name, self.cmake_target)
198
199
def keyword(self):
200
return 'CMake Build'
201
202
cmake_build_task.original_post_run = cmake_build_task.post_run
203
def _cmake_build_task_post_run(self):
204
self.output_patterns = Utils.to_list(self.output_patterns)
205
if not self.output_patterns:
206
return self.original_post_run()
207
bldnode = self.cmake.bldnode
208
for node in bldnode.ant_glob(self.output_patterns, remove=False):
209
self.set_outputs(node)
210
return self.original_post_run()
211
cmake_build_task.post_run = _cmake_build_task_post_run
212
213
class CMakeConfig(object):
214
'''
215
CMake configuration. This object shouldn't be instantiated directly. Use
216
bld.cmake().
217
'''
218
def __init__(self, bld, name, srcnode, bldnode, cmake_vars, cmake_flags):
219
self.bld = bld
220
self.name = name
221
self.srcnode = srcnode
222
self.bldnode = bldnode
223
self.vars = cmake_vars
224
self.flags = cmake_flags
225
226
self._config_task = None
227
self.last_build_task = None
228
229
def vars_keys(self):
230
keys = list(self.vars.keys())
231
if not isinstance(self.vars, OrderedDict):
232
keys.sort()
233
return keys
234
235
def config_sig(self):
236
m = Utils.md5()
237
def u(s):
238
m.update(s.encode('utf-8'))
239
u(self.srcnode.abspath())
240
u(self.bldnode.abspath())
241
for v in self.flags:
242
u(v)
243
keys = self.vars_keys()
244
for k in keys:
245
u(k)
246
u(self.vars[k])
247
return m.digest()
248
249
def config_task(self, taskgen):
250
sig = self.config_sig()
251
if self._config_task and self._config_task.cmake_config_sig == sig:
252
return self._config_task
253
254
self._config_task = taskgen.create_task('cmake_configure_task')
255
self._config_task.cwd = self.bldnode
256
self._config_task.cmake = self
257
self._config_task.cmake_config_sig = sig
258
259
env = self._config_task.env
260
env.CMAKE_BLD_DIR = self.bldnode.abspath()
261
env.CMAKE_SRC_DIR = self.srcnode.abspath()
262
263
keys = self.vars_keys()
264
env.CMAKE_VARS = ["-D%s='%s'" % (k, self.vars[k]) for k in keys]
265
env.CMAKE_FLAGS = self.flags
266
267
self._config_task.set_outputs(
268
self.bldnode.find_or_declare('CMakeCache.txt'),
269
)
270
271
if self.last_build_task:
272
self._config_task.set_run_after(self.last_build_task)
273
274
self.bldnode.mkdir()
275
276
return self._config_task
277
278
def build(self, cmake_target, **kw):
279
return self.bld.cmake_build(self.name, cmake_target, **kw)
280
281
_cmake_instances = {}
282
def get_cmake(name):
283
if name not in _cmake_instances:
284
raise Exception('cmake: configuration named "%s" not found' % name)
285
return _cmake_instances[name]
286
287
@conf
288
def cmake(bld, name, cmake_src=None, cmake_bld=None, cmake_vars={}, cmake_flags=''):
289
'''
290
This function has two signatures:
291
- bld.cmake(name, cmake_src, cmake_bld, cmake_vars):
292
Create a cmake configuration.
293
- bld.cmake(name):
294
Get the cmake configuration with name.
295
'''
296
if not cmake_src and not cmake_bld and not cmake_vars:
297
return get_cmake(name)
298
299
if name in _cmake_instances:
300
bld.fatal('cmake: configuration named "%s" already exists' % name)
301
302
if not isinstance(cmake_src, Node.Node):
303
cmake_src = bld.path.find_dir(cmake_src)
304
305
if not cmake_bld:
306
cmake_bld = cmake_src.get_bld()
307
elif not isinstance(cmake_bld, Node.Node):
308
cmake_bld = bld.bldnode.make_node(cmake_bld)
309
310
c = CMakeConfig(bld, name, cmake_src, cmake_bld, cmake_vars, cmake_flags)
311
_cmake_instances[name] = c
312
return c
313
314
@feature('cmake_build')
315
def process_cmake_build(self):
316
if not hasattr(self, 'cmake_target'):
317
self.bld.fatal('cmake_build: taskgen is missing cmake_target')
318
if not hasattr(self, 'cmake_config'):
319
self.bld.fatal('cmake_build: taskgen is missing cmake_config')
320
321
tsk = self.create_cmake_build_task(self.cmake_config, self.cmake_target)
322
self.cmake_build_task = tsk
323
324
outputs = Utils.to_list(getattr(self, 'target', ''))
325
if not isinstance(outputs, list):
326
outputs = [outputs]
327
328
for o in outputs:
329
if not isinstance(o, Node.Node):
330
o = self.path.find_or_declare(o)
331
tsk.set_outputs(o)
332
333
tsk.output_patterns = getattr(self, 'cmake_output_patterns', [])
334
335
@conf
336
def cmake_build(bld, cmake_config, cmake_target, **kw):
337
kw['cmake_config'] = cmake_config
338
kw['cmake_target'] = cmake_target
339
kw['features'] = Utils.to_list(kw.get('features', [])) + ['cmake_build']
340
341
if 'name' not in kw:
342
kw['name'] = '%s_%s' % (cmake_config, cmake_target)
343
344
return bld(**kw)
345
346
@taskgen_method
347
def create_cmake_build_task(self, cmake_config, cmake_target):
348
cmake = get_cmake(cmake_config)
349
350
tsk = self.create_task('cmake_build_task')
351
tsk.cmake = cmake
352
tsk.cmake_target = cmake_target
353
tsk.output_patterns = []
354
tsk.env.CMAKE_BLD_DIR = cmake.bldnode.abspath()
355
tsk.env.CMAKE_TARGET = cmake_target
356
357
self.cmake_config_task = cmake.config_task(self)
358
tsk.set_run_after(self.cmake_config_task)
359
360
if cmake.last_build_task:
361
tsk.set_run_after(cmake.last_build_task)
362
cmake.last_build_task = tsk
363
364
return tsk
365
366
def _check_min_version(cfg):
367
cfg.start_msg('Checking cmake version')
368
cmd = cfg.env.get_flat('CMAKE'), '--version'
369
out = cfg.cmd_and_log(cmd, quiet=Context.BOTH)
370
m = re.search(r'\d+\.\d+(\.\d+(\.\d+)?)?', out)
371
if not m:
372
cfg.end_msg(
373
'unable to parse version, build is not guaranteed to succeed',
374
color='YELLOW',
375
)
376
else:
377
version = Utils.num2ver(m.group(0))
378
minver_str = cfg.env.get_flat('CMAKE_MIN_VERSION')
379
minver = Utils.num2ver(minver_str)
380
if version < minver:
381
cfg.fatal('cmake must be at least at version %s' % minver_str)
382
cfg.end_msg(m.group(0))
383
384
generators = dict(
385
default=[
386
(['ninja', 'ninja-build'], 'Ninja'),
387
(['make'], 'Unix Makefiles'),
388
],
389
win32=[
390
(['ninja', 'ninja-build'], 'Ninja'),
391
(['nmake'], 'NMake Makefiles'),
392
],
393
)
394
395
def configure(cfg):
396
cfg.find_program('cmake')
397
398
if cfg.env.CMAKE_MIN_VERSION:
399
_check_min_version(cfg)
400
401
l = generators.get(Utils.unversioned_sys_platform(), generators['default'])
402
for names, generator in l:
403
if cfg.find_program(names, mandatory=False):
404
cfg.env.CMAKE_GENERATOR_OPTION = '-G%s' % generator
405
break
406
else:
407
cfg.fatal("cmake: couldn't find a suitable CMake generator. " +
408
"The ones supported by this Waf tool for this platform are: %s" % ', '.join(g for _, g in l))
409
410