1#SConstruct 2# vim: set fenc=utf-8 sw=4 ts=4 : 3# 157ec666685cddafbea84c8d292a530c7190103c 4 5# needed imports 6from collections import (defaultdict, Counter as collections_counter) 7import base64 8import binascii 9import errno 10import itertools 11import subprocess 12import sys 13import os 14import SCons.Util 15import SCons.Script 16 17# Disable injecting tools into default namespace 18SCons.Defaults.DefaultEnvironment(tools = []) 19 20def message(program,msg): 21 print("%s: %s" % (program.program_message_prefix, msg)) 22 23def get_Werror_string(l): 24 if l and '-Werror' in l: 25 return '-W' 26 return '-Werror=' 27 28class StaticSubprocess: 29 # This class contains utility functions for calling subprocesses 30 # that are expected to return the same output for every call. The 31 # output is cached after the first call, so that callers can request 32 # the output again later without causing the program to be run again. 33 # 34 # Suitable programs are not required to depend solely on the 35 # parameters, but are assumed to depend only on state that is 36 # unlikely to change in the brief time that SConstruct runs. For 37 # example, a tool might be upgraded by the system administrator, 38 # changing its version string. However, we assume nobody upgrades 39 # their tools in the middle of an SConstruct run. Results are not 40 # cached to persistent storage, so an upgrade performed after one 41 # SConstruct run, but before the next, will not cause any 42 # inconsistencies. 43 from shlex import split as shlex_split 44 class CachedCall: 45 def __init__(self,out,err,returncode): 46 self.out = out 47 self.err = err 48 self.returncode = returncode 49 # @staticmethod delayed so that default arguments pick up the 50 # undecorated form. 51 def pcall(args,stderr=None,_call_cache={},_CachedCall=CachedCall): 52 # Use repr since callers may construct the same argument 53 # list independently. 54 ## >>> a = ['git', '--version'] 55 ## >>> b = ['git', '--version'] 56 ## >>> a is b 57 ## False 58 ## >>> id(a) == id(b) 59 ## False 60 ## >>> a == b 61 ## True 62 ## >>> repr(a) == repr(b) 63 ## True 64 a = repr(args) 65 c = _call_cache.get(a) 66 if c is not None: 67 return c 68 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=stderr) 69 (o, e) = p.communicate() 70 _call_cache[a] = c = _CachedCall(o, e, p.wait()) 71 return c 72 def qcall(args,stderr=None,_pcall=pcall,_shlex_split=shlex_split): 73 return _pcall(_shlex_split(args),stderr) 74 @staticmethod 75 def get_version_head(cmd,_pcall=pcall,_shlex_split=shlex_split): 76 # If cmd is bytes, then it is output from a program and should 77 # not be reparsed. 78 v = _pcall(([cmd] if isinstance(cmd, bytes) else _shlex_split(cmd)) + ['--version'], stderr=subprocess.PIPE) 79 try: 80 return v.__version_head 81 except AttributeError: 82 v.__version_head = r = (v.out or v.err).splitlines()[0].decode() if not v.returncode and (v.out or v.err) else None 83 return r 84 pcall = staticmethod(pcall) 85 qcall = staticmethod(qcall) 86 shlex_split = staticmethod(shlex_split) 87 88class ToolchainInformation(StaticSubprocess): 89 @staticmethod 90 def get_tool_path(env,tool,_qcall=StaticSubprocess.qcall): 91 # Include $LINKFLAGS since -fuse-ld=gold influences the path 92 # printed for the linker. 93 tool = env.subst('$CXX $CXXFLAGS $LINKFLAGS -print-prog-name=%s' % tool) 94 return tool, _qcall(tool).out.strip() 95 @staticmethod 96 def show_partial_environ(env, user_settings, f, msgprefix, append_newline='\n'): 97 f("{}: CHOST: {!r}{}".format(msgprefix, user_settings.CHOST, append_newline)) 98 for v in ( 99 'CXX', 100 'CPPDEFINES', 101 'CPPPATH', 102 'CPPFLAGS', 103 'CXXFLAGS', 104 'LIBS', 105 'LINKFLAGS', 106 ) + ( 107 ( 108 'RC', 109 'RCFLAGS', 110 ) if user_settings.host_platform == 'win32' else ( 111 'FRAMEWORKPATH', 112 'FRAMEWORKS', 113 ) if user_settings.host_platform == 'darwin' else ( 114 ) 115 ): 116 f("{}: {}: {!r}{}".format(msgprefix, v, env.get(v, None), append_newline)) 117 penv = env['ENV'] 118 for v in ( 119 'CCACHE_PREFIX', 120 'DISTCC_HOSTS', 121 ): 122 f("{}: ${}: {!r}{}".format(msgprefix, v, penv.get(v, None), append_newline)) 123 124class Git(StaticSubprocess): 125 __git_archive_export_commit = '157ec666685cddafbea84c8d292a530c7190103c' 126 if len(__git_archive_export_commit) == 40: 127 # If the length is 40, then `git archive` has rewritten the 128 # string to be a commit ID. Use that commit ID as a guessed 129 # default when Git is not available to resolve a current commit 130 # ID. 131 __git_archive_export_commit = 'archive_{}'.format(__git_archive_export_commit) 132 else: 133 # Otherwise, assume that this is a checked-in copy. 134 __git_archive_export_commit = None 135 class ComputedExtraVersion: 136 __slots__ = ('describe', 'status', 'diffstat_HEAD', 'revparse_HEAD') 137 def __init__(self,describe,status,diffstat_HEAD,revparse_HEAD): 138 self.describe = describe 139 self.status = status 140 self.diffstat_HEAD = diffstat_HEAD 141 self.revparse_HEAD = revparse_HEAD 142 def __repr__(self): 143 return 'ComputedExtraVersion(%r,%r,%r,%r)' % (self.describe, self.status, self.diffstat_HEAD, self.revparse_HEAD) 144 UnknownExtraVersion = ComputedExtraVersion(None, None, None, __git_archive_export_commit) 145 # None when unset. Instance of ComputedExtraVersion once cached. 146 __computed_extra_version = None 147 __path_git = None 148 # `__pcall_missing_git`, `__pcall_found_git`, and `pcall` must have 149 # compatible signatures. In any given run, there will be at most 150 # one call to `pcall`, which then patches itself out of the call 151 # chain. A run will call either __pcall_missing_git or 152 # __pcall_found_git (depending on whether $GIT is blank), but never 153 # both in the same run. 154 @classmethod 155 def __pcall_missing_git(cls,args,stderr=None,_missing_git=StaticSubprocess.CachedCall(None, None, 1)): 156 return _missing_git 157 @classmethod 158 def __pcall_found_git(cls,args,stderr=None,_pcall=StaticSubprocess.pcall): 159 return _pcall(cls.__path_git + args, stderr=stderr) 160 @classmethod 161 def pcall(cls,args,stderr=None): 162 git = cls.__path_git 163 if git is None: 164 cls.__path_git = git = cls.shlex_split(os.environ.get('GIT', 'git')) 165 cls.pcall = f = cls.__pcall_found_git if git else cls.__pcall_missing_git 166 return f(args, stderr) 167 def spcall(cls,args,stderr=None): 168 g = cls.pcall(args, stderr) 169 if g.returncode: 170 return None 171 return g.out 172 @classmethod 173 def compute_extra_version(cls,_spcall=spcall): 174 c = cls.__computed_extra_version 175 if c is None: 176 v = cls.__compute_extra_version() 177 cls.__computed_extra_version = c = cls.ComputedExtraVersion( 178 v, 179 _spcall(cls, ['status', '--short', '--branch']).decode(), 180 _spcall(cls, ['diff', '--stat', 'HEAD']).decode(), 181 _spcall(cls, ['rev-parse', 'HEAD']).rstrip().decode(), 182 ) if v is not None else cls.UnknownExtraVersion 183 return c 184 # Run `git describe --tags --abbrev=12`. 185 # On failure, return None. 186 # On success, return a string containing: 187 # first line of output of describe 188 # '*' if there are unstaged changes else '' 189 # '+' if there are staged changes else '' 190 @classmethod 191 def __compute_extra_version(cls): 192 try: 193 g = cls.pcall(['describe', '--tags', '--abbrev=12'], stderr=subprocess.PIPE) 194 except OSError as e: 195 if e.errno == errno.ENOENT: 196 return None 197 raise 198 if g.returncode: 199 return None 200 _pcall = cls.pcall 201 return (g.out.splitlines()[0] + \ 202 (b'*' if _pcall(['diff', '--quiet']).returncode else b'') + \ 203 (b'+' if _pcall(['diff', '--quiet', '--cached']).returncode else b'') 204 ).decode() 205 206class _ConfigureTests: 207 class Collector: 208 class RecordedTest: 209 __slots__ = ('desc', 'name', 'predicate') 210 def __init__(self,name,desc,predicate=()): 211 self.name = name 212 self.desc = desc 213 # predicate is a sequence of zero-or-more functions that 214 # take a UserSettings object as the only argument. 215 # A recorded test is only passed to the SConf logic if 216 # at most zero predicates return a false-like value. 217 # 218 # Callers use this to exclude from execution tests which 219 # make no sense in the current environment, such as a 220 # Windows-specific test when building for a 221 # Linux target. 222 self.predicate = predicate 223 def __repr__(self): 224 return '_ConfigureTests.Collector.RecordedTest(%r, %r, %r)' % (self.name, self.desc, self.predicate) 225 226 def __init__(self,record): 227 self.record = record 228 def __call__(self,f): 229 desc = None 230 doc = getattr(f, '__doc__', None) 231 if doc is not None: 232 doc = doc.rstrip().splitlines() 233 if doc and doc[-1].startswith("help:"): 234 desc = doc[-1][5:] 235 self.record(self.RecordedTest(f.__name__, desc)) 236 return f 237 238class ConfigureTests(_ConfigureTests): 239 class Collector(_ConfigureTests.Collector): 240 def __init__(self): 241 # An unguarded collector maintains a list of tests, and adds 242 # to the list as the decorator is called on individual 243 # functions. 244 self.tests = tests = [] 245 super().__init__(tests.append) 246 247 class GuardedCollector(_ConfigureTests.Collector): 248 def __init__(self,collector,guard): 249 # A guarded collector delegates to the list maintained by 250 # the input collector, instead of keeping a list of its own. 251 super().__init__(collector.record) 252 # `guard` is a single function, but the predicate handling 253 # expects a sequence of functions, so store a single-element 254 # tuple. 255 self.__guard = guard, 256 self.__RecordedTest = collector.RecordedTest 257 def RecordedTest(self, name, desc, predicate=()): 258 # Record a test with both the guard of this GuardedCollector 259 # and any guards from the input collector objects, which may 260 # in turn be instances of GuardedCollector with predicates 261 # of their own. 262 return self.__RecordedTest(name, desc, self.__guard + predicate) 263 264 class CxxRequiredFeature: 265 __slots__ = ('main', 'name', 'text') 266 def __init__(self,name,text,main=''): 267 self.name = name 268 # Avoid generating consecutive underscores if the input 269 # string has multiple adjacent unacceptable characters. 270 f = '_{:x}'.format 271 name = {'N' : 'test_' + ''.join([c if c.isalnum() else f(ord(c)) for c in name])} 272 self.text = text % name 273 self.main = ('{' + (main % name) + '}\n') if main else '' 274 class Cxx11RequiredFeature(CxxRequiredFeature): 275 std = 11 276 class Cxx14RequiredFeature(CxxRequiredFeature): 277 std = 14 278 class Cxx17RequiredFeature(CxxRequiredFeature): 279 std = 17 280 class CxxRequiredFeatures: 281 __slots__ = ('features', 'main', 'text') 282 def __init__(self,features): 283 self.features = features 284 s = '/* C++{} {} */\n{}'.format 285 self.main = '\n'.join((s(f.std, f.name, f.main) for f in features)) 286 self.text = '\n'.join((s(f.std, f.name, f.text) for f in features)) 287 class PCHAction: 288 def __init__(self,context): 289 self._context = context 290 def __call__(self,text,ext): 291 # Ignore caller-supplied text, since _Test always includes a 292 # definition of main(). Using the caller-supplied text 293 # would provide one main() in the PCH and another in the 294 # test which includes the PCH. 295 env = self._context.env 296 s = env['OBJSUFFIX'] 297 env['OBJSUFFIX'] = '.h.gch' 298 result = self._context.TryCompile(''' 299/* Define this here. Use it in the file which includes the PCH. If the 300 * compiler skips including the PCH, and does not fail for that reason 301 * alone, then it will fail when the symbol is used in the later test, 302 * since the only definition comes from the PCH. 303 */ 304#define dxx_compiler_supports_pch 305 ''', ext) 306 env['OBJSUFFIX'] = s 307 return result 308 class PreservedEnvironment: 309 # One empty list for all the defaults. The comprehension 310 # creates copies, so it is safe for the default value to be 311 # shared. 312 def __init__(self,env,keyviews,_l=[]): 313 self.flags = {k: env.get(k, _l)[:] for k in itertools.chain.from_iterable(keyviews)} 314 def restore(self,env): 315 env.Replace(**self.flags) 316 def __getitem__(self,name): 317 return self.flags.__getitem__(name) 318 class ForceVerboseLog(PreservedEnvironment): 319 def __init__(self,env): 320 # Force verbose output to sconf.log 321 self.flags = {} 322 for k in ( 323 'CXXCOMSTR', 324 'LINKCOMSTR', 325 ): 326 try: 327 # env is like a dict, but does not have .pop(), so 328 # emulate it with a lookup + delete. 329 self.flags[k] = env[k] 330 del env[k] 331 except KeyError: 332 pass 333 class pkgconfig: 334 def _get_pkg_config_exec_path(context,msgprefix,pkgconfig): 335 Display = context.Display 336 if not pkgconfig: 337 Display("%s: pkg-config disabled by user settings\n" % msgprefix) 338 return pkgconfig 339 if os.sep in pkgconfig: 340 # Split early, so that the user can pass a command with 341 # arguments. Convert to tuple so that the value is not 342 # modified later. 343 pkgconfig = tuple(StaticSubprocess.shlex_split(pkgconfig)) 344 Display("%s: using pkg-config at user specified path %s\n" % (msgprefix, pkgconfig)) 345 return pkgconfig 346 join = os.path.join 347 # No path specified, search in $PATH 348 for p in os.environ.get('PATH', '').split(os.pathsep): 349 fp = join(p, pkgconfig) 350 try: 351 os.close(os.open(fp, os.O_RDONLY)) 352 except OSError as e: 353 # Ignore on permission errors. If pkg-config is 354 # runnable but not readable, the user must 355 # specify its path. 356 if e.errno == errno.ENOENT or e.errno == errno.EACCES: 357 continue 358 raise 359 Display("%s: using pkg-config at discovered path %s\n" % (msgprefix, fp)) 360 return (fp,) 361 Display("%s: no usable pkg-config %r found in $PATH\n" % (msgprefix, pkgconfig)) 362 def __get_pkg_config_path(context,message,user_settings,display_name, 363 _get_pkg_config_exec_path=_get_pkg_config_exec_path, 364 _cache={}): 365 pkgconfig = user_settings.PKG_CONFIG 366 if pkgconfig is None: 367 CHOST = user_settings.CHOST 368 pkgconfig = ('%s-pkg-config' % CHOST) if CHOST else 'pkg-config' 369 if sys.platform == 'win32': 370 pkgconfig += '.exe' 371 path = _cache.get(pkgconfig, _cache) 372 if path is _cache: 373 _cache[pkgconfig] = path = _get_pkg_config_exec_path(context, message, pkgconfig) 374 return path 375 @staticmethod 376 def merge(context,message,user_settings,pkgconfig_name,display_name, 377 guess_flags, 378 __get_pkg_config_path=__get_pkg_config_path, 379 _cache={}): 380 Display = context.Display 381 Display("%s: checking %s pkg-config %s\n" % (message, display_name, pkgconfig_name)) 382 pkgconfig = __get_pkg_config_path(context, message, user_settings, display_name) 383 if not pkgconfig: 384 Display("%s: skipping %s pkg-config; using default flags %r\n" % (message, display_name, guess_flags)) 385 return guess_flags 386 cmd = pkgconfig + ('--cflags', '--libs', pkgconfig_name) 387 flags = _cache.get(cmd, None) 388 if flags is not None: 389 Display("%s: reusing %s settings from %s: %r\n" % (message, display_name, cmd, flags)) 390 return flags 391 mv_cmd = pkgconfig + ('--modversion', pkgconfig_name) 392 try: 393 Display("%s: reading %s version from %s\n" % (message, pkgconfig_name, mv_cmd)) 394 v = StaticSubprocess.pcall(mv_cmd) 395 if v.out: 396 Display("%s: %s version: %r\n" % (message, display_name, v.out.splitlines()[0])) 397 except OSError as o: 398 Display("%s: failed with error %s; using default flags for '%s': %r\n" % (message, repr(o.message) if o.errno is None else ('%u ("%s")' % (o.errno, o.strerror)), pkgconfig_name, guess_flags)) 399 flags = guess_flags 400 else: 401 Display("%s: reading %s settings from %s\n" % (message, display_name, cmd)) 402 try: 403 flags = { 404 k:v for k,v in context.env.ParseFlags(' ' + StaticSubprocess.pcall(cmd).out.decode()).items() 405 if v and (k[0] in 'CL') 406 } 407 Display("%s: %s settings: %r\n" % (message, display_name, flags)) 408 except OSError as o: 409 Display("%s: failed with error %s; using default flags for '%s': %r\n" % (message, repr(o.message) if o.errno is None else ('%u ("%s")' % (o.errno, o.strerror)), pkgconfig_name, guess_flags)) 410 flags = guess_flags 411 _cache[cmd] = flags 412 return flags 413 414 # Force test to report failure 415 sconf_force_failure = 'force-failure' 416 # Force test to report success, and modify flags like it 417 # succeeded 418 sconf_force_success = 'force-success' 419 # Force test to report success, do not modify flags 420 sconf_assume_success = 'assume-success' 421 expect_sconf_success = 'success' 422 expect_sconf_failure = 'failure' 423 _implicit_test = Collector() 424 _custom_test = Collector() 425 _guarded_test_windows = GuardedCollector(_custom_test, lambda user_settings: user_settings.host_platform == 'win32') 426 _guarded_test_darwin = GuardedCollector(_custom_test, lambda user_settings: user_settings.host_platform == 'darwin') 427 implicit_tests = _implicit_test.tests 428 custom_tests = _custom_test.tests 429 comment_not_supported = '/* not supported */' 430 __python_import_struct = None 431 _cxx_conformance_cxx17 = 17 432 __cxx_std_required_features = CxxRequiredFeatures([ 433 Cxx17RequiredFeature('constexpr if', ''' 434template <bool b> 435int f_%(N)s() 436{ 437 if constexpr (b) 438 return 1; 439 else 440 return 0; 441} 442''', 443''' 444 f_%(N)s<false>(); 445'''), 446 Cxx17RequiredFeature('fold expressions', ''' 447static inline void g_%(N)s(int) {} 448template <int... i> 449void f_%(N)s() 450{ 451 (g_%(N)s(i), ...); 452 (g_%(N)s(i), ..., (void)0); 453 ((void)0, ..., g_%(N)s(i)); 454} 455''', 456''' 457 f_%(N)s<0, 1, 2, 3>(); 458'''), 459 Cxx17RequiredFeature('structured binding declarations', '', 460''' 461 int a_%(N)s[2] = {0, 1}; 462 auto &&[b_%(N)s, c_%(N)s] = a_%(N)s; 463 (void)b_%(N)s; 464 (void)c_%(N)s; 465'''), 466 Cxx14RequiredFeature('template variables', ''' 467template <unsigned U_%(N)s> 468int a_%(N)s = U_%(N)s + 1; 469''', ''), 470 Cxx14RequiredFeature('std::exchange', ''' 471#include <utility> 472''', ''' 473 argc |= std::exchange(argc, 5); 474'''), 475 Cxx14RequiredFeature('std::index_sequence', ''' 476#include <utility> 477''', ''' 478 std::integer_sequence<int, 0> integer_%(N)s = std::make_integer_sequence<int, 1>(); 479 (void)integer_%(N)s; 480 std::index_sequence<0> index_%(N)s = std::make_index_sequence<1>(); 481 (void)index_%(N)s; 482'''), 483 Cxx14RequiredFeature('std::make_unique', ''' 484#include <memory> 485''', ''' 486 std::make_unique<int>(0); 487 std::make_unique<int[]>(1); 488'''), 489 Cxx11RequiredFeature('std::addressof', ''' 490#include <memory> 491''', ''' 492 struct A_%(N)s 493 { 494 void operator&() = delete; 495 }; 496 A_%(N)s i_%(N)s; 497 (void)std::addressof(i_%(N)s); 498'''), 499 Cxx11RequiredFeature('constexpr', ''' 500struct %(N)s {}; 501static constexpr %(N)s get_%(N)s(){return {};} 502''', ''' 503 get_%(N)s(); 504'''), 505 Cxx11RequiredFeature('nullptr', ''' 506#include <cstddef> 507std::nullptr_t %(N)s1 = nullptr; 508int *%(N)s2 = nullptr; 509''', ''' 510 %(N)s2 = %(N)s1; 511'''), 512 Cxx11RequiredFeature('explicit operator bool', ''' 513struct %(N)s { 514 explicit operator bool(); 515}; 516'''), 517 Cxx11RequiredFeature('template aliases', ''' 518using %(N)s_typedef = int; 519template <typename> 520struct %(N)s_struct; 521template <typename T> 522using %(N)s_alias = %(N)s_struct<T>; 523''', ''' 524 %(N)s_struct<int> *a = nullptr; 525 %(N)s_alias<int> *b = a; 526 %(N)s_typedef *c = nullptr; 527 (void)b; 528 (void)c; 529'''), 530 Cxx11RequiredFeature('trailing function return type', ''' 531auto %(N)s()->int; 532'''), 533 Cxx11RequiredFeature('class scope static constexpr assignment', ''' 534struct %(N)s_instance { 535}; 536struct %(N)s_container { 537 static constexpr %(N)s_instance a = {}; 538}; 539'''), 540 Cxx11RequiredFeature('braced base class initialization', ''' 541struct %(N)s_base { 542 int a; 543}; 544struct %(N)s_derived : %(N)s_base { 545 %(N)s_derived(int e) : %(N)s_base{e} {} 546}; 547'''), 548 Cxx11RequiredFeature('std::array', ''' 549#include <array> 550''', ''' 551 std::array<int,2>b; 552 b[0]=1; 553''' 554), 555 Cxx11RequiredFeature('std::begin(), std::end()', ''' 556#include <array> 557''', ''' 558 char ac[1]; 559 std::array<char, 1> as; 560%s 561''' % (''.join([''' 562 auto %(i)s = std::%(f)s(%(a)s); 563 (void)%(i)s; 564''' % {'i' : 'i%s%s' % (f, a), 'f' : f, 'a' : a} for f in ('begin', 'end') for a in ('ac', 'as')] 565 ) 566 )), 567 Cxx11RequiredFeature('range-based for', ''' 568#include "compiler-range_for.h" 569''', ''' 570 int b[2]; 571 range_for(int&c,b)c=0; 572''' 573), 574 Cxx11RequiredFeature('<type_traits>', ''' 575#include <type_traits> 576''', ''' 577 typename std::conditional<sizeof(argc) == sizeof(int),int,long>::type a = 0; 578 typename std::conditional<sizeof(argc) != sizeof(int),int,long>::type b = 0; 579 (void)a; 580 (void)b; 581''' 582), 583 Cxx11RequiredFeature('std::unordered_map::emplace', ''' 584#include <unordered_map> 585''', ''' 586 std::unordered_map<int,int> m; 587 m.emplace(0, 0); 588''' 589), 590 Cxx11RequiredFeature('reference qualified methods', ''' 591struct %(N)s { 592 int a()const &{return 1;} 593 int a()const &&{return 2;} 594}; 595''', ''' 596 %(N)s a; 597 auto b = a.a() != %(N)s().a(); 598 (void)b; 599''' 600), 601]) 602 def __init__(self,msgprefix,user_settings,platform_settings): 603 self.msgprefix = msgprefix 604 self.user_settings = user_settings 605 self.platform_settings = platform_settings 606 self.successful_flags = defaultdict(list) 607 self._sconf_results = [] 608 self.__tool_versions = [] 609 self.__defined_macros = '' 610 # Some tests check the functionality of the compiler's 611 # optimizer. 612 # 613 # When LTO is used, the optimizer is deferred to link time. 614 # Force all tests to be Link tests when LTO is enabled. 615 self.Compile = self.Link if user_settings.lto else self._Compile 616 self.custom_tests = [t for t in self.custom_tests if all(predicate(user_settings) for predicate in t.predicate)] 617 def _quote_macro_value(v): 618 return v.strip().replace('\n', ' \\\n') 619 def _check_sconf_forced(self,calling_function): 620 return self._check_forced(calling_function), self._check_expected(calling_function) 621 @staticmethod 622 def _find_calling_sconf_function(): 623 try: 624 1//0 625 except ZeroDivisionError: 626 frame = sys.exc_info()[2].tb_frame.f_back.f_back 627 while frame is not None: 628 co_name = frame.f_code.co_name 629 if co_name[:6] == 'check_': 630 return co_name[6:] 631 frame = frame.f_back 632 # This assertion is hit if a test is asked to deduce its caller 633 # (calling_function=None), but no function in the call stack appears to 634 # be a checking function. 635 assert False, "SConf caller not specified and no acceptable caller in stack." 636 def _check_forced(self,name): 637 # This getattr will raise AttributeError if called for a function which 638 # is not a registered test. Tests must be registered as an implicit 639 # test (in implicit_tests, usually by applying the @_implicit_test 640 # decorator) or a custom test (in custom_tests, usually by applying the 641 # @_custom_test decorator). 642 # 643 # Unregistered tests are never documented and cannot be overridden by 644 # the user. 645 return getattr(self.user_settings, 'sconf_%s' % name) 646 def _check_expected(self,name): 647 # The remarks for _check_forced apply here too. 648 r = getattr(self.user_settings, 'expect_sconf_%s' % name) 649 if r is not None: 650 if r == self.expect_sconf_success: 651 return 1 652 if r == self.expect_sconf_failure: 653 return 0 654 return r 655 def _check_macro(self,context,macro_name,macro_value,test,_comment_not_supported=comment_not_supported,**kwargs): 656 r = self.Compile(context, text=""" 657#define {macro_name} {macro_value} 658{test} 659""".format(macro_name=macro_name, macro_value=macro_value, test=test), **kwargs) 660 if not r: 661 macro_value = _comment_not_supported 662 self._define_macro(context, macro_name, macro_value) 663 def _define_macro(self,context,macro_name,macro_value): 664 context.sconf.Define(macro_name, macro_value) 665 self.__defined_macros += '#define %s %s\n' % (macro_name, macro_value) 666 implicit_tests.append(_implicit_test.RecordedTest('check_ccache_distcc_ld_works', "assume ccache, distcc, C++ compiler, and C++ linker work")) 667 implicit_tests.append(_implicit_test.RecordedTest('check_ccache_ld_works', "assume ccache, C++ compiler, and C++ linker work")) 668 implicit_tests.append(_implicit_test.RecordedTest('check_distcc_ld_works', "assume distcc, C++ compiler, and C++ linker work")) 669 implicit_tests.append(_implicit_test.RecordedTest('check_ld_works', "assume C++ compiler and linker work")) 670 implicit_tests.append(_implicit_test.RecordedTest('check_ld_blank_libs_works', "assume C++ compiler and linker work with empty $LIBS")) 671 implicit_tests.append(_implicit_test.RecordedTest('check_ld_blank_libs_ldflags_works', "assume C++ compiler and linker work with empty $LIBS and empty $LDFLAGS")) 672 implicit_tests.append(_implicit_test.RecordedTest('check_cxx_blank_cxxflags_works', "assume C++ compiler works with empty $CXXFLAGS")) 673 # This must be the first custom test. This test verifies the compiler 674 # works and disables any use of ccache/distcc for the duration of the 675 # configure run. 676 # 677 # SCons caches configuration results and tests are usually very small, so 678 # ccache will provide limited benefit. 679 # 680 # Some tests are expected to raise a compiler error. If distcc is used 681 # and DISTCC_FALLBACK prevents local retries, then distcc interprets a 682 # compiler error as an indication that the volunteer which served that 683 # compile is broken and should be blacklisted. Suppress use of distcc for 684 # all tests to avoid spurious blacklist entries. 685 # 686 # During the main build, compiling remotely can allow more jobs to run in 687 # parallel. Tests are serialized by SCons, so distcc is helpful during 688 # testing only if compiling remotely is faster than compiling locally. 689 # This may be true for embedded systems that distcc to desktops, but will 690 # not be true for desktops or laptops that distcc to similar sized 691 # machines. 692 @_custom_test 693 def check_cxx_works(self,context): 694 """ 695help:assume C++ compiler works 696""" 697 # Use %r to print the tuple in an unambiguous form. 698 context.Log('scons: dxx: version: %r\n' % (Git.compute_extra_version(),)) 699 cenv = context.env 700 penv = cenv['ENV'] 701 self.__cxx_com_prefix = cenv['CXXCOM'] 702 # Require ccache to run the next stage, but allow it to write the 703 # result to cache. This lets the test validate that ccache fails for 704 # an unusable CCACHE_DIR and also validate that the next stage handles 705 # the input correctly. Without this, a cached result may hide that 706 # the next stage compiler (or wrapper) worked when a prior run 707 # performed the test, but is now broken. 708 CCACHE_RECACHE = penv.get('CCACHE_RECACHE', None) 709 penv['CCACHE_RECACHE'] = '1' 710 most_recent_error = self._check_cxx_works(context) 711 if most_recent_error is not None: 712 raise SCons.Errors.StopError(most_recent_error) 713 if CCACHE_RECACHE is None: 714 del penv['CCACHE_RECACHE'] 715 else: 716 penv['CCACHE_RECACHE'] = CCACHE_RECACHE 717 # If ccache/distcc are in use, disable them during testing. 718 # This assignment is also done in _check_cxx_works, but only on an 719 # error path. Repeat it here so that it is effective on the success 720 # path. It cannot be moved above the call to _check_cxx_works because 721 # some tests in _check_cxx_works rely on its original value. 722 cenv['CXXCOM'] = cenv._dxx_cxxcom_no_prefix 723 self._check_cxx_conformance_level(context) 724 def _show_tool_version(self,context,tool,desc,save_tool_version=True): 725 # These version results are not used for anything, but are 726 # collected here so that users who post only a build log will 727 # still supply at least some useful information. 728 # 729 # This is split into two lines so that the first line is printed 730 # before the function call required to format the string for the 731 # second line. 732 Display = context.Display 733 Display('%s: checking version of %s %r ... ' % (self.msgprefix, desc, tool)) 734 try: 735 v = StaticSubprocess.get_version_head(tool) 736 except OSError as e: 737 if e.errno == errno.ENOENT or e.errno == errno.EACCES: 738 Display('error: %s\n' % e.strerror) 739 raise SCons.Errors.StopError('Failed to run %r.' % tool) 740 raise 741 if save_tool_version: 742 self.__tool_versions.append((tool, v)) 743 Display('%r\n' % v) 744 def _show_indirect_tool_version(self,context,CXX,tool,desc): 745 Display = context.Display 746 Display('%s: checking path to %s ... ' % (self.msgprefix, desc)) 747 tool, name = ToolchainInformation.get_tool_path(context.env, tool) 748 self.__tool_versions.append((tool, name)) 749 if not name: 750 # Strange, but not fatal for this to fail. 751 Display('! %r\n' % name) 752 return 753 Display('%r\n' % name) 754 self._show_tool_version(context,name,desc) 755 def _check_cxx_works(self,context,_crc32=binascii.crc32): 756 # Test whether the compiler+linker+optional wrapper(s) work. If 757 # anything fails, a StopError is guaranteed on return. However, to 758 # help the user, this function pushes through all the combinations and 759 # reports the StopError for the least complicated issue. If both the 760 # compiler and the linker fail, the compiler will be reported, since 761 # the linker might work once the compiler is fixed. 762 # 763 # If a test fails, then the pending StopError allows this function to 764 # safely modify the construction environment and process environment 765 # without reverting its changes. 766 most_recent_error = None 767 Link = self.Link 768 cenv = context.env 769 user_settings = self.user_settings 770 use_distcc = user_settings.distcc 771 use_ccache = user_settings.ccache 772 if user_settings.show_tool_version: 773 CXX = cenv['CXX'] 774 self._show_tool_version(context, CXX, 'C++ compiler') 775 if user_settings.show_assembler_version: 776 self._show_indirect_tool_version(context, CXX, 'as', 'assembler') 777 if user_settings.show_linker_version: 778 self._show_indirect_tool_version(context, CXX, 'ld', 'linker') 779 if use_distcc: 780 self._show_tool_version(context, use_distcc, 'distcc', False) 781 if use_ccache: 782 self._show_tool_version(context, use_ccache, 'ccache', False) 783 # Use C++ single line comment so that it is guaranteed to extend 784 # to the end of the line. repr ensures that embedded newlines 785 # will be escaped and that the final character will not be a 786 # backslash. 787 self.__commented_tool_versions = s = ''.join('// %r => %r\n' % (v[0], v[1]) for v in self.__tool_versions) 788 self.__tool_versions = ''' 789/* This test is always false. Use a non-trivial condition to 790 * discourage external text scanners from recognizing that the block 791 * is never compiled. 792 */ 793#if 1 < -1 794%.8x 795%s 796#endif 797''' % (_crc32(s.encode()), s) 798 ToolchainInformation.show_partial_environ(cenv, user_settings, context.Display, self.msgprefix) 799 if use_ccache: 800 if use_distcc: 801 if Link(context, text='', msg='whether ccache, distcc, C++ compiler, and linker work', calling_function='ccache_distcc_ld_works'): 802 return 803 most_recent_error = 'ccache and C++ linker work, but distcc does not work.' 804 # Disable distcc so that the next call to self.Link tests only 805 # ccache+linker. 806 del cenv['ENV']['CCACHE_PREFIX'] 807 if Link(context, text='', msg='whether ccache, C++ compiler, and linker work', calling_function='ccache_ld_works'): 808 return most_recent_error 809 most_recent_error = 'C++ linker works, but ccache does not work.' 810 elif use_distcc: 811 if Link(context, text='', msg='whether distcc, C++ compiler, and linker work', calling_function='distcc_ld_works'): 812 return 813 most_recent_error = 'C++ linker works, but distcc does not work.' 814 else: 815 # This assertion fails if the environment's $CXXCOM was modified 816 # to use a prefix, but both user_settings.ccache and 817 # user_settings.distcc evaluate to false. 818 assert cenv._dxx_cxxcom_no_prefix is cenv['CXXCOM'], "Unexpected prefix in $CXXCOM." 819 # If ccache/distcc are in use, then testing with one or both of them 820 # failed. Disable them so that the next test can check whether the 821 # local linker works. 822 # 823 # If they are not in use, this assignment is a no-op. 824 cenv['CXXCOM'] = cenv._dxx_cxxcom_no_prefix 825 if Link(context, text='', msg='whether C++ compiler and linker work', calling_function='ld_works'): 826 # If ccache or distcc are in use, this block is only reached 827 # when one or both of them failed. `most_recent_error` will 828 # be a description of the failure. If neither are in use, 829 # `most_recent_error` will be None. 830 return most_recent_error 831 # Force only compile, even if LTO is enabled. 832 elif self._Compile(context, text='', msg='whether C++ compiler works', calling_function='cxx_works'): 833 specified_LIBS = 'or ' if cenv.get('LIBS') else '' 834 if specified_LIBS: 835 cenv['LIBS'] = [] 836 if Link(context, text='', msg='whether C++ compiler and linker work with blank $LIBS', calling_function='ld_blank_libs_works'): 837 # Using $LIBS="" allowed the test to succeed. $LIBS 838 # specifies one or more unusable libraries. Usually 839 # this is because it specifies a library which does 840 # not exist or is an incompatible architecture. 841 return 'C++ compiler works. C++ linker works with blank $LIBS. C++ linker does not work with specified $LIBS.' 842 if cenv['LINKFLAGS']: 843 cenv['LINKFLAGS'] = [] 844 if Link(context, text='', msg='whether C++ compiler and linker work with blank $LIBS and blank $LDFLAGS', calling_function='ld_blank_libs_ldflags_works'): 845 # Using LINKFLAGS="" allowed the test to succeed. 846 # To avoid bloat, there is no further test to see 847 # whether the link will work with user-specified 848 # LIBS after LINKFLAGS is cleared. The user must 849 # fix at least one problem anyway. If the user is 850 # unlucky, fixing LINKFLAGS will result in a 851 # different error on the next run. If the user is 852 # lucky, fixing LINKFLAGS will allow the build to 853 # run. 854 return 'C++ compiler works. C++ linker works with blank $LIBS and blank $LDFLAGS. C++ linker does not work with blank $LIBS and specified $LDFLAGS.' 855 return 'C++ compiler works. C++ linker does not work with specified %(LIBS)sblank $LIBS and specified $LINKFLAGS. C++ linker does not work with blank $LIBS and blank $LINKFLAGS.' % { 856 'LIBS' : specified_LIBS, 857 } 858 else: 859 if cenv['CXXFLAGS']: 860 cenv['CXXFLAGS'] = [] 861 if self._Compile(context, text='', msg='whether C++ compiler works with blank $CXXFLAGS', calling_function='cxx_blank_cxxflags_works'): 862 return 'C++ compiler works with blank $CXXFLAGS. C++ compiler does not work with specified $CXXFLAGS.' 863 return 'C++ compiler does not work.' 864 implicit_tests.append(_implicit_test.RecordedTest('check_cxx17', "assume C++ compiler supports C++17")) 865 __cxx_conformance_CXXFLAGS = [None] 866 def _check_cxx_conformance_level(self,context,_levels=( 867 # List standards in descending order of preference. 868 # 869 # C++17 is required, so list it last. 870 _cxx_conformance_cxx17, 871 ), _CXXFLAGS=__cxx_conformance_CXXFLAGS, 872 _successflags={'CXXFLAGS' : __cxx_conformance_CXXFLAGS} 873 ): 874 # Testing the compiler option parser only needs Compile, even when LTO 875 # is enabled. 876 Compile = self._Compile 877 # Accepted options by version: 878 # 879 # gcc-7 -std=gnu++1y 880 # gcc-7 -std=gnu++14 881 # gcc-7 -std=gnu++1z 882 # gcc-7 -std=gnu++17 883 # 884 # gcc-8 -std=gnu++1y 885 # gcc-8 -std=gnu++14 886 # gcc-8 -std=gnu++1z 887 # gcc-8 -std=gnu++17 888 # gcc-8 -std=gnu++2a 889 # 890 # gcc-9 -std=gnu++1y 891 # gcc-9 -std=gnu++14 892 # gcc-9 -std=gnu++1z 893 # gcc-9 -std=gnu++17 894 # gcc-9 -std=gnu++2a 895 for level in _levels: 896 opt = '-std=gnu++%u' % level 897 _CXXFLAGS[0] = opt 898 if Compile(context, text='', msg='whether C++ compiler accepts {opt}'.format(opt=opt), successflags=_successflags, calling_function='cxx%s' % level): 899 return 900 raise SCons.Errors.StopError('C++ compiler does not accept any supported C++ -std option.') 901 def _Test(self,context,text,msg,action,main='',ext='.cpp',testflags={},successflags={},skipped=None,successmsg=None,failuremsg=None,expect_failure=False,calling_function=None,__flags_Werror = {'CXXFLAGS' : ['-Werror']}): 902 if calling_function is None: 903 calling_function = self._find_calling_sconf_function() 904 context.Message('%s: checking %s...' % (self.msgprefix, msg)) 905 if skipped is not None: 906 context.Result('(skipped){skipped}'.format(skipped=skipped)) 907 if self.user_settings.record_sconf_results: 908 self._sconf_results.append((calling_function, 'skipped')) 909 return 910 env_flags = self.PreservedEnvironment(context.env, (successflags.keys(), testflags.keys(), __flags_Werror.keys(), ('CPPDEFINES',))) 911 context.env.MergeFlags(successflags) 912 forced, expected = self._check_sconf_forced(calling_function) 913 caller_modified_env_flags = self.PreservedEnvironment(context.env, (testflags.keys(), __flags_Werror.keys())) 914 # Always pass -Werror to configure tests. 915 context.env.Append(**__flags_Werror) 916 context.env.Append(**testflags) 917 # If forced is None, run the test. Otherwise, skip the test and 918 # take an action determined by the value of forced. 919 if forced is None: 920 r = action(''' 921{tools} 922{macros} 923{text} 924 925#undef main /* avoid -Dmain=SDL_main from libSDL (and, on some platforms, from libSDL2) */ 926 927{main} 928'''.format( 929 tools=self.__tool_versions, 930 macros=self.__defined_macros, 931 text=text, 932 main=('' if main is None else 933''' 934int main(int argc,char**argv){(void)argc;(void)argv; 935%s 936 937;} 938''' % main 939 )), ext) 940 # Some tests check that the compiler rejects an input. 941 # SConf considers the result a failure when the compiler 942 # rejects the input. For tests that consider a rejection to 943 # be the good result, this conditional flips the sense of 944 # the result so that a compiler rejection is reported as 945 # success. 946 if expect_failure: 947 r = not r 948 context.Result((successmsg if r else failuremsg) or r) 949 if expected is not None and r != expected: 950 raise SCons.Errors.StopError('Expected and actual results differ. Test should %s, but it did not.' % ('succeed' if expected else 'fail')) 951 else: 952 choices = (self.sconf_force_failure, self.sconf_force_success, self.sconf_assume_success) 953 if forced not in choices: 954 try: 955 forced = choices[int(forced)] 956 except ValueError: 957 raise SCons.Errors.UserError("Unknown force value for sconf_%s: %s" % (co_name[6:], forced)) 958 except IndexError: 959 raise SCons.Errors.UserError("Out of range force value for sconf_%s: %s" % (co_name[6:], forced)) 960 if forced == self.sconf_force_failure: 961 # Pretend the test returned a failure result 962 r = False 963 elif forced == self.sconf_force_success or forced == self.sconf_assume_success: 964 # Pretend the test succeeded. Forced success modifies 965 # the environment as if the test had run and succeeded. 966 # Assumed success modifies the environment as if the 967 # test had run and failed. 968 # 969 # The latter is used when the user arranges for the 970 # environment to be correct. For example, if the 971 # compiler understands C++14, but uses a non-standard 972 # name for the option, the user would set assume-success 973 # and add the appropriate option to CXXFLAGS. 974 r = True 975 else: 976 raise SCons.Errors.UserError("Unknown force value for sconf_%s: %s" % (co_name[6:], forced)) 977 # Flip the sense of the forced value, so that users can 978 # treat "force-failure" as force-bad-result regardless of 979 # whether the bad result is that the compiler rejected good 980 # input or that the compiler accepted bad input. 981 if expect_failure: 982 r = not r 983 context.Result('(forced){inverted}{forced}'.format(forced=forced, inverted='(inverted)' if expect_failure else '')) 984 # On success, revert to base flags + successflags 985 # On failure, revert to base flags 986 if r and forced != self.sconf_assume_success: 987 caller_modified_env_flags.restore(context.env) 988 context.env.Replace(CPPDEFINES=env_flags['CPPDEFINES']) 989 f = self.successful_flags 990 # Move most CPPDEFINES to the generated header, so that 991 # command lines are shorter. 992 for k, v in successflags.items(): 993 if k == 'CPPDEFINES': 994 continue 995 f[k].extend(v) 996 d = successflags.get('CPPDEFINES', None) 997 if d: 998 append_CPPDEFINE = f['CPPDEFINES'].append 999 Define = context.sconf.Define 1000 for v in d: 1001 # v is 'NAME' for -DNAME 1002 # v is ('NAME', 'VALUE') for -DNAME=VALUE 1003 d = (v, None) if isinstance(v, str) else v 1004 if d[0] in ('_REENTRANT',): 1005 # Blacklist defines that must not be moved to the 1006 # configuration header. 1007 append_CPPDEFINE(v) 1008 continue 1009 Define(d[0], d[1]) 1010 else: 1011 env_flags.restore(context.env) 1012 self._sconf_results.append((calling_function, r)) 1013 return r 1014 def _Compile(self,context,_Test=_Test,**kwargs): 1015 return _Test(self, context,action=context.TryCompile, **kwargs) 1016 def Link(self,context,_Test=_Test,**kwargs): 1017 return _Test(self, context,action=context.TryLink, **kwargs) 1018 # Compile and link a program that uses a system library. On 1019 # success, return None. On failure, return a tuple indicating which 1020 # stage failed and providing a text error message to show to the 1021 # user. Some callers handle failure by retrying with other options. 1022 # Others abort the SConf run. 1023 def _soft_check_system_library(self,context,header,main,lib,pretext='',text='',successflags={},testflags={}): 1024 include = pretext + '\n'.join(['#include <%s>' % h for h in header]) 1025 header = header[-1] 1026 # Test library. On success, good. On failure, test header to 1027 # give the user more help. 1028 if self.Link(context, text=include + text, main=main, msg='for usable library %s' % lib, successflags=successflags, testflags=testflags): 1029 return 1030 # If linking failed, an error report is inevitable. Probe 1031 # progressively simpler configurations to help the user trace 1032 # the problem. 1033 successflags = successflags.copy() 1034 successflags.update(testflags) 1035 Compile = self.Compile 1036 if Compile(context, text=include + text, main=main, msg='for usable header %s' % header, testflags=successflags): 1037 # If this Compile succeeds, then the test program can be 1038 # compiled, but it cannot be linked. The required library 1039 # may be missing or broken. 1040 return (0, "Header %s is usable, but library %s is not usable." % (header, lib)) 1041 if Compile(context, text=include, main=main and '', msg='whether compiler can parse header %s' % header, testflags=successflags): 1042 # If this Compile succeeds, then the test program cannot be 1043 # compiled, but the header can be used with an empty test 1044 # program. Either the test program is broken or it relies 1045 # on the header working differently than the used header 1046 # actually works. 1047 return (1, "Header %s is parseable, but cannot compile the test program." % header) 1048 CXXFLAGS = successflags.setdefault('CXXFLAGS', []) 1049 CXXFLAGS.append('-E') 1050 if Compile(context, text=include, main=main and '', msg='whether preprocessor can parse header %s' % header, testflags=successflags): 1051 # If this Compile succeeds, then the used header can be 1052 # preprocessed, but cannot be compiled as C++ even with an 1053 # empty test program. The header likely has a C++ syntax 1054 # error or assumes prerequisite headers will be included by 1055 # the calling program. 1056 return (2, "Header %s exists, but cannot compile an empty program." % header) 1057 CXXFLAGS.extend(('-M', '-MG')) 1058 # If the header exists and is accepted by the preprocessor, an 1059 # earlier test would have returned and this Compile would not be 1060 # reached. Therefore, this Compile runs only if the header: 1061 # - Is directly missing 1062 # - Is indirectly missing (present, but includes a missing 1063 # header) 1064 # - Is present, but rejected by the preprocessor (such as from 1065 # an active `#error`) 1066 if Compile(context, text=include, main=main and '', msg='whether preprocessor can locate header %s (and supporting headers)' % header, expect_failure=True, testflags=successflags): 1067 # If this Compile succeeds, then the header does not exist, 1068 # or exists and includes (possibly through layers of 1069 # indirection) a header which does not exist. Passing `-MG` 1070 # makes non-existent headers legal, but still rejects 1071 # headers with `#error` and similar constructs. 1072 # 1073 # `expect_failure=True` inverts it to a failure in the 1074 # logged output and in the Python value returned from 1075 # `Compile(...)`. 1076 # - Compile success means that the header was not found, 1077 # which is not an error when `-MG` is passed, but was an 1078 # error in earlier tests. 1079 # - Compile failure means that the header was found, but 1080 # unusable, which is an error even when `-MG` is passed. 1081 # This can happen if the header contains `#error` 1082 # directives that are not preprocessed out. 1083 # Therefore, use `expect_failure=True` so that "success" 1084 # (header not found) prints "no" ("cannot locate header") 1085 # and "failure" (header found, but unusable) prints "yes" 1086 # ("can locate header"). This keeps the confusing double 1087 # negatives confined to the code and this comment. User 1088 # visible log messages are clear. 1089 # 1090 # Compile failure (unusable) is converted to a True return 1091 # by `expect_failure=True`, so the guarded path should 1092 # return "unusable" and the fallthrough path should return 1093 # "missing". 1094 return (3, "Header %s is unusable." % header) 1095 return (4, "Header %s is missing or includes a missing supporting header." % header) 1096 # Compile and link a program that uses a system library. On 1097 # success, return None. On failure, abort the SConf run. 1098 def _check_system_library(self,*args,**kwargs): 1099 e = self._soft_check_system_library(*args, **kwargs) 1100 if e: 1101 raise SCons.Errors.StopError(e[1]) 1102 # User settings tests are hidden because they do not respect sconf_* 1103 # overrides, so the user should not be offered an override. 1104 @_custom_test 1105 def _check_user_settings_endian(self,context,__endian_names=('little', 'big')): 1106 cls = self.__class__ 1107 endian = self.user_settings.host_endian 1108 if endian is None: 1109 struct = cls.__python_import_struct 1110 if struct is None: 1111 import struct 1112 cls.__python_import_struct = struct 1113 a = struct.pack('cccc', b'1', b'2', b'3', b'4') 1114 unpack = struct.unpack 1115 i = unpack('i', a) 1116 if i == unpack('<i', a): 1117 endian = 0 1118 elif i == unpack('>i', a): 1119 endian = 1 1120 else: 1121 raise SCons.Errors.UserError("Unknown host endian: unpack('i', %r) == %r; set host_endian='big' or host_endian='little' as appropriate." % (a, i)) 1122 else: 1123 if endian == __endian_names[0]: 1124 endian = 0 1125 else: 1126 endian = 1 1127 context.Result('%s: checking endian to use...%s' % (self.msgprefix, __endian_names[endian])) 1128 self._define_macro(context, 'DXX_WORDS_BIGENDIAN', endian) 1129 1130 @_custom_test 1131 def _check_user_settings_words_need_alignment(self,context): 1132 self._result_check_user_setting(context, self.user_settings.words_need_alignment, 'DXX_WORDS_NEED_ALIGNMENT', 'word alignment fixups') 1133 1134 @_custom_test 1135 def _check_user_settings_opengl(self,context): 1136 user_settings = self.user_settings 1137 Result = context.Result 1138 _define_macro = self._define_macro 1139 _define_macro(context, 'DXX_USE_OGL', int(user_settings.opengl)) 1140 _define_macro(context, 'DXX_USE_OGLES', int(user_settings.opengles)) 1141 if user_settings.opengles: 1142 s = 'OpenGL ES' 1143 elif user_settings.opengl: 1144 s = 'OpenGL' 1145 else: 1146 s = 'software renderer' 1147 Result('%s: building with %s' % (self.msgprefix, s)) 1148 1149 def _result_check_user_setting(self,context,condition,CPPDEFINES,label,int=int,str=str): 1150 if isinstance(CPPDEFINES, str): 1151 self._define_macro(context, CPPDEFINES, int(condition)) 1152 elif condition: 1153 self.successful_flags['CPPDEFINES'].extend(CPPDEFINES) 1154 context.Result('%s: checking whether to enable %s...%s' % (self.msgprefix, label, 'yes' if condition else 'no')) 1155 1156 @_custom_test 1157 def _check_user_settings_debug(self,context,_CPPDEFINES=(('NDEBUG',), ('RELEASE',))): 1158 self._result_check_user_setting(context, not self.user_settings.debug, _CPPDEFINES, 'release options') 1159 1160 @_custom_test 1161 def _check_user_settings_memdebug(self,context,_CPPDEFINES=(('DEBUG_MEMORY_ALLOCATIONS',),)): 1162 self._result_check_user_setting(context, self.user_settings.memdebug, _CPPDEFINES, 'memory allocation tracking') 1163 1164 @_custom_test 1165 def _check_user_settings_editor(self,context,_CPPDEFINES='DXX_USE_EDITOR'): 1166 self._result_check_user_setting(context, self.user_settings.editor, _CPPDEFINES, 'level editor') 1167 1168 @_custom_test 1169 def _check_user_settings_ipv6(self,context,_CPPDEFINES='DXX_USE_IPv6'): 1170 self._result_check_user_setting(context, self.user_settings.ipv6, _CPPDEFINES, 'IPv6 support') 1171 1172 @_custom_test 1173 def _check_user_settings_udp(self,context,_CPPDEFINES='DXX_USE_UDP'): 1174 self._result_check_user_setting(context, self.user_settings.use_udp, _CPPDEFINES, 'multiplayer over UDP') 1175 1176 @_custom_test 1177 def _check_user_settings_tracker(self,context,_CPPDEFINES='DXX_USE_TRACKER'): 1178 use_tracker = self.user_settings.use_tracker 1179 self._result_check_user_setting(context, use_tracker, _CPPDEFINES, 'UDP game tracker') 1180 # The legacy UDP tracker does not need either curl or jsoncpp. 1181 # The new HTTP tracker requires both. Force `use_tracker` to 1182 # False for now. Remove this comment and this assignment when 1183 # the HTTP tracker is made active. 1184 use_tracker = False 1185 if use_tracker: 1186 self.check_curl(context) 1187 self.check_jsoncpp(context) 1188 1189 @_implicit_test 1190 def check_libpng(self,context, 1191 _header=( 1192 'cstdint', 1193 'png.h', 1194 ), 1195 _guess_flags={'LIBS' : ['png']}, 1196 _text=''' 1197namespace { 1198struct d_screenshot 1199{ 1200 png_struct *png_ptr; 1201 png_info *info_ptr = nullptr; 1202 static void png_error_cb(png_struct *, const char *) {} 1203 static void png_warn_cb(png_struct *, const char *) {} 1204 static void png_write_cb(png_struct *, uint8_t *, png_size_t) {} 1205 static void png_flush_cb(png_struct *) {} 1206}; 1207} 1208''', 1209 _main=''' 1210 d_screenshot ss; 1211 ss.png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, &ss, &d_screenshot::png_error_cb, &d_screenshot::png_warn_cb); 1212 png_set_write_fn(ss.png_ptr, &ss, &d_screenshot::png_write_cb, &d_screenshot::png_flush_cb); 1213 ss.info_ptr = png_create_info_struct(ss.png_ptr); 1214#ifdef PNG_tIME_SUPPORTED 1215 png_time pt; 1216 pt.year = 2018; 1217 pt.month = 1; 1218 pt.day = 1; 1219 pt.hour = 1; 1220 pt.minute = 1; 1221 pt.second = 1; 1222 png_set_tIME(ss.png_ptr, ss.info_ptr, &pt); 1223#endif 1224#if DXX_USE_OGL 1225 const auto color_type = PNG_COLOR_TYPE_RGB; 1226#else 1227 png_set_PLTE(ss.png_ptr, ss.info_ptr, reinterpret_cast<const png_color *>(&ss), 256 * 3); 1228 const auto color_type = PNG_COLOR_TYPE_PALETTE; 1229#endif 1230 png_set_IHDR(ss.png_ptr, ss.info_ptr, 1, 1, 8, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); 1231#ifdef PNG_TEXT_SUPPORTED 1232 png_text text_fields[1] = {}; 1233 png_set_text(ss.png_ptr, ss.info_ptr, text_fields, 1); 1234#endif 1235 png_write_info(ss.png_ptr, ss.info_ptr); 1236 png_byte *row_pointers[1024]{}; 1237 png_write_rows(ss.png_ptr, row_pointers, 1024); 1238 png_write_end(ss.png_ptr, ss.info_ptr); 1239 png_destroy_write_struct(&ss.png_ptr, &ss.info_ptr); 1240'''): 1241 successflags = self.pkgconfig.merge(context, self.msgprefix, self.user_settings, 'libpng', 'libpng', _guess_flags) 1242 return self._soft_check_system_library(context, header=_header, main=_main, lib='png', text=_text, successflags=successflags) 1243 1244 @_custom_test 1245 def _check_user_settings_adldmidi(self,context): 1246 user_settings = self.user_settings 1247 adlmidi = user_settings.adlmidi 1248 if adlmidi == 'none': 1249 adlmidi_load_type = 'disabled' 1250 elif adlmidi == 'runtime': 1251 adlmidi_load_type = 'load dynamically' 1252 else: 1253 return 1254 context.Result('%s: checking how to handle ADL MIDI...%s' % (self.msgprefix, adlmidi_load_type)) 1255 Define = context.sconf.Define 1256 Define('DXX_USE_ADLMIDI', int(user_settings._enable_adlmidi())) 1257 1258 @_custom_test 1259 def _check_user_settings_screenshot(self,context): 1260 user_settings = self.user_settings 1261 screenshot_mode = user_settings.screenshot 1262 if screenshot_mode == 'png': 1263 screenshot_format_type = screenshot_mode 1264 elif screenshot_mode == 'legacy': 1265 screenshot_format_type = 'tga' if user_settings.opengl else 'pcx' 1266 elif screenshot_mode == 'none': 1267 screenshot_format_type = 'screenshot support disabled' 1268 else: 1269 return 1270 context.Result('%s: checking how to format screenshots...%s' % (self.msgprefix, screenshot_format_type)) 1271 Define = context.sconf.Define 1272 Define('DXX_USE_SCREENSHOT_FORMAT_PNG', int(screenshot_mode == 'png')) 1273 Define('DXX_USE_SCREENSHOT_FORMAT_LEGACY', int(screenshot_mode == 'legacy')) 1274 Define('DXX_USE_SCREENSHOT', int(screenshot_mode != 'none')) 1275 if screenshot_mode == 'png': 1276 e = self.check_libpng(context) 1277 if e: 1278 raise SCons.Errors.StopError(e[1] + ' Set screenshot=legacy to remove screenshot libpng requirement or set screenshot=none to remove screenshot support.') 1279 1280 # Require _WIN32_WINNT >= 0x0501 to enable getaddrinfo 1281 # Require _WIN32_WINNT >= 0x0600 to enable some useful AI_* flags 1282 @_guarded_test_windows 1283 def _check_user_CPPDEFINES__WIN32_WINNT(self,context,_msg='%s: checking whether to define _WIN32_WINNT...%s',_CPPDEFINES_WIN32_WINNT=('_WIN32_WINNT', 0x600)): 1284 env = context.env 1285 for f in env['CPPDEFINES']: 1286 # Ignore the case that an element is a string with this 1287 # value, since that would not define the macro to a 1288 # useful value. In CPPDEFINES, only a tuple of 1289 # (name,number) is useful for macro _WIN32_WINNT. 1290 if f[0] == '_WIN32_WINNT': 1291 f = f[1] 1292 context.Result(_msg % (self.msgprefix, 'no, already set in CPPDEFINES as %s' % (('%#x' if isinstance(f, int) else '%r') % f))) 1293 return 1294 for f in env['CPPFLAGS']: 1295 if f.startswith('-D_WIN32_WINNT='): 1296 context.Result(_msg % (self.msgprefix, 'no, already set in CPPFLAGS as %r' % f)) 1297 return 1298 context.Result(_msg % (self.msgprefix, 'yes, define _WIN32_WINNT=%#x' % _CPPDEFINES_WIN32_WINNT[1])) 1299 self.successful_flags['CPPDEFINES'].append(_CPPDEFINES_WIN32_WINNT) 1300 self.__defined_macros += '#define %s %s\n' % (_CPPDEFINES_WIN32_WINNT[0], _CPPDEFINES_WIN32_WINNT[1]) 1301 1302 @_implicit_test 1303 def check_curl(self,context, 1304 _header=('curl/curl.h',), 1305 _guess_flags={'LIBS' : ['curl']}, 1306 _main=''' 1307 CURL *c = curl_easy_init(); 1308 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, nullptr); 1309 curl_easy_cleanup(c); 1310'''): 1311 successflags = self.pkgconfig.merge(context, self.msgprefix, self.user_settings, 'libcurl', 'curl', _guess_flags).copy() 1312 successflags['CPPDEFINES'] = successflags.get('CPPDEFINES', []) + ['DXX_HAVE_LIBCURL'] 1313 self._check_system_library(context, header=_header, main=_main, lib='curl', successflags=successflags) 1314 1315 @_implicit_test 1316 def check_jsoncpp(self,context, 1317 _header=( 1318 'memory', 1319 'json/json.h', 1320 ), 1321 _guess_flags={'LIBS' : ['jsoncpp'], 'CPPPATH': ['/usr/include/jsoncpp']}, 1322 _main=''' 1323 Json::Value v; 1324 v["a"] = "a"; 1325 v["b"] = 1; 1326 // This code is silly, but it uses many of the required 1327 // symbols, so if it builds, jsoncpp is probably installed correctly. 1328 const std::string &&s = Json::writeString(Json::StreamWriterBuilder(), v); 1329 std::string errs; 1330 std::unique_ptr<Json::CharReader>( 1331 Json::CharReaderBuilder().newCharReader() 1332 )->parse(s.data(), &s.data()[s.size()], &v, &errs); 1333'''): 1334 successflags = self.pkgconfig.merge(context, self.msgprefix, self.user_settings, 'jsoncpp', 'jsoncpp', _guess_flags) 1335 self._check_system_library(context, header=_header, main=_main, lib='jsoncpp', successflags=successflags) 1336 1337 @_guarded_test_windows 1338 def check_dbghelp_header(self,context,_CPPDEFINES='DXX_ENABLE_WINDOWS_MINIDUMP'): 1339 windows_minidump = self.user_settings.windows_minidump 1340 self._result_check_user_setting(context, windows_minidump, _CPPDEFINES, 'minidump on exception') 1341 if not windows_minidump: 1342 return 1343 include_dbghelp_header = ''' 1344// <dbghelp.h> assumes that <windows.h> was included earlier and fails 1345// to parse if this assumption is false 1346#include <windows.h> 1347#include <dbghelp.h> 1348''' 1349 if self.Compile(context, text=include_dbghelp_header + ''' 1350#include <stdexcept> 1351 1352using MiniDumpWriteDump_type = decltype(MiniDumpWriteDump); 1353MiniDumpWriteDump_type *pMiniDumpWriteDump; 1354 1355[[noreturn]] 1356static void terminate_handler() 1357{ 1358 MINIDUMP_EXCEPTION_INFORMATION mei; 1359 MINIDUMP_USER_STREAM musa[1]; 1360 MINIDUMP_USER_STREAM_INFORMATION musi; 1361 EXCEPTION_POINTERS ep; 1362 mei.ExceptionPointers = &ep; 1363 musi.UserStreamCount = 1; 1364 musi.UserStreamArray = musa; 1365 (*pMiniDumpWriteDump)(GetCurrentProcess(), GetCurrentProcessId(), GetCurrentProcess(), MiniDumpWithFullMemory, &mei, &musi, nullptr); 1366 ExitProcess(0); 1367} 1368''', main=''' 1369 std::set_terminate(&terminate_handler); 1370''', msg='for usable header dbghelp.h'): 1371 return 1372 errtext = "Header dbghelp.h is parseable but cannot compile the test program." \ 1373 if self.Compile(context, text=include_dbghelp_header, msg='for parseable header dbghelp.h') \ 1374 else "Header dbghelp.h is missing or unusable." 1375 raise SCons.Errors.StopError(errtext + " Upgrade the Windows API header package or disable minidump support.") 1376 @_custom_test 1377 def check_libphysfs(self,context,_header=('physfs.h',)): 1378 main = ''' 1379 PHYSFS_File *f; 1380 char b[1] = {0}; 1381 if (!PHYSFS_init("")) 1382 return 1; 1383 { 1384 const char *r = PHYSFS_getRealDir(""); 1385 (void)r; 1386 } 1387 PHYSFS_isDirectory(""); 1388 { 1389 const char *sep = PHYSFS_getDirSeparator(); 1390 (void)sep; 1391 } 1392 { 1393 const char *dir = PHYSFS_getBaseDir(); 1394 (void)dir; 1395 } 1396 { 1397 const char *dir = PHYSFS_getUserDir(); 1398 (void)dir; 1399 } 1400 f = PHYSFS_openWrite("a"); 1401 PHYSFS_sint64 w = PHYSFS_write(f, b, 1, 1); 1402 (void)w; 1403 PHYSFS_close(f); 1404 f = PHYSFS_openRead("a"); 1405 PHYSFS_sint64 r = PHYSFS_read(f, b, 1, 1); 1406 (void)r; 1407 PHYSFS_close(f); 1408 PHYSFS_mount("", nullptr, 0); 1409 PHYSFS_unmount(""); 1410 PHYSFS_delete(""); 1411''' 1412 l = ['physfs'] 1413 successflags = self.pkgconfig.merge(context, self.msgprefix, self.user_settings, 'physfs', 'physfs', {'LIBS' : l}) 1414 e = self._soft_check_system_library(context, header=_header, main=main, lib='physfs', successflags=successflags) 1415 if not e: 1416 return 1417 if e[0] == 0: 1418 context.Display("%s: physfs header usable; adding zlib and retesting library\n" % self.msgprefix) 1419 l.append('z') 1420 e = self._soft_check_system_library(context, header=_header, main=main, lib='physfs', successflags=successflags) 1421 if e: 1422 raise SCons.Errors.StopError(e[1]) 1423 1424 @_custom_test 1425 def check_glu(self,context): 1426 if not self.user_settings.opengl: 1427 return 1428 if self.user_settings.opengles: 1429 # GLES builds do not use the GL utility functions 1430 return 1431 if sys.platform == 'darwin': 1432 # macOS provides these in a system framework, so this test always fails 1433 return 1434 ogllibs = self.platform_settings.ogllibs 1435 self._check_system_library(context, header=['GL/glu.h'], main=''' 1436 gluPerspective(90.0,1.0,0.1,5000.0); 1437 gluBuild2DMipmaps (GL_TEXTURE_2D, 0, 1, 1, 1, GL_UNSIGNED_BYTE, nullptr); 1438''', lib=ogllibs, testflags={'LIBS': ogllibs}) 1439 1440 @_custom_test 1441 def _check_SDL(self,context): 1442 if self.user_settings.sdl2: 1443 check_libSDL = self.check_libSDL2 1444 check_SDL_image = self.check_SDL2_image 1445 check_SDL_mixer = self.check_SDL2_mixer 1446 else: 1447 check_libSDL = self.check_libSDL 1448 check_SDL_image = self.check_SDL_image 1449 check_SDL_mixer = self.check_SDL_mixer 1450 check_libSDL(context) 1451 check_SDL_image(context) 1452 check_SDL_mixer(context) 1453 1454 @_implicit_test 1455 def check_libSDL(self,context,_guess_flags={ 1456 'LIBS' : ['SDL'] if sys.platform != 'darwin' else [], 1457 }): 1458 self._check_libSDL(context, '', _guess_flags) 1459 1460 @_implicit_test 1461 def check_libSDL2(self,context,_guess_flags={ 1462 'LIBS' : ['SDL2'] if sys.platform != 'darwin' else [], 1463 }): 1464 if not self.user_settings.opengl: 1465 raise SCons.Errors.StopError('Rebirth does not support SDL2 without OpenGL. Set opengl=1 or sdl2=0.') 1466 self._check_libSDL(context, '2', _guess_flags) 1467 1468 def _check_libSDL(self,context,sdl2,guess_flags): 1469 user_settings = self.user_settings 1470 successflags = self.pkgconfig.merge(context, self.msgprefix, user_settings, 'sdl%s' % sdl2, 'SDL%s' % sdl2, guess_flags).copy() 1471 if user_settings.max_joysticks: 1472 # If joysticks are enabled, but all possible inputs are 1473 # disabled, then disable joystick support. 1474 if not (user_settings.max_axes_per_joystick or user_settings.max_buttons_per_joystick or user_settings.max_hats_per_joystick): 1475 user_settings.max_joysticks = 0 1476 elif not user_settings.max_buttons_per_joystick: 1477 user_settings.max_hats_per_joystick = 0 1478 else: 1479 # If joysticks are disabled, then disable all possible 1480 # inputs. 1481 user_settings.max_axes_per_joystick = user_settings.max_buttons_per_joystick = user_settings.max_hats_per_joystick = 0 1482 successflags['CPPDEFINES'] = CPPDEFINES = successflags.get('CPPDEFINES', [])[:] 1483 # use Redbook if at least one of the following applies 1484 # 1. we are on SDL1 1485 # 2. we are building for a platform for which we have a custom CD implementation (currently only win32) 1486 use_redbook = int(not sdl2 or user_settings.host_platform == 'win32') 1487 CPPDEFINES.extend(( 1488 ('DXX_MAX_JOYSTICKS', user_settings.max_joysticks), 1489 ('DXX_MAX_AXES_PER_JOYSTICK', user_settings.max_axes_per_joystick), 1490 ('DXX_MAX_BUTTONS_PER_JOYSTICK', user_settings.max_buttons_per_joystick), 1491 ('DXX_MAX_HATS_PER_JOYSTICK', user_settings.max_hats_per_joystick), 1492 ('DXX_USE_SDL_REDBOOK_AUDIO', use_redbook), 1493 )) 1494 context.Display('%s: checking whether to enable joystick support...%s\n' % (self.msgprefix, 'yes' if user_settings.max_joysticks else 'no')) 1495 # SDL2 removed CD-rom support. 1496 init_cdrom = '0' if sdl2 else 'SDL_INIT_CDROM' 1497 error_text_opengl_mismatch = 'Rebirth configured with OpenGL enabled, but SDL{0} configured with OpenGL disabled. Disable Rebirth OpenGL or install an SDL{0} with OpenGL enabled.'.format(sdl2) 1498 test_opengl = (''' 1499#ifndef SDL_VIDEO_OPENGL 1500#error "%s" 1501#endif 1502''' % error_text_opengl_mismatch) if user_settings.opengl else '' 1503 main = ''' 1504 SDL_RWops *ops = reinterpret_cast<SDL_RWops *>(argv); 1505#if DXX_MAX_JOYSTICKS 1506#ifdef SDL_JOYSTICK_DISABLED 1507#error "Rebirth configured with joystick support enabled, but SDL{sdl2} configured with joystick support disabled. Disable Rebirth joystick support or install an SDL{sdl2} with joystick support enabled." 1508#endif 1509#define DXX_SDL_INIT_JOYSTICK SDL_INIT_JOYSTICK | 1510#else 1511#define DXX_SDL_INIT_JOYSTICK 1512#endif 1513 SDL_Init(DXX_SDL_INIT_JOYSTICK {init_cdrom} | SDL_INIT_VIDEO | SDL_INIT_AUDIO); 1514{test_opengl} 1515#if DXX_MAX_JOYSTICKS 1516 auto n = SDL_NumJoysticks(); 1517 (void)n; 1518#endif 1519 SDL_QuitSubSystem(SDL_INIT_VIDEO); 1520 SDL_FreeRW(ops); 1521 SDL_Quit(); 1522''' 1523 e = self._soft_check_system_library(context,header=['SDL.h'],main=main.format(init_cdrom=init_cdrom, sdl2=sdl2, test_opengl=test_opengl), 1524 lib=('SDL{0} with OpenGL' if test_opengl else 'SDL{0}').format(sdl2), successflags=successflags 1525 ) 1526 if not e: 1527 return 1528 if test_opengl: 1529 e2 = self._soft_check_system_library(context,header=['SDL.h'],main=main.format(init_cdrom=init_cdrom, sdl2=sdl2, test_opengl=''), 1530 lib='SDL without OpenGL', successflags=successflags 1531 ) 1532 if not e2 and e[0] == 1: 1533 e = (None, error_text_opengl_mismatch) 1534 raise SCons.Errors.StopError(e[1]) 1535 1536 @_implicit_test 1537 def check_SDL_image(self,context): 1538 self._check_SDL_image(context, '') 1539 1540 @_implicit_test 1541 def check_SDL2_image(self,context): 1542 self._check_SDL_image(context, '2') 1543 1544 def _check_SDL_image(self,context,sdl2): 1545 self._check_SDL_addon_library(context, sdl2, 'SDL%s_image', 'DXX_USE_SDLIMAGE', self.user_settings.sdlimage, ''' 1546 IMG_Init(0); 1547 SDL_RWops *rw = reinterpret_cast<SDL_RWops *>(argv); 1548 SDL_Surface *s = IMG_LoadPCX_RW(rw); 1549 (void)s; 1550 IMG_Quit(); 1551''') 1552 1553 # SDL_mixer/SDL2_mixer use the same -I line as SDL/SDL2 1554 @_implicit_test 1555 def check_SDL_mixer(self,context): 1556 self._check_SDL_mixer(context, '') 1557 1558 @_implicit_test 1559 def check_SDL2_mixer(self,context): 1560 self._check_SDL_mixer(context, '2') 1561 1562 def _check_SDL_mixer(self,context,sdl2): 1563 self._check_SDL_addon_library(context, sdl2, 'SDL%s_mixer', 'DXX_USE_SDLMIXER', self.user_settings.sdlmixer, ''' 1564 int i = Mix_Init(MIX_INIT_FLAC | MIX_INIT_OGG); 1565 (void)i; 1566 Mix_Pause(0); 1567 Mix_ResumeMusic(); 1568 Mix_Quit(); 1569''') 1570 1571 def _check_SDL_addon_library(self,context,sdl2,library_format_name,macro_name,use_addon,main): 1572 library_name = library_format_name % sdl2 1573 self._define_macro(context, macro_name, int(use_addon)) 1574 context.Display('%s: checking whether to use %s...%s\n' % (self.msgprefix, library_name, 'yes' if use_addon else 'no')) 1575 if not use_addon: 1576 return 1577 user_settings = self.user_settings 1578 guess_flags = { 1579 'LIBS' : [library_name] if sys.platform != 'darwin' else [], 1580 } 1581 successflags = self.pkgconfig.merge(context, self.msgprefix, user_settings, library_name, library_name, guess_flags) 1582 if user_settings.host_platform == 'darwin' and user_settings.macos_add_frameworks: 1583 successflags = successflags.copy() 1584 successflags['FRAMEWORKS'] = [library_name] 1585 relative_headers = 'Library/Frameworks/%s.framework/Headers' % library_name 1586 successflags['CPPPATH'] = [h for h in (os.path.join(os.getenv("HOME"), relative_headers), '/%s' % relative_headers) if os.path.isdir(h)] 1587 # SDL2 headers still use SDL_*.h for their filename, not 1588 # SDL2_*.h, so expanded library_format_name with an explicitly 1589 # blank insert, regardless of whether building for SDL1 or SDL2. 1590 self._check_system_library(context, header=['%s.h' % (library_format_name % '')], main=main, lib=library_name, successflags=successflags) 1591 1592 @_custom_test 1593 def check_compiler_missing_field_initializers(self,context, 1594 _testflags_warn={'CXXFLAGS' : ['-Wmissing-field-initializers']}, 1595 _successflags_nowarn={'CXXFLAGS' : ['-Wno-missing-field-initializers']} 1596 ): 1597 """ 1598Test whether the compiler warns for a statement of the form 1599 1600 variable={}; 1601 1602gcc-4.x warns for this form, but -Wno-missing-field-initializers silences it. 1603gcc-5 does not warn. 1604 1605This form is used extensively in the code as a shorthand for resetting 1606variables to their default-constructed value. 1607""" 1608 text = 'struct A{int a;};' 1609 main = 'A a{};(void)a;' 1610 Compile = self.Compile 1611 if Compile(context, text=text, main=main, msg='whether C++ compiler accepts {} initialization', testflags=_testflags_warn) or \ 1612 Compile(context, text=text, main=main, msg='whether C++ compiler understands -Wno-missing-field-initializers', successflags=_successflags_nowarn) or \ 1613 not Compile(context, text=text, main=main, msg='whether C++ compiler always errors for {} initialization', expect_failure=True): 1614 return 1615 raise SCons.Errors.StopError("C++ compiler errors on {} initialization, even with -Wno-missing-field-initializers.") 1616 1617 @_custom_test 1618 def check_attribute_error(self,context): 1619 """ 1620Test whether the compiler accepts and properly implements gcc's function 1621attribute [__attribute__((__error__))][1]. 1622 1623A proper implementation will compile correctly if the function is 1624declared and, after optimizations are applied, the function is not 1625called. If this function attribute is not supported, then 1626DXX_ALWAYS_ERROR_FUNCTION results in link-time errors, rather than 1627compile-time errors. 1628 1629This test will report failure if the optimizer does not remove the call 1630to the marked function. 1631 1632[1]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007berror_007d-function-attribute-3097 1633 1634help:assume compiler supports __attribute__((error)) 1635""" 1636 self._check_function_dce_attribute(context, 'error') 1637 def _check_function_dce_attribute(self,context,attribute): 1638 __attribute__ = '__%s__' % attribute 1639 f = ''' 1640void a()__attribute__((%s("a called"))); 1641''' % __attribute__ 1642 macro_name = '__attribute_%s(M)' % attribute 1643 Compile = self.Compile 1644 Define = context.sconf.Define 1645 if Compile(context, text=f, main='if("0"[0]==\'1\')a();', msg='whether compiler optimizes function __attribute__((%s))' % __attribute__): 1646 Define('DXX_HAVE_ATTRIBUTE_%s' % attribute.upper()) 1647 macro_value = '__attribute__((%s(M)))' % __attribute__ 1648 else: 1649 Compile(context, text=f, msg='whether compiler accepts function __attribute__((%s))' % __attribute__) and \ 1650 Compile(context, text=f, main='a();', msg='whether compiler understands function __attribute__((%s))' % __attribute__, expect_failure=True) 1651 macro_value = self.comment_not_supported 1652 Define(macro_name, macro_value) 1653 self.__defined_macros += '#define %s %s\n' % (macro_name, macro_value) 1654 1655 @_custom_test 1656 def check_builtin_bswap(self,context, 1657 _main=''' 1658 (void)__builtin_bswap64(static_cast<uint64_t>(argc)); 1659 (void)__builtin_bswap32(static_cast<uint32_t>(argc)); 1660 (void)__builtin_bswap16(static_cast<uint16_t>(argc)); 1661''', 1662 _successflags_bswap16={'CPPDEFINES' : ['DXX_HAVE_BUILTIN_BSWAP', 'DXX_HAVE_BUILTIN_BSWAP16']}, 1663 ): 1664 """ 1665Test whether the compiler accepts the gcc byte swapping intrinsic 1666functions. These functions may be optimized into architecture-specific 1667swap instructions when the idiomatic swap is not. 1668 1669 u16 = (u16 << 8) | (u16 >> 8); 1670 1671The 16-bit ([__builtin_bswap16][3]), 32-bit ([__builtin_bswap32][1]), 1672and 64-bit ([__builtin_bswap64][2]) versions are present in all 1673supported versions of gcc. 1674 1675[1]: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005fbswap32-4135 1676[2]: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005fbswap64-4136 1677[3]: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005fbswap16-4134 1678""" 1679 include = ''' 1680#include <cstdint> 1681''' 1682 self.Compile(context, text=include, main=_main, msg='whether compiler implements __builtin_bswap{16,32,64} functions', successflags=_successflags_bswap16) 1683 1684 implicit_tests.append(_implicit_test.RecordedTest('check_optimize_builtin_constant_p', "assume compiler optimizes __builtin_constant_p")) 1685 1686 @_custom_test 1687 def check_builtin_constant_p(self,context): 1688 """ 1689Test whether the compiler accepts and properly implements gcc's 1690intrinsic [__builtin_constant_p][1]. A proper implementation will 1691compile correctly if the intrinsic is recognized and, after 1692optimizations are applied, the intrinsic returns true for a constant 1693input and that return value is used to optimize away dead code. 1694If this intrinsic is not supported, or if applying optimizations 1695does not make the intrinsic report true, then the test reports 1696failure. A failure here disables some compile-time sanity checks. 1697 1698This test is known to fail when optimizations are disabled. The failure 1699is not a bug in the test or in the compiler. 1700 1701[1]: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005fconstant_005fp-4082 1702 1703help:assume compiler supports compile-time __builtin_constant_p 1704""" 1705 f = ''' 1706/* 1707 * Begin `timekeeping.i` from gcc bug #72785, comment #0. This block 1708 * provokes gcc-7 into generating a call to ____ilog2_NaN, which then 1709 * fails to link when ____ilog2_NaN is intentionally undefined. 1710 * 1711 * Older versions of gcc declare the expression to be false in 1712 * this case, and do not generate the call to ____ilog2_NaN. This looks 1713 * fragile since b is provably non-zero (`b` = `a` if `a` is not zero, 1714 * else = `c` = `1`, so `b` is non-zero regardless of the path used in 1715 * the ternary operator), but the specific non-zero value is not 1716 * provable. However, this matches the testcase posted in gcc's 1717 * Bugzilla and produces the desired results on each of gcc-5 (enable), 1718 * gcc-6 (enable), and gcc-7 (disable). 1719 */ 1720int a, b; 1721int ____ilog2_NaN(); 1722static void by(void) { 1723 int c = 1; 1724 b = a ?: c; 1725 __builtin_constant_p(b) ? b ? ____ilog2_NaN() : 0 : 0; 1726} 1727/* 1728 * End `timekeeping.i`. 1729 */ 1730int c(int); 1731static int x(int y){ 1732 return __builtin_constant_p(y) ? 1 : %s; 1733} 1734''' 1735 main = ''' 1736 /* 1737 ** Tell the compiler not to make any assumptions about the value of 1738 ** `a`. In LTO mode, the compiler would otherwise prove that it 1739 ** knows there are no writes to `a` and therefore prove that `a` 1740 ** must be zero. That proof then causes it to compile `by()` as: 1741 ** 1742 ** b = 0 ? 0 : 1; 1743 ** __builtin_constant_p(1) ? 1 ? ____ilog2_NaN() : 0 : 0; 1744 ** 1745 ** That then generates a reference to the intentionally undefined 1746 ** ____ilog2_NaN, causing the test to report failure. Prevent this 1747 ** by telling the compiler that it cannot prove the value of `a`, 1748 ** forcing it not to propagate a zero `a` into `by()`. 1749 **/ 1750 asm("" : "=rm" (a)); 1751 by(); 1752 return x(1) + x(2); 1753''' 1754 Define = context.sconf.Define 1755 if self.Link(context, text=f % 'c(y)', main=main, msg='whether compiler optimizes __builtin_constant_p', calling_function='optimize_builtin_constant_p'): 1756 Define('DXX_HAVE_BUILTIN_CONSTANT_P') 1757 Define('DXX_CONSTANT_TRUE(E)', '(__builtin_constant_p((E)) && (E))') 1758 dxx_builtin_constant_p = '__builtin_constant_p(A)' 1759 else: 1760 # This is present because it may be useful to see in the 1761 # debug log. It is not expected to modify the build 1762 # environment. 1763 self.Compile(context, text=f % '2', main=main, msg='whether compiler accepts __builtin_constant_p') 1764 dxx_builtin_constant_p = '((void)(A),0)' 1765 Define('dxx_builtin_constant_p(A)', dxx_builtin_constant_p) 1766 1767 @_custom_test 1768 def check_builtin_expect(self,context): 1769 """ 1770Test whether the compiler accepts gcc's intrinsic 1771[__builtin_expect][1]. This intrinsic is a hint to the optimizer, 1772which it may ignore. The test does not try to detect whether the 1773optimizer respects such hints. 1774 1775When this test succeeds, conditional tests can hint to the optimizer 1776which path should be considered hot. 1777 1778[1]: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005fexpect-4083 1779""" 1780 main = ''' 1781return __builtin_expect(argc == 1, 1) ? 1 : 0; 1782''' 1783 if self.Compile(context, text='', main=main, msg='whether compiler accepts __builtin_expect'): 1784 likely = '__builtin_expect(!!(A), 1)' 1785 unlikely = '__builtin_expect(!!(A), 0)' 1786 else: 1787 likely = unlikely = '(!!(A))' 1788 Define = context.sconf.Define 1789 Define('likely(A)', likely) 1790 Define('unlikely(A)', unlikely) 1791 1792 @_custom_test 1793 def check_builtin_file(self,context): 1794 if self.Compile(context, text=''' 1795static void f(const char * = __builtin_FILE(), unsigned = __builtin_LINE()) 1796{ 1797} 1798''', main='f();', msg='whether compiler accepts __builtin_FILE, __builtin_LINE'): 1799 context.sconf.Define('DXX_HAVE_CXX_BUILTIN_FILE_LINE') 1800 1801 @_custom_test 1802 def check_builtin_object_size(self,context): 1803 """ 1804Test whether the compiler accepts and optimizes gcc's intrinsic 1805[__builtin_object_size][1]. If this intrinsic is optimized, 1806compile-time checks can verify that the caller-specified constant 1807size of a variable does not exceed the compiler-determined size of 1808the variable. If the compiler cannot determine the size of the 1809variable, no compile-time check is done. 1810 1811[1]: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html#index-g_t_005f_005fbuiltin_005fobject_005fsize-3657 1812 1813help:assume compiler supports __builtin_object_size 1814""" 1815 f = ''' 1816/* a() is never defined. An optimizing compiler will eliminate the 1817 * attempt to call it, allowing the Link to succeed. A non-optimizing 1818 * compiler will emit the call, and the Link will fail. 1819 */ 1820int a(); 1821static inline int a(char *c){ 1822 return __builtin_object_size(c,0) == 4 ? 1 : %s; 1823} 1824''' 1825 main = ''' 1826 char c[4]; 1827 return a(c); 1828''' 1829 if self.Link(context, text=f % 'a()', main=main, msg='whether compiler optimizes __builtin_object_size'): 1830 context.sconf.Define('DXX_HAVE_BUILTIN_OBJECT_SIZE') 1831 else: 1832 self.Compile(context, text=f % '2', main=main, msg='whether compiler accepts __builtin_object_size') 1833 1834 @_custom_test 1835 def check_embedded_compound_statement(self,context, 1836 _compound_statement_native=('', ''), 1837 _compound_statement_emulated=('[&]', '()') 1838 ): 1839 """ 1840Test whether the compiler implements gcc's [statement expression][1] 1841extension. If this extension is present, statements can be used where 1842expressions are expected. If it is absent, it is emulated by defining, 1843calling, and then discarding a lambda function. The compiler produces 1844better error messages for errors in statement expressions than for 1845errors in lambdas, so prefer statement expressions when they are 1846available. 1847 1848[1]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html 1849""" 1850 f = ''' 1851 return ({ 1 + 2; }); 1852''' 1853 t = _compound_statement_native if self.Compile(context, text='', main=f, msg='whether compiler understands embedded compound statements') else _compound_statement_emulated 1854 Define = context.sconf.Define 1855 Define('DXX_BEGIN_COMPOUND_STATEMENT', t[0]) 1856 Define('DXX_END_COMPOUND_STATEMENT', t[1]) 1857 1858 @_custom_test 1859 def check_compiler_always_error_optimizer(self,context, 1860 # Good case: <gcc-6 takes this path. Declare a function with 1861 # __attribute__((__error__)) and call it. 1862 _macro_value_simple=_quote_macro_value('''( DXX_BEGIN_COMPOUND_STATEMENT { 1863 void F() __attribute_error(S); 1864 F(); 1865} DXX_END_COMPOUND_STATEMENT ) 1866'''), 1867 # Bad case: >=gcc-6 takes this path. Declare a local scope class 1868 # with a static method marked __attribute__((__error__)) and call 1869 # that method. 1870 _macro_value_complicated=_quote_macro_value('''( DXX_BEGIN_COMPOUND_STATEMENT { 1871 struct DXX_ALWAYS_ERROR_FUNCTION { 1872 __attribute_error(S) 1873 /* The function must be declared inline because it is a member 1874 ** method, but must be marked noinline to discourage gcc from 1875 ** inlining it. If it is inlined, then the error message is not 1876 ** generated even if F() is called. 1877 ** 1878 ** To make matters worse, if this function calls an undefined 1879 ** global scope function with __attribute__((__error__)), the 1880 ** compiler retains that call even when F() is never called. That 1881 ** does not produce a compile error, but does cause a link error 1882 ** when the intentionally undefined global function is not found. 1883 **/ 1884 __attribute__((__noinline__)) 1885 static void F() 1886 { 1887 /* If the function body is empty, noinline is not sufficient 1888 ** to prevent the compiler from skipping the error message. 1889 ** Use a no-op asm() with clobber statements to discourage 1890 ** gcc from inlining F(). This is only called if the build 1891 ** is supposed to fail, so the clobber has no performance 1892 ** consequences. 1893 **/ 1894 __asm__ __volatile__("" ::: "memory", "cc"); 1895 } 1896 }; 1897 DXX_ALWAYS_ERROR_FUNCTION::F(); 1898} DXX_END_COMPOUND_STATEMENT ) 1899''') 1900 ): 1901 ''' 1902Rebirth defines a macro DXX_ALWAYS_ERROR_FUNCTION that, when expanded, 1903results in a call to an undefined function. If the optimizer cannot 1904prove the call to be unreachable, the build fails. This is used to 1905diagnose certain types of always-incorrect code, such as accessing 1906elements beyond the end of an array. 1907 1908Functions declared in local scope make the name available until the end 1909of the containing block. The name is treated as if it was declared in 1910the same scope as the function itself, so a name declared inside a 1911function in an anonymous namespace is also considered to be in the 1912anonymous namespace. 1913 1914Starting in gcc-6, declaring a function while in local scope in an 1915anonymous namespace triggers a compiler diagnostic about "used but never 1916defined" even if the optimizer proves the call to be unreachable. 1917Proving the call to be unreachable would cause the function not to be 1918used. Failing to prove it to be unreachable would lead to the 1919__attribute__((__error__)) marker triggering an error message from the 1920compiler. Before gcc-6, the compiler permitted such declarations 1921provided that the optimizer proved the function was never called. The 1922compiler never permitted such declarations when the optimizer failed to 1923prove the function was never called. 1924 1925This SConf test checks whether the compiler warns when that construct is 1926used. If the compiler does not warn, a simple definition of 1927DXX_ALWAYS_ERROR_FUNCTION is used. If the compiler warns, a complicated 1928and fragile definition of DXX_ALWAYS_ERROR_FUNCTION is used. 1929As stated in `import this`: 1930 1931 Simple is better than complex. 1932 Complex is better than complicated. 1933 1934Therefore, we prefer the simple form whenever the compiler allows it, 1935even though the complicated form works for both old and new compilers. 1936''' 1937 context.sconf.Define('DXX_ALWAYS_ERROR_FUNCTION(F,S)', 1938 _macro_value_simple if self.Compile(context, text=''' 1939namespace { 1940void f() 1941{ 1942 (void)("i"[0] == 's' && 1943 ( 1944 ( 1945 { 1946 void e() __attribute_error(""); 1947 e(); 1948 } 1949 ), 0 1950 ) 1951 ); 1952} 1953} 1954''', main='f();', msg='whether compiler allows dead calls to undefined functions in the anonymous namespace') \ 1955 else _macro_value_complicated 1956 , ''' 1957Declare a function named F and immediately call it. If gcc's 1958__attribute__((__error__)) is supported, __attribute_error will expand 1959to use __attribute__((__error__)) with the explanatory string S, causing 1960it to be a compilation error if this expression is not optimized out. 1961 1962Use this macro to implement static assertions that depend on values that 1963are known to the optimizer, but are not considered "compile time 1964constant expressions" for the purpose of the static_assert intrinsic. 1965 1966C++11 deleted functions cannot be used here because the compiler raises 1967an error for the call before the optimizer has an opportunity to delete 1968the call via a dead code elimination pass. 1969''') 1970 1971 @_custom_test 1972 def check_attribute_always_inline(self,context): 1973 """ 1974help:assume compiler supports __attribute__((always_inline)) 1975""" 1976 macro_name = '__attribute_always_inline()' 1977 macro_value = '__attribute__((__always_inline__))' 1978 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test='%s static inline void a(){}' % macro_name, main='a();', msg='for function __attribute__((always_inline))') 1979 1980 @_custom_test 1981 def check_attribute_alloc_size(self,context): 1982 """ 1983help:assume compiler supports __attribute__((alloc_size)) 1984""" 1985 macro_name = '__attribute_alloc_size(A,...)' 1986 macro_value = '__attribute__((alloc_size(A, ## __VA_ARGS__)))' 1987 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 1988char*a(int)__attribute_alloc_size(1); 1989char*b(int,int)__attribute_alloc_size(1,2); 1990""", msg='for function __attribute__((alloc_size))') 1991 @_custom_test 1992 def check_attribute_cold(self,context): 1993 """ 1994Test whether the compiler accepts gcc's function attribute 1995[__attribute__((cold))][1]. Use this to annotate functions which are 1996rarely called, such as error reporting functions. 1997 1998[1]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bcold_007d-function-attribute-3090 1999 2000help:assume compiler supports __attribute__((cold)) 2001""" 2002 macro_name = '__attribute_cold' 2003 macro_value = '__attribute__((cold))' 2004 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 2005__attribute_cold char*a(int); 2006""", msg='for function __attribute__((cold))') 2007 @_custom_test 2008 def check_attribute_format_arg(self,context): 2009 """ 2010help:assume compiler supports __attribute__((format_arg)) 2011""" 2012 macro_name = '__attribute_format_arg(A)' 2013 macro_value = '__attribute__((format_arg(A)))' 2014 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 2015char*a(char*)__attribute_format_arg(1); 2016""", msg='for function __attribute__((format_arg))') 2017 @_custom_test 2018 def check_attribute_format_printf(self,context): 2019 """ 2020help:assume compiler supports __attribute__((format(printf))) 2021""" 2022 macro_name = '__attribute_format_printf(A,B)' 2023 macro_value = '__attribute__((format(printf,A,B)))' 2024 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 2025int a(char*,...)__attribute_format_printf(1,2); 2026int b(char*)__attribute_format_printf(1,0); 2027""", msg='for function __attribute__((format(printf)))') 2028 @_custom_test 2029 def check_attribute_malloc(self,context): 2030 """ 2031help:assume compiler supports __attribute__((malloc)) 2032""" 2033 macro_name = '__attribute_malloc()' 2034 macro_value = '__attribute__((malloc))' 2035 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 2036int *a()__attribute_malloc(); 2037""", msg='for function __attribute__((malloc))') 2038 @_custom_test 2039 def check_attribute_nonnull(self,context): 2040 """ 2041help:assume compiler supports __attribute__((nonnull)) 2042""" 2043 macro_name = '__attribute_nonnull(...)' 2044 macro_value = '__attribute__((nonnull __VA_ARGS__))' 2045 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 2046int a(int*)__attribute_nonnull(); 2047int b(int*)__attribute_nonnull((1)); 2048""", msg='for function __attribute__((nonnull))') 2049 2050 @_custom_test 2051 def check_attribute_used(self,context): 2052 """ 2053help:assume compiler supports __attribute__((used)) 2054""" 2055 macro_name = '__attribute_used' 2056 macro_value = '__attribute__((used))' 2057 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 2058static void a()__attribute_used; 2059static void a(){} 2060""", msg='for function __attribute__((used))') 2061 @_custom_test 2062 def check_attribute_unused(self,context): 2063 """ 2064help:assume compiler supports __attribute__((unused)) 2065""" 2066 macro_name = '__attribute_unused' 2067 macro_value = '__attribute__((unused))' 2068 self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=""" 2069__attribute_unused 2070static void a(){} 2071""", msg='for function __attribute__((unused))', successflags={'CXXFLAGS' : [get_Werror_string(context.env['CXXFLAGS']) + 'unused']}) 2072 2073 @_custom_test 2074 def check_attribute_warning(self,context,_check_function_dce_attribute=_check_function_dce_attribute): 2075 _check_function_dce_attribute(self, context, 'warning') 2076 2077 @_custom_test 2078 def check_cxx11_static_assert(self,context,_f=''' 2079static_assert(%(expr)s, "global"); 2080struct A 2081{ 2082 static const bool value = %(expr)s; 2083 static_assert(%(expr)s, "class literal"); 2084 static_assert(A::value, "class static"); 2085 A() 2086 { 2087 static_assert(%(expr)s, "constructor literal"); 2088 static_assert(value, "constructor static"); 2089 } 2090}; 2091template <typename> 2092struct B 2093{ 2094 static const bool value = %(expr)s; 2095 static_assert(%(expr)s, "template class literal"); 2096 static_assert(value, "template class static"); 2097 B(A a) 2098 { 2099 static_assert(%(expr)s, "constructor literal"); 2100 static_assert(value, "constructor self static"); 2101 static_assert(A::value, "constructor static"); 2102 static_assert(a.value, "constructor member"); 2103 } 2104 template <typename R> 2105 B(B<R> &&) 2106 { 2107 static_assert(%(expr)s, "template constructor literal"); 2108 static_assert(value, "template constructor self static"); 2109 static_assert(B<R>::value, "template constructor static"); 2110 } 2111}; 2112template <typename T> 2113static void f(B<T> b) 2114{ 2115 static_assert(%(expr)s, "template function literal"); 2116 static_assert(B<T>::value, "template function static"); 2117 static_assert(b.value, "template function member"); 2118} 2119void f(A a); 2120void f(A a) 2121{ 2122 static_assert(%(expr)s, "function literal"); 2123 static_assert(A::value, "function static"); 2124 static_assert(a.value, "function member"); 2125 f(B<long>(B<int>(a))); 2126} 2127''' 2128 ,_msg='for C++11 intrinsic static_assert when %s',_tdict={'expr' : 'true&&true'},_fdict={'expr' : 'false||false'}): 2129 """ 2130help:assume compiler supports C++ intrinsic static_assert 2131""" 2132 _Compile = self.Compile 2133 if not (_Compile(context, text=_f % _tdict, main='f(A());', msg=_msg % 'true') and \ 2134 _Compile(context, text=_f % _fdict, main='f(A());', msg=_msg % 'false', expect_failure=True)): 2135 raise SCons.Errors.StopError('C++ compiler does not support tested versions of C++11 static_assert.') 2136 2137 @_custom_test 2138 def check_namespace_disambiguate(self,context,_successflags={'CPPDEFINES' : ['DXX_HAVE_CXX_DISAMBIGUATE_USING_NAMESPACE']}): 2139 self.Compile(context, text=''' 2140namespace A 2141{ 2142 int a; 2143} 2144using namespace A; 2145namespace B 2146{ 2147 class A; 2148} 2149using namespace B; 2150''', main='return A::a;', msg='whether compiler handles classes from "using namespace"', successflags=_successflags) 2151 2152 @_custom_test 2153 def check_cxx_std_required_features(self,context,_features=__cxx_std_required_features): 2154 # First test all the features at once. If all work, then done. 2155 # If any fail, then the configure run will stop. 2156 _Compile = self.Compile 2157 if _Compile(context, text=_features.text, main=_features.main, msg='for required C++11, C++14, C++17 standard features'): 2158 return 2159 # Some failed. Run each test separately and report to the user 2160 # which ones failed. 2161 failures = [f.name for f in _features.features if not _Compile(context, text=f.text, main=f.main, msg='for C++%u %s' % (f.std, f.name))] 2162 raise SCons.Errors.StopError(("C++ compiler does not support %s." % 2163 ', '.join(failures) 2164 ) if failures else 'C++ compiler supports each feature individually, but not all of them together. Please report this as a bug in the Rebirth configure script.') 2165 2166 def _show_pch_count_message(self,context,which,user_setting): 2167 count = user_setting if user_setting else 0 2168 context.Display('%s: checking when to pre-compile %s headers...%s\n' % (self.msgprefix, which, ('if used at least %u time%s' % (count, 's' if count > 1 else '')) if count > 0 else 'never')) 2169 return count > 0 2170 2171 implicit_tests.append(_implicit_test.RecordedTest('check_pch_compile', "assume C++ compiler can create pre-compiled headers")) 2172 implicit_tests.append(_implicit_test.RecordedTest('check_pch_use', "assume C++ compiler can use pre-compiled headers")) 2173 2174 @_custom_test 2175 def _check_pch(self,context, 2176 _Test=_Test, 2177 _testflags_compile_pch={'CXXFLAGS' : ['-x', 'c++-header']}, 2178 _testflags_use_pch={'CXXFLAGS' : ['-Winvalid-pch', '-include', None]} 2179 ): 2180 self.pch_flags = None 2181 # Always evaluate both 2182 co = self._show_pch_count_message(context, 'own', self.user_settings.pch) 2183 cs = self._show_pch_count_message(context, 'system', self.user_settings.syspch) 2184 context.did_show_result = True 2185 if not co and not cs: 2186 return 2187 context.Display('%s: checking when to compute pre-compiled header input *pch.cpp...%s\n' % (self.msgprefix, 'if missing' if self.user_settings.pch_cpp_assume_unchanged else 'always')) 2188 result = _Test(self, context, action=self.PCHAction(context), text='', msg='whether compiler can create pre-compiled headers', testflags=_testflags_compile_pch, calling_function='pch_compile') 2189 if not result: 2190 raise SCons.Errors.StopError("C++ compiler cannot create pre-compiled headers.") 2191 _testflags_use_pch = _testflags_use_pch.copy() 2192 _testflags_use_pch['CXXFLAGS'][-1] = str(context.lastTarget)[:-4] 2193 result = self.Compile(context, text=''' 2194/* This symbol is defined in the PCH. If the PCH is included, this 2195 * symbol will preprocess away to nothing. If the PCH is not included, 2196 * then the compiler is not using PCHs as expected. 2197 */ 2198dxx_compiler_supports_pch 2199''', msg='whether compiler uses pre-compiled headers', testflags=_testflags_use_pch, calling_function='pch_use') 2200 if not result: 2201 raise SCons.Errors.StopError("C++ compiler cannot use pre-compiled headers.") 2202 self.pch_flags = _testflags_compile_pch 2203 @_custom_test 2204 def _check_cxx11_explicit_delete(self,context): 2205 # clang 3.4 warns when a named parameter to a deleted function 2206 # is not used, even though there is no body in which it could be 2207 # used, so every named parameter to a deleted function is always 2208 # unused. 2209 f = 'int a(int %s)=delete;' 2210 if self.check_cxx11_explicit_delete_named(context, f): 2211 # No bug: named parameters with explicitly deleted functions 2212 # work correctly. 2213 return 2214 if self.check_cxx11_explicit_delete_named_unused(context, f): 2215 # Clang bug hit. Called function adds -Wno-unused-parameter 2216 # to work around the bug, but affected users will not get 2217 # warnings about parameters that are unused in regular 2218 # functions. 2219 return 2220 raise SCons.Errors.StopError( 2221 "C++ compiler rejects explicitly deleted functions with named parameters, even with -Wno-unused-parameter." \ 2222 if self.check_cxx11_explicit_delete_anonymous(context, f) else \ 2223 "C++ compiler does not support explicitly deleted functions." 2224 ) 2225 @_implicit_test 2226 def check_cxx11_explicit_delete_named(self,context,f): 2227 """ 2228help:assume compiler supports explicitly deleted functions with named parameters 2229""" 2230 return self.Compile(context, text=f % 'b', msg='for explicitly deleted functions with named parameters') 2231 @_implicit_test 2232 def check_cxx11_explicit_delete_named_unused(self,context,f,_successflags={'CXXFLAGS' : ['-Wno-unused-parameter']}): 2233 """ 2234help:assume compiler supports explicitly deleted functions with named parameters with -Wno-unused-parameter 2235""" 2236 return self.Compile(context, text=f % 'b', msg='for explicitly deleted functions with named parameters and -Wno-unused-parameter', successflags=_successflags) 2237 @_implicit_test 2238 def check_cxx11_explicit_delete_anonymous(self,context,f): 2239 """ 2240help:assume compiler supports explicitly deleted functions with anonymous parameters 2241""" 2242 return self.Compile(context, text=f % '', msg='for explicitly deleted functions with anonymous parameters') 2243 2244 @_implicit_test 2245 def check_cxx11_inherit_constructor(self,context,text,_macro_value=_quote_macro_value(''' 2246/* Use a typedef for the base type to avoid parsing issues when type 2247 * B is a qualified name. Without this typedef, B = std::BASE would 2248 * expand as `using std::BASE::std::BASE`, which causes a parsing error. 2249 * The correct way to inherit from std::BASE is `using std::BASE::BASE;`. 2250 * With using dxx_constructor_base_type = std::BASE;`, 2251 * `using dxx_constructor_base_type::dxx_constructor_base_type` produces 2252 * the correct result. 2253 */ 2254 using dxx_constructor_base_type = B,##__VA_ARGS__; 2255 using dxx_constructor_base_type::dxx_constructor_base_type;'''), 2256 **kwargs): 2257 """ 2258help:assume compiler supports inheriting constructors 2259""" 2260 blacklist_clang_libcxx = ''' 2261/* Test for bug where clang + libc++ + constructor inheritance causes a 2262 * compilation failure when returning nullptr. 2263 * 2264 * Works: gcc 2265 * Works: clang + gcc libstdc++ 2266 * Works: old clang + old libc++ (cutoff date unknown). 2267 * Works: new clang + new libc++ + unique_ptr<T> 2268 * Fails: new clang + new libc++ + unique_ptr<T[]> (v3.6.0 confirmed broken). 2269 2270memory:2676:32: error: no type named 'type' in 'std::__1::enable_if<false, std::__1::unique_ptr<int [], std::__1::default_delete<int []> >::__nat>'; 'enable_if' cannot be used to disable this declaration 2271 typename enable_if<__same_or_less_cv_qualified<_Pp, pointer>::value, __nat>::type = __nat()) _NOEXCEPT 2272 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2273.sconf_temp/conftest_43.cpp:26:11: note: in instantiation of member function 'std::__1::unique_ptr<int [], std::__1::default_delete<int []> >::unique_ptr' requested here 2274 using B::B; 2275 ^ 2276.sconf_temp/conftest_43.cpp:30:2: note: while substituting deduced template arguments into function template 'I' [with _Pp = I] 2277 return nullptr; 2278 ^ 2279 */ 2280#include <memory> 2281class I : std::unique_ptr<int[]> 2282{ 2283public: 2284 typedef std::unique_ptr<int[]> B; 2285 using B::B; 2286}; 2287I a(); 2288I a() 2289{ 2290 return nullptr; 2291} 2292''' 2293 return _macro_value \ 2294 if self.Compile(context, text=text.format(leading_text=blacklist_clang_libcxx, macro_value=_macro_value), msg='for C++11 inherited constructors with good unique_ptr<T[]> support', **kwargs) \ 2295 else None 2296 2297 @_implicit_test 2298 def check_cxx11_variadic_forward_constructor(self,context,text,_macro_value=_quote_macro_value(''' 2299 template <typename... Args> 2300 constexpr D(Args&&... args) : 2301 B,##__VA_ARGS__(std::forward<Args>(args)...) {} 2302'''),**kwargs): 2303 """ 2304help:assume compiler supports variadic template-based constructor forwarding 2305""" 2306 return _macro_value \ 2307 if self.Compile(context, text=text.format(leading_text='#include <algorithm>\n', macro_value=_macro_value), msg='for C++11 variadic templates on constructors', **kwargs) \ 2308 else None 2309 2310 @_custom_test 2311 def _check_forward_constructor(self,context,_text=''' 2312{leading_text} 2313#define DXX_INHERIT_CONSTRUCTORS(D,B,...) {macro_value} 2314struct A {{ 2315 A(int){{}} 2316}}; 2317struct B:A {{ 2318DXX_INHERIT_CONSTRUCTORS(B,A); 2319}}; 2320''', 2321 _macro_define='DXX_INHERIT_CONSTRUCTORS(D,B,...)', 2322 _methods=(check_cxx11_inherit_constructor, check_cxx11_variadic_forward_constructor) 2323): 2324 for f in _methods: 2325 macro_value = f(self, context, text=_text, main='B(0)') 2326 if macro_value: 2327 context.sconf.Define(_macro_define, macro_value, ''' 2328Declare that derived type D inherits applicable constructors from base 2329type B. Use a variadic macro with the base type second so that types 2330such as std::pair<int,int> pass through correctly without the need to 2331parenthesize them. 2332''') 2333 return 2334 raise SCons.Errors.StopError("C++ compiler does not support constructor forwarding.") 2335 @_custom_test 2336 def check_deep_tuple(self,context): 2337 text = ''' 2338#include <tuple> 2339static inline std::tuple<{type}> make() {{ 2340 return std::make_tuple({value}); 2341}} 2342static void a(){{ 2343 std::tuple<{type}> t = make(); 2344 (void)t; 2345}} 2346''' 2347 count = 20 2348 Compile = self.Compile 2349 if Compile(context, text=text.format(type=','.join(('int',)*count), value=','.join(('0',)*count)), main='a()', msg='whether compiler handles 20-element tuples'): 2350 return 2351 count = 2 2352 raise SCons.Errors.StopError( 2353 "Compiler cannot handle tuples of 20 elements. Raise the template instantiation depth." \ 2354 if Compile(context, text=text.format(type=','.join(('int',)*count), value=','.join(('0',)*count)), main='a()', msg='whether compiler handles 2-element tuples') \ 2355 else "Compiler cannot handle tuples of 2 elements." 2356 ) 2357 2358 @_implicit_test 2359 def check_poison_valgrind(self,context): 2360 ''' 2361help:add Valgrind annotations; wipe certain freed memory when running under Valgrind 2362''' 2363 context.Message('%s: checking %s...' % (self.msgprefix, 'whether to use Valgrind poisoning')) 2364 r = 'valgrind' in self.user_settings.poison 2365 context.Result(r) 2366 self._define_macro(context, 'DXX_HAVE_POISON_VALGRIND', int(r)) 2367 if not r: 2368 return 2369 text = ''' 2370#define DXX_HAVE_POISON 1 2371#include "compiler-poison.h" 2372''' 2373 main = ''' 2374 DXX_MAKE_MEM_UNDEFINED(&argc, sizeof(argc)); 2375''' 2376 if self.Compile(context, text=text, main=main, msg='whether Valgrind memcheck header works'): 2377 return True 2378 raise SCons.Errors.StopError("Valgrind poison requested, but <valgrind/memcheck.h> does not work.") 2379 @_implicit_test 2380 def check_poison_overwrite(self,context): 2381 ''' 2382help:always wipe certain freed memory 2383''' 2384 context.Message('%s: checking %s...' % (self.msgprefix, 'whether to use overwrite poisoning')) 2385 r = 'overwrite' in self.user_settings.poison 2386 context.Result(r) 2387 self._define_macro(context, 'DXX_HAVE_POISON_OVERWRITE', int(r)) 2388 return r 2389 @_custom_test 2390 def _check_poison_method(self,context, 2391 _methods=(check_poison_overwrite, check_poison_valgrind), 2392 poison=0 2393 ): 2394 # Always run both checks. The user may want a program that 2395 # always uses overwrite poisoning and, when running under 2396 # Valgrind, marks the memory as undefined. 2397 for f in _methods: 2398 if f(self, context): 2399 poison = 1 2400 self._define_macro(context, 'DXX_HAVE_POISON', poison) 2401 implicit_tests.append(_implicit_test.RecordedTest('check_size_type_size', "assume size_t is formatted as `size_t`")) 2402 implicit_tests.append(_implicit_test.RecordedTest('check_size_type_long', "assume size_t is formatted as `unsigned long`")) 2403 implicit_tests.append(_implicit_test.RecordedTest('check_size_type_int', "assume size_t is formatted as `unsigned int`")) 2404 implicit_tests.append(_implicit_test.RecordedTest('check_size_type_I64', "assume size_t is formatted as `unsigned I64`")) 2405 2406 @_custom_test 2407 def _check_size_type_format_modifier(self,context,_text=''' 2408#include <cstddef> 2409#define DXX_PRI_size_type %s 2410__attribute_format_printf(1, 2) 2411void f(const char *, ...); 2412void f(const char *, ...) 2413{ 2414} 2415''',_main=''' 2416 std::size_t s = 0; 2417 f("%" DXX_PRI_size_type, s); 2418'''): 2419 ''' 2420The test must declare a custom function to call with this format string. 2421gcc has hardcoded knowledge about how printf works, but that knowledge 2422on Mingw64 differs from the processing of 2423__attribute__((format(printf,...))). Mingw64 requires I64u for 2424__attribute__((format)) functions, but llu for printf. Mingw32 is 2425consistent in its use of u. Linux gcc is consistent in its use of 2426lu. 2427 2428-- cut -- 2429#include <cstdio> 2430#include <cstddef> 2431__attribute__((format(printf,1,2))) void f(const char *,...); 2432void a() { 2433 std::size_t b = 0; 2434 printf("%I64u", b); 2435 f("%I64u", b); 2436 printf("%llu", b); 2437 f("%llu", b); 2438 printf("%lu", b); 2439 f("%lu", b); 2440 printf("%u", b); 2441 f("%u", b); 2442} 2443-- cut -- 2444 2445$ x86_64-w64-mingw32-g++-5.4.0 -x c++ -S -Wformat -o /dev/null - 2446<stdin>: In function 'void a()': 2447<stdin>:6:19: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'std::size_t {aka long long unsigned int}' [-Wformat=] 2448<stdin>:9:13: warning: unknown conversion type character 'l' in format [-Wformat=] 2449<stdin>:9:13: warning: too many arguments for format [-Wformat-extra-args] 2450<stdin>:10:17: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'std::size_t {aka long long unsigned int}' [-Wformat=] 2451<stdin>:11:12: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'std::size_t {aka long long unsigned int}' [-Wformat=] 2452<stdin>:12:16: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'std::size_t {aka long long unsigned int}' [-Wformat=] 2453<stdin>:13:11: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'std::size_t {aka long long unsigned int}' [-Wformat=] 2454 2455$ i686-w64-mingw32-g++-5.4.0 -x c++ -S -Wformat -o /dev/null - 2456<stdin>: In function 'void a()': 2457<stdin>:7:14: warning: format '%I64u' expects argument of type 'long long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2458<stdin>:8:18: warning: format '%llu' expects argument of type 'long long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2459<stdin>:9:13: warning: unknown conversion type character 'l' in format [-Wformat=] 2460<stdin>:9:13: warning: too many arguments for format [-Wformat-extra-args] 2461<stdin>:10:17: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2462<stdin>:11:12: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2463 2464$ mingw32-g++-5.4.0 -x c++ -S -Wformat -o /dev/null - 2465<stdin>: In function 'void a()': 2466<stdin>:6:19: warning: format '%I64u' expects argument of type 'long long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2467<stdin>:7:14: warning: format '%I64u' expects argument of type 'long long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2468<stdin>:8:18: warning: unknown conversion type character 'l' in format [-Wformat=] 2469<stdin>:8:18: warning: too many arguments for format [-Wformat-extra-args] 2470<stdin>:9:13: warning: unknown conversion type character 'l' in format [-Wformat=] 2471<stdin>:9:13: warning: too many arguments for format [-Wformat-extra-args] 2472<stdin>:10:17: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2473<stdin>:11:12: warning: format '%lu' expects argument of type 'long unsigned int', but argument 2 has type 'std::size_t {aka unsigned int}' [-Wformat=] 2474 2475$ x86_64-pc-linux-gnu-g++-5.4.0 -x c++ -S -Wformat -o /dev/null - 2476<stdin>: In function 'void a()': 2477<stdin>:6:19: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'std::size_t {aka long unsigned int}' [-Wformat=] 2478<stdin>:7:14: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'std::size_t {aka long unsigned int}' [-Wformat=] 2479<stdin>:8:18: warning: format '%llu' expects argument of type 'long long unsigned int', but argument 2 has type 'std::size_t {aka long unsigned int}' [-Wformat=] 2480<stdin>:9:13: warning: format '%llu' expects argument of type 'long long unsigned int', but argument 2 has type 'std::size_t {aka long unsigned int}' [-Wformat=] 2481<stdin>:12:16: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'std::size_t {aka long unsigned int}' [-Wformat=] 2482<stdin>:13:11: warning: format '%u' expects argument of type 'unsigned int', but argument 2 has type 'std::size_t {aka long unsigned int}' [-Wformat=] 2483 2484''' 2485 # Test types in order of decreasing probability. 2486 for how in ( 2487 # Linux 2488 ('z', 'size_type_size'), 2489 ('l', 'size_type_long'), 2490 # Win64 2491 ('I64', 'size_type_I64'), 2492 # Win32 2493 ('', 'size_type_int'), 2494 ): 2495 DXX_PRI_size_type = how[0] 2496 f = '"%su"' % DXX_PRI_size_type 2497 if self.Compile(context, text=_text % f, main=_main, msg='whether to format std::size_t with "%%%su"' % DXX_PRI_size_type, calling_function=how[1]): 2498 context.sconf.Define('DXX_PRI_size_type', f) 2499 return 2500 raise SCons.Errors.StopError("C++ compiler rejects all candidate format strings for std::size_t.") 2501 implicit_tests.append(_implicit_test.RecordedTest('check_compiler_accepts_useless_cast', "assume compiler accepts -Wuseless-cast")) 2502 2503 @_custom_test 2504 def check_compiler_useless_cast(self,context): 2505 Compile = self.Compile 2506 flags = {'CXXFLAGS' : [get_Werror_string(context.env['CXXFLAGS']) + 'useless-cast']} 2507 if Compile(context, text=''' 2508/* 2509 * SDL on Raspbian provokes a warning from -Wuseless-cast 2510 * 2511 * Reported-by: derhass <https://github.com/dxx-rebirth/dxx-rebirth/issues/257> 2512 */ 2513#include <SDL_endian.h> 2514 2515/* 2516 * Recent gcc[1] create a useless cast when synthesizing constructor 2517 * inheritance, then warn the user about the compiler-generated cast. 2518 * Since the user did not write the cast in the source, the user 2519 * cannot remove the cast to eliminate the warning. 2520 * 2521 * The only way to avoid the problem is to avoid using constructor 2522 * inheritance in cases where the compiler would synthesize a useless 2523 * cast. 2524 * 2525 * Reported-by: zicodxx <https://github.com/dxx-rebirth/dxx-rebirth/issues/316> 2526 * gcc Bugzilla: <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70844> 2527 * 2528 * [1] gcc-6.x, gcc-7.x (all currently released versions) 2529 */ 2530class base 2531{ 2532public: 2533 base(int &) {} 2534}; 2535 2536class derived : public base 2537{ 2538public: 2539 using base::base; 2540}; 2541 2542''', main=''' 2543 derived d(argc); 2544 return SDL_Swap32(argc); 2545''', msg='whether compiler argument -Wuseless-cast works with SDL and with constructor inheritance', successflags=flags): 2546 return 2547 # <=clang-3.7 does not understand -Wuseless-cast 2548 # This test does not influence the compile environment, but is 2549 # run to distinguish in the output whether the failure is 2550 # because the compiler does not accept -Wuseless-cast or because 2551 # SDL's headers provoke a warning from -Wuseless-cast. 2552 Compile(context, text='', main='', msg='whether compiler accepts -Wuseless-cast', testflags=flags, calling_function='compiler_accepts_useless_cast') 2553 2554 @_custom_test 2555 def check_compiler_ptrdiff_cast_int(self,context): 2556 context.sconf.Define('DXX_ptrdiff_cast_int', 'static_cast<int>' 2557 if self.Compile(context, text=''' 2558#include <cstdio> 2559''', main=''' 2560 char a[8]; 2561 snprintf(a, sizeof(a), "%.*s", static_cast<int>(&argv[0][1] - &argv[0][0]), "0"); 2562''', msg='whether to cast operator-(T*,T*) to int') 2563 else '', 2564 ''' 2565For mingw32-gcc-5.4.0 and x86_64-pc-linux-gnu-gcc-5.4.0, gcc defines 2566`long operator-(T *, T *)`. 2567 2568For mingw32, gcc reports a useless cast converting `long` to `int`. 2569For x86_64-pc-linux-gnu, gcc does not report a useless cast converting 2570`long` to `int`. 2571 2572Various parts of the code take the difference of two pointers, then cast 2573it to `int` to be passed as a parameter to `snprintf` field width 2574conversion `.*`. The field width conversion is defined to take `int`, 2575so the cast is necessary to avoid a warning from `-Wformat` on 2576x86_64-pc-linux-gnu, but is not necessary on mingw32. However, the cast 2577causes a -Wuseless-cast warning on mingw32. Resolve these conflicting 2578requirements by defining a macro that expands to `static_cast<int>` on 2579platforms where the cast is required and expands to nothing on platforms 2580where the cast is useless. 2581''' 2582 ) 2583 2584 @_custom_test 2585 def check_strcasecmp_present(self,context,_successflags={'CPPDEFINES' : ['DXX_HAVE_STRCASECMP']}): 2586 main = ''' 2587 return !strcasecmp(argv[0], argv[0] + 1) && !strncasecmp(argv[0] + 1, argv[0], 1); 2588''' 2589 self.Compile(context, text='#include <cstring>', main=main, msg='for strcasecmp', successflags=_successflags) 2590 2591 @_custom_test 2592 def check_getaddrinfo_present(self,context,_successflags={'CPPDEFINES' : ['DXX_HAVE_GETADDRINFO']}): 2593 self.Compile(context, text=''' 2594#ifdef WIN32 2595#include <winsock2.h> 2596#include <ws2tcpip.h> 2597#else 2598#include <sys/types.h> 2599#include <sys/socket.h> 2600#include <netdb.h> 2601#endif 2602''', main=''' 2603 addrinfo *res = nullptr; 2604 addrinfo hints{}; 2605#ifdef AI_NUMERICSERV 2606 /* Test that it is defined to a term that can be used with bit-or */ 2607 hints.ai_flags |= AI_NUMERICSERV; 2608#endif 2609#if DXX_USE_IPv6 2610 hints.ai_flags |= AI_V4MAPPED | AI_ALL; 2611#endif 2612 int i = getaddrinfo("", "", &hints, &res); 2613 (void)i; 2614 freeaddrinfo(res); 2615 return 0; 2616''', msg='for getaddrinfo', successflags=_successflags) 2617 2618 @_guarded_test_windows 2619 def check_inet_ntop_present(self,context,_successflags={'CPPDEFINES' : ['DXX_HAVE_INET_NTOP']}): 2620 # Linux and OS X have working inet_ntop on all supported 2621 # platforms. Only Windows sometimes lacks support for this 2622 # function. 2623 if self.Compile(context, text=''' 2624#include <winsock2.h> 2625#include <ws2tcpip.h> 2626''', main=''' 2627 struct sockaddr_in sai; 2628 char dbuf[64]; 2629 return inet_ntop(AF_INET, &sai.sin_addr, dbuf, sizeof(dbuf)) ? 0 : 1; 2630''', msg='for inet_ntop', successflags=_successflags): 2631 return 2632 if self.user_settings.ipv6: 2633 raise SCons.Errors.StopError("IPv6 enabled and inet_ntop not available: disable IPv6 or upgrade headers to support inet_ntop.") 2634 2635 @_custom_test 2636 def check_timespec_present(self,context,_successflags={'CPPDEFINES' : ['DXX_HAVE_STRUCT_TIMESPEC']}): 2637 self.Compile(context, text=''' 2638#include <time.h> 2639''', main=''' 2640 struct timespec ts; 2641 (void)ts; 2642 return 0; 2643''', msg='for struct timespec', successflags=_successflags) 2644 2645 @_implicit_test 2646 def check_warn_implicit_fallthrough(self,context,text,main,testflags,_successflags={'CXXFLAGS' : ['-Wimplicit-fallthrough=5']}): 2647 self.Compile(context, text=text, main=main, msg='for -Wimplicit-fallthrough=5', testflags=testflags, successflags=_successflags) 2648 2649 @_implicit_test 2650 def check_no_warn_implicit_fallthrough(self,context,_successflags={'CXXFLAGS' : ['-Wno-implicit-fallthrough']}): 2651 self.Compile(context, text='', main='', msg='for -Wno-implicit-fallthrough', successflags=_successflags) 2652 2653 @_custom_test 2654 def check_boost_config(self,context,_successflags={'CPPDEFINES' : [('DXX_BOOST_FALLTHROUGH', 'BOOST_FALLTHROUGH')]}): 2655 text =''' 2656#include <boost/config.hpp> 2657''' 2658 main = ''' 2659 int a = 1; 2660 switch (argc) { 2661 case 1: 2662 ++ a; 2663 DXX_BOOST_FALLTHROUGH; 2664 case 2: 2665 ++ a; 2666 break; 2667 } 2668 (void)a; 2669''' 2670 sconf = context.sconf 2671 if not self.Compile(context, text=text, main=main, msg='for Boost.Config', successflags=_successflags): 2672 sconf.Define('DXX_BOOST_FALLTHROUGH', '((void)0)') 2673 self.check_no_warn_implicit_fallthrough(context) 2674 return 2675 else: 2676 sconf.config_h_text += ''' 2677#ifndef DXX_SCONF_NO_INCLUDES 2678/* For BOOST_FALLTHROUGH */ 2679#include <boost/config.hpp> 2680#endif 2681''' 2682 self.check_warn_implicit_fallthrough(context, text, main, testflags=_successflags) 2683 2684 @_custom_test 2685 def check_compiler_overzealous_unused_lambda_capture(self,context): 2686 ''' 2687<clang-5: untested 2688>clang-8: untested 2689>=clang-5 && <=clang-8: when -Wunused is passed, clang will warn when a lambda captures by reference a local variable that is itself a reference, regardless of whether the lambda body uses the captured value. The warning is: 2690 2691``` 2692similar/main/object.cpp:1060:13: error: lambda capture 'vmobjptr' is not required to be captured for this use [-Werror,-Wunused-lambda-capture] 2693 auto l = [&vmobjptr, &r, &num_to_free](bool (*predicate)(const vcobjptr_t)) -> bool { 2694``` 2695 2696<gcc-8 require such variables to be captured and will fail the build if the capture is removed. 2697>=gcc-8 permit the variable not to be captured, and will not warn regardless of whether it is captured. 2698 2699Test whether the compiler warns for this case and, if it does, tell it 2700not to warn. Once support is removed for gcc versions which require the 2701capture, this test can be dropped and the warning reinstated. 2702 2703If clang learns to treat -Wunused-lambda-capture (by way of -Wunused) as 2704a request to warn only about captures that are _actually_ unused, rather 2705than also those that are used but not required, this test will stop 2706disabling the warning on clang. Since this quirk was discussed when the 2707warning was added[1], and the warning was added in its current form 2708anyway, it seems unlikely this will be fixed soon. 2709 2710[1]: https://marc.info/?l=cfe-commits&m=148494796318826&w=2 2711|: https://marc.info/?l=cfe-commits&m=148509467111194&w=2 2712|: https://marc.info/?l=cfe-commits&m=148520882106501&w=2 2713 ''' 2714 if not self.Compile(context, text=''' 2715int a; 2716''', main=''' 2717 auto &b = a; 2718 [&b]() { 2719 ++b; 2720 }(); 2721''', msg='whether compiler allows lambda capture of local references'): 2722 self.successful_flags['CXXFLAGS'].append('-Wno-unused-lambda-capture') 2723 2724 @_custom_test 2725 def check_compiler_overzealous_warn_extension_string_literal_operator_template(self,context): 2726 ''' 2727String literal operator templates are a GNU extension. GCC accepts them 2728without complaint in mode `-std=gnu++14` (or later). Clang warns that 2729the template is a GNU extension, even when given `-std=gnu++14`, which 2730requests C++14 with GNU extensions. Since warnings are normally treated 2731as errors, this breaks the build. If the warning is suppressed, Clang 2732can compile the file correctly. 2733 2734Test for whether the compiler will accept a string literal operator 2735template with the default options and, if not, add the clang option to 2736suppress the warning. 2737 ''' 2738 if not self.Compile(context, text=''' 2739template <typename T, T... v> 2740struct literal_as_type {}; 2741 2742template <typename T, T... v> 2743constexpr literal_as_type<T, v...> operator""_literal_as_type(); 2744''', main=''' 2745 auto b = decltype("a"_literal_as_type){}; 2746 (void)b; 2747''', msg='whether compiler accepts string literal operator templates'): 2748 self.successful_flags['CXXFLAGS'].append('-Wno-gnu-string-literal-operator-template') 2749 2750 __preferred_compiler_options = ( 2751 '-fvisibility=hidden', 2752 '-Wduplicated-branches', 2753 '-Wduplicated-cond', 2754 '-Wsuggest-attribute=noreturn', 2755 '-Wlogical-op', 2756 '-Wold-style-cast', 2757 '-Wredundant-decls', 2758 ) 2759 __preferred_win32_linker_options = ( 2760 '-Wl,--large-address-aware', 2761 '-Wl,--dynamicbase', 2762 '-Wl,--nxcompat', 2763 ) 2764 # No build directives can modify the process environment, so 2765 # modifying this at class scope is safe. Either every build will 2766 # have $SOURCE_DATE_EPOCH set or every build will have it clear. 2767 if os.getenv('SOURCE_DATE_EPOCH') is None: 2768 __preferred_win32_linker_options += ('-Wl,--insert-timestamp',) 2769 def __mangle_compiler_option_name(opt): 2770 return 'check_compiler_option%s' % opt.replace('-', '_').replace('=', '_') 2771 def __mangle_linker_option_name(opt): 2772 return 'check_linker_option%s' % opt.replace('-', '_').replace(',', '_') 2773 @_custom_test 2774 def check_preferred_compiler_options(self,context, 2775 ccopts=list(__preferred_compiler_options), 2776 ldopts=(), 2777 _text=''' 2778/* clang on OS X incorrectly diagnoses mistakes in Apple system headers 2779 * even when -Wsystem-headers is not specified. Include one known buggy 2780 * header here, so that the compilation will fail and the compiler will be 2781 * marked as not supporting -Wold-style-cast. 2782 */ 2783#if defined(__APPLE__) && defined(__MACH__) 2784#include <ApplicationServices/ApplicationServices.h> 2785#endif 2786 2787#include <SDL.h> 2788 2789/* gcc's warning -Wduplicated-branches was initially overzealous and 2790 * warned if the branches were identical after expanding template 2791 * parameters. This was documented in gcc bug #82541, which was fixed 2792 * for gcc-8.x. As of this writing, the fix has not been backported to 2793 * gcc-7.x. 2794 * 2795 * Work around this unwanted quirk by including code which will provoke 2796 * a -Wduplicated-branches warning in affected versions, but not in 2797 * fixed versions, so that the configure stage blacklists the warning on 2798 * affected versions. 2799 */ 2800 2801namespace gcc_pr82541 { 2802 2803template <unsigned U1, unsigned U2> 2804unsigned u(bool b) 2805{ 2806 return b ? U1 : U2; 2807} 2808 2809unsigned u2(bool b); 2810unsigned u2(bool b) 2811{ 2812 return u<1, 1>(b); 2813} 2814 2815} 2816''', 2817 _mangle_compiler_option_name=__mangle_compiler_option_name, 2818 _mangle_linker_option_name=__mangle_linker_option_name 2819 ): 2820 if self.user_settings.host_platform == 'win32': 2821 ldopts = self.__preferred_win32_linker_options 2822 Compile = self.Compile 2823 Link = self.Link 2824 f, desc = (Link, 'linker') if ldopts else (Compile, 'compiler') 2825 if f(context, text=_text, main=''' 2826 using gcc_pr82541::u2; 2827 u2(false); 2828 u2(true); 2829 u2(argc > 2); 2830''', msg='whether %s accepts preferred options' % desc, successflags={'CXXFLAGS' : ccopts, 'LINKFLAGS' : ldopts}, calling_function='preferred_%s_options' % desc): 2831 # Everything is supported. Skip individual tests. 2832 return 2833 # Compiler+linker together failed. Check if compiler alone will work. 2834 if f is Compile or not Compile(context, text=_text, main='', msg='whether compiler accepts preferred options', successflags={'CXXFLAGS' : ccopts}): 2835 # Compiler alone failed. 2836 # Run down the individual compiler options to find any that work. 2837 for opt in ccopts: 2838 Compile(context, text=_text, main='', msg='whether compiler accepts option %s' % opt, successflags={'CXXFLAGS' : (opt,)}, calling_function=_mangle_compiler_option_name(opt)[6:]) 2839 # Run down the individual linker options to find any that work. 2840 for opt in ldopts: 2841 Link(context, text=_text, main='', msg='whether linker accepts option %s' % opt, successflags={'LINKFLAGS' : (opt,)}, calling_function=_mangle_linker_option_name(opt)[6:]) 2842 @classmethod 2843 def register_preferred_compiler_options(cls, 2844 ccopts = ('-Wextra',) + __preferred_compiler_options, 2845 # Always register target-specific tests on the class. Individual 2846 # targets will decide whether to run the tests. 2847 ldopts = __preferred_win32_linker_options, 2848 RecordedTest = Collector.RecordedTest, 2849 record = implicit_tests.append, 2850 _mangle_compiler_option_name=__mangle_compiler_option_name, 2851 _mangle_linker_option_name=__mangle_linker_option_name 2852 ): 2853 del cls.register_preferred_compiler_options 2854 record(RecordedTest('check_preferred_linker_options', 'assume linker accepts preferred options')) 2855 mangle = _mangle_compiler_option_name 2856 for opt in ccopts: 2857 record(RecordedTest(mangle(opt), 'assume compiler accepts %s' % opt)) 2858 mangle = _mangle_linker_option_name 2859 for opt in ldopts: 2860 record(RecordedTest(mangle(opt), 'assume linker accepts %s' % opt)) 2861 assert cls.custom_tests[0].name == cls.check_cxx_works.__name__, cls.custom_tests[0].name 2862 assert cls.custom_tests[-1].name == cls._cleanup_configure_test_state.__name__, cls.custom_tests[-1].name 2863 2864 @_implicit_test 2865 def check_boost_test(self,context): 2866 self._check_system_library(context, header=('boost/test/unit_test.hpp',), pretext=''' 2867#define BOOST_TEST_DYN_LINK 2868#define BOOST_TEST_MODULE Rebirth 2869''', main=None, lib='Boost.Test', text=''' 2870BOOST_AUTO_TEST_CASE(f) 2871{ 2872 BOOST_TEST(true); 2873} 2874''', testflags={'LIBS' : ['boost_unit_test_framework']}) 2875 2876 @_custom_test 2877 def _check_boost_test_required(self,context): 2878 register_runtime_test_link_targets = self.user_settings.register_runtime_test_link_targets 2879 context.Display('%s: checking whether to build runtime tests...' % self.msgprefix) 2880 context.Result(register_runtime_test_link_targets) 2881 if register_runtime_test_link_targets: 2882 self.check_boost_test(context) 2883 2884 # dylibbundler is used to embed libraries into macOS app bundles, 2885 # however it's only meaningful for macOS builds, and, for those, 2886 # only required when frameworks are not used for the build. Builds 2887 # should not fail on other operating system targets if it's absent. 2888 @GuardedCollector(_guarded_test_darwin, lambda user_settings: user_settings.macos_bundle_libs and not user_settings.macos_add_frameworks) 2889 def _check_dylibbundler(self, context, _common_error_text='; dylibbundler is required for compilation for a macOS target when not using frameworks and bundling libraries. Set macos_bundle_libs=False or macos_add_frameworks=True, or install dylibbundler.'): 2890 context.Display('%s: checking whether dylibbundler is installed and accepts -h...' % self.msgprefix) 2891 try: 2892 p = StaticSubprocess.pcall(('dylibbundler', '-h'), stderr=subprocess.PIPE) 2893 except FileNotFoundError as e: 2894 context.Result('no; %s' % (e,)) 2895 raise SCons.Errors.StopError('dylibbundler not found%s' % (_common_error_text,)) 2896 expected = b'dylibbundler is a utility' 2897 first_output_line = p.out.splitlines() 2898 if first_output_line: 2899 first_output_line = first_output_line[0] 2900 # This test allows the expected text to appear anywhere in the 2901 # output. Only the first line of output will be shown to SCons' 2902 # stdout. The full output will be written to the SConf log. 2903 if p.returncode: 2904 reason = 'successful exit, but return code was %d' % p.returncode 2905 elif expected not in p.out: 2906 reason = 'output to contain %r, but first line of output was: %r' % (expected.decode(), first_output_line) 2907 else: 2908 context.Result('yes; %s' % (first_output_line,)) 2909 return 2910 context.Result('no; expected %s' % reason) 2911 context.Log('''scons: dylibbundler return code: %r 2912scons: dylibbundler stdout: %r 2913scons: dylibbundler stderr: %r 2914''' % (p.returncode, p.out, p.err)) 2915 raise SCons.Errors.StopError('`dylibbundler -h` failed to return expected output; dylibbundler is required for compilation for a macOS target when not using frameworks. Set macos_bundle_libs=False or macos_add_frameworks=False (and handle the libraries manually), or install dylibbundler.') 2916 2917 # This must be the last custom test. It does not test the environment, 2918 # but is responsible for reversing test-environment-specific changes made 2919 # by check_cxx_works. 2920 @_custom_test 2921 def _cleanup_configure_test_state(self,context): 2922 sconf = context.sconf 2923 sconf.config_h_text = self.__commented_tool_versions + sconf.config_h_text 2924 context.env['CXXCOM'] = self.__cxx_com_prefix 2925 context.did_show_result = True 2926 ToolchainInformation.show_partial_environ(context.env, self.user_settings, context.Display, self.msgprefix) 2927 2928ConfigureTests.register_preferred_compiler_options() 2929 2930class cached_property: 2931 __slots__ = 'method', 2932 def __init__(self,f): 2933 self.method = f 2934 def __get__(self,instance,cls): 2935 # This should never be accessed directly on the class. 2936 assert instance is not None 2937 method = self.method 2938 name = method.__name__ 2939 # Python will rewrite double-underscore references to the 2940 # attribute while processing references inside the class, but 2941 # will not rewrite the key used to store the computed value 2942 # below, so subsequent accesses will not find the computed value 2943 # and will instead call this getter again. For now, disable 2944 # attempts to use double-underscore properties instead of trying 2945 # to handle their names correctly. 2946 assert not name.startswith('__') 2947 d = instance.__dict__ 2948 # After the first access, Python should find the cached value in 2949 # the instance dictionary instead of calling __get__ again. 2950 assert name not in d 2951 d[name] = r = method(instance) 2952 return r 2953 2954class LazyObjectConstructor: 2955 class LazyObjectState: 2956 def __init__(self,sources,transform_env=None,StaticObject_hook=None,transform_target=None): 2957 # `sources` must be non-empty, since it would have no use if 2958 # it was empty. 2959 # 2960 # Every element in `sources` must be a string. Verify this 2961 # with an assertion that directly checks that the sequence 2962 # is not empty and indirectly, by way of attribute lookup, 2963 # checks that the elements are string-like. 2964 assert([s.encode for s in sources]), "sources must be a non-empty list of strings" 2965 self.sources = sources 2966 self.transform_env = transform_env 2967 self.StaticObject_hook = StaticObject_hook 2968 # If transform_target is not used, let references to 2969 # `self.transform_target` fall through to 2970 # `cls.transform_target`. 2971 if transform_target: 2972 self.transform_target = transform_target 2973 2974 @staticmethod 2975 def transform_target(_, name, _splitext=os.path.splitext): 2976 return _splitext(name)[0] 2977 2978 def __lazy_objects(self,source, 2979 cache={} 2980 ): 2981 env = self.env 2982 # Use id because name needs to be hashable and have a 1-to-1 2983 # mapping to source. 2984 name = (id(env), id(source)) 2985 value = cache.get(name) 2986 if value is None: 2987 prepare_target_name = '{}{}'.format 2988 StaticObject = env.StaticObject 2989 OBJSUFFIX = env['OBJSUFFIX'] 2990 builddir = self.builddir 2991 value = [] 2992 append = value.append 2993 for s in source: 2994 transform_target = s.transform_target 2995 transform_env = s.transform_env 2996 StaticObject_hook = s.StaticObject_hook 2997 for srcname in s.sources: 2998 target = builddir.File(prepare_target_name(transform_target(self, srcname), OBJSUFFIX)) 2999 s = StaticObject(target=target, source=srcname, 3000 **({} if transform_env is None else transform_env(self, env)) 3001 ) 3002 append(s) 3003 if StaticObject_hook is not None: 3004 StaticObject_hook(self, env, srcname, target, s) 3005 # Convert to a tuple so that attempting to modify a cached 3006 # result raises an error. 3007 value = tuple(value) 3008 cache[name] = value 3009 return value 3010 3011 def create_lazy_object_states_getter(states,__lazy_objects=__lazy_objects): 3012 def get_objects(self): 3013 return __lazy_objects(self, states) 3014 return get_objects 3015 3016 @staticmethod 3017 def create_lazy_object_getter(sources,LazyObjectState=LazyObjectState,create_lazy_object_states_getter=create_lazy_object_states_getter): 3018 return create_lazy_object_states_getter((LazyObjectState(sources=sources),)) 3019 3020 create_lazy_object_states_getter = staticmethod(create_lazy_object_states_getter) 3021 3022class FilterHelpText: 3023 _sconf_align = None 3024 def __init__(self): 3025 self.visible_arguments = [] 3026 def FormatVariableHelpText(self, env, opt, help, default, actual, aliases): 3027 if not opt in self.visible_arguments: 3028 return '' 3029 if not self._sconf_align: 3030 self._sconf_align = len(max((s for s in self.visible_arguments if s[:6] == 'sconf_'), key=len)) 3031 l = [] 3032 if default is not None: 3033 if isinstance(default, str) and not default.isalnum(): 3034 default = '"%s"' % default 3035 l.append("default: {default}".format(default=default)) 3036 actual = getattr(self, opt, None) 3037 if actual is not None: 3038 if isinstance(actual, str) and not actual.isalnum(): 3039 actual = '"%s"' % actual 3040 l.append("current: {current}".format(current=actual)) 3041 return (" {opt:%u} {help}{l}\n" % (self._sconf_align if opt[:6] == 'sconf_' else 15)).format(opt=opt, help=help, l=(" [" + "; ".join(l) + "]" if l else '')) 3042 3043class PCHManager: 3044 class ScannedFile: 3045 def __init__(self,candidates): 3046 self.candidates = candidates 3047 3048 syspch_cpp_filename = None 3049 ownpch_cpp_filename = None 3050 syspch_object_node = None 3051 ownpch_object_node = None 3052 required_pch_object_node = None 3053 3054 # Compile on first use, so that non-PCH builds skip the compile 3055 _re_preproc_match = None 3056 _re_include_match = None 3057 _re_singleline_comments_sub = None 3058 # Source files are tracked at class scope because all builds share 3059 # the same source tree. 3060 _cls_scanned_files = None 3061 3062 # Import required modules when the first PCHManager is created. 3063 @classmethod 3064 def __initialize_cls_static_variables(cls): 3065 from re import compile as c 3066 # Match C preprocessor directives that start with i or e, 3067 # capture any arguments, and allow no arguments. This accepts: 3068 # - #if 3069 # - #ifdef 3070 # - #ifndef 3071 # - #else 3072 # - #endif 3073 # - #include 3074 # - #error 3075 cls._re_preproc_match = c(br'#\s*([ie]\w+)(?:\s+(.*))?').match 3076 # Capture the argument in a #include statement, including the 3077 # angle brackets or quotes. 3078 # 3079 # Rebirth currently has no computed includes, so ignore computed 3080 # includes. 3081 cls._re_include_match = c(br'(<[^>]+>|"[^"]+")').match 3082 # Strip a single-line C++ style comment ("// Text") and any 3083 # preceding whitespace. The C preprocessor will discard these 3084 # when compiling the header. Discarding them from the 3085 # environment Value will prevent rebuilding pch.cpp when the 3086 # only change is in such a comment. 3087 cls._re_singleline_comments_sub = c(br'\s*//.*').sub 3088 # dict with key=filename with path, value=ScannedFile 3089 cls._cls_scanned_files = {} 3090 from tempfile import mkstemp 3091 cls._tempfile_mkstemp = staticmethod(mkstemp) 3092 3093 def __init__(self,program,configure_pch_flags,common_pch_manager): 3094 if self._re_preproc_match is None: 3095 self.__initialize_cls_static_variables() 3096 assert self._re_preproc_match is not None 3097 self.user_settings = user_settings = program.user_settings 3098 assert user_settings.syspch or user_settings.pch or not configure_pch_flags 3099 self.env = env = program.env 3100 # dict with key=fs.File, value=ScannedFile 3101 self._instance_scanned_files = {} 3102 self._common_pch_manager = common_pch_manager 3103 syspch_cpp_filename = ownpch_cpp_filename = None 3104 syspch_object_node = None 3105 CXXFLAGS = env['CXXFLAGS'] + (configure_pch_flags['CXXFLAGS'] if configure_pch_flags else []) 3106 File = env.File 3107 builddir = program.builddir 3108 pch_subdir_node = builddir.Dir(program.srcdir) 3109 if user_settings.syspch: 3110 self.syspch_cpp_node = pch_subdir_node.File('syspch.cpp') 3111 self.syspch_cpp_filename = syspch_cpp_filename = str(self.syspch_cpp_node) 3112 self.required_pch_object_node = self.syspch_object_node = syspch_object_node = env.StaticObject(target='%s.gch' % syspch_cpp_filename, source=self.syspch_cpp_node, CXXCOM=env._dxx_cxxcom_no_ccache_prefix, CXXFLAGS=CXXFLAGS) 3113 if user_settings.pch: 3114 self.ownpch_cpp_node = pch_subdir_node.File('ownpch.cpp') 3115 self.ownpch_cpp_filename = ownpch_cpp_filename = str(self.ownpch_cpp_node) 3116 if syspch_object_node: 3117 CXXFLAGS += ['-include', syspch_cpp_node, '-Winvalid-pch'] 3118 self.required_pch_object_node = self.ownpch_object_node = ownpch_object_node = env.StaticObject(target='%s.gch' % ownpch_cpp_filename, source=self.ownpch_cpp_node, CXXCOM=env._dxx_cxxcom_no_ccache_prefix, CXXFLAGS=CXXFLAGS) 3119 env.Depends(ownpch_object_node, builddir.File('dxxsconf.h')) 3120 if syspch_object_node: 3121 env.Depends(ownpch_object_node, syspch_object_node) 3122 if not configure_pch_flags: 3123 return 3124 self.pch_CXXFLAGS = ['-include', self.ownpch_cpp_node or syspch_cpp_node, '-Winvalid-pch'] 3125 # If assume unchanged and the file exists, set __files_included 3126 # to a dummy value. This bypasses scanning source files and 3127 # guarantees that the text of pch.cpp is not changed. SCons 3128 # will still recompile pch.cpp into a new .gch file if pch.cpp 3129 # includes files that SCons recognizes as changed. 3130 if user_settings.pch_cpp_assume_unchanged and \ 3131 (not syspch_cpp_filename or os.path.exists(syspch_cpp_filename)) and \ 3132 (not ownpch_cpp_filename or os.path.exists(ownpch_cpp_filename)): 3133 self.__files_included = True 3134 else: 3135 # collections.defaultdict with key from ScannedFile.candidates, 3136 # value is a collections.Counter with key=tuple of preprocessor 3137 # guards, value=count of times this header was included under 3138 # that set of guards. 3139 self.__files_included = defaultdict(collections_counter) 3140 self.__env_Program = env.Program 3141 self.__env_StaticObject = env.StaticObject 3142 env.Program = self.Program 3143 env.StaticObject = self.StaticObject 3144 3145 def record_file(self,env,source_file): 3146 # Every scanned file goes into self._cls_scanned_files to 3147 # prevent duplicate scanning from multiple targets. 3148 f = self._cls_scanned_files.get(source_file, None) 3149 if f is None: 3150 self._cls_scanned_files[source_file] = f = self.scan_file(env, source_file) 3151 self._instance_scanned_files[source_file] = f 3152 return f 3153 3154 # Scan a file for preprocessor directives related to conditional 3155 # compilation and file inclusion. 3156 # 3157 # The #include directives found are eventually written to pch.cpp 3158 # with their original preprocessor guards in place. Since the 3159 # preprocessor guards are kept and the C preprocessor will evaluate 3160 # them when compiling the header, this scanner does not attempt to 3161 # track which branches are true and which are false. 3162 # 3163 # This scanner makes no attempt to normalize guard conditions. It 3164 # considers each of these examples to be distinct guards, even 3165 # though a full preprocessor will produce the same result for each: 3166 # 3167 # #if 1 3168 # #if 2 3169 # #if 3 < 5 3170 # 3171 # or: 3172 # 3173 # #ifdef A 3174 # #if defined(A) 3175 # #if (defined(A)) 3176 # #if !!defined(A) 3177 # 3178 # or: 3179 # 3180 # #ifndef A 3181 # #else 3182 # #endif 3183 # 3184 # #ifdef A 3185 # #endif 3186 # 3187 # Include directives are followed only if the calling file will not 3188 # be part of the output pch.cpp. When the calling file will be in 3189 # pch.cpp, then the C preprocessor will include the called file, so 3190 # there is no need to scan it for other headers to include. 3191 # 3192 # This scanner completely ignores pragmas, #define, #undef, and 3193 # computed includes. 3194 @classmethod 3195 def scan_file(cls,env,source_filenode): 3196 match_preproc_directive = cls._re_preproc_match 3197 match_include_directive = cls._re_include_match 3198 preceding_line = None 3199 lines_since_preprocessor = None 3200 # defaultdict with key=name of header to include, value=set of 3201 # preprocessor guards under which an include was seen. Set is 3202 # used because duplicate inclusions from a single source file 3203 # should not adjust the usage counter. 3204 candidates = defaultdict(set) 3205 # List of currently active preprocessor guards 3206 guard = [] 3207 header_search_path = None 3208 os_path_join = os.path.join 3209 os_path_exists = os.path.exists 3210 for line in map(bytes.strip, source_filenode.get_contents().splitlines()): 3211 if preceding_line is not None: 3212 # Basic support for line continuation. 3213 line = b'%s %s' % (preceding_line[:-1], line) 3214 preceding_line = None 3215 elif not line.startswith(b'#'): 3216 # Allow unlimited non-preprocessor lines before the 3217 # first preprocessor line. Once one preprocessor line 3218 # is found, track how many lines since the most recent 3219 # preprocessor line was seen. If too many 3220 # non-preprocessor lines appear in a row, assume the 3221 # scanner is now down in source text and move to the 3222 # next file. 3223 if lines_since_preprocessor is not None: 3224 lines_since_preprocessor += 1 3225 if lines_since_preprocessor > 50: 3226 break 3227 continue 3228 lines_since_preprocessor = 0 3229 # Joined lines are rare. Ignore complicated quoting. 3230 if line.endswith(b'\\'): 3231 preceding_line = line 3232 continue 3233 m = match_preproc_directive(line) 3234 if not m: 3235 # Not a preprocessor directive or not a directive that 3236 # this scanner handles. 3237 continue 3238 directive = m.group(1) 3239 if directive == b'include': 3240 m = match_include_directive(m.group(2)) 3241 if not m: 3242 # This directive is probably a computed include. 3243 continue 3244 name = m.group(1) 3245 bare_name = name[1:-1] 3246 if name.startswith(b'"'): 3247 # Canonicalize paths to non-system headers 3248 if name == b'"dxxsconf.h"': 3249 # Ignore generated header here. PCH generation 3250 # will insert it in the right order. 3251 continue 3252 if not name.endswith(b'.h"'): 3253 # Exclude non-header files from PCH. 3254 # 3255 # Starting in gcc-7, static functions defined in 3256 # valptridx.tcc generate -Wunused-function 3257 # warnings if the including file does not 3258 # instantiate any templates that use the static 3259 # function. 3260 continue 3261 if header_search_path is None: 3262 header_search_path = [ 3263 d.encode() for d in ([os.path.dirname(str(source_filenode))] + env['CPPPATH']) 3264 # Filter out SDL paths 3265 if not d.startswith('/') 3266 ] 3267 name = None 3268 for d in header_search_path: 3269 effective_name = os_path_join(d, bare_name) 3270 if os_path_exists(effective_name): 3271 name = effective_name 3272 break 3273 else: 3274 # name is None if: 3275 # - A system header was included using quotes. 3276 # 3277 # - A game-specific header was included in a 3278 # shared file. A game-specific preprocessor 3279 # guard will prevent the preprocessor from 3280 # including the file, but the PCH scan logic 3281 # looks inside branches that the C preprocessor 3282 # will evaluate as false. 3283 continue 3284 name = env.File(name.decode()) 3285 candidates[name].add(tuple(guard)) 3286 elif directive == b'endif': 3287 while guard: 3288 g = guard.pop() 3289 if g == b'#else': 3290 # guard should always be True here, but test to 3291 # avoid ugly errors if scanning an ill-formed 3292 # source file. 3293 if guard: 3294 guard.pop() 3295 break 3296 if not g.startswith(b'#elif '): 3297 break 3298 elif directive == b'else': 3299 # #else is handled separately because it has no 3300 # arguments 3301 guard.append(b'#%s' % (directive)) 3302 elif directive in ( 3303 b'elif', 3304 b'if', 3305 b'ifdef', 3306 b'ifndef', 3307 ): 3308 guard.append(b'#%s %s' % (directive, m.group(2))) 3309 elif directive not in (b'error',): 3310 raise SCons.Errors.StopError("Scanning %s found unhandled C preprocessor directive %r" % (str(source_filenode), directive)) 3311 return cls.ScannedFile(candidates) 3312 3313 def _compute_pch_text(self): 3314 self._compute_indirect_includes() 3315 own_header_inclusion_threshold = self.user_settings.pch 3316 sys_header_inclusion_threshold = self.user_settings.syspch 3317 configured_threshold = 0x7fffffff if self.user_settings.pch_cpp_exact_counts else None 3318 # defaultdict with key=name of tuple of active preprocessor 3319 # guards, value=tuple of (included file, count of times file was 3320 # seen with this set of guards, count of times file would be 3321 # included with this set of guards defined). 3322 syscpp_includes = defaultdict(list) 3323 owncpp_includes = defaultdict(list) if own_header_inclusion_threshold else None 3324 for included_file, usage_dict in self.__files_included.items(): 3325 if isinstance(included_file, (bytes, str)): 3326 # System header 3327 cpp_includes = syscpp_includes 3328 name = (included_file.encode() if isinstance(included_file, str) else included_file) 3329 threshold = own_header_inclusion_threshold if sys_header_inclusion_threshold is None else sys_header_inclusion_threshold 3330 else: 3331 # Own header 3332 cpp_includes = owncpp_includes 3333 name = b'"%s"' % (str(included_file).encode()) 3334 threshold = own_header_inclusion_threshold 3335 if not threshold: 3336 continue 3337 count_threshold = configured_threshold or threshold 3338 g = usage_dict.get((), 0) 3339 # As a special case, if this header is included 3340 # without any preprocessor guards, ignore all the 3341 # conditional includes. 3342 guards = \ 3343 [((), g)] if (g >= threshold) else \ 3344 sorted(usage_dict.items(), reverse=True) 3345 while guards: 3346 preprocessor_guard_directives, local_count_seen = guards.pop() 3347 total_count_seen = local_count_seen 3348 if total_count_seen < count_threshold: 3349 # If not eligible on its own, add in the count from 3350 # preprocessor guards that are always true when this 3351 # set of guards is true. Since the scanner does not 3352 # normalize preprocessor directives, this is a 3353 # conservative count of always-true guards. 3354 g = preprocessor_guard_directives 3355 while g and total_count_seen < count_threshold: 3356 g = g[:-1] 3357 total_count_seen += usage_dict.get(g, 0) 3358 if total_count_seen < threshold: 3359 # If still not eligible, skip. 3360 continue 3361 cpp_includes[preprocessor_guard_directives].append((name, local_count_seen, total_count_seen)) 3362 3363 if syscpp_includes: 3364 self.__generated_syspch_lines = self._compute_pch_generated_lines(syscpp_includes) 3365 if owncpp_includes: 3366 self.__generated_ownpch_lines = self._compute_pch_generated_lines(owncpp_includes) 3367 3368 def _compute_pch_generated_lines(self,cpp_includes): 3369 generated_pch_lines = [] 3370 # Append guarded #include directives for files which passed the 3371 # usage threshold above. This loop could try to combine related 3372 # preprocessor guards, but: 3373 # - The C preprocessor handles the noncombined guards correctly. 3374 # - As a native program optimized for text processing, the C 3375 # preprocessor almost certainly handles guard checking faster 3376 # than this script could handle guard merging. 3377 # - This loop runs whenever pch.cpp might be regenerated, even 3378 # if the result eventually shows that pch.cpp has not changed. 3379 # The C preprocessor will only run over the file when it is 3380 # actually changed and is processed to build a new .gch file. 3381 for preprocessor_guard_directives, included_file_tuples in sorted(cpp_includes.items()): 3382 generated_pch_lines.append(b'') 3383 generated_pch_lines.extend(preprocessor_guard_directives) 3384 # local_count_seen is the direct usage count for this 3385 # combination of preprocessor_guard_directives. 3386 # 3387 # total_count_seen is the sum of local_count_seen and all 3388 # guards that are a superset of this 3389 # preprocessor_guard_directives. The total stops counting 3390 # when it reaches threshold, so it may undercount actual 3391 # usage. 3392 for (name, local_count_seen, total_count_seen) in sorted(included_file_tuples): 3393 assert(isinstance(name, bytes)), repr(name) 3394 generated_pch_lines.append(b'#include %s\t// %u %u' % (name, local_count_seen, total_count_seen)) 3395 # d[2] == l if d is '#else' or d is '#elif' 3396 # Only generate #endif when d is a '#if*' directive, since 3397 # '#else/#elif' do not introduce a new scope. 3398 generated_pch_lines.extend(b'#endif' for d in preprocessor_guard_directives if d[2:3] != b'l') 3399 return generated_pch_lines 3400 3401 def _compute_indirect_includes(self): 3402 own_header_inclusion_threshold = None if self.user_settings.pch_cpp_exact_counts else self.user_settings.pch 3403 # Count how many times each header is used for each preprocessor 3404 # guard combination. After the outer loop finishes, 3405 # files_included is a dictionary that maps the name of the 3406 # include file to a collections.counter instance. The mapped 3407 # counter maps a preprocessor guard to a count of how many times 3408 # it was used. 3409 # 3410 # Given: 3411 # 3412 # a.cpp 3413 # #include "a.h" 3414 # #ifdef A 3415 # #include "b.h" 3416 # #endif 3417 # 3418 # b.cpp 3419 # #include "b.h" 3420 # 3421 # files_included = { 3422 # 'a.h' : { () : 1 } 3423 # 'b.h' : { 3424 # ('#ifdef A',) : 1, # From a.cpp 3425 # () : 1, # From b.cpp 3426 # } 3427 # } 3428 files_included = self.__files_included 3429 for scanned_file in self._instance_scanned_files.values(): 3430 for included_file, guards in scanned_file.candidates.items(): 3431 i = files_included[included_file] 3432 for g in guards: 3433 i[g] += 1 3434 # If own_header_inclusion_threshold == 1, then every header 3435 # found will be listed in pch.cpp, so any indirect headers will 3436 # be included by the C preprocessor. 3437 if own_header_inclusion_threshold == 1: 3438 return 3439 # For each include file which is below the threshold, scan it 3440 # for includes which may end up above the threshold. 3441 File = self.env.File 3442 includes_to_check = sorted(files_included.iterkeys(), key=str) 3443 while includes_to_check: 3444 included_file = includes_to_check.pop() 3445 if isinstance(included_file, (bytes, str)): 3446 # System headers are str. Internal headers are 3447 # fs.File. 3448 continue 3449 guards = files_included[included_file] 3450 unconditional_use_count = guards.get((), 0) 3451 if unconditional_use_count >= own_header_inclusion_threshold and own_header_inclusion_threshold: 3452 # Header will be unconditionally included in the PCH. 3453 continue 3454 f = self.record_file(self.env, File(included_file)) 3455 for nested_included_file, nested_guards in sorted(f.candidates.items(), key=str): 3456 if not isinstance(included_file, (bytes, str)) and not nested_included_file in files_included: 3457 # If the header is a system header, it will be 3458 # str. Skip system headers. 3459 # 3460 # Otherwise, if it has not been seen before, 3461 # append it to includes_to_check for recursive 3462 # scanning. 3463 includes_to_check.append(nested_included_file) 3464 i = files_included[nested_included_file] 3465 # If the nested header is included 3466 # unconditionally, skip the generator. 3467 for g in (nested_guards if unconditional_use_count else (a + b for a in guards for b in nested_guards)): 3468 i[g] += 1 3469 3470 @classmethod 3471 def write_pch_inclusion_file(cls,target,source,env): 3472 target = str(target[0]) 3473 dn, bn = os.path.split(target) 3474 fd, path = cls._tempfile_mkstemp(suffix='', prefix='%s.' % bn, dir=dn, text=True) 3475 # source[0].get_contents() returns the comment-stripped form 3476 os.write(fd, source[0].__generated_pch_text) 3477 os.close(fd) 3478 os.rename(path, target) 3479 3480 def StaticObject(self,target,source,CXXFLAGS=None,*args,**kwargs): 3481 env = self.env 3482 source = env.File(source) 3483 o = self.__env_StaticObject(target=target, source=source, CXXFLAGS=self.pch_CXXFLAGS + (env['CXXFLAGS'] if CXXFLAGS is None else CXXFLAGS), *args, **kwargs) 3484 # Force an order dependency on the .gch file. It is never 3485 # referenced by the command line or the source files, so SCons 3486 # may not recognize it as an input. 3487 env.Requires(o, self.required_pch_object_node) 3488 if self.__files_included is not True: 3489 self.record_file(env, source) 3490 return o 3491 3492 def Program(self,*args,**kwargs): 3493 self._register_pch_commands() 3494 if self._common_pch_manager: 3495 self._common_pch_manager._register_pch_commands() 3496 return self.__env_Program(*args, **kwargs) 3497 3498 def _register_pch_commands(self): 3499 if self.__files_included: 3500 # Common code calls this function once for each game which 3501 # uses it. Only one call is necessary for proper operation. 3502 # Ignore all later calls. 3503 return 3504 self._compute_pch_text() 3505 syspch_lines = self.__generated_syspch_lines 3506 pch_begin_banner_template = b''' 3507// BEGIN PCH GENERATED FILE 3508// %r 3509// Threshold=%u 3510 3511// SConf generated header 3512#include "dxxsconf.h" 3513''' 3514 pch_end_banner = (b''' 3515// END PCH GENERATED FILE 3516''',) 3517 if self.syspch_object_node: 3518 self._register_write_pch_inclusion(self.syspch_cpp_node, 3519 ( 3520 (pch_begin_banner_template % (self.syspch_cpp_filename, self.user_settings.syspch),), 3521 syspch_lines, 3522 pch_end_banner, 3523 ) 3524 ) 3525 # ownpch.cpp will include syspch.cpp.gch from the command 3526 # line, so clear syspch_lines to avoid repeating system 3527 # includes in ownpch.cpp 3528 syspch_lines = () 3529 if self.ownpch_object_node: 3530 self._register_write_pch_inclusion(self.ownpch_cpp_node, 3531 ( 3532 (pch_begin_banner_template % (self.ownpch_cpp_filename, self.user_settings.pch),), 3533 (b'// System headers' if syspch_lines else b'',), 3534 syspch_lines, 3535 (b''' 3536// Own headers 3537''',), 3538 self.__generated_ownpch_lines, 3539 pch_end_banner, 3540 ) 3541 ) 3542 3543 def _register_write_pch_inclusion(self,node,lineseq): 3544 # The contents of pch.cpp are taken from the iterables in 3545 # lineseq. Set the contents as an input Value instead of 3546 # listing file nodes, so that pch.cpp is not rebuilt if a change 3547 # to a source file does not change what headers are listed in 3548 # pch.cpp. 3549 # 3550 # Strip C++ single line comments so that changes in comments are 3551 # ignored when deciding whether pch.cpp needs to be rebuilt. 3552 env = self.env 3553 text = b'\n'.join(itertools.chain.from_iterable(lineseq)) 3554 v = env.Value(self._re_singleline_comments_sub(b'', text)) 3555 v.__generated_pch_text = text 3556 # Use env.Command instead of env.Textfile or env.Substfile since 3557 # the latter use their input both for freshness and as a data 3558 # source. This Command writes the commented form of the Value 3559 # to the file, but uses a comment-stripped form for SCons 3560 # freshness checking. 3561 env.Command(node, v, self.write_pch_inclusion_file) 3562 3563class DXXCommon(LazyObjectConstructor): 3564 # version number 3565 VERSION_MAJOR = 0 3566 VERSION_MINOR = 61 3567 VERSION_MICRO = 0 3568 DXX_VERSION_SEQ = ','.join([str(VERSION_MAJOR), str(VERSION_MINOR), str(VERSION_MICRO)]) 3569 pch_manager = None 3570 runtime_test_boost_tests = None 3571 # dict compilation_database_dict_fn_to_entries: 3572 # key: str: name of JSON file to which the data will be written 3573 # value: tuple: (SCons.Environment, list_of_entries_to_write) 3574 # The first environment to use the named JSON file will be stored as 3575 # the environment in the tuple, and later used to provide 3576 # env.Textfile for all the entries written to the file. When other 3577 # environments write to the same JSON file, they will append their 3578 # entries to the original list, and reuse the initial environment. 3579 # Since the build system does not customize env.Textfile, any 3580 # instance of it should be equally good. This design permits 3581 # writing all the records for a build into a single file, even 3582 # though the build will use multiple SCons.Environment instances to 3583 # compile its source files. 3584 compilation_database_dict_fn_to_entries = {} 3585 3586 class RuntimeTest(LazyObjectConstructor): 3587 nodefaultlibs = True 3588 def __init__(self,target,source): 3589 self.target = target 3590 self.source = LazyObjectConstructor.create_lazy_object_getter(source) 3591 3592 @cached_property 3593 def program_message_prefix(self): 3594 return '%s.%d' % (self.PROGRAM_NAME, self.program_instance) 3595 3596 @cached_property 3597 def builddir(self): 3598 return self.env.Dir(self.user_settings.builddir) 3599 3600 # Settings which affect how the files are compiled 3601 class UserBuildSettings: 3602 class IntVariable: 3603 def __new__(cls,key,help,default): 3604 return (key, help, default, cls._validator, int) 3605 @staticmethod 3606 def _validator(key, value, env): 3607 try: 3608 int(value) 3609 return True 3610 except ValueError: 3611 raise SCons.Errors.UserError('Invalid value for integer-only option %s: %s.' % (key, value)) 3612 class UIntVariable(IntVariable): 3613 @staticmethod 3614 def _validator(key, value, env): 3615 try: 3616 if int(value) < 0: 3617 raise ValueError 3618 return True 3619 except ValueError: 3620 raise SCons.Errors.UserError('Invalid value for unsigned-integer-only option %s: %s.' % (key, value)) 3621 # Paths for the Videocore libs/includes on the Raspberry Pi 3622 RPI_DEFAULT_VC_PATH='/opt/vc' 3623 default_OGLES_LIB = 'GLES_CM' 3624 default_EGL_LIB = 'EGL' 3625 _default_prefix = '/usr/local' 3626 __stdout_is_not_a_tty = None 3627 __has_git_dir = None 3628 def default_poison(self): 3629 return 'overwrite' if self.debug else 'none' 3630 def default_builddir(self): 3631 builddir_prefix = self.builddir_prefix 3632 builddir_suffix = self.builddir_suffix 3633 if builddir_prefix is not None or builddir_suffix is not None: 3634 fields = [ 3635 self.host_platform, 3636 os.path.basename(self.CXX) if self.CXX else None, 3637 ] 3638 compiler_flags = '\n'.join((getattr(self, attr) or '').strip() for attr in ['CPPFLAGS', 'CXXFLAGS']) 3639 if compiler_flags: 3640 # Mix in CRC of CXXFLAGS to get reasonable uniqueness 3641 # when flags are changed. A full hash is 3642 # unnecessary here. 3643 crc = binascii.crc32(compiler_flags.encode()) 3644 if crc < 0: 3645 crc = crc + 0x100000000 3646 fields.append('{:08x}'.format(crc)) 3647 if self.pch: 3648 fields.append('p%u' % self.pch) 3649 elif self.syspch: 3650 fields.append('sp%u' % self.syspch) 3651 fields.append(''.join(a[1] if getattr(self, a[0]) else (a[2] if len(a) > 2 else '') 3652 for a in ( 3653 ('debug', 'dbg'), 3654 ('lto', 'lto'), 3655 ('editor', 'ed'), 3656 ('opengl', 'ogl', 'sdl'), 3657 ('opengles', 'es'), 3658 ('raspberrypi', 'rpi'), 3659 ))) 3660 default_builddir = (builddir_prefix or '') + '-'.join([f for f in fields if f]) 3661 if builddir_suffix is not None: 3662 default_builddir += builddir_suffix 3663 else: 3664 default_builddir = 'build/' 3665 return default_builddir 3666 def default_memdebug(self): 3667 return self.debug 3668 # automatic setup for raspberrypi 3669 def default_opengles(self): 3670 if self.raspberrypi in ('yes',): 3671 return True 3672 return False 3673 def default_sdl2(self): 3674 if self.raspberrypi in ('mesa',): 3675 return True 3676 return False 3677 @classmethod 3678 def default_verbosebuild(cls): 3679 # Enable verbosebuild when the output is not directed to a 3680 # terminal. When output is not a terminal, it is likely 3681 # either a pager or a log file, both of which can readily 3682 # handle the very long lines generated by verbose mode. 3683 r = cls.__stdout_is_not_a_tty 3684 if r is None: 3685 isatty = getattr(sys.stdout, 'isatty', None) 3686 # If isatty is None, then assume output is a TTY. 3687 cls.__stdout_is_not_a_tty = r = False if isatty is None else not isatty() 3688 return r 3689 def default_words_need_alignment(self): 3690 if self.raspberrypi in ('yes', 'mesa'): 3691 return True 3692 return False 3693 def selected_OGLES_LIB(self): 3694 if self.raspberrypi == 'yes': 3695 return 'brcmGLESv2' 3696 return self.default_OGLES_LIB 3697 def selected_EGL_LIB(self): 3698 if self.raspberrypi == 'yes': 3699 return 'brcmEGL' 3700 return self.default_EGL_LIB 3701 def need_dynamic_library_load(self): 3702 return self.adlmidi == 'runtime' 3703 def _enable_adlmidi(self): 3704 return self.adlmidi != 'none' 3705 3706 def __default_DATA_DIR(self): 3707 platform_settings_type = self._program.get_platform_settings_type(self.host_platform) 3708 sharepath = platform_settings_type.sharepath 3709 if sharepath is None: 3710 return None 3711 return sharepath(prefix=self.prefix, program_target=self._program.target) 3712 3713 def _generic_variable(key,help,default): 3714 return (key, help, default) 3715 def __get_configure_tests(tests,_filter=lambda s: s.name[0] != '_'): 3716 # Construct combined list on first use, then cache it 3717 # forever. 3718 try: 3719 return tests.__configure_tests 3720 except AttributeError: 3721 # Freeze the results into a tuple, to prevent accidental 3722 # modification later. 3723 # 3724 # In Python 2, this is merely a safety feature and could 3725 # be skipped. 3726 # 3727 # In Python 3, filter returns an iterable that is 3728 # exhausted after one full traversal. Since this object 3729 # is intended to be retained and reused, the first 3730 # traversal must copy the results into a container that 3731 # can be walked multiple times. A tuple serves this 3732 # purpose, in addition to freezing the contents. 3733 tests.__configure_tests = c = tuple(filter(_filter, tests.implicit_tests + tests.custom_tests)) 3734 return c 3735 @classmethod 3736 def __get_has_git_dir(cls): 3737 r = cls.__has_git_dir 3738 if r is None: 3739 # SConstruct is always at the top of the repository. 3740 # The user might have run `scons` from elsewhere and 3741 # used `-f` to indicate this file, but a false negative 3742 # is acceptable here. 3743 cls.__has_git_dir = r = os.path.exists(os.environ.get('GIT_DIR', '.git')) 3744 return r 3745 def _options(self, 3746 __get_configure_tests=__get_configure_tests, 3747 generic_variable=_generic_variable, 3748 BoolVariable=BoolVariable, 3749 EnumVariable=EnumVariable, 3750 conftests=ConfigureTests, 3751 getenv=os.environ.get 3752 ): 3753 tests = __get_configure_tests(conftests) 3754 expect_sconf_tuple = ('0', '1', conftests.expect_sconf_success, conftests.expect_sconf_failure) 3755 sconf_tuple = ('0', '1', '2', conftests.sconf_force_failure, conftests.sconf_force_success, conftests.sconf_assume_success) 3756 sys_platform = sys.platform 3757 for platform in ('linux', 'dragonfly', 'openbsd'): 3758 if sys_platform.startswith(platform): 3759 sys_platform = platform 3760 break 3761 return ( 3762 { 3763 'variable': EnumVariable, 3764 'arguments': [ 3765 ('expect_sconf_%s' % t.name[6:], 3766 None, 3767 None, 3768 {'allowed_values' : expect_sconf_tuple} 3769 ) for t in tests 3770 ], 3771 }, 3772 { 3773 'variable': EnumVariable, 3774 'arguments': [ 3775 ('sconf_%s' % t.name[6:], 3776 None, 3777 t.desc or ('assume result of %s' % t.name), 3778 {'allowed_values' : sconf_tuple} 3779 ) for t in tests 3780 ], 3781 }, 3782 { 3783 'variable': EnumVariable, 3784 'arguments': ( 3785 ('host_platform', 3786 sys_platform, 3787 'cross-compile to specified platform', 3788 { 3789 'map': {'msys':'win32'}, 3790 'allowed_values' : ('darwin', 'linux', 'dragonfly', 'openbsd', 'win32', 'haiku1'), 3791 } 3792 ), 3793 ('raspberrypi', None, 'build for Raspberry Pi (automatically selects opengles)', {'ignorecase': 2, 'map': {'1':'yes', 'true':'yes', '0':'no', 'false':'no'}, 'allowed_values': ('yes', 'no', 'mesa')}), 3794 ), 3795 }, 3796 { 3797 'variable': BoolVariable, 3798 'arguments': ( 3799 ('record_sconf_results', False, 'write sconf results to dxxsconf.h'), 3800 ('git_describe_version', self.__get_has_git_dir(), 'include git --describe in extra_version'), 3801 ('git_status', True, 'include git status'), 3802 ('versid_depend_all', False, 'rebuild vers_id.cpp if any object file changes'), 3803 ), 3804 }, 3805 { 3806 'variable': generic_variable, 3807 'arguments': ( 3808 ('rpi_vc_path', self.RPI_DEFAULT_VC_PATH, 'directory for RPi VideoCore libraries'), 3809 ('opengles_lib', self.selected_OGLES_LIB, 'name of the OpenGL ES library to link against'), 3810 ('egl_lib', self.selected_EGL_LIB, 'name of the OpenGL ES Graphics Library to link against'), 3811 ('prefix', self._default_prefix, 'installation prefix directory (Linux only)'), 3812 ('sharepath', self.__default_DATA_DIR, 'directory for shared game data'), 3813 ), 3814 }, 3815 { 3816 'variable': self.UIntVariable, 3817 'arguments': ( 3818 ('lto', 0, 'enable gcc link time optimization'), 3819 ('pch', None, 'pre-compile own headers used at least this many times'), 3820 ('syspch', None, 'pre-compile system headers used at least this many times'), 3821 ('max_joysticks', 8, 'maximum number of usable joysticks'), 3822 ('max_axes_per_joystick', 128, 'maximum number of axes per joystick'), 3823 ('max_buttons_per_joystick', 128, 'maximum number of buttons per joystick'), 3824 ('max_hats_per_joystick', 4, 'maximum number of hats per joystick'), 3825 ), 3826 }, 3827 { 3828 'variable': BoolVariable, 3829 'arguments': ( 3830 ('pch_cpp_assume_unchanged', False, 'assume text of *pch.cpp is unchanged'), 3831 ('pch_cpp_exact_counts', False, None), 3832 ('check_header_includes', False, 'compile test each header (developer option)'), 3833 ('debug', False, 'build DEBUG binary which includes asserts, debugging output, cheats and more output'), 3834 ('memdebug', self.default_memdebug, 'build with malloc tracking'), 3835 ('opengl', True, 'build with OpenGL support'), 3836 ('opengles', self.default_opengles, 'build with OpenGL ES support'), 3837 ('editor', False, 'include editor into build (!EXPERIMENTAL!)'), 3838 ('sdl2', self.default_sdl2, 'use libSDL2+SDL2_mixer (!EXPERIMENTAL!)'), 3839 # Build with SDL_Image support for PCX file support 3840 # Currently undocumented because the user experience 3841 # without PCX support is ugly, so this should always 3842 # be left enabled. 3843 ('sdlimage', True, None), 3844 ('sdlmixer', True, 'build with SDL_Mixer support for sound and music (includes external music support)'), 3845 ('ipv6', False, 'enable UDP/IPv6 for multiplayer'), 3846 ('use_udp', True, 'enable UDP support'), 3847 ('use_tracker', True, 'enable Tracker support (requires UDP)'), 3848 ('verbosebuild', self.default_verbosebuild, 'print out all compiler/linker messages during building'), 3849 # This is only examined for Mac OS X targets. 3850 # 3851 # Some users, particularly those who install 3852 # dependencies via Homebrew, may have the required C 3853 # headers and libraries available, but not packaged 3854 # as a Framework. Such users should set 3855 # macos_add_frameworks=False, and (if the required 3856 # headers and libraries are not in a default search 3857 # location), use the standard C search path 3858 # directives to tell the compiler where to find the 3859 # required files. 3860 # 3861 # For users who have the framework installed in the 3862 # standard place, if macos_add_frameworks=True, 3863 # SCons should find the headers and libraries 3864 # automatically. 3865 ('macos_add_frameworks', True, 'add required frameworks to CPPPATH, FRAMEWORKS, and FRAMEWORKPATH search variables (MacOS only); Homebrew users may want macos_add_frameworks=False'), 3866 # This is only examined for Mac OS X targets. 3867 # 3868 # dylibbundler can have issues on some systems, so 3869 # it is an optional step. If used (and successful), 3870 # dylibbundler includes required libraries in the 3871 # generated app bundle and updates the executable 3872 # to reference them within the bundle. 3873 ('macos_bundle_libs', False, 'bundle required libs into app bundle using dylibbundler'), 3874 # This is only examined for Windows targets, so 3875 # there is no need to make the default value depend 3876 # on the host_platform. 3877 ('windows_minidump', True, 'generate a minidump on unhandled C++ exception (Windows only)'), 3878 ('words_need_alignment', self.default_words_need_alignment, 'align words at load (needed for many non-x86 systems)'), 3879 ('register_compile_target', True, 'report compile targets to SCons core'), 3880 ('register_cpp_output_targets', None, None), 3881 ('register_runtime_test_link_targets', False, None), 3882 ('enable_build_failure_summary', True, 'print failed nodes and their commands'), 3883 ('wrap_PHYSFS_read', False, None), 3884 ('wrap_PHYSFS_write', False, None), 3885 # This is intentionally undocumented. If a bug 3886 # report includes a log with this set to False, the 3887 # reporter will be asked to provide a log with the 3888 # value set to True. Try to prevent the extra round 3889 # trip by hiding the option. 3890 ('show_tool_version', True, None), 3891 # Only applicable if show_tool_version=True 3892 ('show_assembler_version', True, None), 3893 ('show_linker_version', True, None), 3894 ), 3895 }, 3896 { 3897 'variable': generic_variable, 3898 'arguments': ( 3899 ('CHOST', getenv('CHOST'), 'CHOST of output'), 3900 ('CXX', getenv('CXX'), 'C++ compiler command'), 3901 ('PKG_CONFIG', getenv('PKG_CONFIG'), 'PKG_CONFIG to run (Linux only)'), 3902 ('RC', getenv('RC'), 'Windows resource compiler command'), 3903 ('extra_version', None, 'text to append to version, such as VCS identity'), 3904 ('ccache', None, 'path to ccache'), 3905 ('compilation_database', None, None), 3906 ('distcc', None, 'path to distcc'), 3907 ('distcc_hosts', getenv('DISTCC_HOSTS'), 'hosts to distribute compilation'), 3908 # This is only examined for Mac OS X targets. 3909 # 3910 # This option signs a bundle (and the libraries 3911 # if embedded with macos_bundle_libs) with the 3912 # designated code signing identity. To use it, 3913 # pass the common name of the certificate in the 3914 # Keychain you wish to use to sign the bundle and 3915 # libraries (as applicable). 3916 ('macos_code_signing_identity', None, 'sign app bundle and embedded libs (if applicable) with this identity'), 3917 # This is only examined for Mac OS X targets. 3918 # 3919 # This option specifies a Keychain item to use 3920 # for credentials when submitting a signed app 3921 # bundle for notarization. If this is not specified, 3922 # then specifying an Apple ID and team ID is required. 3923 # For details about storing credentials in a 3924 # compatible Keychain entry, see the store-credentials 3925 # example at: 3926 # https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow 3927 ('macos_notarization_keychain_item', None, 'keychain item for notarization credentials'), 3928 # This is only examined for Mac OS X targets. 3929 # 3930 # An Apple ID to be used for notarization submissions. 3931 # If a valid Keychain item is not used, then this is 3932 # required. 3933 ('macos_notarization_apple_id', None, 'Apple ID for notarization submissions'), 3934 # This is only examined for Mac OS X targets. 3935 # 3936 # The Apple team ID to use for notarization submissions. 3937 # If a valid Keychain item is not used, then this is 3938 # required. 3939 ('macos_notarization_team_id', None, 'Apple team ID for notarization submissions'), 3940 # This is only examined for Mac OS X targets. 3941 # 3942 # The Apple app-specific password to use for 3943 # notarization submissions. If a valid Keychain item 3944 # is not used, then this will be used if provided. 3945 # Otherwise, the submission process will provide an 3946 # interactive prompt for the password. 3947 ('macos_notarization_password', None, 'Apple app-specific password for notarization submissions'), 3948 ), 3949 }, 3950 { 3951 'variable': generic_variable, 3952 'stack': ' ', 3953 'arguments': ( 3954 ('CPPFLAGS', getenv('CPPFLAGS'), 'C preprocessor flags'), 3955 ('CXXFLAGS', getenv('CXXFLAGS'), 'C++ compiler flags'), 3956 ('LINKFLAGS', getenv('LDFLAGS'), 'Linker flags'), 3957 ('LIBS', getenv('LIBS'), 'Libraries to link'), 3958 # These are intentionally undocumented. They are 3959 # meant for developers who know the implications of 3960 # using them. 3961 ('CPPFLAGS_unchecked', None, None), 3962 ('CXXFLAGS_unchecked', None, None), 3963 ('LINKFLAGS_unchecked', None, None), 3964 # Flags that are injected into the compilation 3965 # database, but which are not used in the actual 3966 # compilation. Use this to work around clang's 3967 # inability to find its own headers when invoked 3968 # from custom tools. 3969 ('CXXFLAGS_compilation_database', None, None), 3970 ), 3971 }, 3972 { 3973 'variable': EnumVariable, 3974 'arguments': ( 3975 ('host_endian', None, 'endianness of host platform', {'allowed_values' : ('little', 'big')}), 3976 ('adlmidi', 'none', 'include ADL MIDI support (none: disabled; runtime: dynamically load at runtime)', {'allowed_values' : ('none', 'runtime')}), 3977 ('screenshot', 'png', 'screenshot file format', {'allowed_values' : ('none', 'legacy', 'png')}), 3978 ), 3979 }, 3980 { 3981 'variable': ListVariable, 3982 'arguments': ( 3983 ('poison', self.default_poison, 'method for poisoning free memory', {'names' : ('valgrind', 'overwrite')}), 3984 ), 3985 }, 3986 { 3987 'variable': generic_variable, 3988 'arguments': ( 3989 ('builddir_prefix', None, 'prefix to generated build directory'), 3990 ('builddir_suffix', None, 'suffix to generated build directory'), 3991 # This must be last so that default_builddir will 3992 # have access to other properties. 3993 ('builddir', self.default_builddir, 'build in specified directory'), 3994 ), 3995 }, 3996 ) 3997 _generic_variable = staticmethod(_generic_variable) 3998 3999 @staticmethod 4000 def _names(name,prefix): 4001 return ['%s%s%s' % (p, '_' if p else '', name) for p in prefix] 4002 4003 def __init__(self,program=None): 4004 self._program = program 4005 4006 def register_variables(self,prefix,variables,filtered_help): 4007 self.known_variables = [] 4008 append_known_variable = self.known_variables.append 4009 add_variable = variables.Add 4010 for grp in self._options(): 4011 variable = grp['variable'] 4012 stack = grp.get('stack', None) 4013 for opt in grp['arguments']: 4014 (name,value,help) = opt[0:3] 4015 kwargs = opt[3] if len(opt) > 3 else {} 4016 if name not in variables.keys(): 4017 if help is not None: 4018 filtered_help.visible_arguments.append(name) 4019 add_variable(variable(key=name, help=help, default=None if callable(value) else value, **kwargs)) 4020 names = self._names(name, prefix) 4021 for n in names: 4022 if n not in variables.keys(): 4023 add_variable(variable(key=n, help=help, default=None, **kwargs)) 4024 if not name in names: 4025 names.append(name) 4026 append_known_variable((names, name, value, stack)) 4027 if stack: 4028 for n in names: 4029 add_variable(self._generic_variable(key='%s_stop' % n, help=None, default=None)) 4030 def read_variables(self,program,variables,d): 4031 verbose_settings_init = os.getenv('DXX_SCONS_DEBUG_USER_SETTINGS') 4032 for (namelist,cname,dvalue,stack) in self.known_variables: 4033 value = None 4034 found_value = False 4035 for n in namelist: 4036 try: 4037 v = d[n] 4038 found_value = True 4039 if stack: 4040 if callable(v): 4041 if verbose_settings_init: 4042 message(program, 'append to stackable %r from %r by call to %r(%r, %r, %r)' % (cname, n, v, dvalue, value, stack)) 4043 value = v(dvalue=dvalue, value=value, stack=stack) 4044 else: 4045 if value: 4046 value = (value, v) 4047 if verbose_settings_init: 4048 message(program, 'append to stackable %r from %r by join of %r.join(%r, %r)' % (cname, n, stack, value)) 4049 value = stack.join(value) 4050 else: 4051 if verbose_settings_init: 4052 message(program, 'assign to stackable %r from %r value %r' % (cname, n, v)) 4053 value = v 4054 stop = '%s_stop' % n 4055 if d.get(stop, None): 4056 if verbose_settings_init: 4057 message(program, 'terminating search early due to presence of %s' % stop) 4058 break 4059 continue 4060 if verbose_settings_init: 4061 message(program, 'assign to non-stackable %r from %r value %r' % (cname, n, v)) 4062 value = v 4063 break 4064 except KeyError as e: 4065 pass 4066 if not found_value: 4067 if callable(dvalue): 4068 value = dvalue() 4069 if verbose_settings_init: 4070 message(program, 'assign to %r by default value %r from call %r' % (cname, value, dvalue)) 4071 else: 4072 value = dvalue 4073 if verbose_settings_init: 4074 message(program, 'assign to %r by default value %r from direct' % (cname, value)) 4075 setattr(self, cname, value) 4076 if self.builddir != '' and self.builddir[-1:] != '/': 4077 self.builddir += '/' 4078 def clone(self): 4079 clone = DXXCommon.UserBuildSettings(None) 4080 for grp in clone._options(): 4081 for o in grp['arguments']: 4082 name = o[0] 4083 value = getattr(self, name) 4084 setattr(clone, name, value) 4085 return clone 4086 class UserInstallSettings: 4087 def _options(self): 4088 return ( 4089 { 4090 'variable': self._generic_variable, 4091 'arguments': ( 4092 ('DESTDIR', None, 'installation stage directory'), 4093 ('program_name', None, 'name of built program'), 4094 ), 4095 }, 4096 { 4097 'variable': BoolVariable, 4098 'arguments': ( 4099 ('register_install_target', True, 'report install target to SCons core'), 4100 ), 4101 }, 4102 ) 4103 class UserSettings(UserBuildSettings,UserInstallSettings): 4104 def _options(self): 4105 return DXXCommon.UserBuildSettings._options(self) + DXXCommon.UserInstallSettings._options(self) 4106 # Base class for platform-specific settings processing 4107 class _PlatformSettings: 4108 tools = ('g++', 'gnulink') 4109 ogllibs = [] 4110 platform_objects = () 4111 sharepath = None 4112 4113 def __init__(self,program,user_settings): 4114 self.__program = program 4115 self.user_settings = user_settings 4116 4117 @property 4118 def builddir(self): 4119 return self.__program.builddir 4120 4121 @property 4122 def env(self): 4123 return self.__program.env 4124 4125 # Settings to apply to mingw32 builds 4126 class Win32PlatformSettings(_PlatformSettings): 4127 ogllibs = ['opengl32', 'glu32'] 4128 tools = ('mingw',) 4129 def adjust_environment(self,program,env): 4130 env.Append( 4131 CPPDEFINES = ['_WIN32', 'WIN32_LEAN_AND_MEAN'], 4132 LINKFLAGS = ['-mwindows'], 4133 ) 4134 class DarwinPlatformSettings(_PlatformSettings): 4135 # Darwin targets include Objective-C (not Objective-C++) code to 4136 # access Apple-specific functionality. Add 'gcc' to the target 4137 # list to support this. 4138 # 4139 # Darwin targets need a special linker, because OS X uses 4140 # frameworks instead of standard libraries. Using `gnulink` 4141 # omits framework-related arguments, causing the linker to skip 4142 # including required libraries. SCons's `applelink` target 4143 # understands these quirks and ensures that framework-related 4144 # arguments are included. 4145 tools = ('gcc', 'g++', 'applelink') 4146 def adjust_environment(self,program,env): 4147 macos_add_frameworks = self.user_settings.macos_add_frameworks 4148 if macos_add_frameworks: 4149 # The user may or may not have a private installation of 4150 # frameworks. If there are no private frameworks, then 4151 # the path to the private frameworks should not be 4152 # added, because some versions of the tools issue a 4153 # diagnostic for searching a non-existant path. 4154 library_frameworks = os.path.join(os.getenv("HOME"), 'Library/Frameworks') 4155 if os.path.isdir(library_frameworks): 4156 # The private framework directory exists. Check 4157 # whether the user has a private copy of the SDL 4158 # framework. 4159 env.Append(FRAMEWORKPATH = [library_frameworks]) 4160 SDL_private_framework = os.path.join(library_frameworks, 'SDL.framework/Headers') 4161 if os.path.isdir(SDL_private_framework): 4162 # Yes, so add its headers to the C preprocessor 4163 # path. 4164 env.Append(CPPPATH = [SDL_private_framework]) 4165 # else No, so omit the non-existant directory from 4166 # the C preprocessor path. 4167 # Check whether a system-wide SDL framework is 4168 # available. 4169 SDL_system_framework = '/Library/Frameworks/SDL.framework/Headers' 4170 if os.path.isdir(SDL_system_framework): 4171 env.Append(CPPPATH = [SDL_system_framework]) 4172 env.Append( 4173 CPPDEFINES = ['__unix__'], 4174 FRAMEWORKS = ['ApplicationServices', 'Cocoa'], 4175 LINKFLAGS = ['-Wl,-rpath,@loader_path/../Frameworks'], # Allow libraries & frameworks to go in app bundle 4176 ) 4177 if macos_add_frameworks: 4178 env.Append(FRAMEWORKS = ['SDL']) 4179 if self.user_settings.opengl or self.user_settings.opengles: 4180 env.Append(FRAMEWORKS = ['OpenGL']) 4181 # Settings to apply to Linux builds 4182 class LinuxPlatformSettings(_PlatformSettings): 4183 sharepath = '{prefix}/share/games/{program_target}'.format 4184 @property 4185 def ogllibs(self): 4186 user_settings = self.user_settings 4187 return [user_settings.opengles_lib, user_settings.egl_lib] if user_settings.opengles else ['GL', 'GLU'] 4188 @staticmethod 4189 def get_platform_objects(_empty=()): 4190 return _empty 4191 def adjust_environment(self,program,env): 4192 env.Append( 4193 CXXFLAGS = ['-pthread'], 4194 ) 4195 4196 def __init__(self,user_settings,__program_instance=itertools.count(1)): 4197 self.program_instance = next(__program_instance) 4198 self.user_settings = user_settings 4199 4200 def create_header_targets(self,__shared_header_file_list=[],__shared_cpp_dict={}): 4201 fs = SCons.Node.FS.get_default_fs() 4202 env = self.env 4203 builddir = self.builddir 4204 check_header_includes = __shared_cpp_dict.get(builddir) 4205 if check_header_includes is None: 4206 check_header_includes = builddir.File('check_header_includes.cpp') 4207 # Generate the list once, on first use. Any other targets 4208 # will reuse it. 4209 # 4210 # Touch the file into existence. It is always empty, but 4211 # must exist and have an extension of '.cpp'. 4212 check_header_includes = env.Textfile(target=check_header_includes, source=env.Value(''' 4213/* This file is always empty. It is only present to act as the source 4214 * file for SCons targets that test individual headers. 4215 */ 4216''')) 4217 __shared_cpp_dict[builddir] = check_header_includes 4218 # This is ugly, but all other known methods are worse. The 4219 # object file needs to depend on the header that created it 4220 # and recursively depend on headers that contributed to the 4221 # header that created it. Adding a dependency using 4222 # env.Depend creates the first-level dependency, but does 4223 # not recurse into the header. Overriding 4224 # get_found_includes will cause SCons to recurse in the 4225 # desired way. 4226 # 4227 # Calling get_found_includes requires parameters that are 4228 # not easily obtained here. There does not appear to be a 4229 # way to patch the node's dependency list to insert the 4230 # header other than to override get_found_includes. 4231 # 4232 # Newer SCons applied a performance optimization based on 4233 # using Python __slots__. Using __slots__ makes it a 4234 # runtime error to overwrite get_found_includes on the 4235 # instance, so overwrite the class method instead. However, 4236 # some instances of the class may not want this hook, so 4237 # keep a whitelist of nodes for which the hook is active. 4238 c = check_header_includes[0].__class__ 4239 # If the attribute is missing, this is the first time that 4240 # an env.Textfile has been created for this purpose. Create 4241 # the attribute so that subsequent passes do not add extra 4242 # copies of the same hook. 4243 # 4244 # Since a flag attribute is needed anyway, make it useful by 4245 # storing the hook whitelist in it. 4246 if not hasattr(c, '_dxx_node_header_target_set'): 4247 c._dxx_node_header_target_set = set() 4248 def __get_found_includes(node, env, scanner, path): 4249 # Always call the original and always return at 4250 # least what it returned. If this node is in the 4251 # whitelist, then also apply the hook logic. 4252 # Otherwise, this is a pass-through. 4253 r = node.__get_found_includes(env, scanner, path) 4254 return (r + [fs.File(env['DXX_EFFECTIVE_SOURCE'])]) \ 4255 if node in node._dxx_node_header_target_set else r 4256 c.__get_found_includes = c.get_found_includes 4257 c.get_found_includes = __get_found_includes 4258 # Update the whitelist here, outside the hasattr block. 4259 # This is necessary so that the hook is created once, but 4260 # every env.Textfile created by this block is whitelisted. 4261 c._dxx_node_header_target_set.add(check_header_includes[0]) 4262 if not __shared_header_file_list: 4263 headers = Git.pcall(['ls-files', '-z', '--', '*.h']).out.decode() 4264 if not headers: 4265 g = Git.pcall(['--version'], stderr=subprocess.STDOUT) 4266 raise SCons.Errors.StopError( 4267 "`git ls-files` failed. `git --version` failed. Check that Git is installed." \ 4268 if g.returncode else \ 4269 "`git ls-files` failed, but `git --version` works. Check that scons is run from a Git repository." 4270 ) 4271 # Filter out OS X related directories. Files in those 4272 # directories assume they are only ever built on OS X, so 4273 # they unconditionally include headers specific to OS X. 4274 excluded_directories = ( 4275 'common/arch/cocoa/', 4276 'common/arch/carbon/', 4277 ) 4278 __shared_header_file_list.extend([h for h in headers.split('\0') if h and not h.startswith(excluded_directories)]) 4279 if not __shared_header_file_list: 4280 raise SCons.Errors.StopError("`git ls-files` found headers, but none can be checked.") 4281 subbuilddir = builddir.Dir(self.srcdir, 'chi') 4282 Depends = env.Depends 4283 StaticObject = env.StaticObject 4284 CPPFLAGS_template = env['CPPFLAGS'] 4285 CPPFLAGS_no_sconf = CPPFLAGS_template + ['-g0', '-include', '$DXX_EFFECTIVE_SOURCE'] 4286 CPPFLAGS_with_sconf = ['-include', 'dxxsconf.h'] + CPPFLAGS_no_sconf 4287 CXXCOMSTR = env.__header_check_output_COMSTR 4288 CXXFLAGS = env['CXXFLAGS'] 4289 target = os.path.join('%s/${DXX_EFFECTIVE_SOURCE}%s' % (subbuilddir, env['OBJSUFFIX'])) 4290 for name in __shared_header_file_list: 4291 if not name: 4292 continue 4293 if self.srcdir == 'common' and not name.startswith('common/'): 4294 # Skip game-specific headers when testing common 4295 continue 4296 if self.srcdir[0] == 'd' and name[0] == 'd' and not name.startswith(self.srcdir): 4297 # Skip d1 in d2 and d2 in d1 4298 continue 4299 # Compiler feature headers cannot include dxxsconf.h because 4300 # it confuses the dependency resolver when SConf runs. 4301 # Calling code must include dxxsconf.h before including the 4302 # compiler feature header, so add the inclusion here. 4303 # 4304 # For best test coverage, only headers that must avoid 4305 # including dxxsconf.h receive an implicit include. Any 4306 # header which needs dxxsconf.h and can include it without 4307 # side effects must do so. 4308 StaticObject(target=target, 4309 CPPFLAGS=CPPFLAGS_with_sconf if name[:24] == 'common/include/compiler-' else CPPFLAGS_no_sconf, 4310 CXXCOMSTR=CXXCOMSTR, 4311 CXXFLAGS=CXXFLAGS, 4312 DXX_EFFECTIVE_SOURCE=name, 4313 source=check_header_includes) 4314 4315 def _cpp_output_StaticObject(self,target=None,source=None,DXX_EFFECTIVE_SOURCE='$SOURCE',*args,**kwargs): 4316 CPPFLAGS = kwargs.get('CPPFLAGS', None) 4317 CXXFLAGS = kwargs.get('CXXFLAGS', None) 4318 env = self.env 4319 OBJSUFFIX = env['OBJSUFFIX'] 4320 StaticObject = env.__cpp_output_StaticObject 4321 StaticObject( 4322 target='%s.i' % (target[:-len(OBJSUFFIX)] if target.endswith(OBJSUFFIX) else target), 4323 source=source, OBJSUFFIX='.i', 4324 # Bypass ccache 4325 CXXCOM=env._dxx_cxxcom_no_prefix, 4326 CPPFLAGS=(env['CPPFLAGS'] if CPPFLAGS is None else CPPFLAGS), 4327 CXXFLAGS=(env['CXXFLAGS'] if CXXFLAGS is None else CXXFLAGS) + ['-E'], 4328 CXXCOMSTR=env.__generate_cpp_output_COMSTR, 4329 DXX_EFFECTIVE_SOURCE=DXX_EFFECTIVE_SOURCE, 4330 ) 4331 return StaticObject(target=target, source=source, DXX_EFFECTIVE_SOURCE=DXX_EFFECTIVE_SOURCE, *args, **kwargs) 4332 4333 def _compilation_database_StaticObject(self,target=None,source=None,*args,**kwargs): 4334 env = self.env 4335 StaticObject = env.__compilation_database_StaticObject 4336 objects = StaticObject(target=target, source=source, *args, **kwargs) 4337 # Exclude ccache/distcc from the persisted command line. Store 4338 # `directory` as the directory of the `SConstruct` file. 4339 # Calls to `str` are necessary here to coerce SCons.Node objects 4340 # into strings that `json.dumps` can handle. 4341 relative_file_path = str(source) 4342 # clang documentation is silent on whether this must be 4343 # absolute, but `clang-check` refuses to find files when this is 4344 # relative. 4345 directory = env.Dir('.').get_abspath() 4346 CXXFLAGS_compilation_database = self.user_settings.CXXFLAGS_compilation_database 4347 _dxx_cxxcom_no_prefix = env._dxx_cxxcom_no_prefix 4348 if CXXFLAGS_compilation_database: 4349 _dxx_cxxcom_no_prefix = _dxx_cxxcom_no_prefix.replace('$CXXFLAGS', '$CXXFLAGS $CXXFLAGS_compilation_database') 4350 kwargs['CXXFLAGS_compilation_database'] = CXXFLAGS_compilation_database 4351 self._compilation_database_entries.extend([ 4352 { 4353 'command' : env.Override(kwargs).subst(_dxx_cxxcom_no_prefix, target=[o], source=source), 4354 'directory' : directory, 4355 'file' : relative_file_path, 4356 'output' : str(o), 4357 } 4358 for o in objects]) 4359 return objects 4360 4361 def create_special_target_nodes(self,archive): 4362 env = self.env 4363 StaticObject = env.StaticObject 4364 env._rebirth_nopch_StaticObject = StaticObject 4365 user_settings = self.user_settings 4366 if user_settings.register_cpp_output_targets: 4367 env.__cpp_output_StaticObject = StaticObject 4368 env.StaticObject = StaticObject = self._cpp_output_StaticObject 4369 if user_settings.compilation_database: 4370 env.__compilation_database_StaticObject = StaticObject 4371 env.StaticObject = self._compilation_database_StaticObject 4372 if user_settings.check_header_includes: 4373 # Create header targets before creating the PCHManager, so that 4374 # create_header_targets() does not call the PCHManager 4375 # StaticObject hook. 4376 self.create_header_targets() 4377 if user_settings.register_runtime_test_link_targets: 4378 self._register_runtime_test_link_targets() 4379 configure_pch_flags = archive.configure_pch_flags 4380 if configure_pch_flags or env.GetOption('clean'): 4381 self.pch_manager = PCHManager(self, configure_pch_flags, archive.pch_manager) 4382 4383 @staticmethod 4384 def _quote_cppdefine(s,f=repr,b2a_hex=binascii.b2a_hex): 4385 r = '' 4386 prior = False 4387 for c in f(s): 4388 # No xdigit support in str 4389 if c in '+,-./:=_' or (c.isalnum() and not (prior and (c.isdigit() or c in 'abcdefABCDEF'))): 4390 r += c 4391 elif c == '\n': 4392 r += r'\n' 4393 else: 4394 r += r'\\x%s' % b2a_hex(c.encode()).decode() 4395 prior = True 4396 continue 4397 prior = False 4398 return '\\"%s\\"' % r 4399 4400 @staticmethod 4401 def _encode_cppdefine_for_identifier(s,b32encode=base64.b32encode): 4402 # Identifiers cannot contain `=`; `z` is not generated by 4403 # b32encode, so use it in place of the pad character. 4404 if not s: 4405 return s 4406 return (b32encode(s.encode()) \ 4407 .rstrip() \ 4408 .decode() \ 4409 .replace('=', 'z') \ 4410 ) 4411 4412 def prepare_environment(self): 4413 # Prettier build messages...... 4414 # Move target to end of C++ source command 4415 target_string = ' -o $TARGET' 4416 env = self.env 4417 user_settings = self.user_settings 4418 # Expand $CXX immediately. 4419 # $CCFLAGS is never used. Remove it. 4420 cxxcom = env['CXXCOM'] \ 4421 .replace('$CXX ', '%s ' % env['CXX']) \ 4422 .replace('$CCFLAGS ', '') 4423 if target_string + ' ' in cxxcom: 4424 cxxcom = '%s%s' % (cxxcom.replace(target_string, ''), target_string) 4425 env._dxx_cxxcom_no_prefix = cxxcom 4426 distcc_path = user_settings.distcc 4427 distcc_cxxcom = ('%s %s' % (distcc_path, cxxcom)) if distcc_path else cxxcom 4428 env._dxx_cxxcom_no_ccache_prefix = distcc_cxxcom 4429 ccache_path = user_settings.ccache 4430 # Add ccache/distcc only for compile, not link 4431 if ccache_path: 4432 cxxcom = '%s %s' % (ccache_path, cxxcom) 4433 if distcc_path is not None: 4434 penv = self.env['ENV'] 4435 if distcc_path: 4436 penv['CCACHE_PREFIX'] = distcc_path 4437 elif distcc_path is not None: 4438 penv.pop('CCACHE_PREFIX', None) 4439 elif distcc_path: 4440 cxxcom = distcc_cxxcom 4441 # Expand $LINK immediately. 4442 linkcom = env['LINKCOM'].replace('$LINK ', '%s ' % env['LINK']) 4443 # Move target to end of link command 4444 if target_string + ' ' in linkcom: 4445 linkcom = '%s%s' % (linkcom.replace(target_string, ''), target_string) 4446 # Add $CXXFLAGS to link command 4447 cxxflags = '$CXXFLAGS ' 4448 if ' ' + cxxflags not in linkcom: 4449 linkflags = '$LINKFLAGS' 4450 linkcom = linkcom.replace(linkflags, cxxflags + linkflags) 4451 env.Replace( 4452 CXXCOM = cxxcom, 4453 LINKCOM = linkcom, 4454 ) 4455 # Custom DISTCC_HOSTS per target 4456 distcc_hosts = user_settings.distcc_hosts 4457 if distcc_hosts is not None: 4458 env['ENV']['DISTCC_HOSTS'] = distcc_hosts 4459 if user_settings.verbosebuild: 4460 env.__header_check_output_COMSTR = None 4461 env.__generate_cpp_output_COMSTR = None 4462 else: 4463 target = self.target[:3] 4464 format_tuple = (target, user_settings.builddir or '.') 4465 env.__header_check_output_COMSTR = "CHK %s %s $DXX_EFFECTIVE_SOURCE" % format_tuple 4466 env.__generate_cpp_output_COMSTR = "CPP %s %s $DXX_EFFECTIVE_SOURCE" % format_tuple 4467 env.Replace( 4468 CXXCOMSTR = "CXX %s %s $SOURCE" % format_tuple, 4469 # `builddir` is implicit since $TARGET is the full path to 4470 # the output 4471 LINKCOMSTR = "LD %s $TARGET" % target, 4472 RCCOMSTR = "RC %s %s $SOURCE" % format_tuple, 4473 ) 4474 4475 Werror = get_Werror_string(user_settings.CXXFLAGS) 4476 env.Prepend(CXXFLAGS = [ 4477 '-ftabstop=4', 4478 '-Wall', 4479 Werror + 'extra', 4480 Werror + 'format=2', 4481 Werror + 'missing-braces', 4482 Werror + 'missing-include-dirs', 4483 Werror + 'uninitialized', 4484 Werror + 'undef', 4485 Werror + 'pointer-arith', 4486 Werror + 'cast-qual', 4487 Werror + 'missing-declarations', 4488 Werror + 'vla', 4489 ]) 4490 env.Append( 4491 CXXFLAGS = ['-funsigned-char'], 4492 CPPPATH = ['common/include', 'common/main', '.'], 4493 CPPFLAGS = SCons.Util.CLVar('-Wno-sign-compare'), 4494 # PhysFS 2.1 and later deprecate functions PHYSFS_read, 4495 # PHYSFS_write, which Rebirth uses extensively. PhysFS 2.0 4496 # does not implement the new non-deprecated functions. 4497 # Disable the deprecation error until PhysFS 2.0 support is 4498 # removed. 4499 CPPDEFINES = [('PHYSFS_DEPRECATED', '')], 4500 ) 4501 add_flags = defaultdict(list) 4502 if user_settings.builddir: 4503 add_flags['CPPPATH'].append(user_settings.builddir) 4504 if user_settings.editor: 4505 add_flags['CPPPATH'].append('common/include/editor') 4506 CLVar = SCons.Util.CLVar 4507 for flags in ('CPPFLAGS', 'CXXFLAGS', 'LIBS', 'LINKFLAGS'): 4508 value = getattr(self.user_settings, flags) 4509 if value is not None: 4510 add_flags[flags] = CLVar(value) 4511 env.Append(**add_flags) 4512 if self.user_settings.lto: 4513 env.Append(CXXFLAGS = [ 4514 # clang does not support =N syntax 4515 ('-flto=%s' % self.user_settings.lto) if self.user_settings.lto > 1 else '-flto', 4516 ]) 4517 4518 @cached_property 4519 def platform_settings(self): 4520 # windows or *nix? 4521 platform_name = self.user_settings.host_platform 4522 try: 4523 machine = os.uname()[4] 4524 except AttributeError: 4525 machine = None 4526 message(self, "compiling on %r/%r for %r into %s%s" % (sys.platform, machine, platform_name, self.user_settings.builddir or '.', 4527 (' with prefix list %s' % str(self._argument_prefix_list)) if self._argument_prefix_list else '')) 4528 return self.get_platform_settings_type(platform_name)(self, self.user_settings) 4529 4530 @classmethod 4531 def get_platform_settings_type(cls,platform_name): 4532 # By happy accident, LinuxPlatformSettings produces the desired 4533 # result on OpenBSD, so there is no need for specific handling 4534 # of `platform_name == 'openbsd'`. 4535 return ( 4536 cls.Win32PlatformSettings if platform_name == 'win32' else ( 4537 cls.DarwinPlatformSettings if platform_name == 'darwin' else 4538 cls.LinuxPlatformSettings 4539 ) 4540 ) 4541 4542 @cached_property 4543 def env(self): 4544 platform_settings = self.platform_settings 4545 # Acquire environment object... 4546 user_settings = self.user_settings 4547 # Get traditional compiler environment variables 4548 kw = {} 4549 chost_aware_tools = ('CXX', 'RC') 4550 for cc in chost_aware_tools: 4551 value = getattr(user_settings, cc) 4552 if value is not None: 4553 kw[cc] = value 4554 tools = platform_settings.tools + ('textfile',) 4555 env = Environment(ENV = os.environ, tools = tools, **kw) 4556 CHOST = user_settings.CHOST 4557 if CHOST: 4558 denv = None 4559 for cc in chost_aware_tools: 4560 value = kw.get(cc) 4561 if value is not None: 4562 # If the user set a value, that value is always 4563 # used. 4564 continue 4565 if denv is None: 4566 # Lazy load denv. 4567 denv = Environment(tools = tools) 4568 # If the option in the base environment, ignoring both 4569 # user_settings and the process environment, matches the 4570 # option in the customized environment, then assume that 4571 # this is an SCons default, not a user-chosen value. 4572 value = denv.get(cc) 4573 if value and env.get(cc) == value: 4574 env[cc] = '{}-{}'.format(CHOST, value) 4575 platform_settings.adjust_environment(self, env) 4576 return env 4577 4578 def process_user_settings(self): 4579 env = self.env 4580 user_settings = self.user_settings 4581 compilation_database = self.user_settings.compilation_database 4582 if compilation_database: 4583 # Only set self._compilation_database_entries if collection 4584 # of the compilation database is enabled. 4585 try: 4586 compilation_database_entries = self.compilation_database_dict_fn_to_entries[compilation_database][1] 4587 except KeyError: 4588 compilation_database_entries = [] 4589 self.compilation_database_dict_fn_to_entries[compilation_database] = (env, compilation_database_entries) 4590 self._compilation_database_entries = compilation_database_entries 4591 4592 # Insert default CXXFLAGS. User-specified CXXFLAGS, if any, are 4593 # appended to this list and will override these defaults. The 4594 # defaults are present to ensure that a user who does not set 4595 # any options gets a good default experience. 4596 env.Prepend(CXXFLAGS = ['-g', '-O2']) 4597 # Raspberry Pi? 4598 if user_settings.raspberrypi == 'yes': 4599 rpi_vc_path = user_settings.rpi_vc_path 4600 message(self, "Raspberry Pi: using VideoCore libs in %r" % rpi_vc_path) 4601 env.Append( 4602 CPPDEFINES = ['RPI'], 4603 # use CPPFLAGS -isystem instead of CPPPATH because these those header files 4604 # are not very clean and would trigger some warnings we usually consider as 4605 # errors. Using them as system headers will make gcc ignoring any warnings. 4606 CPPFLAGS = [ 4607 '-isystem%s/include' % rpi_vc_path, 4608 '-isystem%s/include/interface/vcos/pthreads' % rpi_vc_path, 4609 '-isystem%s/include/interface/vmcs_host/linux' % rpi_vc_path, 4610 ], 4611 LIBPATH = '%s/lib' % rpi_vc_path, 4612 LIBS = ['bcm_host'], 4613 ) 4614 4615 def _register_runtime_test_link_targets(self): 4616 runtime_test_boost_tests = self.runtime_test_boost_tests 4617 if not runtime_test_boost_tests: 4618 return 4619 env = self.env 4620 user_settings = self.user_settings 4621 builddir = env.Dir(user_settings.builddir).Dir(self.srcdir) 4622 for test in runtime_test_boost_tests: 4623 LIBS = [] if test.nodefaultlibs else env['LIBS'][:] 4624 LIBS.append('boost_unit_test_framework') 4625 env.Program(target=builddir.File(test.target), source=test.source(self), LIBS=LIBS) 4626 4627class DXXArchive(DXXCommon): 4628 PROGRAM_NAME = 'DXX-Archive' 4629 _argument_prefix_list = None 4630 srcdir = 'common' 4631 target = 'dxx-common' 4632 RuntimeTest = DXXCommon.RuntimeTest 4633 runtime_test_boost_tests = ( 4634 RuntimeTest('test-serial', ( 4635 'common/unittest/serial.cpp', 4636 )), 4637 RuntimeTest('test-partial-range', ( 4638 'common/unittest/partial_range.cpp', 4639 )), 4640 RuntimeTest('test-valptridx-range', ( 4641 'common/unittest/valptridx-range.cpp', 4642 )), 4643 RuntimeTest('test-xrange', ( 4644 'common/unittest/xrange.cpp', 4645 )), 4646 RuntimeTest('test-zip', ( 4647 'common/unittest/zip.cpp', 4648 )), 4649 ) 4650 del RuntimeTest 4651 4652 def get_objects_common(self, 4653 __get_objects_common=DXXCommon.create_lazy_object_getter(( 4654'common/2d/2dsline.cpp', 4655'common/2d/bitblt.cpp', 4656'common/2d/bitmap.cpp', 4657'common/2d/box.cpp', 4658'common/2d/canvas.cpp', 4659'common/2d/circle.cpp', 4660'common/2d/disc.cpp', 4661'common/2d/gpixel.cpp', 4662'common/2d/line.cpp', 4663'common/2d/pixel.cpp', 4664'common/2d/rect.cpp', 4665'common/2d/rle.cpp', 4666'common/2d/scalec.cpp', 4667'common/3d/draw.cpp', 4668'common/3d/globvars.cpp', 4669'common/3d/instance.cpp', 4670'common/3d/matrix.cpp', 4671'common/3d/points.cpp', 4672'common/3d/rod.cpp', 4673'common/3d/setup.cpp', 4674'common/arch/sdl/event.cpp', 4675'common/arch/sdl/joy.cpp', 4676'common/arch/sdl/key.cpp', 4677'common/arch/sdl/mouse.cpp', 4678'common/arch/sdl/timer.cpp', 4679'common/arch/sdl/window.cpp', 4680'common/main/cli.cpp', 4681'common/main/cmd.cpp', 4682'common/main/cvar.cpp', 4683'common/maths/fixc.cpp', 4684'common/maths/rand.cpp', 4685'common/maths/tables.cpp', 4686'common/maths/vecmat.cpp', 4687'common/mem/mem.cpp', 4688'common/misc/error.cpp', 4689'common/misc/hash.cpp', 4690'common/misc/hmp.cpp', 4691'common/misc/ignorecase.cpp', 4692'common/misc/physfsrwops.cpp', 4693'common/misc/strutil.cpp', 4694'common/misc/vgrphys.cpp', 4695'common/misc/vgwphys.cpp', 4696)), \ 4697 __get_objects_use_adlmidi=DXXCommon.create_lazy_object_getter(( 4698'common/music/adlmidi_dynamic.cpp', 4699)), 4700 __get_objects_use_sdl1=DXXCommon.create_lazy_object_getter(( 4701'common/arch/sdl/rbaudio.cpp', 4702)) 4703 ): 4704 value = list(__get_objects_common(self)) 4705 extend = value.extend 4706 user_settings = self.user_settings 4707 if user_settings._enable_adlmidi(): 4708 extend(__get_objects_use_adlmidi(self)) 4709 if not user_settings.sdl2: 4710 extend(__get_objects_use_sdl1(self)) 4711 extend(self.platform_settings.get_platform_objects()) 4712 return value 4713 4714 get_objects_editor = DXXCommon.create_lazy_object_getter(( 4715'common/editor/autosave.cpp', 4716'common/editor/func.cpp', 4717'common/ui/button.cpp', 4718'common/ui/checkbox.cpp', 4719'common/ui/dialog.cpp', 4720'common/ui/file.cpp', 4721'common/ui/gadget.cpp', 4722'common/ui/icon.cpp', 4723'common/ui/inputbox.cpp', 4724'common/ui/keypad.cpp', 4725'common/ui/keypress.cpp', 4726'common/ui/listbox.cpp', 4727'common/ui/menu.cpp', 4728'common/ui/menubar.cpp', 4729'common/ui/message.cpp', 4730'common/ui/radio.cpp', 4731'common/ui/scroll.cpp', 4732'common/ui/ui.cpp', 4733'common/ui/uidraw.cpp', 4734'common/ui/userbox.cpp', 4735)) 4736 # for non-ogl 4737 get_objects_arch_sdl = DXXCommon.create_lazy_object_getter(( 4738'common/3d/clipper.cpp', 4739'common/texmap/ntmap.cpp', 4740'common/texmap/scanline.cpp', 4741'common/texmap/tmapflat.cpp', 4742)) 4743 # for ogl 4744 get_objects_arch_ogl = DXXCommon.create_lazy_object_getter(( 4745'common/arch/ogl/ogl_extensions.cpp', 4746'common/arch/ogl/ogl_sync.cpp', 4747)) 4748 get_objects_arch_sdlmixer = DXXCommon.create_lazy_object_getter(( 4749'common/arch/sdl/digi_mixer_music.cpp', 4750)) 4751 class Win32PlatformSettings(DXXCommon.Win32PlatformSettings): 4752 __get_platform_objects = LazyObjectConstructor.create_lazy_object_getter(( 4753'common/arch/win32/except.cpp', 4754'common/arch/win32/messagebox.cpp', 4755)) 4756 __get_sdl2_objects = LazyObjectConstructor.create_lazy_object_getter(( 4757'common/arch/win32/rbaudio.cpp', 4758)) 4759 def get_platform_objects(self): 4760 result = self.__get_platform_objects() 4761 if self.user_settings.sdl2: 4762 result += self.__get_sdl2_objects() 4763 return result 4764 4765 class DarwinPlatformSettings(DXXCommon.DarwinPlatformSettings): 4766 get_platform_objects = LazyObjectConstructor.create_lazy_object_getter(( 4767 'common/arch/cocoa/messagebox.mm', 4768 )) 4769 4770 def __init__(self,user_settings): 4771 user_settings = user_settings.clone() 4772 super().__init__(user_settings) 4773 if not user_settings.register_compile_target: 4774 return 4775 self.prepare_environment() 4776 self.process_user_settings() 4777 self.configure_environment() 4778 self.create_special_target_nodes(self) 4779 4780 def configure_environment(self): 4781 fs = SCons.Node.FS.get_default_fs() 4782 user_settings = self.user_settings 4783 builddir = user_settings.builddir or '.' 4784 try: 4785 builddir = fs.Dir(builddir) 4786 except TypeError as e: 4787 raise SCons.Errors.StopError(e.args[0]) 4788 tests = ConfigureTests(self.program_message_prefix, user_settings, self.platform_settings) 4789 log_file = builddir.File('sconf.log') 4790 env = self.env 4791 conf = env.Configure(custom_tests = { 4792 k.name:getattr(tests, k.name) for k in tests.custom_tests 4793 }, 4794 conf_dir=fs.Dir('.sconf_temp', builddir), 4795 log_file=log_file, 4796 config_h=fs.File('dxxsconf.h', builddir), 4797 clean=False, 4798 help=False 4799 ) 4800 self.configure_added_environment_flags = tests.successful_flags 4801 self.configure_pch_flags = None 4802 if not conf.env: 4803 return 4804 cc_env_strings = tests.ForceVerboseLog(conf.env) 4805 try: 4806 for k in tests.custom_tests: 4807 getattr(conf, k.name)() 4808 except SCons.Errors.StopError as e: 4809 raise SCons.Errors.StopError('{e0} See {log_file} for details.'.format(e0=e.args[0], log_file=log_file), *e.args[1:]) 4810 cc_env_strings.restore(conf.env) 4811 if user_settings.pch: 4812 conf.Define('DXX_VERSION_SEQ', self.DXX_VERSION_SEQ) 4813 if user_settings.record_sconf_results: 4814 conf.config_h_text += ''' 4815/* 4816%s 4817 */ 4818''' % '\n'.join(['check_%s=%s' % (n,v) for n,v in tests._sconf_results]) 4819 conf.Finish() 4820 self.configure_pch_flags = tests.pch_flags 4821 add_flags = self.configure_added_environment_flags 4822 CLVar = SCons.Util.CLVar 4823 for flags in ('CPPFLAGS', 'CXXFLAGS', 'LINKFLAGS'): 4824 value = getattr(user_settings, '%s_unchecked' % flags) 4825 if value: 4826 add_flags[flags] += CLVar(value) 4827 # Wrapping PHYSFS_read / PHYSFS_write is only useful when 4828 # Valgrind poisoning is enabled. If Valgrind poisoning is not 4829 # enabled, the wrappers are not built (unless the appropriate 4830 # preprocessor define is added to $CPPFLAGS by the user). The 4831 # link will fail if wrapping is enabled, Valgrind poisoning is 4832 # disabled, and the preprocessor define is not set. 4833 # 4834 # Only $LINKFLAGS are set here. The preprocessor define is only 4835 # relevant to 2 source files, both of which delegate it to 4836 # header common/misc/vg-wrap-physfs.h, so omit it from the 4837 # general options to avoid unnecessary rebuilds of other source 4838 # files. 4839 if user_settings.wrap_PHYSFS_read: 4840 add_flags['LINKFLAGS'].extend(( 4841 '-Wl,--wrap,PHYSFS_read', 4842 '-Wl,--wrap,PHYSFS_readSBE16', 4843 '-Wl,--wrap,PHYSFS_readSBE32', 4844 '-Wl,--wrap,PHYSFS_readSLE16', 4845 '-Wl,--wrap,PHYSFS_readSLE32', 4846 )) 4847 if user_settings.wrap_PHYSFS_write: 4848 add_flags['LINKFLAGS'].extend(( 4849 '-Wl,--wrap,PHYSFS_write', 4850 '-Wl,--wrap,PHYSFS_writeSBE16', 4851 '-Wl,--wrap,PHYSFS_writeSBE32', 4852 '-Wl,--wrap,PHYSFS_writeSLE16', 4853 '-Wl,--wrap,PHYSFS_writeSLE32', 4854 '-Wl,--wrap,PHYSFS_writeULE16', 4855 '-Wl,--wrap,PHYSFS_writeULE32', 4856 )) 4857 env.MergeFlags(add_flags) 4858 4859class DXXProgram(DXXCommon): 4860 LazyObjectState = DXXCommon.LazyObjectState 4861 def _generate_kconfig_ui_table(program,env,source,target,kconfig_static_object): 4862 builddir = program.builddir.Dir(program.target) 4863 kwargs = {} 4864 # Bypass ccache, if any, since this is a preprocess only 4865 # call. 4866 kwargs['CXXFLAGS'] = (env['CXXFLAGS'] or []) + ['-E'] 4867 kwargs['CPPDEFINES'] = (env['CPPDEFINES'] or []) + [ 4868 # Define these tokens to themselves so that 4869 # `#ifndef` does not try to redefine them. 4870 ('DXX_KCONFIG_UI_ENUM', 'DXX_KCONFIG_UI_ENUM'), 4871 ('DXX_KCONFIG_UI_LABEL', 'DXX_KCONFIG_UI_LABEL'), 4872 # Define this token to itself so that instances of 4873 # the kc_item structure are defined in the output 4874 # file. 4875 ('kc_item', 'kc_item'), 4876 ] 4877 cpp_kconfig_udlr = env._rebirth_nopch_StaticObject(target=str(target)[:-1] + 'ui-table.i', source=source[:-3] + 'ui-table.cpp', CXXCOM=env._dxx_cxxcom_no_ccache_prefix, **kwargs) 4878 generated_udlr_header = builddir.File('kconfig.udlr.h') 4879 generate_kconfig_udlr = env.File('similar/main/generate-kconfig-udlr.py') 4880 env.Command(generated_udlr_header, [cpp_kconfig_udlr, generate_kconfig_udlr], [[sys.executable, generate_kconfig_udlr, '$SOURCE', '$TARGET']]) 4881 env.Depends(kconfig_static_object, generated_udlr_header) 4882 4883 static_archive_construction = {} 4884 def _apply_target_name(self,name): 4885 return os.path.join(os.path.dirname(name), '.%s.%s' % (self.target, os.path.splitext(os.path.basename(name))[0])) 4886 def _apply_env_version_seq(self,env,_empty={}): 4887 return _empty if self.user_settings.pch else {'CPPDEFINES' : env['CPPDEFINES'] + [('DXX_VERSION_SEQ', self.DXX_VERSION_SEQ)]} 4888 get_objects_similar_arch_ogl = DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 4889'similar/arch/ogl/gr.cpp', 4890'similar/arch/ogl/ogl.cpp', 4891), 4892 transform_target=_apply_target_name, 4893 ), 4894 )) 4895 get_objects_similar_arch_sdl = DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 4896'similar/arch/sdl/gr.cpp', 4897), 4898 transform_target=_apply_target_name, 4899 ), 4900 )) 4901 get_objects_similar_arch_sdlmixer = DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 4902'similar/arch/sdl/digi_mixer.cpp', 4903'similar/arch/sdl/jukebox.cpp', 4904), 4905 transform_target=_apply_target_name, 4906 ), 4907 )) 4908 __get_objects_common = DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 4909'similar/2d/font.cpp', 4910'similar/2d/palette.cpp', 4911'similar/2d/pcx.cpp', 4912'similar/3d/interp.cpp', 4913'similar/arch/sdl/digi.cpp', 4914'similar/arch/sdl/digi_audio.cpp', 4915'similar/arch/sdl/init.cpp', 4916'similar/main/ai.cpp', 4917'similar/main/aipath.cpp', 4918'similar/main/automap.cpp', 4919'similar/main/bm.cpp', 4920'similar/main/cntrlcen.cpp', 4921'similar/main/collide.cpp', 4922'similar/main/config.cpp', 4923'similar/main/console.cpp', 4924'similar/main/controls.cpp', 4925'similar/main/credits.cpp', 4926'similar/main/digiobj.cpp', 4927'similar/main/effects.cpp', 4928'similar/main/endlevel.cpp', 4929'similar/main/fireball.cpp', 4930'similar/main/fuelcen.cpp', 4931'similar/main/fvi.cpp', 4932'similar/main/game.cpp', 4933'similar/main/gamecntl.cpp', 4934'similar/main/gamefont.cpp', 4935'similar/main/gamemine.cpp', 4936'similar/main/gamerend.cpp', 4937'similar/main/gamesave.cpp', 4938'similar/main/gameseg.cpp', 4939'similar/main/gameseq.cpp', 4940'similar/main/gauges.cpp', 4941'similar/main/hostage.cpp', 4942'similar/main/hud.cpp', 4943'similar/main/iff.cpp', 4944'similar/main/kmatrix.cpp', 4945'similar/main/laser.cpp', 4946'similar/main/lighting.cpp', 4947'similar/main/menu.cpp', 4948'similar/main/mglobal.cpp', 4949'similar/main/mission.cpp', 4950'similar/main/morph.cpp', 4951'similar/main/multi.cpp', 4952'similar/main/multibot.cpp', 4953'similar/main/newdemo.cpp', 4954'similar/main/newmenu.cpp', 4955'similar/main/object.cpp', 4956'similar/main/paging.cpp', 4957'similar/main/physics.cpp', 4958'similar/main/piggy.cpp', 4959'similar/main/player.cpp', 4960'similar/main/polyobj.cpp', 4961'similar/main/powerup.cpp', 4962'similar/main/render.cpp', 4963'similar/main/robot.cpp', 4964'similar/main/scores.cpp', 4965'similar/main/segment.cpp', 4966'similar/main/slew.cpp', 4967'similar/main/songs.cpp', 4968'similar/main/state.cpp', 4969'similar/main/switch.cpp', 4970'similar/main/terrain.cpp', 4971'similar/main/texmerge.cpp', 4972'similar/main/text.cpp', 4973'similar/main/titles.cpp', 4974'similar/main/vclip.cpp', 4975'similar/main/wall.cpp', 4976'similar/main/weapon.cpp', 4977'similar/misc/args.cpp', 4978), 4979 transform_target=_apply_target_name, 4980 ), LazyObjectState(sources=( 4981'similar/main/inferno.cpp', 4982), 4983 transform_env = (lambda self, env: {'CPPDEFINES' : env['CPPDEFINES'] + env.__dxx_CPPDEFINE_SHAREPATH + env.__dxx_CPPDEFINE_git_version}), 4984 transform_target=_apply_target_name, 4985 ), LazyObjectState(sources=( 4986'similar/main/kconfig.cpp', 4987), 4988 StaticObject_hook=_generate_kconfig_ui_table, 4989 transform_target=_apply_target_name, 4990 ), LazyObjectState(sources=( 4991'similar/misc/physfsx.cpp', 4992), 4993 transform_env = (lambda self, env: {'CPPDEFINES' : env['CPPDEFINES'] + env.__dxx_CPPDEFINE_SHAREPATH}), 4994 transform_target=_apply_target_name, 4995 ), LazyObjectState(sources=( 4996'similar/main/playsave.cpp', 4997), 4998 transform_env=_apply_env_version_seq, 4999 transform_target=_apply_target_name, 5000 ), 5001 )) 5002 get_objects_editor = DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 5003'similar/editor/centers.cpp', 5004'similar/editor/curves.cpp', 5005'similar/main/dumpmine.cpp', 5006'similar/editor/eglobal.cpp', 5007'similar/editor/elight.cpp', 5008'similar/editor/eobject.cpp', 5009'similar/editor/eswitch.cpp', 5010'similar/editor/group.cpp', 5011'similar/editor/info.cpp', 5012'similar/editor/kbuild.cpp', 5013'similar/editor/kcurve.cpp', 5014'similar/editor/kfuncs.cpp', 5015'similar/editor/kgame.cpp', 5016'similar/editor/khelp.cpp', 5017'similar/editor/kmine.cpp', 5018'similar/editor/ksegmove.cpp', 5019'similar/editor/ksegsel.cpp', 5020'similar/editor/ksegsize.cpp', 5021'similar/editor/ktmap.cpp', 5022'similar/editor/kview.cpp', 5023'similar/editor/med.cpp', 5024'similar/editor/meddraw.cpp', 5025'similar/editor/medmisc.cpp', 5026'similar/editor/medrobot.cpp', 5027'similar/editor/medsel.cpp', 5028'similar/editor/medwall.cpp', 5029'similar/editor/mine.cpp', 5030'similar/editor/objpage.cpp', 5031'similar/editor/segment.cpp', 5032'similar/editor/seguvs.cpp', 5033'similar/editor/texpage.cpp', 5034'similar/editor/texture.cpp', 5035), 5036 transform_target=_apply_target_name, 5037 ), 5038 )) 5039 5040 class UserSettings(DXXCommon.UserSettings): 5041 @property 5042 def BIN_DIR(self): 5043 # installation path 5044 return '%s/bin' % self.prefix 5045 # Settings to apply to mingw32 builds 5046 class Win32PlatformSettings(DXXCommon.Win32PlatformSettings): 5047 def adjust_environment(self,program,env): 5048 super().adjust_environment(program, env) 5049 rcdir = 'similar/arch/win32' 5050 j = os.path.join 5051 resfile = env.RES(target=j(program.user_settings.builddir, rcdir, '%s.res%s' % (program.target, env["OBJSUFFIX"])), source=j(rcdir, 'dxx-rebirth.rc')) 5052 Depends = env.Depends 5053 File = env.File 5054 Depends(resfile, File(j(rcdir, 'dxx-rebirth.manifest'))) 5055 Depends(resfile, File(j(rcdir, '%s.ico' % program.target))) 5056 self.platform_objects = [resfile] 5057 env.Prepend( 5058 CXXFLAGS = ['-fno-omit-frame-pointer'], 5059 RCFLAGS = ['-D%s' % d for d in program.env_CPPDEFINES], 5060 ) 5061 env.Append( 5062 LIBS = ['wsock32', 'ws2_32', 'winmm', 'mingw32'], 5063 ) 5064 # Settings to apply to Apple builds 5065 class DarwinPlatformSettings(DXXCommon.DarwinPlatformSettings): 5066 def adjust_environment(self,program,env): 5067 super().adjust_environment(program, env) 5068 VERSION = '%s.%s' % (program.VERSION_MAJOR, program.VERSION_MINOR) 5069 if (program.VERSION_MICRO): 5070 VERSION += '.%s' % program.VERSION_MICRO 5071 env.Replace( 5072 VERSION_NUM = VERSION, 5073 VERSION_NAME = '%s v%s' % (program.PROGRAM_NAME, VERSION), 5074 ) 5075 # Settings to apply to Linux builds 5076 class LinuxPlatformSettings(DXXCommon.LinuxPlatformSettings): 5077 def __init__(self,program,user_settings): 5078 super().__init__(program, user_settings) 5079 if user_settings.sharepath and user_settings.sharepath[-1] != '/': 5080 user_settings.sharepath += '/' 5081 def adjust_environment(self,program,env): 5082 super().adjust_environment(program, env) 5083 user_settings = self.user_settings 5084 if user_settings.need_dynamic_library_load(): 5085 env.Append(LIBS = ['dl']) 5086 5087 def get_objects_common(self, 5088 __get_objects_common=__get_objects_common, 5089 __get_objects_use_udp=DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 5090'similar/main/net_udp.cpp', 5091), 5092 transform_env= _apply_env_version_seq, 5093 transform_target=_apply_target_name, 5094 ), 5095 )) 5096 ): 5097 value = list(__get_objects_common(self)) 5098 extend = value.extend 5099 if self.user_settings.use_udp: 5100 extend(__get_objects_use_udp(self)) 5101 extend(self.platform_settings.platform_objects) 5102 return value 5103 5104 def __init__(self,prefix,variables,filtered_help): 5105 self.variables = variables 5106 self._argument_prefix_list = prefix 5107 user_settings = self.UserSettings(program=self) 5108 super().__init__(user_settings) 5109 compute_extra_version = Git.compute_extra_version() 5110 git_describe_version = compute_extra_version.describe 5111 extra_version = 'v%s.%s.%s' % (self.VERSION_MAJOR, self.VERSION_MINOR, self.VERSION_MICRO) 5112 if git_describe_version and not (extra_version == git_describe_version or extra_version[1:] == git_describe_version): 5113 extra_version += ' ' + git_describe_version 5114 print('===== %s %s %s =====' % (self.PROGRAM_NAME, extra_version, compute_extra_version.revparse_HEAD)) 5115 user_settings.register_variables(prefix, variables, filtered_help) 5116 5117 def init(self,substenv): 5118 user_settings = self.user_settings 5119 user_settings.read_variables(self, self.variables, substenv) 5120 archive = DXXProgram.static_archive_construction.get(user_settings.builddir, None) 5121 if archive is None: 5122 DXXProgram.static_archive_construction[user_settings.builddir] = archive = DXXArchive(user_settings) 5123 if user_settings.register_compile_target: 5124 self.prepare_environment(archive) 5125 self.process_user_settings() 5126 self.register_program() 5127 if user_settings.enable_build_failure_summary: 5128 self._register_build_failure_hook(archive) 5129 return self.variables.GenerateHelpText(self.env) 5130 5131 def prepare_environment(self,archive): 5132 super().prepare_environment() 5133 env = self.env 5134 env.MergeFlags(archive.configure_added_environment_flags) 5135 self.create_special_target_nodes(archive) 5136 sharepath = self.user_settings.sharepath 5137 # Must use [] here, not (), since it is concatenated with other 5138 # lists. 5139 env.__dxx_CPPDEFINE_SHAREPATH = [('DXX_SHAREPATH', self._quote_cppdefine(sharepath, f=str))] if sharepath else [] 5140 SCons.Script.Main.progress_display("{}: default sharepath is {!r}".format(self.program_message_prefix, sharepath or None)) 5141 env.Append( 5142 CPPDEFINES = [ 5143 self.env_CPPDEFINES, 5144 # For PRIi64 5145 ('__STDC_FORMAT_MACROS',), 5146 # Every file that sees `GameArg` must know whether a 5147 # sharepath exists. Most files do not need to know the 5148 # value of sharepath. Pass only its existence, so that 5149 # changing the sharepath does not needlessly rebuild 5150 # those files. 5151 ('DXX_USE_SHAREPATH', int(not not sharepath)), 5152 ], 5153 CPPPATH = [os.path.join(self.srcdir, 'main')], 5154 LIBS = ['m'], 5155 ) 5156 5157 def register_program(self): 5158 user_settings = self.user_settings 5159 exe_target = user_settings.program_name 5160 if not exe_target: 5161 exe_target = os.path.join(self.srcdir, self.target) 5162 if user_settings.editor: 5163 exe_target += '-editor' 5164 env = self.env 5165 PROGSUFFIX = env['PROGSUFFIX'] 5166 if PROGSUFFIX and not exe_target.endswith(PROGSUFFIX): 5167 exe_target += PROGSUFFIX 5168 exe_target = self.builddir.File(exe_target) 5169 if user_settings.register_compile_target: 5170 exe_target = self._register_program(exe_target) 5171 ToolchainInformation.show_partial_environ(env, user_settings, SCons.Script.Main.progress_display, self.program_message_prefix, append_newline='') 5172 if user_settings.register_install_target: 5173 bundledir = self._register_install(self.shortname, exe_target) 5174 if user_settings.macos_code_signing_identity is not None: 5175 self._macos_sign_and_notarize_bundle(self.shortname, bundledir) 5176 5177 def _register_program(self,exe_target): 5178 env = self.env 5179 user_settings = self.user_settings 5180 static_archive_construction = self.static_archive_construction[user_settings.builddir] 5181 objects = static_archive_construction.get_objects_common() 5182 git_describe_version = Git.compute_extra_version() if user_settings.git_describe_version else Git.UnknownExtraVersion 5183 env.__dxx_CPPDEFINE_git_version = [ 5184 ('DXX_git_commit', git_describe_version.revparse_HEAD), 5185 ('DXX_git_describe', self._encode_cppdefine_for_identifier(git_describe_version.describe)) 5186 ] 5187 objects.extend(self.get_objects_common()) 5188 if user_settings.sdlmixer: 5189 objects.extend(static_archive_construction.get_objects_arch_sdlmixer()) 5190 objects.extend(self.get_objects_similar_arch_sdlmixer()) 5191 if user_settings.opengl or user_settings.opengles: 5192 env.Append(LIBS = self.platform_settings.ogllibs) 5193 static_objects_arch = static_archive_construction.get_objects_arch_ogl 5194 objects_similar_arch = self.get_objects_similar_arch_ogl 5195 else: 5196 static_objects_arch = static_archive_construction.get_objects_arch_sdl 5197 objects_similar_arch = self.get_objects_similar_arch_sdl 5198 objects.extend(static_objects_arch()) 5199 objects.extend(objects_similar_arch()) 5200 if user_settings.editor: 5201 objects.extend(self.get_objects_editor()) 5202 objects.extend(static_archive_construction.get_objects_editor()) 5203 versid_build_environ = ['CXX', 'CPPFLAGS', 'CXXFLAGS', 'LINKFLAGS'] 5204 versid_cppdefines = env['CPPDEFINES'][:] 5205 extra_version = user_settings.extra_version 5206 if extra_version is None: 5207 extra_version = 'v%u.%u' % (self.VERSION_MAJOR, self.VERSION_MINOR) 5208 if self.VERSION_MICRO: 5209 extra_version += '.%u' % self.VERSION_MICRO 5210 git_describe_version_describe_output = git_describe_version.describe 5211 if git_describe_version_describe_output and not (extra_version and (extra_version == git_describe_version_describe_output or (extra_version[0] == 'v' and extra_version[1:] == git_describe_version_describe_output))): 5212 # Suppress duplicate output 5213 if extra_version: 5214 extra_version += ' ' 5215 extra_version += git_describe_version_describe_output 5216 get_version_head = StaticSubprocess.get_version_head 5217 ld_path = ToolchainInformation.get_tool_path(env, 'ld')[1] 5218 _quote_cppdefine = self._quote_cppdefine 5219 versid_cppdefines.extend([('DESCENT_%s' % k, _quote_cppdefine(env.get(k, ''))) for k in versid_build_environ]) 5220 versid_cppdefines.extend(( 5221 # VERSION_EXTRA is special. Format it as a string so that 5222 # it can be pasted into g_descent_version (in vers_id.cpp) 5223 # and look normal when printed as part of the startup 5224 # banner. Since it is pasted into g_descent_version, it is 5225 # NOT included in versid_build_environ as an independent 5226 # field. 5227 ('DESCENT_VERSION_EXTRA', _quote_cppdefine(extra_version, f=str)), 5228 ('DESCENT_CXX_version', _quote_cppdefine(get_version_head(env['CXX']))), 5229 ('DESCENT_LINK', _quote_cppdefine(ld_path.decode())), 5230 ('DESCENT_git_status', _quote_cppdefine(git_describe_version.status)), 5231 ('DESCENT_git_diffstat', _quote_cppdefine(git_describe_version.diffstat_HEAD)), 5232 )) 5233 if ld_path: 5234 versid_cppdefines.append( 5235 ('DESCENT_LINK_version', _quote_cppdefine(get_version_head(ld_path))), 5236 ) 5237 versid_build_environ.append('LINK_version') 5238 versid_build_environ.extend(( 5239 'CXX_version', 5240 'LINK', 5241 'git_status', 5242 'git_diffstat', 5243 )) 5244 versid_cppdefines.append(('DXX_RBE"(A)"', '"%s"' % ''.join(['A(%s)' % k for k in versid_build_environ]))) 5245 versid_environ = self.env['ENV'].copy() 5246 # Direct mode conflicts with __TIME__ 5247 versid_environ['CCACHE_NODIRECT'] = 1 5248 versid_cpp = 'similar/main/vers_id.cpp' 5249 versid_obj = env.StaticObject(target='%s%s%s' % (user_settings.builddir, self._apply_target_name(versid_cpp), self.env["OBJSUFFIX"]), source=versid_cpp, CPPDEFINES=versid_cppdefines, ENV=versid_environ) 5250 Depends = env.Depends 5251 # If $SOURCE_DATE_EPOCH is set, add its value as a shadow 5252 # dependency to ensure that vers_id.cpp is rebuilt if 5253 # $SOURCE_DATE_EPOCH on this run differs from $SOURCE_DATE_EPOCH 5254 # on last run. If it is unset, use None for this test, so that 5255 # unset differs from all valid values. Using a fixed value for 5256 # unset means that vers_id.cpp will not be rebuilt for this 5257 # dependency if $SOURCE_DATE_EPOCH was previously unset and is 5258 # currently unset, regardless of when it was most recently 5259 # built, but vers_id.cpp will be rebuilt if the user changes 5260 # $SOURCE_DATE_EPOCH and reruns scons. 5261 # 5262 # The process environment is not modified, so the default None 5263 # is never seen by tools that would reject it. 5264 Depends(versid_obj, env.Value(os.getenv('SOURCE_DATE_EPOCH'))) 5265 if user_settings.versid_depend_all: 5266 # Optional fake dependency to force vers_id to rebuild so 5267 # that it picks up the latest timestamp. 5268 Depends(versid_obj, objects) 5269 objects.append(versid_obj) 5270 # finally building program... 5271 return env.Program(target=exe_target, source = objects) 5272 5273 def _show_build_failure_summary(): 5274 from SCons.Script import GetBuildFailures 5275 build_failures = GetBuildFailures() 5276 if not build_failures: 5277 return 5278 node = [] 5279 command = [] 5280 total = 0 5281 for f in build_failures: 5282 if not f: 5283 continue 5284 e = f.executor 5285 if not e: 5286 continue 5287 total = total + 1 5288 e = e.get_build_env() 5289 try: 5290 e.__show_build_failure_summary 5291 except AttributeError: 5292 continue 5293 node.append('\t%s\n' % f.node) 5294 # Sacrifice some precision so that the printed output is 5295 # valid shell input. 5296 c = f.command 5297 command.append('\n %s' % (' '.join(c) if isinstance(c, list) else c)) 5298 if not total: 5299 return 5300 print("Failed target count: total=%u; targets with enable_build_failure_summary=1: %u" % (total, len(node))) 5301 if node: 5302 print('Failed node list:\n%sFailed command list:%s' % (''.join(node), ''.join(command))) 5303 5304 def _register_build_failure_hook(self,archive,show_build_failure_summary=[_show_build_failure_summary]): 5305 # Always mark the environment as report-enabled. 5306 # Only register the atexit hook once. 5307 env = self.env 5308 env.__show_build_failure_summary = None 5309 archive.env.__show_build_failure_summary = None 5310 if not show_build_failure_summary: 5311 return 5312 from atexit import register 5313 register(show_build_failure_summary.pop()) 5314 5315 def _register_install(self,dxxstr,exe_node): 5316 env = self.env 5317 if self.user_settings.host_platform != 'darwin': 5318 install_dir = '%s%s' % (self.user_settings.DESTDIR or '', self.user_settings.BIN_DIR) 5319 env.Install(install_dir, exe_node) 5320 env.Alias('install', install_dir) 5321 else: 5322 syspath = sys.path[:] 5323 cocoa = 'common/arch/cocoa' 5324 sys.path += [cocoa] 5325 import tool_bundle 5326 sys.path = syspath 5327 tool_bundle.TOOL_BUNDLE(env) 5328 # bundledir is an SCons.Environment.Dir corresponding to the 5329 # first argument passed to MakeBundle, after any adjustments 5330 # made inside MakeBundle. 5331 # 5332 # appfile is an SCons.Environment.File corresponding to the 5333 # game executable copied into the `.app` directory. 5334 bundledir, appfile = env.MakeBundle(os.path.join(self.user_settings.builddir, '%s.app' % self.PROGRAM_NAME), exe_node, 5335 'free.%s-rebirth' % dxxstr, os.path.join(cocoa, 'Info.plist'), 5336 typecode='APPL', creator='DCNT', 5337 icon_file=os.path.join(cocoa, '%s-rebirth.icns' % dxxstr), 5338 resources=[[os.path.join(self.srcdir, s), s] for s in ['English.lproj/InfoPlist.strings']]) 5339 if self.user_settings.macos_bundle_libs and not self.user_settings.macos_add_frameworks: 5340 message(self, 'Bundling libraries for %s' % (str(bundledir),)) 5341 # If the user has set $PATH, use it in preference to the 5342 # built-in SCons path. 5343 dylibenv = env['ENV'] 5344 user_environment_path = os.environ.get('PATH') 5345 if user_environment_path: 5346 dylibenv = dylibenv.copy() 5347 dylibenv['PATH'] = user_environment_path 5348 env.Command(target=bundledir.Dir('Contents/libs'), 5349 source=appfile, 5350 action="dylibbundler -od -b -x $SOURCE -d $TARGET", 5351 ENV = dylibenv) 5352 return bundledir 5353 5354 def _macos_sign_and_notarize_bundle(self,dxxstr,bundledir): 5355 if self.user_settings.host_platform != 'darwin' or bundledir is None: 5356 return 5357 env = self.env 5358 compressed_bundle = env.File(os.path.join(self.user_settings.builddir, '%s.zip' % self.PROGRAM_NAME)) 5359 if self.user_settings.macos_notarization_keychain_item is not None: 5360 env.Command(target=compressed_bundle, 5361 source=bundledir, 5362 action=[['common/arch/macos/notarize_dxx_bundles.zsh', '--signing-identity', self.user_settings.macos_code_signing_identity, '--notarization-keychain-profile', self.user_settings.macos_notarization_keychain_item, '--binary-name', '%s-rebirth' % (dxxstr), '--app-bundle-path', '$SOURCE', '--zip-path', '$TARGET']]) 5363 elif self.user_settings.macos_notarization_apple_id is not None and self.user_settings.macos_notarization_team_id is not None: 5364 if self.user_settings.macos_notarization_password is None: 5365 env.Command(target=compressed_bundle, 5366 source=bundledir, 5367 action=[['common/arch/macos/notarize_dxx_bundles.zsh', '--signing-identity', self.user_settings.macos_code_signing_identity, '--apple-id', self.user_settings.macos_notarization_apple_id, '--team-id', self.user_settings.macos_notarization_team_id, '--binary-name', '%s-rebirth' % (dxxstr), '--app-bundle-path', '$SOURCE', '--zip-path', '$TARGET']]) 5368 else: 5369 notarization_password_env = env['ENV'].copy() 5370 notarization_password_env['DXX_NOTARIZATION_PASSWORD'] = self.user_settings.macos_notarization_password 5371 env.Command(target=compressed_bundle, 5372 source=bundledir, 5373 action=[['common/arch/macos/notarize_dxx_bundles.zsh', '--signing-identity', self.user_settings.macos_code_signing_identity, '--apple-id', self.user_settings.macos_notarization_apple_id, '--team-id', self.user_settings.macos_notarization_team_id, '--binary-name', '%s-rebirth' % (dxxstr), '--app-bundle-path', '$SOURCE', '--zip-path', '$TARGET']], 5374 ENV = notarization_password_env) 5375 else: 5376 raise SCons.Errors.StopError('Either macos_notarization_keychain_item or both macos_notarization_apple_id and macos_notarization_team_id must be specified for notarization') 5377 5378class D1XProgram(DXXProgram): 5379 LazyObjectState = DXXProgram.LazyObjectState 5380 PROGRAM_NAME = 'D1X-Rebirth' 5381 target = \ 5382 srcdir = 'd1x-rebirth' 5383 shortname = 'd1x' 5384 env_CPPDEFINES = ('DXX_BUILD_DESCENT_I',) 5385 5386 # general source files 5387 def get_objects_common(self, 5388 __get_dxx_objects_common=DXXProgram.get_objects_common, \ 5389 __get_dsx_objects_common=DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 5390'd1x-rebirth/main/custom.cpp', 5391'd1x-rebirth/main/snddecom.cpp', 5392), 5393 ), 5394 LazyObjectState(sources=( 5395 # In Descent 1, bmread.cpp is used for both the regular 5396 # build and the editor build. 5397 # 5398 # In Descent 2, bmread.cpp is only used for the editor 5399 # build. 5400 # 5401 # Handle that inconsistency by defining it in the 5402 # per-program lookup (D1XProgram, D2XProgram), not in the 5403 # shared program lookup (DXXProgram). 5404'similar/main/bmread.cpp', 5405), 5406 transform_target=DXXProgram._apply_target_name, 5407 ), 5408 )) 5409 ): 5410 value = __get_dxx_objects_common(self) 5411 value.extend(__get_dsx_objects_common(self)) 5412 return value 5413 5414 # for editor 5415 def get_objects_editor(self, 5416 __get_dxx_objects_editor=DXXProgram.get_objects_editor, 5417 __get_dsx_objects_editor=DXXCommon.create_lazy_object_getter(( 5418'd1x-rebirth/editor/ehostage.cpp', 5419), 5420 )): 5421 value = list(__get_dxx_objects_editor(self)) 5422 value.extend(__get_dsx_objects_editor(self)) 5423 return value 5424 5425class D2XProgram(DXXProgram): 5426 LazyObjectState = DXXProgram.LazyObjectState 5427 PROGRAM_NAME = 'D2X-Rebirth' 5428 target = \ 5429 srcdir = 'd2x-rebirth' 5430 shortname = 'd2x' 5431 env_CPPDEFINES = ('DXX_BUILD_DESCENT_II',) 5432 5433 # general source files 5434 def get_objects_common(self, 5435 __get_dxx_objects_common=DXXProgram.get_objects_common, \ 5436 __get_dsx_objects_common=DXXCommon.create_lazy_object_getter(( 5437'd2x-rebirth/libmve/decoder8.cpp', 5438'd2x-rebirth/libmve/decoder16.cpp', 5439'd2x-rebirth/libmve/mve_audio.cpp', 5440'd2x-rebirth/libmve/mvelib.cpp', 5441'd2x-rebirth/libmve/mveplay.cpp', 5442'd2x-rebirth/main/escort.cpp', 5443'd2x-rebirth/main/gamepal.cpp', 5444'd2x-rebirth/main/movie.cpp', 5445 ))): 5446 value = __get_dxx_objects_common(self) 5447 value.extend(__get_dsx_objects_common(self)) 5448 return value 5449 5450 # for editor 5451 def get_objects_editor(self, 5452 __get_dxx_objects_editor=DXXProgram.get_objects_editor, \ 5453 __get_dsx_objects_editor=DXXCommon.create_lazy_object_states_getter((LazyObjectState(sources=( 5454 # See comment by D1XProgram reference to bmread.cpp for why 5455 # this is here instead of in the usual handling for similar 5456 # files. 5457'similar/main/bmread.cpp', 5458), 5459 transform_target=DXXProgram._apply_target_name, 5460 ), 5461 ))): 5462 value = list(__get_dxx_objects_editor(self)) 5463 value.extend(__get_dsx_objects_editor(self)) 5464 return value 5465 5466def register_program(program,other_program,variables,filtered_help,append,_itertools_product=itertools.product): 5467 s = program.shortname 5468 l = [v for (k,v) in ARGLIST if k == s or k == 'dxx'] or [other_program.shortname not in ARGUMENTS] 5469 # Legacy case: build one configuration. 5470 if len(l) == 1: 5471 try: 5472 if not int(l[0]): 5473 # If the user specifies an integer that evaluates to 5474 # False, this configuration is disabled. This allows 5475 # the user to build only one game, instead of both. 5476 return 5477 # Coerce to an empty string, then reuse the case for stacked 5478 # profiles. This is slightly less efficient, but reduces 5479 # maintenance by not having multiple sites that call 5480 # program(). 5481 l = [''] 5482 except ValueError: 5483 # If not an integer, treat this as a configuration profile. 5484 pass 5485 seen = set() 5486 add_seen = seen.add 5487 for e in l: 5488 for prefix in _itertools_product(*[v.split('+') for v in e.split(',')]): 5489 duplicates = set() 5490 # This would be simpler if set().add(V) returned the result 5491 # of `V not in self`, as seen before the add. Instead, it 5492 # always returns None. 5493 # 5494 # Test for presence, then add if not present. add() always 5495 # returns None, which coerces to False in boolean context. 5496 # This is slightly inefficient since we know the right hand 5497 # side will always be True after negation, so we ought to be 5498 # able to skip testing its truth value. However, the 5499 # alternative is to use a wrapper function that checks 5500 # membership, adds the value, and returns the result of the 5501 # test. Calling such a function is even less efficient. 5502 # 5503 # In most cases, this loop is not run often enough for the 5504 # efficiency to matter. 5505 prefix = tuple([ 5506 p for p in prefix \ 5507 if (p not in duplicates and not duplicates.add(p)) 5508 ]) 5509 if prefix in seen: 5510 continue 5511 add_seen(prefix) 5512 append(program(tuple(('%s%s%s' % (s, '_' if p else '', p) for p in prefix)) + prefix, variables, filtered_help)) 5513 5514def main(register_program,_d1xp=D1XProgram,_d2xp=D2XProgram): 5515 if os.path.isdir('build'): 5516 SConsignFile('build/.sconsign') 5517 variables = Variables([v for k, v in ARGLIST if k == 'site'] or ['site-local.py'], ARGUMENTS) 5518 filtered_help = FilterHelpText() 5519 dxx = [] 5520 register_program(_d1xp, _d2xp, variables, filtered_help, dxx.append) 5521 register_program(_d2xp, _d1xp, variables, filtered_help, dxx.append) 5522 substenv = SCons.Environment.SubstitutionEnvironment() 5523 variables.FormatVariableHelpText = filtered_help.FormatVariableHelpText 5524 variables.Update(substenv) 5525# show some help when running scons -h 5526 Help("""DXX-Rebirth, SConstruct file help: 5527 5528 Type 'scons' to build the binary. 5529 Type 'scons install' to build (if it hasn't been done) and install. 5530 Type 'scons -c' to clean up. 5531 5532 Extra options (add them to command line, like 'scons extraoption=value'): 5533 d1x=[0/1] Disable/enable D1X-Rebirth 5534 d1x=prefix-list Enable D1X-Rebirth with prefix-list modifiers 5535 d2x=[0/1] Disable/enable D2X-Rebirth 5536 d2x=prefix-list Enable D2X-Rebirth with prefix-list modifiers 5537 dxx=VALUE Equivalent to d1x=VALUE d2x=VALUE 5538""" + \ 5539 ''.join(['%s:\n%s' % (d.program_message_prefix, d.init(substenv)) for d in dxx]) 5540 ) 5541 if not dxx: 5542 return 5543 compilation_database_dict_fn_to_entries = DXXCommon.compilation_database_dict_fn_to_entries 5544 if compilation_database_dict_fn_to_entries: 5545 import json 5546 for fn, entry_tuple in compilation_database_dict_fn_to_entries.items(): 5547 env, entries = entry_tuple 5548 e = env.Entry(fn) 5549 # clang tools search for this specific filename. As a 5550 # convenience to the caller, if the target is a directory, 5551 # assume this filename in the named directory. If the 5552 # caller wishes to generate the file under some other name, 5553 # that will be respected, though clang tools may refuse to 5554 # see it. 5555 if e.isdir() or fn.endswith('/'): 5556 e = e.File('compile_commands.json') 5557 # Sort by key so that dictionary ordering changes do not 5558 # cause unnecessary regeneration of the file. 5559 env.Textfile(target=e, source=env.Value(json.dumps(entries, indent=4, sort_keys=True))) 5560 5561 unknown = variables.UnknownVariables() 5562 # Delete known unregistered variables 5563 unknown.pop('d1x', None) 5564 unknown.pop('d2x', None) 5565 unknown.pop('dxx', None) 5566 unknown.pop('site', None) 5567 ignore_unknown_variables = unknown.pop('ignore_unknown_variables', '0') 5568 if unknown: 5569 # Protect user from misspelled options by reporting an error. 5570 # Provide a way for the user to override the check, which might 5571 # be necessary if the user is setting an option that is only 5572 # understood by older (or newer) versions of SConstruct. 5573 try: 5574 ignore_unknown_variables = int(ignore_unknown_variables) 5575 except ValueError: 5576 ignore_unknown_variables = False 5577 j = 'Unknown values specified on command line.%s' % \ 5578''.join(['\n\t%s' % k for k in unknown.keys()]) 5579 if not ignore_unknown_variables: 5580 raise SCons.Errors.StopError(j + 5581'\nRemove unknown values or set ignore_unknown_variables=1 to continue.') 5582 print('warning: %s\nBuild will continue, but these values have no effect.\n' % j) 5583 5584main(register_program) 5585 5586# Local Variables: 5587# tab-width: 4 5588# End: 5589