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