You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libshit/wscript

987 lines
38 KiB
Python

# -*- mode: python -*-
# idx map:
# 500xx libshit
# 510xx boost
# 513xx lua53
# 514xx libc++
# 515xx tracy
# 516xx capnproto (optional)
# 517xx yaml-cpp (optional)
import subprocess
try:
VERSION = subprocess.check_output(
['git', 'describe', '--tags', '--always'],
stderr = subprocess.PIPE,
universal_newlines = True).strip('\n').lstrip('v')
except:
with open('VERSION', 'r') as f:
VERSION = f.readline().strip('\n')
from waflib.TaskGen import before_method, after_method, feature, extension
def fixup_rc():
# ignore .rc files when not on windows/no resource compiler
@extension('.rc')
def rc_override(self, node):
if self.env.WINRC:
from waflib.Tools.winres import rc_file
rc_file(self, node)
from waflib import Context
try:
with open('wscript_user.py', 'r') as f:
exec(compile(f.read(), 'wscript_user.py', 'exec'), Context.g_module.__dict__)
except IOError:
pass
if not Context.g_module:
APPNAME = 'libshit'
try: did_I_mention_that_python_is_a_horrible_language()
except:
class AttributeDict(dict):
def __init__(self, *args, **kwargs):
super(AttributeDict, self).__init__(*args, **kwargs)
self.__dict__ = self
import sys
Context.g_module = AttributeDict(sys.exc_info()[2].tb_frame.f_globals)
from waflib.Tools import c_config, c_preproc
c_config.MACRO_TO_DESTOS['__vita__'] = 'vita'
c_preproc.go_absolute = True # TODO?
app = Context.g_module.APPNAME.upper()
libshit_cross = getattr(Context.g_module, 'LIBSHIT_CROSS', False)
libshit_translate = getattr(Context.g_module, 'LIBSHIT_TRANSLATE', app == 'LIBSHIT')
libshit_translate_yaml = getattr(Context.g_module, 'LIBSHIT_TRANSLATE_YAML', False)
libshit_translate_generate = getattr(Context.g_module, 'LIBSHIT_TRANSLATE_GENERATE', False)
import os
translate_yaml_hack = os.getenv('LIBSHIT_HAS_YAML_CPP_IN_MISC')
if translate_yaml_hack:
Context.g_module.LIBSHIT_CROSS = True
# I have no freakin idea how can a module get a reference itself when a
# module is not loaded normally but evaled, the normal sys.modules trick
# doesn't work that's for sure.
LIBSHIT_CROSS = True
libshit_cross = True
libshit_translate_yaml = True
libshit_translate_generate = True
def options(opt):
opt.load('with_selector', tooldir=opt.path.abspath())
opt.load('compiler_c compiler_cxx clang_compilation_database md5_tstamp')
grp = opt.get_option_group('configure options')
grp.add_option('--optimize', action='store_true', default=False,
help='Enable some default optimizations')
grp.add_option('--optimize-ext', action='store_true', default=False,
help='Optimize ext libs even if %s is in debug mode' % app.title())
grp.add_option('--release', action='store_true', default=False,
help='Release mode (NDEBUG + optimize)')
grp.add_option('--with-tests', action='store_true', default=False,
help='Enable tests')
grp.add_option('--with-valgrind', action='store_true', default=False,
help='Enable valgrind instrumentation')
grp.add_option('--min-windows-version', action='store',
help='Minimal windows version to support. 5.1 for XP, 6.1 for win7, etc')
grp.add_option('--no-pie', action='store_true', default=False,
help='Do not use -fPIE even when supported (use if you static link libraries built without -fPIC/-fPIE or you just want marginally faster code)')
opt.recurse('ext', name='options', once=False)
if translate_yaml_hack:
opt.recurse('misc', name='options', wscript='wscript_yaml_cpp', once=False)
def configure(cfg):
from waflib import Logs
if cfg.options.release:
cfg.options.optimize = True
cfg.load('with_selector', tooldir=cfg.path.abspath())
variant = cfg.variant
environ = cfg.environ
# sanity check
if variant != '' and cfg.bldnode.name != variant:
cfg.fatal('cfg.bldnode %r doesn not end in %r' % (cfg.bldnode, variant))
# setup host compiler
cfg.setenv(variant + '_host')
Logs.pprint('NORMAL', 'Configuring host compiler '+variant)
cross = False
# replace xxx with HOST_xxx env vars
cfg.environ = environ.copy()
for k in list(cfg.environ):
if k[0:5] == 'HOST_':
cross = True
cfg.environ[k[5:]] = cfg.environ[k]
del cfg.environ[k]
if cross:
configure_variant(cfg)
# host build executables don't need memcheck
cfg.env.append_value('DEFINES_'+app, ['LIBSHIT_WITH_MEMCHECK=0'])
if cfg.options.optimize or cfg.options.optimize_ext:
# host executables on cross compile, so -march=native is ok
cfg.filter_flags(['CFLAGS', 'CXXFLAGS'], ['-O1', '-march=native'])
# ----------------------------------------------------------------------
# setup target
cfg.setenv(variant)
Logs.pprint('NORMAL', 'Configuring target compiler '+variant)
cfg.env.CROSS = cross
cfg.environ = environ
configure_variant(cfg)
if cfg.env.DEST_OS == 'win32':
cfg.env.append_value('CFLAGS', '-gcodeview')
cfg.env.append_value('CXXFLAGS', '-gcodeview')
cfg.env.append_value('LINKFLAGS', '-g')
else:
cfg.filter_flags(['CFLAGS', 'CXXFLAGS', 'LINKFLAGS'], ['-ggdb3'])
if cfg.options.optimize:
cfg.filter_flags(['CFLAGS', 'CXXFLAGS', 'LINKFLAGS'], [
'-O3', '-flto', '-fno-fat-lto-objects',
'-fomit-frame-pointer'])
if cfg.env.DEST_OS == 'win32':
cfg.env.append_value('LINKFLAGS', '-Wl,-opt:icf,-opt:lldtailmerge')
else:
cfg.env.append_value('LINKFLAGS', '-Wl,-O1')
elif cfg.options.optimize_ext:
cfg.filter_flags(['CFLAGS_EXT', 'CXXFLAGS_EXT'], ['-O3'])
if cfg.options.release:
cfg.define('NDEBUG', 1)
cfg.env.WITH_TESTS = cfg.options.with_tests
if cfg.options.with_valgrind:
cfg.check_cxx(header_name='valgrind/memcheck.h', define_name='')
cfg.env.append_value(
'DEFINES_'+app, ['LIBSHIT_WITH_MEMCHECK=%d' % cfg.options.with_valgrind])
Logs.pprint('NORMAL', 'Configuring ext '+variant)
cfg.recurse('ext', name='configure', once=False)
if translate_yaml_hack:
cfg.recurse('misc', name='configure', wscript='wscript_yaml_cpp', once=False)
def configure_variant(ctx):
ctx.add_os_flags('ASFLAGS', dup=False)
# override flags specific to app/bundled libraries
for v in [app, 'EXT']:
ctx.add_os_flags('ASFLAGS_'+v, dup=False)
ctx.add_os_flags('CPPFLAGS_'+v, dup=False)
ctx.add_os_flags('CFLAGS_'+v, dup=False)
ctx.add_os_flags('CXXFLAGS_'+v, dup=False)
ctx.add_os_flags('LINKFLAGS_'+v, dup=False)
ctx.add_os_flags('LDFLAGS_'+v, dup=False)
ctx.load('compiler_c compiler_cxx gccdeps')
ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], [
# error on unknown arguments, including unknown options that turns
# unknown argument warnings into error. どうして?
'-Werror=unknown-warning-option',
'-Werror=ignored-optimization-argument',
'-Werror=unknown-argument',
], seq=False)
ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], [
'-fdiagnostics-color', '-fdiagnostics-show-option',
'-fdata-sections', '-ffunction-sections', '-fexceptions',
])
if ctx.env.DEST_OS == 'win32':
ctx.env.append_value('LINKFLAGS', ['-Wl,-opt:ref'])
elif ctx.check_cc(linkflags='-Wl,--gc-sections', mandatory=False):
ctx.env.append_value('LINKFLAGS', ['-Wl,--gc-sections'])
if ctx.env.DEST_OS != 'win32' and \
ctx.check_cc(linkflags='-Wl,--wrap=foo', mandatory=False):
ctx.env.WITH_LINKER_WRAP = True
ctx.env.append_value('LINKFLAGS_LIBSHIT', ['-Wl,--wrap=abort'])
ctx.filter_flags(['CFLAGS_'+app, 'CXXFLAGS_'+app], [
'-Wall', '-Wextra', '-pedantic', '-Wdouble-promotion',
'-Wno-parentheses', '-Wno-assume', '-Wno-attributes',
'-Wimplicit-fallthrough', '-Wno-dangling-else', '-Wno-unused-parameter',
# I don't even know what this warning supposed to mean, gcc. how can you
# not set a parameter?
'-Wno-unused-but-set-parameter',
# __try in lua exception handler
'-Wno-language-extension-token',
# parameter passing changed in an ancient gcc verison warning
'-Wno-psabi',
])
# c++ only warnings, shut up gcc
ctx.filter_flags(['CXXFLAGS_'+app], [
'-Wold-style-cast', '-Woverloaded-virtual',
'-Wno-undefined-var-template', # TYPE_NAME usage
# -Wsubobject-linkage: warning message is criminally bad, basically it
# complains about defining/using something from anonymous namespace in a
# header which normally makes sense, except in "*.binding.hpp"s.
# unfortunately pragma disabling this warning in the binding files does
# not work. see also https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51440
'-Wno-subobject-linkage', '-Wno-sign-compare',
'-Wno-extra-semi', # warns on valid ; in structs
])
# gcc is a piece of crap: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431
# missing-prototypes/missing-declarations: of course, clang and gcc flags
# mean different things, and gcc yells when -Wmissing-prototypes is used in
# c++
if ctx.env.COMPILER_CXX == 'clang++':
ctx.filter_flags(['CFLAGS_'+app, 'CXXFLAGS_'+app], [
'-Werror=undef', '-Wmissing-prototypes',
])
elif ctx.env.COMPILER_CXX == 'g++':
ctx.filter_flags(['CFLAGS_'+app, 'CXXFLAGS_'+app], [
'-Wno-pedantic', # empty semicolons outside functions are valid since 2011...
'-Wno-unknown-pragmas' # do not choke on #pragma clang
])
ctx.filter_flags(['CXXFLAGS_'+app], ['-Wmissing-declarations'])
ctx.filter_flags(['CFLAGS_EXT', 'CXXFLAGS_EXT'], [
'-Wno-parentheses-equality', # boost fs, windows build
'-Wno-assume', '-Wno-inconsistent-missing-override', # boost fs
'-Wno-string-plus-int', # lua53
'-Wno-deprecated-declarations', '-Wno-ignored-qualifiers', # capnp
])
if ctx.check_cxx(cxxflags=['-isystem', '.'],
features='cxx', mandatory=False,
msg='Checking for compiler flag -isystem'):
ctx.env.CPPSYSPATH_ST = ['-isystem']
else:
ctx.env.CPPSYSPATH_ST = ctx.env.CPPPATH_ST
ctx.check_cc(cflags='-std=c11')
ctx.check_cxx(cxxflags='-std=c++17')
ctx.env.append_value('CFLAGS', ['-std=c11'])
ctx.env.append_value('CXXFLAGS', ['-std=c++17'])
if ctx.options.release:
# only hide symbols in release builds, this creates better stacktraces
# in tools that doesn't support DWARF debug info
ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], ['-fvisibility=hidden'])
if not ctx.options.no_pie and \
ctx.check_cc(cflags='-fPIE', linkflags='-pie', mandatory=False):
ctx.env.append_value('CFLAGS', ['-fPIE'])
ctx.env.append_value('CXXFLAGS', ['-fPIE'])
ctx.env.append_value('LINKFLAGS', ['-fPIE', '-pie'])
ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], ['-fstack-protector'])
if ctx.check_cc(linkflags='-Wl,-z,relro,-z,now', mandatory=False):
ctx.env.append_value('LINKFLAGS', ['-Wl,-z,relro,-z,now'])
# TODO: glibc is a clusterfuck, as it produces shittons of warnings when you
# define _FORTIFY_SOURCE without optimization because this shit doesn't work
# without optimizations, and they think this is a feature. So I'd have to
# somehow figure out whether the user specified any flag that turns on
# optimizations and define this shit only then. Or just ignore it, I'll use
# your fucking security feature when you make it not fucking impossible to
# use, you fucking buttlicker security idiots.
# https://sourceware.org/bugzilla/show_bug.cgi?id=13979
# https://archive.md/eYs12
# defs = ctx.get_defs('glibc', '#include <stdio.h>')
# glibc = '__GLIBC__' in defs
# ctx.end_msg(glibc)
# if glibc:
# ctx.env.append_value('DEFINES', '_FORTIFY_SOURCE=2')
if ctx.env.DEST_OS == 'win32':
# fixup: waf expects mingw, not clang in half-msvc-emulation-mode
while '-Wl,--enable-auto-import' in ctx.env.LINKFLAGS:
ctx.env.LINKFLAGS.remove('-Wl,--enable-auto-import')
ctx.env.IMPLIB_ST = '-Wl,-implib:%s'
ctx.env.implib_PATTERN = '%s.lib'
ctx.env.cstlib_PATTERN = ctx.env.cxxstlib_PATTERN = '%s.lib'
ctx.env.def_PATTERN = '-Wl,-def:%s'
ctx.env.STLIB_MARKER = ''
ctx.env.SHLIB_MARKER = ''
if ctx.options.min_windows_version:
str_ver = ctx.options.min_windows_version
ver_ary = list(map(int, str_ver.split('.')))
if len(ver_ary) != 2:
ctx.fatal('Invalid windows version %r' % str_ver)
ctx.env.LINKFLAGS_WINDOWS_CLI = [
'-Xlinker', '/subsystem:console,'+str_ver]
ctx.env.LINKFLAGS_WINDOWS_GUI = [
'-Xlinker', '/subsystem:windows,'+str_ver]
ctx.env.WINVER = '0x%02x%02x' % tuple(ver_ary)
ctx.env.append_value(
'DEFINES', ['LIBSHIT_WINVER_MIN=%s' % ctx.env.WINVER])
else:
# use whatever the toolchain defaults to...
ctx.env.LINKFLAGS_WINDOWS_CLI = ['-Xlinker', '/subsystem:console']
ctx.env.LINKFLAGS_WINDOWS_GUI = ['-Xlinker', '/subsystem:windows']
for x in ['CXXFLAGS', 'CFLAGS']:
ctx.env.append_value(x, [
'-mno-incremental-linker-compatible', # no timestamp in obj files
# clang tries to be a smartass and warns about an unused
# argument when we're doing LTO, since in this case the output
# is not a COFF abomination, but an llvm bitcode file, so this
# option indeed has no effect, but it's fucking gay and it looks
# like there are no way to disable this titlicker case other
# than getting rid of the whole warning
# (and no, -Wno-.... -mno-... -Wunu... doesn't work)
'-Wno-unused-command-line-argument',
])
ctx.env.append_value('LINKFLAGS', [
'-nostdlib', '-Wl,-defaultlib:msvcrt', # -MD cont
'-Wl,-defaultlib:oldnames', # lua isatty
'-Wl,-Brepro', # removes timestamp to make output reproducible
'-Wl,-largeaddressaware',
])
ctx.define('_MT', 1)
ctx.define('_DLL', 1)
ctx.load('winres')
ctx.add_os_flags('WINRCFLAGS')
ctx.define('_CRT_SECURE_NO_WARNINGS', 1)
ctx.define('UNICODE', 1)
ctx.define('_UNICODE', 1)
ctx.check_cxx(lib='advapi32')
ctx.check_cxx(lib='dbgeng')
ctx.check_cxx(lib='kernel32')
ctx.check_cxx(lib='ole32')
ctx.check_cxx(lib='shell32')
ctx.check_cxx(lib='user32')
m = ctx.get_defs('msvc lib version', '#include <yvals.h>', cxx=True)
cpp_ver = m['_CPPLIB_VER']
if cpp_ver == '610':
ctx.end_msg('610, activating include patching', color='YELLOW')
inc = [ctx.path.find_node('msvc_include').abspath()]
ctx.env.prepend_value('SYSTEM_INCLUDES', inc)
ctx.env.append_value('DEFINES_BOOST', ['BOOST_NO_CXX14_CONSTEXPR'])
else:
ctx.end_msg(cpp_ver)
elif ctx.env.DEST_OS == 'vita':
inc = [ ctx.path.find_node('vita_include').abspath() ]
# type-limits: char is unsigned, thank you very much
ctx.env.prepend_value('CXXFLAGS', ['-Wno-type-limits'])
ctx.env.prepend_value('SYSTEM_INCLUDES', inc)
ctx.env.append_value('DEFINES_BOOST', ['BOOST_HAS_STDINT_H'])
ldflags = [
# muldefs: override of vita libc's abort with lto
'-Wl,-q,-z,nocopyreloc,-z,muldefs','-nostdlib', '-lsupc++',
'-Wl,--start-group', '-lgcc', '-lSceLibc_stub', '-lpthread',
# linked automatically by vitasdk gcc when not using -nostdlib
# we don't need some of them, but unused libraries don't end
# up in the final executable, so it doesn't hurt
'-lSceRtc_stub', '-lSceSysmem_stub', '-lSceKernelThreadMgr_stub',
'-lSceKernelModulemgr_stub', '-lSceIofilemgr_stub',
'-lSceProcessmgr_stub', '-lSceLibKernel_stub', '-lSceNet_stub',
'-Wl,--end-group'
]
ctx.env.append_value('LDFLAGS', ldflags)
else:
# mutexes (used by logger) need this on some platforms
ctx.check_cc(lib='pthread', mandatory=False)
ctx.env.append_value('LINKFLAGS', '-rdynamic')
# needed by debug stacktrace
ctx.check_cxx(lib='dl')
def build(bld):
fixup_rc()
fixup_fail_cxx()
bld.recurse('ext', name='build', once=False)
if translate_yaml_hack:
bld.recurse('misc', name='build', wscript='wscript_yaml_cpp', once=False)
build_libshit(bld, '')
if libshit_cross: bld.only_host_env(build_libshit)
def build_libshit(ctx, pref):
ctx.objects(idx = 50004 + (len(pref)>0),
source = ['src/libshit/except.cpp'],
uselib = ['LIBBACKTRACE', app],
use = ['BACKTRACE', 'BOOST', 'DBGENG', 'DL', 'DOCTEST',
'LUA', 'OLE32', 'USER32',
'LIBSHIT', pref+'lua'],
includes = 'src',
export_includes = 'src',
defines = ['LIBSHIT_WITH_ABORT_WRAP=%d' % (not not ctx.env.WITH_LINKER_WRAP) ],
target = pref+'libshit-except')
src = [
'src/libshit/container/polymorphic_stack.cpp',
'src/libshit/logger.cpp',
'src/libshit/low_io.cpp',
'src/libshit/maybe_owning_string.cpp',
'src/libshit/number_format.cpp',
'src/libshit/options.cpp',
'src/libshit/random.cpp',
'src/libshit/string_utils.cpp',
'src/libshit/wtf8.cpp',
]
if ctx.env.DEST_OS == 'vita':
src += ['src/libshit/vita_fixup.c']
else:
src += ['src/libshit/dynamic_lib.cpp']
if libshit_translate:
src += ['src/libshit/translate/plural.cpp']
if ctx.env.WITH_LUA != 'none':
ctx(idx = 50006 + (len(pref)>0),
source = [
'src/libshit/logger.lua',
'src/libshit/lua/base_funcs.lua',
'src/libshit/lua/lua53_polyfill.lua',
],
target = pref+'libshit-lua-gen-hdr')
src += [
'src/libshit/lua/base.cpp',
'src/libshit/lua/user_type.cpp',
'src/libshit/lua/userdata.cpp',
]
if ctx.env.WITH_TRACY != 'none':
src += [ 'src/libshit/tracy_alloc.cpp' ]
if ctx.env.WITH_TESTS:
src += [ 'test/test_helper.cpp' ]
ctx.objects(idx = 50000 + (len(pref)>0),
source = src,
tg_after = pref+'libshit-lua-gen-hdr',
uselib = app,
use = ['TRACY', 'PTHREAD',
'ADVAPI32', # windows random
pref+'tracy', pref+'libshit-except'],
target = pref+'libshit-base-notest')
if ctx.env.WITH_TESTS:
src = [
'test/abomination.cpp',
'test/bitfield.cpp',
'test/container/ordered_map.cpp',
'test/container/parent_list.cpp',
'test/container/simple_vector.cpp',
'test/flexible_struct.cpp',
'test/nonowning_string.cpp',
'test/shared_ptr.cpp',
'test/utf_low_level.cpp',
]
if ctx.env.WITH_LUA != 'none':
src += [
'test/lua/function_call.cpp',
'test/lua/function_ref.cpp',
'test/lua/type_traits.cpp',
'test/lua/user_type.cpp',
]
ctx.objects(idx = 50014 + (len(pref)>0),
source = src,
uselib = app,
use = [pref+'libshit-base-notest'],
target = pref+'libshit-base')
else:
ctx.objects(idx = 50014 + (len(pref)>0),
use = [pref+'libshit-base-notest'],
target = pref+'libshit-base')
# This doesn't map cleanly to our cross compilation abstractionism. We need
# to build translate twice regardless whether we're cross compiling or not
# (we don't want the dump thing to end up in the normal target binaries, but
# host binaries are only used for build time tools, so it doesn't make any
# sense to compile them twice too). This means Libshit::Translate will be
# unavailable in cross compiled tools though.
if pref == '' and libshit_translate:
src = [
'src/libshit/translate/context.cpp',
'src/libshit/translate/format.cpp',
'src/libshit/translate/format_opcode_argument.cpp',
'src/libshit/translate/format_opcode_gender.cpp',
'src/libshit/translate/format_opcode_plural.cpp',
'src/libshit/translate/format_parser.cpp',
'src/libshit/translate/node.cpp',
]
def build_host(ctx, pref):
ctx.program(idx = 50010,
features = 'no_doctest',
source = src + ['src/libshit/translate/generate.cpp'],
defines = 'LIBSHIT_TRANSLATE_DUMP=1 LIBSHIT_TRANSLATE_YAML=1',
uselib = ['LIBSHIT', 'YAML-CPP', app],
use = [pref+'libshit-base-notest', pref+'yaml-cpp'],
target = 'libshit-translate-generate')
if libshit_translate_generate: ctx.in_host_env(build_host)
if libshit_translate_generate and ctx.env.WITH_TESTS:
ctx(idx = 50011,
name = 'translate-gen-libshit-test-builtin',
features = 'translate_gen',
source = 'test/translate/builtin.yml',
target = ['test/translate/builtin_ctx.cpp',
'test/translate/builtin_ctx.hpp'],
namespace = 'Libshit::Translate::Test',
id = 'Builtin')
src += [
'test/translate/builtin.cpp',
'test/translate/builtin_ctx.cpp',
]
if libshit_translate_yaml and ctx.env.WITH_TESTS:
src += ['test/translate/translate.cpp']
defs = [
'LIBSHIT_TRANSLATE_DUMP=0',
'LIBSHIT_TRANSLATE_YAML=%d' % libshit_translate_yaml,
]
use = [pref+'libshit-base']
if libshit_translate_yaml:
use += ['YAML-CPP', pref+'yaml-cpp']
ctx.objects(idx = 50012 + (len(pref)>0),
source = src,
tg_after = 'translate-gen-libshit-test-builtin',
includes = 'test',
defines = defs,
export_defines = defs,
uselib = app,
use = use,
target = pref+'libshit')
else:
ctx.objects(idx = 50012 + (len(pref)>0),
use = pref+'libshit-base',
target = pref+'libshit')
if ctx.env.WITH_TESTS and app == 'LIBSHIT':
ctx.program(idx = 50002 + (len(pref)>0),
source = 'test/libshit_standalone.cpp',
uselib = 'WINDOWS_CLI',
use = pref+'libshit',
target = pref+'libshit-tests')
################################################################################
# random utilities
def fixup_fail_cxx():
# feature fail_cxx: expect compilation failure
import sys
from waflib import Logs
from waflib.Task import Task
from waflib.Tools import c_preproc
@extension('.cpp')
def fail_cxx_ext(self, node):
if 'fail_cxx' in self.features:
return self.create_compiled_task('fail_cxx', node)
else:
return self.create_compiled_task('cxx', node)
class fail_cxx(Task):
#run_str = '${CXX} ${ARCH_ST:ARCH} ${CXXFLAGS} ${CPPFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${CPPPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${CXX_SRC_F}${SRC} ${CXX_TGT_F}${TGT[0].abspath()}'
def run(tsk):
env = tsk.env
gen = tsk.generator
bld = gen.bld
cwdx = getattr(bld, 'cwdx', bld.bldnode) # TODO single cwd value in waf 1.9
wd = getattr(tsk, 'cwd', None)
def to_list(xx):
if isinstance(xx, str): return [xx]
return xx
tsk.last_cmd = lst = []
lst.extend(to_list(env['CXX']))
lst.extend(tsk.colon('ARCH_ST', 'ARCH'))
lst.extend(to_list(env['CXXFLAGS']))
lst.extend(to_list(env['CPPFLAGS']))
lst.extend(tsk.colon('FRAMEWORKPATH_ST', 'FRAMEWORKPATH'))
lst.extend(tsk.colon('CPPPATH_ST', 'INCPATHS'))
lst.extend(tsk.colon('DEFINES_ST', 'DEFINES'))
lst.extend(to_list(env['CXX_SRC_F']))
lst.extend([a.path_from(cwdx) for a in tsk.inputs])
lst.extend(to_list(env['CXX_TGT_F']))
lst.append(tsk.outputs[0].abspath())
lst = [x for x in lst if x]
try:
(out,err) = bld.cmd_and_log(
lst, cwd=bld.variant_dir, env=env.env or None,
quiet=0, output=0)
Logs.error("%s compiled successfully, but it shouldn't" % tsk.inputs[0])
Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
return -1
except Exception as e:
# create output to silence missing file errors
open(tsk.outputs[0].abspath(), 'w').close()
return 0
vars = ['CXXDEPS'] # unused variable to depend on, just in case
ext_in = ['.h'] # set the build order easily by using ext_out=['.h']
scan = c_preproc.scan
from waflib.Configure import conf
def filter_single_flag(ctx, vars, flag, feat, ret, **kw):
testflag=flag
if flag[0:5] == '-Wno-':
testflag = '-W'+flag[5:]
ctx.check_cxx(cflags=[testflag], cxxflags=[testflag], features=feat,
msg='Checking for compiler flag '+flag, **kw)
ret.append(flag)
def generic_filter_flags(ctx, vars, flags, feat, seq=False):
ret = []
if len(flags) == 1:
filter_single_flag(ctx, vars, flags[0], feat, ret, mandatory=False)
elif seq:
for flag in flags:
filter_single_flag(ctx, vars, flag, feat, ret, mandatory=False)
elif len(flags) > 1:
args = []
for flag in flags:
def x(ctx, flag=flag):
ctx.in_msg = True
return filter_single_flag(ctx, vars, flag, feat, ret)
args.append({'func': x, 'msg': 'Checking for compiler flag '+flag,
'mandatory': False})
ctx.multicheck(*args)
for flag in flags:
if flag in ret:
for var in vars:
ctx.env.append_value(var, flag)
return ret
@conf
def filter_flags(ctx, vars, flags, **kw):
return generic_filter_flags(ctx, vars, flags, 'cxx', **kw)
@conf
def filter_flags_c(ctx, vars, flags, **kw):
return generic_filter_flags(ctx, vars, flags, 'c', **kw)
def change_var(ctx, to):
if getattr(ctx, 'setenv', False):
ctx.setenv(to)
else:
ctx.variant = to
ctx.all_envs[to].derive()
@conf
def only_host_env(ctx, f):
if not ctx.env.CROSS: return
variant = ctx.variant
change_var(ctx, variant + '_host')
f(ctx, 'host/')
change_var(ctx, variant)
@conf
def in_host_env(ctx, f):
if ctx.env.CROSS:
variant = ctx.variant
change_var(ctx, variant + '_host')
f(ctx, 'host/')
change_var(ctx, variant)
else:
f(ctx, '')
@conf
def both_host_env(ctx, f):
f(ctx, '')
if ctx.env.CROSS:
variant = ctx.variant
change_var(ctx, variant + '_host')
f(ctx, 'host/')
change_var(ctx, variant)
# waf 1.7 style logging: c/cxx instead of vague shit like 'processing',
# 'compiling', etc.
from waflib.Task import Task
def getname(self):
return self.__class__.__name__ + ':'
Task.keyword = getname
@feature('c', 'cxx', 'includes')
@after_method('propagate_uselib_vars', 'process_source', 'apply_incpaths')
def apply_sysincpaths(self):
lst = self.to_incnodes(self.to_list(getattr(self, 'system_includes', [])) +
self.env['SYSTEM_INCLUDES'])
self.includes_nodes += lst
self.env['SYSINCPATHS'] = [x.abspath() for x in lst]
from waflib import Errors
@feature('c', 'cxx')
@after_method('process_source')
def add_tg_after(self):
after = []
for x in self.to_list(getattr(self, 'tg_after', [])):
try:
after.append(self.bld.get_tgen_by_name(x).tasks)
except Errors.WafError:
pass
if not after: return
node_deps = self.bld.node_deps
for tsk in self.compiled_tasks:
try:
node_deps[tsk.uid()]
except KeyError:
for a in after:
for a2 in a:
tsk.set_run_after(a2)
from waflib.Tools import ccroot
@feature('c', 'cxx')
@after_method('apply_link')
def add_msvc_pdb(self):
link = getattr(self, 'link_task', None)
if not link or self.env.DEST_OS != 'win32' or \
isinstance(link, ccroot.stlink_task) or '-g' not in self.env.LINKFLAGS:
return
link.outputs.append(link.outputs[0].change_ext('.pdb'))
from waflib.Tools import c, cxx
from waflib.Task import compile_fun
from waflib import Utils
from waflib.Tools.ccroot import USELIB_VARS
for cls in [c.c, cxx.cxx]:
run_str = cls.orig_run_str.replace('INCPATHS', 'INCPATHS} ${CPPSYSPATH_ST:SYSINCPATHS')
(f, dvars) = compile_fun(run_str, cls.shell)
cls.hcode = Utils.h_cmd(run_str)
cls.run = f
cls.vars = list(set(cls.vars + dvars))
cls.vars.sort()
USELIB_VARS['c'].add('SYSTEM_INCLUDES')
USELIB_VARS['cxx'].add('SYSTEM_INCLUDES')
USELIB_VARS['includes'].add('SYSTEM_INCLUDES')
# No tests, even if libshit has. Only use it for tools that are only needed
# during the build, because libshit will be still linked with tests, you'll just
# save the tests in this target. Also depend on libshit-base-notest instead of
# libshit(-base)? unless you need translation.
@feature('no_doctest')
@after_method('propagate_uselib_vars')
def fixup_test_define(self):
defs = self.env['DEFINES']
baddef = 'LIBSHIT_WITH_TESTS=1'
if baddef in defs:
defs.remove(baddef)
defs.append('LIBSHIT_WITH_TESTS=0')
# random config helpers
# reusable rule-like tasks
@conf
def rule_like(ctx, name):
def x(self):
self.meths.remove('process_source')
tsk = self.create_task(name)
self.task = tsk
tsk.inputs = self.to_nodes(self.source)
# from TaskGen.process_rule
if isinstance(self.target, str):
self.target = self.target.split()
if not isinstance(self.target, list):
self.target = [self.target]
for x in self.target:
if isinstance(x, str):
tsk.outputs.append(self.path.find_or_declare(x))
else:
x.parent.mkdir() # if a node was given, create the required folders
tsk.outputs.append(x)
x.__name__ = 'rule_like_%s' % name
feature(name)(x)
before_method('process_source')(x)
# get preprocessor defs
import re
defs_re = re.compile('^#define ([^ ]+) (.*)$')
@conf
def get_defs(ctx, msg, file, cxx=False):
ctx.start_msg('Checking for '+msg)
cmd = [] # python, why u no have Array#flatten?
cmd += cxx and ctx.env.CXX or ctx.env.CC
cmd += ctx.env.CPPFLAGS
cmd += cxx and ctx.env.CXXFLAGS or ctx.env.CFLAGS
if isinstance(file, str):
node = ctx.bldnode.make_node(cxx and 'test.cpp' or 'test.c')
node.write(file)
file = node
# -MD/MMD with -E causes clang to write a test.d to the working dir!
# yes, this is how Array#delete looks like in python abomination...
try: cmd.remove('-MD')
except ValueError: pass
try: cmd.remove('-MMD')
except ValueError: pass
cmd += ['-E', file.abspath(), '-dM' ]
try:
out = ctx.cmd_and_log(cmd)
except Exception:
ctx.end_msg(False)
raise
return dict(map(lambda l: defs_re.match(l).groups(), out.splitlines()))
@conf
def gen_version_hpp(bld, name):
import re
lst = list(filter(lambda x: re.match('^[0-9]+$', x), re.split('[\.-]', VERSION)))
# prevent parsing a git shortrev as a number
if len(lst) < 2: lst = []
rc_ver = ','.join((lst + ['0']*4)[0:4])
bld(features = 'subst',
source = name + '.in',
target = name,
before = ['c','cxx'],
VERSION = VERSION,
RC_VERSION = rc_ver)
# force the body into a temp environment, then write a header, because you can't
# use write_config_header without brutally polluting the global env
from waflib import Build
import os
class CfgHeaderBlock:
def __init__(self, ctx, name):
self.ctx = ctx
self.name = name
def __enter__(self):
self.ctx.env.stash()
self.ctx.env.define_key = []
def __exit__(self, type, value, traceback):
# because the checks are run parallel, the order of the defines is
# pretty random, so sort them to prevent rebuilding the whole shit after
# almost every reconfigure
if not type:
self.ctx.env.define_key.sort()
self.ctx.write_config_header(self.name, top=True)
# revert env, except CFG_FILES because that's needed for `./waf clean`
# to not clean the generated header files
cfg_files = self.ctx.env[Build.CFG_FILES]
self.ctx.env.revert()
self.ctx.env[Build.CFG_FILES] = cfg_files
@conf
def cfg_header_block(ctx, name):
return CfgHeaderBlock(ctx, name)
# Generate a fragment to check for a function exported by a header.
@conf
def check_func_fragment(ctx, name, inc):
# braindead clang implicitly declares some built-in functions, like strlcpy
# (and produces a warning to include <string.h>, which is inlcluded in the
# previous line), so do something like cmake to force a reference to the
# function, so it will bail out with a link error, even if the compiler
# wants to outsmart us.
return '\n'.join(map(lambda x: '#include <%s>' % x, Utils.to_list(inc))) + \
'\nint main() {\n#ifndef %s\nreturn *((int*)(&%s));\n#endif\n }' % (name,name)
def multi_check_common(ctx, name, kw):
if 'compiler' not in kw:
kw['compiler'] = 'c'
if 'mandatory' not in kw:
kw['mandatory'] = False
if 'define_name' not in kw:
kw['define_name'] = ctx.have_define(name)
@conf
def multi_check_hdr(ctx, name, **kw):
multi_check_common(ctx, name, kw)
if 'msg' not in kw:
kw['msg'] = 'Checking for header %s' % name
kw['header_name'] = name
return kw
@conf
def multi_check_func(ctx, name, inc, **kw):
multi_check_common(ctx, name, kw)
if 'msg' not in kw:
kw['msg'] = 'Checking for function %s' % name
kw['fragment'] = ctx.check_func_fragment(name, inc)
return kw
@conf
def multi_check_lib(ctx, name, **kw):
multi_check_common(ctx, name, kw)
if 'msg' not in kw:
kw['msg'] = 'Checking for library %s' % name
kw['lib'] = name
return kw
def recurse_subdirs(ctx, name, dirs):
for d in dirs:
ctx.recurse(d, name=name, once=False)
Context.Context.recurse_subdirs = recurse_subdirs
# translate generator task
class translate_gen(Task):
run_str = '${tsk.executable} ${SRC} ${tsk.namespace} ${tsk.id} ${TGT}'
color = 'BLUE'
rule_like(None, 'translate_gen')
@feature('translate_gen')
@after_method('rule_like_translate_gen')
def set_executable_translate_gen(self):
nd = self.bld.get_tgen_by_name('libshit-translate-generate').link_task.outputs[0]
self.task.id = self.id
self.task.namespace = self.namespace
self.task.executable = nd.abspath()
self.task.dep_nodes.append(nd)
from waflib import Logs
class CleandeadContext(Build.BuildContext):
'''removes not generated files from build dir'''
cmd = 'cleandead'
def execute_build(self):
# based on BuildContext, ListContext and CleanContext
self.recurse([self.run_dir])
self.pre_build()
self.timer = Utils.Timer()
for g in self.groups:
for tg in g:
try:
f = tg.post
except AttributeError:
pass
else:
f()
try:
# force the cache initialization
self.get_tgen_by_name('')
except Errors.WafError:
pass
outputs = set()
for env in self.all_envs.values():
outputs.update(self.root.find_or_declare(f) for f in env[Build.CFG_FILES])
for tg in self.task_gen_cache_names.values():
for tsk in tg.tasks:
outputs.update(tsk.outputs)
# gccdeps doesn't add .d files as outputs...
outputs.update(map(lambda x: x.change_ext('.d'), tsk.outputs))
excluded_dirs = 'compile_commands.json .lock* .wafpickle* *conf_check_*/** config.log %s' % Build.CACHE_DIR
for n in reversed(self.bldnode.ant_glob('**/*', excl=excluded_dirs, quiet=True, dir=True)):
if n in outputs: continue
try:
if os.path.isdir(n.abspath()):
os.rmdir(n.abspath())
Logs.warn('Removed stale directory %s' % n)
else:
os.remove(n.abspath())
Logs.warn('Removed stale file %s' % n)
except OSError:
pass
n.evict()