mirror of https://github.com/FWGS/xash3d-fwgs
411 lines
11 KiB
Python
411 lines
11 KiB
Python
#! /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]
|