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