libshit

Just some random shit
git clone https://git.neptards.moe/neptards/libshit.git
Log | Files | Refs | Submodules | README | LICENSE

wscript (38396B)


      1 # -*- mode: python -*-
      2 
      3 # idx map:
      4 # 500xx libshit
      5 # 510xx boost
      6 # 513xx lua53
      7 # 514xx libc++
      8 # 515xx tracy
      9 # 516xx capnproto (optional)
     10 # 517xx yaml-cpp (optional)
     11 
     12 import subprocess
     13 try:
     14     VERSION = subprocess.check_output(
     15         ['git', 'describe', '--tags', '--always'],
     16         stderr = subprocess.PIPE,
     17         universal_newlines = True).strip('\n').lstrip('v')
     18 except:
     19     with open('VERSION', 'r') as f:
     20         VERSION = f.readline().strip('\n')
     21 
     22 from waflib.TaskGen import before_method, after_method, feature, extension
     23 def fixup_rc():
     24     # ignore .rc files when not on windows/no resource compiler
     25     @extension('.rc')
     26     def rc_override(self, node):
     27         if self.env.WINRC:
     28             from waflib.Tools.winres import rc_file
     29             rc_file(self, node)
     30 
     31 from waflib import Context
     32 try:
     33     with open('wscript_user.py', 'r') as f:
     34         exec(compile(f.read(), 'wscript_user.py', 'exec'), Context.g_module.__dict__)
     35 except IOError:
     36     pass
     37 
     38 if not Context.g_module:
     39     APPNAME = 'libshit'
     40     try: did_I_mention_that_python_is_a_horrible_language()
     41     except:
     42         class AttributeDict(dict):
     43             def __init__(self, *args, **kwargs):
     44                 super(AttributeDict, self).__init__(*args, **kwargs)
     45                 self.__dict__ = self
     46         import sys
     47         Context.g_module = AttributeDict(sys.exc_info()[2].tb_frame.f_globals)
     48 
     49 from waflib.Tools import c_config, c_preproc
     50 c_config.MACRO_TO_DESTOS['__vita__'] = 'vita'
     51 c_preproc.go_absolute = True # TODO?
     52 
     53 app = Context.g_module.APPNAME.upper()
     54 libshit_cross = getattr(Context.g_module, 'LIBSHIT_CROSS', False)
     55 libshit_translate = getattr(Context.g_module, 'LIBSHIT_TRANSLATE', app == 'LIBSHIT')
     56 libshit_translate_yaml = getattr(Context.g_module, 'LIBSHIT_TRANSLATE_YAML', False)
     57 libshit_translate_generate = getattr(Context.g_module, 'LIBSHIT_TRANSLATE_GENERATE', False)
     58 
     59 import os
     60 translate_yaml_hack = os.getenv('LIBSHIT_HAS_YAML_CPP_IN_MISC')
     61 if translate_yaml_hack:
     62     Context.g_module.LIBSHIT_CROSS = True
     63     # I have no freakin idea how can a module get a reference itself when a
     64     # module is not loaded normally but evaled, the normal sys.modules trick
     65     # doesn't work that's for sure.
     66     LIBSHIT_CROSS = True
     67     libshit_cross = True
     68     libshit_translate_yaml = True
     69     libshit_translate_generate = True
     70 
     71 def options(opt):
     72     opt.load('with_selector', tooldir=opt.path.abspath())
     73     opt.load('compiler_c compiler_cxx clang_compilation_database md5_tstamp')
     74     grp = opt.get_option_group('configure options')
     75 
     76     grp.add_option('--optimize', action='store_true', default=False,
     77                    help='Enable some default optimizations')
     78     grp.add_option('--optimize-ext', action='store_true', default=False,
     79                    help='Optimize ext libs even if %s is in debug mode' % app.title())
     80     grp.add_option('--release', action='store_true', default=False,
     81                    help='Release mode (NDEBUG + optimize)')
     82     grp.add_option('--with-tests', action='store_true', default=False,
     83                    help='Enable tests')
     84     grp.add_option('--with-valgrind', action='store_true', default=False,
     85                    help='Enable valgrind instrumentation')
     86 
     87     grp.add_option('--min-windows-version', action='store',
     88                    help='Minimal windows version to support. 5.1 for XP, 6.1 for win7, etc')
     89     grp.add_option('--no-pie', action='store_true', default=False,
     90                    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)')
     91 
     92     opt.recurse('ext', name='options', once=False)
     93     if translate_yaml_hack:
     94         opt.recurse('misc', name='options', wscript='wscript_yaml_cpp', once=False)
     95 
     96 def configure(cfg):
     97     from waflib import Logs
     98     if cfg.options.release:
     99         cfg.options.optimize = True
    100 
    101     cfg.load('with_selector', tooldir=cfg.path.abspath())
    102 
    103     variant = cfg.variant
    104     environ = cfg.environ
    105 
    106     # sanity check
    107     if variant != '' and cfg.bldnode.name != variant:
    108         cfg.fatal('cfg.bldnode %r doesn not end in %r' % (cfg.bldnode, variant))
    109 
    110     # setup host compiler
    111     cfg.setenv(variant + '_host')
    112     Logs.pprint('NORMAL', 'Configuring host compiler '+variant)
    113 
    114     cross = False
    115     # replace xxx with HOST_xxx env vars
    116     cfg.environ = environ.copy()
    117     for k in list(cfg.environ):
    118         if k[0:5] == 'HOST_':
    119             cross = True
    120             cfg.environ[k[5:]] = cfg.environ[k]
    121             del cfg.environ[k]
    122 
    123     if cross:
    124         configure_variant(cfg)
    125 
    126         # host build executables don't need memcheck
    127         cfg.env.append_value('DEFINES_'+app, ['LIBSHIT_WITH_MEMCHECK=0'])
    128 
    129         if cfg.options.optimize or cfg.options.optimize_ext:
    130             # host executables on cross compile, so -march=native is ok
    131             cfg.filter_flags(['CFLAGS', 'CXXFLAGS'], ['-O1', '-march=native'])
    132 
    133     # ----------------------------------------------------------------------
    134     # setup target
    135     cfg.setenv(variant)
    136     Logs.pprint('NORMAL', 'Configuring target compiler '+variant)
    137     cfg.env.CROSS = cross
    138     cfg.environ = environ
    139 
    140     configure_variant(cfg)
    141 
    142     if cfg.env.DEST_OS == 'win32':
    143         cfg.env.append_value('CFLAGS', '-gcodeview')
    144         cfg.env.append_value('CXXFLAGS', '-gcodeview')
    145         cfg.env.append_value('LINKFLAGS', '-g')
    146     else:
    147         cfg.filter_flags(['CFLAGS', 'CXXFLAGS', 'LINKFLAGS'], ['-ggdb3'])
    148 
    149     if cfg.options.optimize:
    150         cfg.filter_flags(['CFLAGS', 'CXXFLAGS', 'LINKFLAGS'], [
    151             '-O3', '-flto', '-fno-fat-lto-objects',
    152              '-fomit-frame-pointer'])
    153 
    154         if cfg.env.DEST_OS == 'win32':
    155             cfg.env.append_value('LINKFLAGS', '-Wl,-opt:icf,-opt:lldtailmerge')
    156         else:
    157             cfg.env.append_value('LINKFLAGS', '-Wl,-O1')
    158     elif cfg.options.optimize_ext:
    159         cfg.filter_flags(['CFLAGS_EXT', 'CXXFLAGS_EXT'], ['-O3'])
    160 
    161     if cfg.options.release:
    162         cfg.define('NDEBUG', 1)
    163     cfg.env.WITH_TESTS = cfg.options.with_tests
    164 
    165     if cfg.options.with_valgrind:
    166         cfg.check_cxx(header_name='valgrind/memcheck.h', define_name='')
    167     cfg.env.append_value(
    168         'DEFINES_'+app, ['LIBSHIT_WITH_MEMCHECK=%d' % cfg.options.with_valgrind])
    169 
    170     Logs.pprint('NORMAL', 'Configuring ext '+variant)
    171     cfg.recurse('ext', name='configure', once=False)
    172     if translate_yaml_hack:
    173         cfg.recurse('misc', name='configure', wscript='wscript_yaml_cpp', once=False)
    174 
    175 def configure_variant(ctx):
    176     ctx.add_os_flags('ASFLAGS', dup=False)
    177     # override flags specific to app/bundled libraries
    178     for v in [app, 'EXT']:
    179         ctx.add_os_flags('ASFLAGS_'+v, dup=False)
    180         ctx.add_os_flags('CPPFLAGS_'+v, dup=False)
    181         ctx.add_os_flags('CFLAGS_'+v, dup=False)
    182         ctx.add_os_flags('CXXFLAGS_'+v, dup=False)
    183         ctx.add_os_flags('LINKFLAGS_'+v, dup=False)
    184         ctx.add_os_flags('LDFLAGS_'+v, dup=False)
    185 
    186     ctx.load('compiler_c compiler_cxx gccdeps')
    187 
    188     ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], [
    189         # error on unknown arguments, including unknown options that turns
    190         # unknown argument warnings into error. どうして?
    191         '-Werror=unknown-warning-option',
    192         '-Werror=ignored-optimization-argument',
    193         '-Werror=unknown-argument',
    194     ], seq=False)
    195     ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], [
    196         '-fdiagnostics-color', '-fdiagnostics-show-option',
    197         '-fdata-sections', '-ffunction-sections', '-fexceptions',
    198     ])
    199     if ctx.env.DEST_OS == 'win32':
    200         ctx.env.append_value('LINKFLAGS', ['-Wl,-opt:ref'])
    201     elif ctx.check_cc(linkflags='-Wl,--gc-sections', mandatory=False):
    202         ctx.env.append_value('LINKFLAGS', ['-Wl,--gc-sections'])
    203 
    204     if ctx.env.DEST_OS != 'win32' and \
    205        ctx.check_cc(linkflags='-Wl,--wrap=foo', mandatory=False):
    206         ctx.env.WITH_LINKER_WRAP = True
    207         ctx.env.append_value('LINKFLAGS_LIBSHIT', ['-Wl,--wrap=abort'])
    208 
    209     ctx.filter_flags(['CFLAGS_'+app, 'CXXFLAGS_'+app], [
    210         '-Wall', '-Wextra', '-pedantic', '-Wdouble-promotion',
    211         '-Wno-parentheses', '-Wno-assume', '-Wno-attributes',
    212         '-Wimplicit-fallthrough', '-Wno-dangling-else', '-Wno-unused-parameter',
    213         # I don't even know what this warning supposed to mean, gcc. how can you
    214         # not set a parameter?
    215         '-Wno-unused-but-set-parameter',
    216         # __try in lua exception handler
    217         '-Wno-language-extension-token',
    218         # parameter passing changed in an ancient gcc verison warning
    219         '-Wno-psabi',
    220     ])
    221     # c++ only warnings, shut up gcc
    222     ctx.filter_flags(['CXXFLAGS_'+app], [
    223         '-Wold-style-cast', '-Woverloaded-virtual',
    224         '-Wno-undefined-var-template', # TYPE_NAME usage
    225         # -Wsubobject-linkage: warning message is criminally bad, basically it
    226         # complains about defining/using something from anonymous namespace in a
    227         # header which normally makes sense, except in "*.binding.hpp"s.
    228         # unfortunately pragma disabling this warning in the binding files does
    229         # not work. see also https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51440
    230         '-Wno-subobject-linkage', '-Wno-sign-compare',
    231         '-Wno-extra-semi', # warns on valid ; in structs
    232     ])
    233 
    234     # gcc is a piece of crap: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431
    235     # missing-prototypes/missing-declarations: of course, clang and gcc flags
    236     # mean different things, and gcc yells when -Wmissing-prototypes is used in
    237     # c++
    238     if ctx.env.COMPILER_CXX == 'clang++':
    239         ctx.filter_flags(['CFLAGS_'+app, 'CXXFLAGS_'+app], [
    240             '-Werror=undef', '-Wmissing-prototypes',
    241         ])
    242     elif ctx.env.COMPILER_CXX == 'g++':
    243         ctx.filter_flags(['CFLAGS_'+app, 'CXXFLAGS_'+app], [
    244             '-Wno-pedantic', # empty semicolons outside functions are valid since 2011...
    245             '-Wno-unknown-pragmas' # do not choke on #pragma clang
    246         ])
    247         ctx.filter_flags(['CXXFLAGS_'+app], ['-Wmissing-declarations'])
    248 
    249     ctx.filter_flags(['CFLAGS_EXT', 'CXXFLAGS_EXT'], [
    250         '-Wno-parentheses-equality', # boost fs, windows build
    251         '-Wno-assume', '-Wno-inconsistent-missing-override', # boost fs
    252         '-Wno-string-plus-int', # lua53
    253         '-Wno-deprecated-declarations', '-Wno-ignored-qualifiers', # capnp
    254     ])
    255 
    256     if ctx.check_cxx(cxxflags=['-isystem', '.'],
    257                      features='cxx', mandatory=False,
    258                      msg='Checking for compiler flag -isystem'):
    259         ctx.env.CPPSYSPATH_ST = ['-isystem']
    260     else:
    261         ctx.env.CPPSYSPATH_ST = ctx.env.CPPPATH_ST
    262 
    263     ctx.check_cc(cflags='-std=c11')
    264     ctx.check_cxx(cxxflags='-std=c++17')
    265     ctx.env.append_value('CFLAGS', ['-std=c11'])
    266     ctx.env.append_value('CXXFLAGS', ['-std=c++17'])
    267 
    268     if ctx.options.release:
    269         # only hide symbols in release builds, this creates better stacktraces
    270         # in tools that doesn't support DWARF debug info
    271         ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], ['-fvisibility=hidden'])
    272 
    273     if not ctx.options.no_pie and \
    274        ctx.check_cc(cflags='-fPIE', linkflags='-pie', mandatory=False):
    275         ctx.env.append_value('CFLAGS', ['-fPIE'])
    276         ctx.env.append_value('CXXFLAGS', ['-fPIE'])
    277         ctx.env.append_value('LINKFLAGS', ['-fPIE', '-pie'])
    278     ctx.filter_flags(['CFLAGS', 'CXXFLAGS'], ['-fstack-protector'])
    279     if ctx.check_cc(linkflags='-Wl,-z,relro,-z,now', mandatory=False):
    280         ctx.env.append_value('LINKFLAGS', ['-Wl,-z,relro,-z,now'])
    281     # TODO: glibc is a clusterfuck, as it produces shittons of warnings when you
    282     # define _FORTIFY_SOURCE without optimization because this shit doesn't work
    283     # without optimizations, and they think this is a feature. So I'd have to
    284     # somehow figure out whether the user specified any flag that turns on
    285     # optimizations and define this shit only then. Or just ignore it, I'll use
    286     # your fucking security feature when you make it not fucking impossible to
    287     # use, you fucking buttlicker security idiots.
    288     # https://sourceware.org/bugzilla/show_bug.cgi?id=13979
    289     # https://archive.md/eYs12
    290 
    291     # defs = ctx.get_defs('glibc', '#include <stdio.h>')
    292     # glibc = '__GLIBC__' in defs
    293     # ctx.end_msg(glibc)
    294     # if glibc:
    295     #     ctx.env.append_value('DEFINES', '_FORTIFY_SOURCE=2')
    296 
    297     if ctx.env.DEST_OS == 'win32':
    298         # fixup: waf expects mingw, not clang in half-msvc-emulation-mode
    299         while '-Wl,--enable-auto-import' in ctx.env.LINKFLAGS:
    300             ctx.env.LINKFLAGS.remove('-Wl,--enable-auto-import')
    301         ctx.env.IMPLIB_ST = '-Wl,-implib:%s'
    302         ctx.env.implib_PATTERN = '%s.lib'
    303         ctx.env.cstlib_PATTERN = ctx.env.cxxstlib_PATTERN = '%s.lib'
    304         ctx.env.def_PATTERN = '-Wl,-def:%s'
    305         ctx.env.STLIB_MARKER = ''
    306         ctx.env.SHLIB_MARKER = ''
    307 
    308         if ctx.options.min_windows_version:
    309             str_ver = ctx.options.min_windows_version
    310             ver_ary = list(map(int, str_ver.split('.')))
    311             if len(ver_ary) != 2:
    312                 ctx.fatal('Invalid windows version %r' % str_ver)
    313 
    314             ctx.env.LINKFLAGS_WINDOWS_CLI = [
    315                 '-Xlinker', '/subsystem:console,'+str_ver]
    316             ctx.env.LINKFLAGS_WINDOWS_GUI = [
    317                 '-Xlinker', '/subsystem:windows,'+str_ver]
    318             ctx.env.WINVER = '0x%02x%02x' % tuple(ver_ary)
    319             ctx.env.append_value(
    320                 'DEFINES', ['LIBSHIT_WINVER_MIN=%s' % ctx.env.WINVER])
    321         else:
    322             # use whatever the toolchain defaults to...
    323             ctx.env.LINKFLAGS_WINDOWS_CLI = ['-Xlinker', '/subsystem:console']
    324             ctx.env.LINKFLAGS_WINDOWS_GUI = ['-Xlinker', '/subsystem:windows']
    325 
    326         for x in ['CXXFLAGS', 'CFLAGS']:
    327             ctx.env.append_value(x, [
    328                 '-mno-incremental-linker-compatible', # no timestamp in obj files
    329                 # clang tries to be a smartass and warns about an unused
    330                 # argument when we're doing LTO, since in this case the output
    331                 # is not a COFF abomination, but an llvm bitcode file, so this
    332                 # option indeed has no effect, but it's fucking gay and it looks
    333                 # like there are no way to disable this titlicker case other
    334                 # than getting rid of the whole warning
    335                 # (and no, -Wno-....  -mno-... -Wunu... doesn't work)
    336                 '-Wno-unused-command-line-argument',
    337             ])
    338         ctx.env.append_value('LINKFLAGS', [
    339             '-nostdlib', '-Wl,-defaultlib:msvcrt', # -MD cont
    340             '-Wl,-defaultlib:oldnames', # lua isatty
    341             '-Wl,-Brepro', # removes timestamp to make output reproducible
    342             '-Wl,-largeaddressaware',
    343         ])
    344         ctx.define('_MT', 1)
    345         ctx.define('_DLL', 1)
    346 
    347         ctx.load('winres')
    348         ctx.add_os_flags('WINRCFLAGS')
    349         ctx.define('_CRT_SECURE_NO_WARNINGS', 1)
    350         ctx.define('UNICODE', 1)
    351         ctx.define('_UNICODE', 1)
    352 
    353         ctx.check_cxx(lib='advapi32')
    354         ctx.check_cxx(lib='dbgeng')
    355         ctx.check_cxx(lib='kernel32')
    356         ctx.check_cxx(lib='ole32')
    357         ctx.check_cxx(lib='shell32')
    358         ctx.check_cxx(lib='user32')
    359 
    360         m = ctx.get_defs('msvc lib version', '#include <yvals.h>', cxx=True)
    361         cpp_ver = m['_CPPLIB_VER']
    362         if cpp_ver == '610':
    363             ctx.end_msg('610, activating include patching', color='YELLOW')
    364             inc = [ctx.path.find_node('msvc_include').abspath()]
    365             ctx.env.prepend_value('SYSTEM_INCLUDES', inc)
    366             ctx.env.append_value('DEFINES_BOOST', ['BOOST_NO_CXX14_CONSTEXPR'])
    367         else:
    368             ctx.end_msg(cpp_ver)
    369     elif ctx.env.DEST_OS == 'vita':
    370         inc = [ ctx.path.find_node('vita_include').abspath() ]
    371         # type-limits: char is unsigned, thank you very much
    372         ctx.env.prepend_value('CXXFLAGS', ['-Wno-type-limits'])
    373         ctx.env.prepend_value('SYSTEM_INCLUDES', inc)
    374         ctx.env.append_value('DEFINES_BOOST', ['BOOST_HAS_STDINT_H'])
    375         ldflags = [
    376             # muldefs: override of vita libc's abort with lto
    377             '-Wl,-q,-z,nocopyreloc,-z,muldefs','-nostdlib', '-lsupc++',
    378             '-Wl,--start-group', '-lgcc', '-lSceLibc_stub', '-lpthread',
    379             # linked automatically by vitasdk gcc when not using -nostdlib
    380             # we don't need some of them, but unused libraries don't end
    381             # up in the final executable, so it doesn't hurt
    382             '-lSceRtc_stub', '-lSceSysmem_stub', '-lSceKernelThreadMgr_stub',
    383             '-lSceKernelModulemgr_stub', '-lSceIofilemgr_stub',
    384             '-lSceProcessmgr_stub', '-lSceLibKernel_stub', '-lSceNet_stub',
    385             '-Wl,--end-group'
    386         ]
    387         ctx.env.append_value('LDFLAGS', ldflags)
    388     else:
    389         # mutexes (used by logger) need this on some platforms
    390         ctx.check_cc(lib='pthread', mandatory=False)
    391 
    392         ctx.env.append_value('LINKFLAGS', '-rdynamic')
    393         # needed by debug stacktrace
    394         ctx.check_cxx(lib='dl')
    395 
    396 def build(bld):
    397     fixup_rc()
    398     fixup_fail_cxx()
    399     bld.recurse('ext', name='build', once=False)
    400     if translate_yaml_hack:
    401         bld.recurse('misc', name='build', wscript='wscript_yaml_cpp', once=False)
    402 
    403     build_libshit(bld, '')
    404     if libshit_cross: bld.only_host_env(build_libshit)
    405 
    406 def build_libshit(ctx, pref):
    407     ctx.objects(idx      = 50004 + (len(pref)>0),
    408                 source   = ['src/libshit/except.cpp'],
    409                 uselib   = ['LIBBACKTRACE', app],
    410                 use      = ['BACKTRACE', 'BOOST', 'DBGENG', 'DL', 'DOCTEST',
    411                             'LUA', 'OLE32', 'USER32',
    412                             'LIBSHIT', pref+'lua'],
    413                 includes = 'src',
    414                 export_includes = 'src',
    415                 defines  = ['LIBSHIT_WITH_ABORT_WRAP=%d' % (not not ctx.env.WITH_LINKER_WRAP) ],
    416                 target   = pref+'libshit-except')
    417 
    418     src = [
    419         'src/libshit/container/polymorphic_stack.cpp',
    420         'src/libshit/logger.cpp',
    421         'src/libshit/low_io.cpp',
    422         'src/libshit/maybe_owning_string.cpp',
    423         'src/libshit/number_format.cpp',
    424         'src/libshit/options.cpp',
    425         'src/libshit/random.cpp',
    426         'src/libshit/string_utils.cpp',
    427         'src/libshit/wtf8.cpp',
    428     ]
    429     if ctx.env.DEST_OS == 'vita':
    430         src += ['src/libshit/vita_fixup.c']
    431     else:
    432         src += ['src/libshit/dynamic_lib.cpp']
    433     if libshit_translate:
    434         src += ['src/libshit/translate/plural.cpp']
    435     if ctx.env.WITH_LUA != 'none':
    436         ctx(idx    = 50006 + (len(pref)>0),
    437             source = [
    438                 'src/libshit/logger.lua',
    439                 'src/libshit/lua/base_funcs.lua',
    440                 'src/libshit/lua/lua53_polyfill.lua',
    441             ],
    442             target = pref+'libshit-lua-gen-hdr')
    443         src += [
    444             'src/libshit/lua/base.cpp',
    445             'src/libshit/lua/user_type.cpp',
    446             'src/libshit/lua/userdata.cpp',
    447         ]
    448     if ctx.env.WITH_TRACY != 'none':
    449         src += [ 'src/libshit/tracy_alloc.cpp' ]
    450 
    451     if ctx.env.WITH_TESTS:
    452         src += [ 'test/test_helper.cpp' ]
    453 
    454     ctx.objects(idx      = 50000 + (len(pref)>0),
    455                 source   = src,
    456                 tg_after = pref+'libshit-lua-gen-hdr',
    457                 uselib   = app,
    458                 use      = ['TRACY', 'PTHREAD',
    459                             'ADVAPI32', # windows random
    460                             pref+'tracy', pref+'libshit-except'],
    461                 target   = pref+'libshit-base-notest')
    462 
    463     if ctx.env.WITH_TESTS:
    464         src = [
    465             'test/abomination.cpp',
    466             'test/bitfield.cpp',
    467             'test/container/ordered_map.cpp',
    468             'test/container/parent_list.cpp',
    469             'test/container/simple_vector.cpp',
    470             'test/flexible_struct.cpp',
    471             'test/nonowning_string.cpp',
    472             'test/shared_ptr.cpp',
    473             'test/utf_low_level.cpp',
    474         ]
    475         if ctx.env.WITH_LUA != 'none':
    476             src += [
    477                 'test/lua/function_call.cpp',
    478                 'test/lua/function_ref.cpp',
    479                 'test/lua/type_traits.cpp',
    480                 'test/lua/user_type.cpp',
    481             ]
    482 
    483         ctx.objects(idx      = 50014 + (len(pref)>0),
    484                     source   = src,
    485                     uselib   = app,
    486                     use      = [pref+'libshit-base-notest'],
    487                     target   = pref+'libshit-base')
    488     else:
    489         ctx.objects(idx      = 50014 + (len(pref)>0),
    490                     use      = [pref+'libshit-base-notest'],
    491                     target   = pref+'libshit-base')
    492 
    493     # This doesn't map cleanly to our cross compilation abstractionism. We need
    494     # to build translate twice regardless whether we're cross compiling or not
    495     # (we don't want the dump thing to end up in the normal target binaries, but
    496     # host binaries are only used for build time tools, so it doesn't make any
    497     # sense to compile them twice too). This means Libshit::Translate will be
    498     # unavailable in cross compiled tools though.
    499     if pref == '' and libshit_translate:
    500         src = [
    501             'src/libshit/translate/context.cpp',
    502             'src/libshit/translate/format.cpp',
    503             'src/libshit/translate/format_opcode_argument.cpp',
    504             'src/libshit/translate/format_opcode_gender.cpp',
    505             'src/libshit/translate/format_opcode_plural.cpp',
    506             'src/libshit/translate/format_parser.cpp',
    507             'src/libshit/translate/node.cpp',
    508         ]
    509         def build_host(ctx, pref):
    510             ctx.program(idx      = 50010,
    511                         features = 'no_doctest',
    512                         source   = src + ['src/libshit/translate/generate.cpp'],
    513                         defines  = 'LIBSHIT_TRANSLATE_DUMP=1 LIBSHIT_TRANSLATE_YAML=1',
    514                         uselib   = ['LIBSHIT', 'YAML-CPP', app],
    515                         use      = [pref+'libshit-base-notest', pref+'yaml-cpp'],
    516                         target   = 'libshit-translate-generate')
    517         if libshit_translate_generate: ctx.in_host_env(build_host)
    518 
    519         if libshit_translate_generate and ctx.env.WITH_TESTS:
    520             ctx(idx       = 50011,
    521                 name      = 'translate-gen-libshit-test-builtin',
    522                 features  = 'translate_gen',
    523                 source    = 'test/translate/builtin.yml',
    524                 target    = ['test/translate/builtin_ctx.cpp',
    525                              'test/translate/builtin_ctx.hpp'],
    526                 namespace = 'Libshit::Translate::Test',
    527                 id        = 'Builtin')
    528             src += [
    529                 'test/translate/builtin.cpp',
    530                 'test/translate/builtin_ctx.cpp',
    531             ]
    532         if libshit_translate_yaml and ctx.env.WITH_TESTS:
    533             src += ['test/translate/translate.cpp']
    534 
    535         defs = [
    536             'LIBSHIT_TRANSLATE_DUMP=0',
    537             'LIBSHIT_TRANSLATE_YAML=%d' % libshit_translate_yaml,
    538         ]
    539         use = [pref+'libshit-base']
    540         if libshit_translate_yaml:
    541             use += ['YAML-CPP', pref+'yaml-cpp']
    542         ctx.objects(idx            = 50012 + (len(pref)>0),
    543                     source         = src,
    544                     tg_after       = 'translate-gen-libshit-test-builtin',
    545                     includes       = 'test',
    546                     defines        = defs,
    547                     export_defines = defs,
    548                     uselib         = app,
    549                     use            = use,
    550                     target         = pref+'libshit')
    551     else:
    552         ctx.objects(idx      = 50012 + (len(pref)>0),
    553                     use      = pref+'libshit-base',
    554                     target   = pref+'libshit')
    555 
    556     if ctx.env.WITH_TESTS and app == 'LIBSHIT':
    557         ctx.program(idx    = 50002 + (len(pref)>0),
    558                     source = 'test/libshit_standalone.cpp',
    559                     uselib = 'WINDOWS_CLI',
    560                     use    = pref+'libshit',
    561                     target = pref+'libshit-tests')
    562 
    563 ################################################################################
    564 # random utilities
    565 
    566 def fixup_fail_cxx():
    567     # feature fail_cxx: expect compilation failure
    568     import sys
    569     from waflib import Logs
    570     from waflib.Task import Task
    571     from waflib.Tools import c_preproc
    572     @extension('.cpp')
    573     def fail_cxx_ext(self, node):
    574         if 'fail_cxx' in self.features:
    575             return self.create_compiled_task('fail_cxx', node)
    576         else:
    577             return self.create_compiled_task('cxx', node)
    578 
    579     class fail_cxx(Task):
    580         #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()}'
    581         def run(tsk):
    582             env = tsk.env
    583             gen = tsk.generator
    584             bld = gen.bld
    585             cwdx = getattr(bld, 'cwdx', bld.bldnode) # TODO single cwd value in waf 1.9
    586             wd = getattr(tsk, 'cwd', None)
    587             def to_list(xx):
    588                 if isinstance(xx, str): return [xx]
    589                 return xx
    590             tsk.last_cmd = lst = []
    591             lst.extend(to_list(env['CXX']))
    592             lst.extend(tsk.colon('ARCH_ST', 'ARCH'))
    593             lst.extend(to_list(env['CXXFLAGS']))
    594             lst.extend(to_list(env['CPPFLAGS']))
    595             lst.extend(tsk.colon('FRAMEWORKPATH_ST', 'FRAMEWORKPATH'))
    596             lst.extend(tsk.colon('CPPPATH_ST', 'INCPATHS'))
    597             lst.extend(tsk.colon('DEFINES_ST', 'DEFINES'))
    598             lst.extend(to_list(env['CXX_SRC_F']))
    599             lst.extend([a.path_from(cwdx) for a in tsk.inputs])
    600             lst.extend(to_list(env['CXX_TGT_F']))
    601             lst.append(tsk.outputs[0].abspath())
    602             lst = [x for x in lst if x]
    603             try:
    604                 (out,err) = bld.cmd_and_log(
    605                     lst, cwd=bld.variant_dir, env=env.env or None,
    606                     quiet=0, output=0)
    607                 Logs.error("%s compiled successfully, but it shouldn't" % tsk.inputs[0])
    608                 Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
    609                 Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
    610                 return -1
    611             except Exception as e:
    612                 # create output to silence missing file errors
    613                 open(tsk.outputs[0].abspath(), 'w').close()
    614                 return 0
    615 
    616         vars    = ['CXXDEPS'] # unused variable to depend on, just in case
    617         ext_in  = ['.h'] # set the build order easily by using ext_out=['.h']
    618         scan    = c_preproc.scan
    619 
    620 
    621 from waflib.Configure import conf
    622 def filter_single_flag(ctx, vars, flag, feat, ret, **kw):
    623     testflag=flag
    624     if flag[0:5] == '-Wno-':
    625         testflag = '-W'+flag[5:]
    626     ctx.check_cxx(cflags=[testflag], cxxflags=[testflag], features=feat,
    627                   msg='Checking for compiler flag '+flag, **kw)
    628 
    629     ret.append(flag)
    630 
    631 def generic_filter_flags(ctx, vars, flags, feat, seq=False):
    632     ret = []
    633 
    634     if len(flags) == 1:
    635         filter_single_flag(ctx, vars, flags[0], feat, ret, mandatory=False)
    636     elif seq:
    637         for flag in flags:
    638             filter_single_flag(ctx, vars, flag, feat, ret, mandatory=False)
    639     elif len(flags) > 1:
    640         args = []
    641         for flag in flags:
    642             def x(ctx, flag=flag):
    643                 ctx.in_msg = True
    644                 return filter_single_flag(ctx, vars, flag, feat, ret)
    645             args.append({'func': x, 'msg': 'Checking for compiler flag '+flag,
    646                          'mandatory': False})
    647         ctx.multicheck(*args)
    648 
    649     for flag in flags:
    650         if flag in ret:
    651             for var in vars:
    652                 ctx.env.append_value(var, flag)
    653     return ret
    654 
    655 @conf
    656 def filter_flags(ctx, vars, flags, **kw):
    657     return generic_filter_flags(ctx, vars, flags, 'cxx', **kw)
    658 
    659 @conf
    660 def filter_flags_c(ctx, vars, flags, **kw):
    661     return generic_filter_flags(ctx, vars, flags, 'c', **kw)
    662 
    663 def change_var(ctx, to):
    664     if getattr(ctx, 'setenv', False):
    665         ctx.setenv(to)
    666     else:
    667         ctx.variant = to
    668         ctx.all_envs[to].derive()
    669 
    670 @conf
    671 def only_host_env(ctx, f):
    672     if not ctx.env.CROSS: return
    673     variant = ctx.variant
    674     change_var(ctx, variant + '_host')
    675     f(ctx, 'host/')
    676     change_var(ctx, variant)
    677 
    678 @conf
    679 def in_host_env(ctx, f):
    680     if ctx.env.CROSS:
    681         variant = ctx.variant
    682         change_var(ctx, variant + '_host')
    683         f(ctx, 'host/')
    684         change_var(ctx, variant)
    685     else:
    686         f(ctx, '')
    687 
    688 @conf
    689 def both_host_env(ctx, f):
    690     f(ctx, '')
    691     if ctx.env.CROSS:
    692         variant = ctx.variant
    693         change_var(ctx, variant + '_host')
    694         f(ctx, 'host/')
    695         change_var(ctx, variant)
    696 
    697 # waf 1.7 style logging: c/cxx instead of vague shit like 'processing',
    698 # 'compiling', etc.
    699 from waflib.Task import Task
    700 def getname(self):
    701     return self.__class__.__name__ + ':'
    702 Task.keyword = getname
    703 
    704 
    705 @feature('c', 'cxx', 'includes')
    706 @after_method('propagate_uselib_vars', 'process_source', 'apply_incpaths')
    707 def apply_sysincpaths(self):
    708     lst = self.to_incnodes(self.to_list(getattr(self, 'system_includes', [])) +
    709                            self.env['SYSTEM_INCLUDES'])
    710     self.includes_nodes += lst
    711     self.env['SYSINCPATHS'] = [x.abspath() for x in lst]
    712 
    713 from waflib import Errors
    714 @feature('c', 'cxx')
    715 @after_method('process_source')
    716 def add_tg_after(self):
    717     after = []
    718     for x in self.to_list(getattr(self, 'tg_after', [])):
    719         try:
    720             after.append(self.bld.get_tgen_by_name(x).tasks)
    721         except Errors.WafError:
    722             pass
    723 
    724     if not after: return
    725 
    726     node_deps = self.bld.node_deps
    727     for tsk in self.compiled_tasks:
    728         try:
    729             node_deps[tsk.uid()]
    730         except KeyError:
    731             for a in after:
    732                 for a2 in a:
    733                     tsk.set_run_after(a2)
    734 
    735 from waflib.Tools import ccroot
    736 @feature('c', 'cxx')
    737 @after_method('apply_link')
    738 def add_msvc_pdb(self):
    739     link = getattr(self, 'link_task', None)
    740     if not link or self.env.DEST_OS != 'win32' or \
    741        isinstance(link, ccroot.stlink_task) or '-g' not in self.env.LINKFLAGS:
    742         return
    743     link.outputs.append(link.outputs[0].change_ext('.pdb'))
    744 
    745 from waflib.Tools import c, cxx
    746 from waflib.Task import compile_fun
    747 from waflib import Utils
    748 from waflib.Tools.ccroot import USELIB_VARS
    749 for cls in [c.c, cxx.cxx]:
    750     run_str = cls.orig_run_str.replace('INCPATHS', 'INCPATHS} ${CPPSYSPATH_ST:SYSINCPATHS')
    751     (f, dvars) = compile_fun(run_str, cls.shell)
    752     cls.hcode = Utils.h_cmd(run_str)
    753     cls.run = f
    754     cls.vars = list(set(cls.vars + dvars))
    755     cls.vars.sort()
    756 USELIB_VARS['c'].add('SYSTEM_INCLUDES')
    757 USELIB_VARS['cxx'].add('SYSTEM_INCLUDES')
    758 USELIB_VARS['includes'].add('SYSTEM_INCLUDES')
    759 
    760 # No tests, even if libshit has. Only use it for tools that are only needed
    761 # during the build, because libshit will be still linked with tests, you'll just
    762 # save the tests in this target. Also depend on libshit-base-notest instead of
    763 # libshit(-base)? unless you need translation.
    764 @feature('no_doctest')
    765 @after_method('propagate_uselib_vars')
    766 def fixup_test_define(self):
    767     defs = self.env['DEFINES']
    768     baddef = 'LIBSHIT_WITH_TESTS=1'
    769     if baddef in defs:
    770         defs.remove(baddef)
    771         defs.append('LIBSHIT_WITH_TESTS=0')
    772 
    773 
    774 # random config helpers
    775 # reusable rule-like tasks
    776 @conf
    777 def rule_like(ctx, name):
    778     def x(self):
    779         self.meths.remove('process_source')
    780         tsk = self.create_task(name)
    781         self.task = tsk
    782         tsk.inputs = self.to_nodes(self.source)
    783 
    784         # from TaskGen.process_rule
    785         if isinstance(self.target, str):
    786             self.target = self.target.split()
    787         if not isinstance(self.target, list):
    788             self.target = [self.target]
    789         for x in self.target:
    790             if isinstance(x, str):
    791                 tsk.outputs.append(self.path.find_or_declare(x))
    792             else:
    793                 x.parent.mkdir() # if a node was given, create the required folders
    794                 tsk.outputs.append(x)
    795 
    796     x.__name__ = 'rule_like_%s' % name
    797     feature(name)(x)
    798     before_method('process_source')(x)
    799 
    800 # get preprocessor defs
    801 import re
    802 defs_re = re.compile('^#define ([^ ]+) (.*)$')
    803 @conf
    804 def get_defs(ctx, msg, file, cxx=False):
    805     ctx.start_msg('Checking for '+msg)
    806     cmd = [] # python, why u no have Array#flatten?
    807     cmd += cxx and ctx.env.CXX or ctx.env.CC
    808     cmd += ctx.env.CPPFLAGS
    809     cmd += cxx and ctx.env.CXXFLAGS or ctx.env.CFLAGS
    810     if isinstance(file, str):
    811         node = ctx.bldnode.make_node(cxx and 'test.cpp' or 'test.c')
    812         node.write(file)
    813         file = node
    814 
    815     # -MD/MMD with -E causes clang to write a test.d to the working dir!
    816     # yes, this is how Array#delete looks like in python abomination...
    817     try: cmd.remove('-MD')
    818     except ValueError: pass
    819     try: cmd.remove('-MMD')
    820     except ValueError: pass
    821     cmd += ['-E', file.abspath(), '-dM' ]
    822     try:
    823         out = ctx.cmd_and_log(cmd)
    824     except Exception:
    825         ctx.end_msg(False)
    826         raise
    827 
    828     return dict(map(lambda l: defs_re.match(l).groups(), out.splitlines()))
    829 
    830 @conf
    831 def gen_version_hpp(bld, name):
    832     import re
    833     lst = list(filter(lambda x: re.match('^[0-9]+$', x), re.split('[\.-]', VERSION)))
    834     # prevent parsing a git shortrev as a number
    835     if len(lst) < 2: lst = []
    836     rc_ver = ','.join((lst + ['0']*4)[0:4])
    837     bld(features   = 'subst',
    838         source     = name + '.in',
    839         target     = name,
    840         before     = ['c','cxx'],
    841         VERSION    = VERSION,
    842         RC_VERSION = rc_ver)
    843 
    844 # force the body into a temp environment, then write a header, because you can't
    845 # use write_config_header without brutally polluting the global env
    846 from waflib import Build
    847 import os
    848 class CfgHeaderBlock:
    849     def __init__(self, ctx, name):
    850         self.ctx = ctx
    851         self.name = name
    852 
    853     def __enter__(self):
    854         self.ctx.env.stash()
    855         self.ctx.env.define_key = []
    856 
    857     def __exit__(self, type, value, traceback):
    858         # because the checks are run parallel, the order of the defines is
    859         # pretty random, so sort them to prevent rebuilding the whole shit after
    860         # almost every reconfigure
    861         if not type:
    862             self.ctx.env.define_key.sort()
    863             self.ctx.write_config_header(self.name, top=True)
    864         # revert env, except CFG_FILES because that's needed for `./waf clean`
    865         # to not clean the generated header files
    866         cfg_files = self.ctx.env[Build.CFG_FILES]
    867         self.ctx.env.revert()
    868         self.ctx.env[Build.CFG_FILES] = cfg_files
    869 
    870 @conf
    871 def cfg_header_block(ctx, name):
    872     return CfgHeaderBlock(ctx, name)
    873 
    874 # Generate a fragment to check for a function exported by a header.
    875 @conf
    876 def check_func_fragment(ctx, name, inc):
    877     # braindead clang implicitly declares some built-in functions, like strlcpy
    878     # (and produces a warning to include <string.h>, which is inlcluded in the
    879     # previous line), so do something like cmake to force a reference to the
    880     # function, so it will bail out with a link error, even if the compiler
    881     # wants to outsmart us.
    882     return '\n'.join(map(lambda x: '#include <%s>' % x, Utils.to_list(inc))) + \
    883         '\nint main() {\n#ifndef %s\nreturn *((int*)(&%s));\n#endif\n }' % (name,name)
    884 
    885 def multi_check_common(ctx, name, kw):
    886     if 'compiler' not in kw:
    887         kw['compiler'] = 'c'
    888     if 'mandatory' not in kw:
    889         kw['mandatory'] = False
    890 
    891     if 'define_name' not in kw:
    892         kw['define_name'] = ctx.have_define(name)
    893 
    894 @conf
    895 def multi_check_hdr(ctx, name, **kw):
    896     multi_check_common(ctx, name, kw)
    897     if 'msg' not in kw:
    898         kw['msg'] = 'Checking for header %s' % name
    899     kw['header_name'] = name
    900     return kw
    901 
    902 @conf
    903 def multi_check_func(ctx, name, inc, **kw):
    904     multi_check_common(ctx, name, kw)
    905     if 'msg' not in kw:
    906         kw['msg'] = 'Checking for function %s' % name
    907     kw['fragment'] = ctx.check_func_fragment(name, inc)
    908     return kw
    909 
    910 @conf
    911 def multi_check_lib(ctx, name, **kw):
    912     multi_check_common(ctx, name, kw)
    913     if 'msg' not in kw:
    914         kw['msg'] = 'Checking for library %s' % name
    915     kw['lib'] = name
    916     return kw
    917 
    918 def recurse_subdirs(ctx, name, dirs):
    919     for d in dirs:
    920         ctx.recurse(d, name=name, once=False)
    921 Context.Context.recurse_subdirs = recurse_subdirs
    922 
    923 # translate generator task
    924 class translate_gen(Task):
    925     run_str = '${tsk.executable} ${SRC} ${tsk.namespace} ${tsk.id} ${TGT}'
    926     color = 'BLUE'
    927 rule_like(None, 'translate_gen')
    928 
    929 @feature('translate_gen')
    930 @after_method('rule_like_translate_gen')
    931 def set_executable_translate_gen(self):
    932     nd = self.bld.get_tgen_by_name('libshit-translate-generate').link_task.outputs[0]
    933 
    934     self.task.id = self.id
    935     self.task.namespace = self.namespace
    936     self.task.executable = nd.abspath()
    937     self.task.dep_nodes.append(nd)
    938 
    939 from waflib import Logs
    940 class CleandeadContext(Build.BuildContext):
    941     '''removes not generated files from build dir'''
    942     cmd = 'cleandead'
    943 
    944     def execute_build(self):
    945         # based on BuildContext, ListContext and CleanContext
    946         self.recurse([self.run_dir])
    947         self.pre_build()
    948         self.timer = Utils.Timer()
    949 
    950         for g in self.groups:
    951             for tg in g:
    952                 try:
    953                     f = tg.post
    954                 except AttributeError:
    955                     pass
    956                 else:
    957                     f()
    958 
    959         try:
    960             # force the cache initialization
    961             self.get_tgen_by_name('')
    962         except Errors.WafError:
    963             pass
    964 
    965         outputs = set()
    966         for env in self.all_envs.values():
    967             outputs.update(self.root.find_or_declare(f) for f in env[Build.CFG_FILES])
    968         for tg in self.task_gen_cache_names.values():
    969             for tsk in tg.tasks:
    970                 outputs.update(tsk.outputs)
    971                 # gccdeps doesn't add .d files as outputs...
    972                 outputs.update(map(lambda x: x.change_ext('.d'), tsk.outputs))
    973 
    974         excluded_dirs = 'compile_commands.json .lock* .wafpickle* *conf_check_*/** config.log %s' % Build.CACHE_DIR
    975         for n in reversed(self.bldnode.ant_glob('**/*', excl=excluded_dirs, quiet=True, dir=True)):
    976             if n in outputs: continue
    977             try:
    978                 if os.path.isdir(n.abspath()):
    979                     os.rmdir(n.abspath())
    980                     Logs.warn('Removed stale directory %s' % n)
    981                 else:
    982                     os.remove(n.abspath())
    983                     Logs.warn('Removed stale file %s' % n)
    984             except OSError:
    985                 pass
    986             n.evict()