mirror of https://github.com/ArduPilot/ardupilot
409 lines
13 KiB
Python
409 lines
13 KiB
Python
# encoding: utf-8
|
|
|
|
# Copyright (C) 2015-2016 Intel Corporation. All rights reserved.
|
|
#
|
|
# This file is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU General Public License as published by the
|
|
# Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This file is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
Waf tool for external builds with cmake. This tool defines the feature
|
|
'cmake_build', for building through the cmake interface.
|
|
|
|
You can use CMAKE_MIN_VERSION environment variable before loading this tool in
|
|
the configuration to set a minimum version required for cmake. Example::
|
|
|
|
def configure(cfg):
|
|
cfg.CMAKE_MIN_VERSION = '3.5.2'
|
|
cfg.load('cmake')
|
|
|
|
Usage example::
|
|
|
|
def build(bld):
|
|
# cmake configuration
|
|
foo = bld.cmake(
|
|
name='foo',
|
|
cmake_src='path/to/foosrc', # where is the source tree
|
|
cmake_bld='path/to/foobld', # where to generate the build system
|
|
cmake_vars=dict(
|
|
CMAKE_BUILD_TYPE='Release',
|
|
...
|
|
),
|
|
)
|
|
|
|
# cmake build for external target 'bar'
|
|
bld(
|
|
features='cmake_build',
|
|
cmake_config='foo', # this build depends on the cmake generation above defined
|
|
cmake_target='bar', # what to pass to option --target of cmake
|
|
)
|
|
|
|
# cmake build for target 'baz' (syntactic sugar)
|
|
foo.build('baz')
|
|
|
|
The keys of cmake_vars are sorted so that unnecessary execution is avoided. If
|
|
you want to ensure an order in which the variables are passed to cmake, use an
|
|
OrderedDict. Example::
|
|
|
|
def build(bld):
|
|
foo_vars = OrderedDict()
|
|
foo_vars['CMAKE_BUILD_TYPE'] = 'Release'
|
|
foo_vars['FOO'] = 'value_of_foo'
|
|
foo_vars['BAR'] = 'value_of_bar'
|
|
|
|
# cmake configuration
|
|
foo = bld.cmake(
|
|
cmake_vars=foo_vars,
|
|
...
|
|
)
|
|
|
|
There may be cases when you want to establish dependency between other tasks and
|
|
the external build system's products (headers and libraries, for example). In
|
|
that case, you can specify the specific files in the option 'target' of your
|
|
cmake_build task generator. Example::
|
|
|
|
def build(bld):
|
|
...
|
|
|
|
# declaring on target only what I'm interested in
|
|
foo.build('baz', target='path/to/foobld/include/baz.h')
|
|
|
|
# myprogram.c includes baz.h, so the dependency is (implicitly)
|
|
# established
|
|
bld.program(target='myprogram', source='myprogram.c')
|
|
|
|
# another example
|
|
foo.build('another', target='another.txt')
|
|
|
|
bld(
|
|
rule='${CP} ${SRC} ${TGT}',
|
|
source=bld.bldnode.find_or_declare('another.txt'),
|
|
target='another_copied.txt',
|
|
)
|
|
|
|
|
|
You can also establish the dependency directly on a task object::
|
|
|
|
@feature('myfeature')
|
|
def process_myfeature(self):
|
|
baz_taskgen = self.bld.get_tgen_by_name('baz')
|
|
baz_taskgen.post()
|
|
|
|
# every cmake_build taskgen stores its task in cmake_build_task
|
|
baz_task = baz_taskgen.cmake_build_task
|
|
|
|
tsk = self.create_task('mytask')
|
|
|
|
tsk.set_run_after(baz_task)
|
|
|
|
# tsk is run whenever baz_task changes its outputs, namely,
|
|
# path/to/foobld/include/baz.h
|
|
tsk.dep_nodes.extend(baz_task.outputs)
|
|
|
|
If your cmake build creates several files (that may be dependency for several
|
|
tasks), you can use the parameter cmake_output_patterns. It receives a pattern
|
|
or a list of patterns relative to the cmake build directory. After the build
|
|
task is run, the files that match those patterns are set as output of the cmake
|
|
build task, so that they get a signature. Example::
|
|
|
|
def build(bld):
|
|
...
|
|
|
|
foo.build('baz', cmake_output_patterns='include/*.h')
|
|
|
|
...
|
|
"""
|
|
|
|
from waflib import Context, Node, Task, Utils
|
|
from waflib.Configure import conf
|
|
from waflib.TaskGen import feature, taskgen_method
|
|
|
|
from collections import OrderedDict
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
class cmake_configure_task(Task.Task):
|
|
vars = ['CMAKE_BLD_DIR']
|
|
run_str = '${CMAKE} ${CMAKE_FLAGS} ${CMAKE_SRC_DIR} ${CMAKE_VARS} ${CMAKE_GENERATOR_OPTION}'
|
|
color = 'BLUE'
|
|
|
|
def exec_command(self, cmd, **kw):
|
|
kw['stdout'] = sys.stdout
|
|
return super(cmake_configure_task, self).exec_command(cmd, **kw)
|
|
|
|
def uid(self):
|
|
if not hasattr(self, 'uid_'):
|
|
m = Utils.md5()
|
|
def u(s):
|
|
m.update(s.encode('utf-8'))
|
|
u(self.__class__.__name__)
|
|
u(self.env.get_flat('CMAKE_SRC_DIR'))
|
|
u(self.env.get_flat('CMAKE_BLD_DIR'))
|
|
u(self.env.get_flat('CMAKE_VARS'))
|
|
u(self.env.get_flat('CMAKE_FLAGS'))
|
|
self.uid_ = m.digest()
|
|
|
|
return self.uid_
|
|
|
|
def __str__(self):
|
|
return self.cmake.name
|
|
|
|
def keyword(self):
|
|
return 'CMake Configure'
|
|
|
|
# Clean cmake configuration
|
|
cmake_configure_task._original_run = cmake_configure_task.run
|
|
def _cmake_configure_task_run(self):
|
|
cmakecache_path = self.outputs[0].abspath()
|
|
if os.path.exists(cmakecache_path):
|
|
os.remove(cmakecache_path)
|
|
self._original_run()
|
|
cmake_configure_task.run = _cmake_configure_task_run
|
|
|
|
class cmake_build_task(Task.Task):
|
|
run_str = '${CMAKE} --build ${CMAKE_BLD_DIR} --target ${CMAKE_TARGET}'
|
|
color = 'BLUE'
|
|
# the cmake-generated build system is responsible of managing its own
|
|
# dependencies
|
|
always_run = True
|
|
|
|
def exec_command(self, cmd, **kw):
|
|
kw['stdout'] = sys.stdout
|
|
return super(cmake_build_task, self).exec_command(cmd, **kw)
|
|
|
|
def uid(self):
|
|
if not hasattr(self, 'uid_'):
|
|
m = Utils.md5()
|
|
def u(s):
|
|
m.update(s.encode('utf-8'))
|
|
u(self.__class__.__name__)
|
|
u(self.env.get_flat('CMAKE_BLD_DIR'))
|
|
u(self.env.get_flat('CMAKE_TARGET'))
|
|
self.uid_ = m.digest()
|
|
|
|
return self.uid_
|
|
|
|
def __str__(self):
|
|
return '%s %s' % (self.cmake.name, self.cmake_target)
|
|
|
|
def keyword(self):
|
|
return 'CMake Build'
|
|
|
|
cmake_build_task.original_post_run = cmake_build_task.post_run
|
|
def _cmake_build_task_post_run(self):
|
|
self.output_patterns = Utils.to_list(self.output_patterns)
|
|
if not self.output_patterns:
|
|
return self.original_post_run()
|
|
bldnode = self.cmake.bldnode
|
|
for node in bldnode.ant_glob(self.output_patterns, remove=False):
|
|
self.set_outputs(node)
|
|
return self.original_post_run()
|
|
cmake_build_task.post_run = _cmake_build_task_post_run
|
|
|
|
class CMakeConfig(object):
|
|
'''
|
|
CMake configuration. This object shouldn't be instantiated directly. Use
|
|
bld.cmake().
|
|
'''
|
|
def __init__(self, bld, name, srcnode, bldnode, cmake_vars, cmake_flags):
|
|
self.bld = bld
|
|
self.name = name
|
|
self.srcnode = srcnode
|
|
self.bldnode = bldnode
|
|
self.vars = cmake_vars
|
|
self.flags = cmake_flags
|
|
|
|
self._config_task = None
|
|
self.last_build_task = None
|
|
|
|
def vars_keys(self):
|
|
keys = list(self.vars.keys())
|
|
if not isinstance(self.vars, OrderedDict):
|
|
keys.sort()
|
|
return keys
|
|
|
|
def config_sig(self):
|
|
m = Utils.md5()
|
|
def u(s):
|
|
m.update(s.encode('utf-8'))
|
|
u(self.srcnode.abspath())
|
|
u(self.bldnode.abspath())
|
|
for v in self.flags:
|
|
u(v)
|
|
keys = self.vars_keys()
|
|
for k in keys:
|
|
u(k)
|
|
u(self.vars[k])
|
|
return m.digest()
|
|
|
|
def config_task(self, taskgen):
|
|
sig = self.config_sig()
|
|
if self._config_task and self._config_task.cmake_config_sig == sig:
|
|
return self._config_task
|
|
|
|
self._config_task = taskgen.create_task('cmake_configure_task')
|
|
self._config_task.cwd = self.bldnode
|
|
self._config_task.cmake = self
|
|
self._config_task.cmake_config_sig = sig
|
|
|
|
env = self._config_task.env
|
|
env.CMAKE_BLD_DIR = self.bldnode.abspath()
|
|
env.CMAKE_SRC_DIR = self.srcnode.abspath()
|
|
|
|
keys = self.vars_keys()
|
|
env.CMAKE_VARS = ["-D%s='%s'" % (k, self.vars[k]) for k in keys]
|
|
env.CMAKE_FLAGS = self.flags
|
|
|
|
self._config_task.set_outputs(
|
|
self.bldnode.find_or_declare('CMakeCache.txt'),
|
|
)
|
|
|
|
if self.last_build_task:
|
|
self._config_task.set_run_after(self.last_build_task)
|
|
|
|
self.bldnode.mkdir()
|
|
|
|
return self._config_task
|
|
|
|
def build(self, cmake_target, **kw):
|
|
return self.bld.cmake_build(self.name, cmake_target, **kw)
|
|
|
|
_cmake_instances = {}
|
|
def get_cmake(name):
|
|
if name not in _cmake_instances:
|
|
raise Exception('cmake: configuration named "%s" not found' % name)
|
|
return _cmake_instances[name]
|
|
|
|
@conf
|
|
def cmake(bld, name, cmake_src=None, cmake_bld=None, cmake_vars={}, cmake_flags=''):
|
|
'''
|
|
This function has two signatures:
|
|
- bld.cmake(name, cmake_src, cmake_bld, cmake_vars):
|
|
Create a cmake configuration.
|
|
- bld.cmake(name):
|
|
Get the cmake configuration with name.
|
|
'''
|
|
if not cmake_src and not cmake_bld and not cmake_vars:
|
|
return get_cmake(name)
|
|
|
|
if name in _cmake_instances:
|
|
bld.fatal('cmake: configuration named "%s" already exists' % name)
|
|
|
|
if not isinstance(cmake_src, Node.Node):
|
|
cmake_src = bld.path.find_dir(cmake_src)
|
|
|
|
if not cmake_bld:
|
|
cmake_bld = cmake_src.get_bld()
|
|
elif not isinstance(cmake_bld, Node.Node):
|
|
cmake_bld = bld.bldnode.make_node(cmake_bld)
|
|
|
|
c = CMakeConfig(bld, name, cmake_src, cmake_bld, cmake_vars, cmake_flags)
|
|
_cmake_instances[name] = c
|
|
return c
|
|
|
|
@feature('cmake_build')
|
|
def process_cmake_build(self):
|
|
if not hasattr(self, 'cmake_target'):
|
|
self.bld.fatal('cmake_build: taskgen is missing cmake_target')
|
|
if not hasattr(self, 'cmake_config'):
|
|
self.bld.fatal('cmake_build: taskgen is missing cmake_config')
|
|
|
|
tsk = self.create_cmake_build_task(self.cmake_config, self.cmake_target)
|
|
self.cmake_build_task = tsk
|
|
|
|
outputs = Utils.to_list(getattr(self, 'target', ''))
|
|
if not isinstance(outputs, list):
|
|
outputs = [outputs]
|
|
|
|
for o in outputs:
|
|
if not isinstance(o, Node.Node):
|
|
o = self.path.find_or_declare(o)
|
|
tsk.set_outputs(o)
|
|
|
|
tsk.output_patterns = getattr(self, 'cmake_output_patterns', [])
|
|
|
|
@conf
|
|
def cmake_build(bld, cmake_config, cmake_target, **kw):
|
|
kw['cmake_config'] = cmake_config
|
|
kw['cmake_target'] = cmake_target
|
|
kw['features'] = Utils.to_list(kw.get('features', [])) + ['cmake_build']
|
|
|
|
if 'name' not in kw:
|
|
kw['name'] = '%s_%s' % (cmake_config, cmake_target)
|
|
|
|
return bld(**kw)
|
|
|
|
@taskgen_method
|
|
def create_cmake_build_task(self, cmake_config, cmake_target):
|
|
cmake = get_cmake(cmake_config)
|
|
|
|
tsk = self.create_task('cmake_build_task')
|
|
tsk.cmake = cmake
|
|
tsk.cmake_target = cmake_target
|
|
tsk.output_patterns = []
|
|
tsk.env.CMAKE_BLD_DIR = cmake.bldnode.abspath()
|
|
tsk.env.CMAKE_TARGET = cmake_target
|
|
|
|
self.cmake_config_task = cmake.config_task(self)
|
|
tsk.set_run_after(self.cmake_config_task)
|
|
|
|
if cmake.last_build_task:
|
|
tsk.set_run_after(cmake.last_build_task)
|
|
cmake.last_build_task = tsk
|
|
|
|
return tsk
|
|
|
|
def _check_min_version(cfg):
|
|
cfg.start_msg('Checking cmake version')
|
|
cmd = cfg.env.get_flat('CMAKE'), '--version'
|
|
out = cfg.cmd_and_log(cmd, quiet=Context.BOTH)
|
|
m = re.search(r'\d+\.\d+(\.\d+(\.\d+)?)?', out)
|
|
if not m:
|
|
cfg.end_msg(
|
|
'unable to parse version, build is not guaranteed to succeed',
|
|
color='YELLOW',
|
|
)
|
|
else:
|
|
version = Utils.num2ver(m.group(0))
|
|
minver_str = cfg.env.get_flat('CMAKE_MIN_VERSION')
|
|
minver = Utils.num2ver(minver_str)
|
|
if version < minver:
|
|
cfg.fatal('cmake must be at least at version %s' % minver_str)
|
|
cfg.end_msg(m.group(0))
|
|
|
|
generators = dict(
|
|
default=[
|
|
(['ninja', 'ninja-build'], 'Ninja'),
|
|
(['make'], 'Unix Makefiles'),
|
|
],
|
|
win32=[
|
|
(['ninja', 'ninja-build'], 'Ninja'),
|
|
(['nmake'], 'NMake Makefiles'),
|
|
],
|
|
)
|
|
|
|
def configure(cfg):
|
|
cfg.find_program('cmake')
|
|
|
|
if cfg.env.CMAKE_MIN_VERSION:
|
|
_check_min_version(cfg)
|
|
|
|
l = generators.get(Utils.unversioned_sys_platform(), generators['default'])
|
|
for names, generator in l:
|
|
if cfg.find_program(names, mandatory=False):
|
|
cfg.env.CMAKE_GENERATOR_OPTION = '-G%s' % generator
|
|
break
|
|
else:
|
|
cfg.fatal("cmake: couldn't find a suitable CMake generator. " +
|
|
"The ones supported by this Waf tool for this platform are: %s" % ', '.join(g for _, g in l))
|