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()