Alibek Omarov
2 years ago
3 changed files with 416 additions and 2 deletions
@ -0,0 +1,410 @@
@@ -0,0 +1,410 @@
|
||||
#! /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] |
Loading…
Reference in new issue