You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
410 lines
11 KiB
410 lines
11 KiB
#! /usr/bin/env python |
|
# -*- encoding: utf-8 -*- |
|
# Licensed under The MIT License (MIT) |
|
# Copyright (c) 2016, Michel Mooij |
|
# Copyright (c) 2023, Velaron |
|
|
|
''' |
|
Summary |
|
------- |
|
Generate *cmake* files of all C/C++ programs, static- and shared libraries |
|
that have been defined within a *waf* build environment. |
|
Once exported to *cmake*, all exported (C/C++) tasks can be build without |
|
any further need for, or dependency, to the *waf* build system itself. |
|
|
|
**cmake** is an open source cross-platform build system designed to build, test |
|
and package software. It is available for all major Desktop Operating Systems |
|
(MS Windows, all major Linux distributions and Macintosh OS-X). |
|
See http://www.cmake.org for a more detailed description on how to install |
|
and use it for your particular Desktop environment. |
|
|
|
|
|
Description |
|
----------- |
|
When exporting *waf* project data, a single top level **CMakeLists.txt** file |
|
will be exported in the top level directory of your *waf* build environment. |
|
This *cmake* build file will contain references to all exported *cmake* |
|
build files of each individual C/C++ build task. It will also contain generic |
|
variables and settings (e.g compiler to use, global preprocessor defines, link |
|
options and so on). |
|
|
|
Example below presents an overview of an environment in which *cmake* |
|
build files already have been exported:: |
|
|
|
. |
|
├── components |
|
│ └── clib |
|
│ ├── program |
|
│ │ ├── CMakeLists.txt |
|
│ │ └── wscript |
|
│ ├── shared |
|
│ │ ├── CMakeLists.txt |
|
│ │ └── wscript |
|
│ └── static |
|
│ ├── CMakeLists.txt |
|
│ └── wscript |
|
│ |
|
├── CMakeLists.txt |
|
└── wscript |
|
|
|
|
|
Usage |
|
----- |
|
Tasks can be exported to *cmake* using the command, as shown in the |
|
example below:: |
|
|
|
$ waf cmake |
|
|
|
All exported *cmake* build files can be removed in 'one go' using the *cmake* |
|
*cleanup* option:: |
|
|
|
$ waf cmake --cmake-clean |
|
|
|
Tasks generators to be excluded can be marked with the *skipme* option |
|
as shown below:: |
|
|
|
def build(bld): |
|
bld.program(name='foo', src='foobar.c', cmake_skip=True) |
|
|
|
''' |
|
|
|
|
|
from waflib.Build import BuildContext |
|
from waflib import Utils, Logs, Context, Errors |
|
|
|
|
|
def get_deps(bld, target): |
|
'''Returns a list of (nested) targets on which this target depends. |
|
|
|
:param bld: a *waf* build instance from the top level *wscript* |
|
:type bld: waflib.Build.BuildContext |
|
:param target: task name for which the dependencies should be returned |
|
:type target: str |
|
:returns: a list of task names on which the given target depends |
|
''' |
|
try: |
|
tgen = bld.get_tgen_by_name(target) |
|
except Errors.WafError: |
|
return [] |
|
else: |
|
uses = Utils.to_list(getattr(tgen, 'use', [])) |
|
deps = uses[:] |
|
for use in uses: |
|
deps += get_deps(bld, use) |
|
return list(set(deps)) |
|
|
|
|
|
def get_tgens(bld, names): |
|
'''Returns a list of task generators based on the given list of task |
|
generator names. |
|
|
|
:param bld: a *waf* build instance from the top level *wscript* |
|
:type bld: waflib.Build.BuildContext |
|
:param names: list of task generator names |
|
:type names: list of str |
|
:returns: list of task generators |
|
''' |
|
tgens = [] |
|
for name in names: |
|
try: |
|
tgen = bld.get_tgen_by_name(name) |
|
except Errors.WafError: |
|
pass |
|
else: |
|
tgens.append(tgen) |
|
return list(set(tgens)) |
|
|
|
|
|
def get_targets(bld): |
|
'''Returns a list of user specified build targets or None if no specific |
|
build targets has been selected using the *--targets=* command line option. |
|
|
|
:param bld: a *waf* build instance from the top level *wscript*. |
|
:type bld: waflib.Build.BuildContext |
|
:returns: a list of user specified target names (using --targets=x,y,z) or None |
|
''' |
|
if bld.targets == '': |
|
return None |
|
targets = bld.targets.split(',') |
|
for target in targets: |
|
targets += get_deps(bld, target) |
|
return targets |
|
|
|
|
|
def options(opt): |
|
'''Adds command line options for the CMake *waftool*. |
|
|
|
:param opt: Options context from the *waf* build environment. |
|
:type opt: waflib.Options.OptionsContext |
|
''' |
|
opt.add_option('--cmake', dest='cmake', default=False, |
|
action='store_true', help='select cmake for export/import actions') |
|
opt.add_option('--clean', dest='clean', default=False, |
|
action='store_true', help='delete exported files') |
|
|
|
|
|
def configure(conf): |
|
'''Method that will be invoked by *waf* when configuring the build |
|
environment. |
|
|
|
:param conf: Configuration context from the *waf* build environment. |
|
:type conf: waflib.Configure.ConfigurationContext |
|
''' |
|
conf.find_program('cmake', var='CMAKE', mandatory=False) |
|
|
|
|
|
class CMakeContext(BuildContext): |
|
'''export C/C++ tasks to CMake.''' |
|
cmd = 'cmake' |
|
|
|
def execute(self): |
|
'''Will be invoked when issuing the *cmake* command.''' |
|
self.restore() |
|
if not self.all_envs: |
|
self.load_envs() |
|
self.recurse([self.run_dir]) |
|
self.pre_build() |
|
|
|
for group in self.groups: |
|
for tgen in group: |
|
try: |
|
f = tgen.post |
|
except AttributeError: |
|
pass |
|
else: |
|
f() |
|
try: |
|
self.get_tgen_by_name('') |
|
except Exception: |
|
pass |
|
|
|
self.cmake = True |
|
if self.options.clean: |
|
cleanup(self) |
|
else: |
|
export(self) |
|
self.timer = Utils.Timer() |
|
|
|
|
|
def export(bld): |
|
'''Exports all C and C++ task generators to cmake. |
|
|
|
:param bld: a *waf* build instance from the top level *wscript*. |
|
:type bld: waflib.Build.BuildContext |
|
''' |
|
if not bld.options.cmake and not hasattr(bld, 'cmake'): |
|
return |
|
|
|
cmakes = {} |
|
loc = bld.path.relpath().replace('\\', '/') |
|
top = CMake(bld, loc) |
|
cmakes[loc] = top |
|
targets = get_targets(bld) |
|
|
|
for tgen in bld.task_gen_cache_names.values(): |
|
if targets and tgen.get_name() not in targets: |
|
continue |
|
if getattr(tgen, 'cmake_skip', False): |
|
continue |
|
if set(('c', 'cxx')) & set(getattr(tgen, 'features', [])): |
|
loc = tgen.path.relpath().replace('\\', '/') |
|
if loc not in cmakes: |
|
cmake = CMake(bld, loc) |
|
cmakes[loc] = cmake |
|
top.add_child(cmake) |
|
cmakes[loc].add_tgen(tgen) |
|
|
|
for cmake in cmakes.values(): |
|
cmake.export() |
|
|
|
|
|
def cleanup(bld): |
|
'''Removes all generated makefiles from the *waf* build environment. |
|
|
|
:param bld: a *waf* build instance from the top level *wscript*. |
|
:type bld: waflib.Build.BuildContext |
|
''' |
|
if not bld.options.clean: |
|
return |
|
|
|
loc = bld.path.relpath().replace('\\', '/') |
|
CMake(bld, loc).cleanup() |
|
targets = get_targets(bld) |
|
|
|
for tgen in bld.task_gen_cache_names.values(): |
|
if targets and tgen.get_name() not in targets: |
|
continue |
|
if getattr(tgen, 'cmake_skip', False): |
|
continue |
|
if set(('c', 'cxx')) & set(getattr(tgen, 'features', [])): |
|
loc = tgen.path.relpath().replace('\\', '/') |
|
CMake(bld, loc).cleanup() |
|
|
|
|
|
class CMake(object): |
|
def __init__(self, bld, location): |
|
self.bld = bld |
|
self.location = location |
|
self.cmakes = [] |
|
self.tgens = [] |
|
|
|
def export(self): |
|
content = self.get_content() |
|
if not content: |
|
return |
|
|
|
node = self.make_node() |
|
if not node: |
|
return |
|
node.write(content) |
|
Logs.pprint('YELLOW', 'exported: %s' % node.abspath()) |
|
|
|
def cleanup(self): |
|
node = self.find_node() |
|
if node: |
|
node.delete() |
|
Logs.pprint('YELLOW', 'removed: %s' % node.abspath()) |
|
|
|
def add_child(self, cmake): |
|
self.cmakes.append(cmake) |
|
|
|
def add_tgen(self, tgen): |
|
self.tgens.append(tgen) |
|
|
|
def get_location(self): |
|
return self.location |
|
|
|
def get_fname(self): |
|
name = '%s/CMakeLists.txt' % (self.location) |
|
return name |
|
|
|
def find_node(self): |
|
name = self.get_fname() |
|
if not name: |
|
return None |
|
return self.bld.srcnode.find_node(name) |
|
|
|
def make_node(self): |
|
name = self.get_fname() |
|
if not name: |
|
return None |
|
return self.bld.srcnode.make_node(name) |
|
|
|
def get_content(self): |
|
is_top = (self.location == self.bld.path.relpath()) |
|
|
|
content = '' |
|
if is_top: |
|
content += 'cmake_minimum_required(VERSION 2.8.12)\n' |
|
content += 'project(%s)\n' % (getattr(Context.g_module, |
|
Context.APPNAME)) |
|
content += '\n' |
|
|
|
env = self.bld.env |
|
defines = env.DEFINES |
|
if len(defines): |
|
content += 'add_definitions(\n -D%s\n)\n' % ( |
|
'\n -D'.join(defines)) |
|
content += '\n' |
|
|
|
flags = env.CFLAGS |
|
if len(flags): |
|
content += 'set(CMAKE_C_FLAGS "%s")\n' % (' '.join(flags)) |
|
|
|
flags = env.CXXFLAGS |
|
if len(flags): |
|
content += 'set(CMAKE_CXX_FLAGS "%s")\n' % (' '.join(flags)) |
|
|
|
if len(self.tgens): |
|
content += '\n' |
|
for tgen in self.tgens: |
|
content += self.get_tgen_content(tgen) |
|
|
|
if len(self.cmakes): |
|
content += '\n' |
|
for cmake in self.cmakes: |
|
content += 'add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/%s)\n' % ( |
|
cmake.get_location()) |
|
|
|
return content |
|
|
|
def get_tgen_content(self, tgen): |
|
content = '' |
|
name = tgen.get_name() |
|
|
|
content += 'set(%s_SRC' % (name.upper()) |
|
for src in tgen.source: |
|
content += '\n %s' % (src.path_from(tgen.path).replace('\\', '/')) |
|
content += '\n)\n\n' |
|
|
|
includes = self.get_includes(tgen) |
|
# includes.extend(tgen.env.INCLUDES) |
|
if len(includes): |
|
content += 'set(%s_INCLUDES' % (name.upper()) |
|
for include in includes: |
|
content += '\n %s' % include |
|
content += '\n)\n\n' |
|
content += 'include_directories(${%s_INCLUDES})\n' % (name.upper()) |
|
|
|
link_dirs = getattr(tgen.env, 'LIBPATH', []) |
|
if len(link_dirs): |
|
content += '\nlink_directories(' |
|
for dir in link_dirs: |
|
content += '\n \"%s\"' % dir.replace('\\', '/') |
|
content += '\n)\n\n' |
|
|
|
if set(('cprogram', 'cxxprogram')) & set(tgen.features): |
|
if tgen.env.DEST_OS == 'win32': |
|
content += 'add_executable(%s WIN32 ${%s_SRC})\n' % ( |
|
name, name.upper()) |
|
else: |
|
content += 'add_executable(%s ${%s_SRC})\n' % (name, |
|
name.upper()) |
|
|
|
elif set(('cshlib', 'cxxshlib')) & set(tgen.features): |
|
content += 'add_library(%s SHARED ${%s_SRC})\n\n' % ( |
|
name, name.upper()) |
|
|
|
else: # cstlib, cxxstlib or objects |
|
content += 'add_library(%s ${%s_SRC})\n\n' % (name, name.upper()) |
|
|
|
defines = self.get_genlist(tgen, 'defines') |
|
defines.extend(tgen.env.DEFINES) |
|
if len(defines): |
|
content += 'target_compile_definitions(%s PRIVATE\n -D%s\n)\n' % ( |
|
name, '\n -D'.join(defines)) |
|
content += '\n' |
|
|
|
libs = getattr(tgen.env, 'LIB', []) |
|
libs.extend(tgen.env.STLIB) |
|
|
|
if len(libs): |
|
content += '\n' |
|
content += 'target_link_libraries(%s\n %s)\n' % (name, '\n '.join(libs)) |
|
content += '\n' |
|
|
|
return content |
|
|
|
def get_includes(self, tgen): |
|
'''returns the include paths for the given task generator. |
|
''' |
|
includes = self.get_genlist(tgen, 'includes') |
|
for use in getattr(tgen, 'use', []): |
|
key = 'INCLUDES_%s' % use |
|
try: |
|
tg = self.bld.get_tgen_by_name(use) |
|
if 'fake_lib' in tg.features: |
|
if key in tgen.env: |
|
includes.extend([l.replace('\\', '/') |
|
for l in tgen.env[key]]) |
|
except Errors.WafError: |
|
if key in tgen.env: |
|
includes.extend([l.replace('\\', '/') |
|
for l in tgen.env[key]]) |
|
return includes |
|
|
|
def get_genlist(self, tgen, name): |
|
lst = Utils.to_list(getattr(tgen, name, [])) |
|
lst = [str(l.path_from(tgen.path)) if hasattr( |
|
l, 'path_from') else l for l in lst] |
|
return [l.replace('\\', '/') for l in lst]
|
|
|