658102a868
Previously, SConstruct tested whether static_assert worked with trivial constant expressions. Extend the test to more complicated expressions like the ones used in the program. Replace the typedef based approach with an inline enum to avoid warnings about unused local typedefs. Allow use with compilers that lack working static_assert or that cannot accept complicated expressions. Add a non-checking definition that uses the input parameter. This prevents warnings when types or constants are used only for the static_assert.
2432 lines
88 KiB
Python
2432 lines
88 KiB
Python
#SConstruct
|
|
|
|
# needed imports
|
|
import binascii
|
|
import errno
|
|
import subprocess
|
|
import sys
|
|
import os
|
|
import SCons.Util
|
|
|
|
def message(program,msg):
|
|
print "%s: %s" % (program.program_message_prefix, msg)
|
|
|
|
# endianess-checker
|
|
def checkEndian():
|
|
if ARGUMENTS.has_key('endian'):
|
|
r = ARGUMENTS['endian']
|
|
if r == "little" or r == "big":
|
|
return r
|
|
raise SCons.Errors.UserError("Unknown endian value: %s" % r)
|
|
import struct
|
|
array = struct.pack('cccc', '\x01', '\x02', '\x03', '\x04')
|
|
i = struct.unpack('i', array)
|
|
if i == struct.unpack('<i', array):
|
|
return "little"
|
|
elif i == struct.unpack('>i', array):
|
|
return "big"
|
|
return "unknown"
|
|
|
|
def get_Werror_string(l):
|
|
if l and '-Werror' in l:
|
|
return '-W'
|
|
return '-Werror='
|
|
|
|
class StaticSubprocess:
|
|
class CachedCall:
|
|
def __init__(self,out,err,returncode):
|
|
self.out = out
|
|
self.err = err
|
|
self.returncode = returncode
|
|
__call_cache = {}
|
|
@classmethod
|
|
def pcall(cls,args,stdout,stderr=None):
|
|
a = repr(args)
|
|
try:
|
|
return cls.__call_cache[a]
|
|
except KeyError:
|
|
pass
|
|
p = subprocess.Popen(args, executable=args[0], stdout=stdout, stderr=stderr, close_fds=True)
|
|
(o, e) = p.communicate()
|
|
cls.__call_cache[a] = c = cls.CachedCall(o, e, p.wait())
|
|
return c
|
|
|
|
class Git(StaticSubprocess):
|
|
__missing_git = StaticSubprocess.CachedCall(None, None, 1)
|
|
__path_git = None
|
|
@classmethod
|
|
def pcall(cls,args,stdout,stderr=None):
|
|
git = cls.__path_git
|
|
if git is None:
|
|
cls.__path_git = git = (os.environ.get('GIT', 'git').split(),)
|
|
git = git[0]
|
|
if not git:
|
|
return cls.__missing_git
|
|
return StaticSubprocess.pcall(git + args, stdout=stdout, stderr=stderr)
|
|
@classmethod
|
|
def spcall(cls,args,stdout,stderr=None):
|
|
g = cls.pcall(args, stdout, stderr)
|
|
if g.returncode:
|
|
return None
|
|
return g.out
|
|
|
|
class ConfigureTests:
|
|
class Collector:
|
|
def __init__(self):
|
|
self.tests = []
|
|
def __call__(self,f):
|
|
self.tests.append(f.__name__)
|
|
return f
|
|
class PreservedEnvironment:
|
|
def __init__(self,env,keys):
|
|
self.flags = {k: env.get(k, [])[:] for k in keys}
|
|
# Do not distribute tests when run under ccache
|
|
# Assume distcc users also use ccache. Harmless when wrong,
|
|
# saves a bit of latency when right.
|
|
self.CCACHE_PREFIX = env['ENV'].pop('CCACHE_PREFIX', None)
|
|
def restore(self,env):
|
|
env.Replace(**self.flags)
|
|
if self.CCACHE_PREFIX:
|
|
env['ENV']['CCACHE_PREFIX'] = self.CCACHE_PREFIX
|
|
def __getitem__(self,key):
|
|
return self.flags.__getitem__(key)
|
|
class ForceVerboseLog:
|
|
def __init__(self,env):
|
|
# Force verbose output to sconf.log
|
|
self.cc_env_strings = {}
|
|
for k in ['CXXCOMSTR']:
|
|
try:
|
|
self.cc_env_strings[k] = env[k]
|
|
del env[k]
|
|
except KeyError:
|
|
pass
|
|
def restore(self,env):
|
|
# Restore potential quiet build options
|
|
env.Replace(**self.cc_env_strings)
|
|
class pkgconfig:
|
|
__pkg_config_path_cache = {}
|
|
__pkg_config_data_cache = {}
|
|
@staticmethod
|
|
def _get_pkg_config_name(user_settings):
|
|
p = user_settings.PKG_CONFIG
|
|
if p is not None:
|
|
return p
|
|
p = user_settings.CHOST
|
|
if p:
|
|
return p + '-pkg-config'
|
|
return 'pkg-config'
|
|
@staticmethod
|
|
def _get_pkg_config_exec_path(context,message,pkgconfig):
|
|
if not pkgconfig:
|
|
message("pkg-config is disabled by user settings")
|
|
return pkgconfig
|
|
if os.sep in pkgconfig:
|
|
message("using pkg-config at user specified path %s" % pkgconfig)
|
|
return pkgconfig
|
|
# No path specified, search in $PATH
|
|
for p in os.environ.get('PATH', '').split(os.pathsep):
|
|
fp = os.path.join(p, pkgconfig)
|
|
try:
|
|
os.close(os.open(fp, os.O_RDONLY))
|
|
except OSError as e:
|
|
# Ignore on permission errors. If pkg-config is
|
|
# runnable but not readable, the user must
|
|
# specify its path.
|
|
if e.errno == errno.ENOENT or e.errno == errno.EACCES:
|
|
continue
|
|
raise
|
|
message("using pkg-config at discovered path %s" % fp)
|
|
return fp
|
|
message("no usable pkg-config %r found in $PATH" % pkgconfig)
|
|
@classmethod
|
|
def _get_pkg_config_path(cls,context,message,user_settings,display_name):
|
|
pkgconfig = cls._get_pkg_config_name(user_settings)
|
|
cache = cls.__pkg_config_path_cache
|
|
try:
|
|
return cache[pkgconfig]
|
|
except KeyError:
|
|
pass
|
|
cache[pkgconfig] = path = cls._get_pkg_config_exec_path(context, message, pkgconfig)
|
|
return path
|
|
@classmethod
|
|
def _find_pkg_config(cls,context,message,user_settings,pkgconfig_name,display_name):
|
|
message("checking %s pkg-config %s" % (display_name, pkgconfig_name))
|
|
pkgconfig = cls._get_pkg_config_path(context, message, user_settings, display_name)
|
|
if not pkgconfig:
|
|
message("skipping %s pkg-config" % display_name)
|
|
return {}
|
|
cmd = '%s --cflags --libs %s' % (pkgconfig, pkgconfig_name)
|
|
cache = cls.__pkg_config_data_cache
|
|
try:
|
|
flags = cache[cmd]
|
|
message("reusing %s settings from `%s`" % (display_name, cmd))
|
|
return flags
|
|
except KeyError as e:
|
|
message("reading %s settings from `%s`" % (display_name, cmd))
|
|
try:
|
|
flags = context.env.ParseFlags('!' + cmd)
|
|
except OSError as o:
|
|
message("%s pkg-config failed; user must add required flags via environment for `%s`" % (display_name, cmd))
|
|
flags = {}
|
|
cache[cmd] = flags
|
|
return flags
|
|
@classmethod
|
|
def merge(cls,context,message,user_settings,pkgconfig_name,display_name):
|
|
flags = cls._find_pkg_config(context, message, user_settings, pkgconfig_name, display_name)
|
|
return {k:v for k,v in flags.items() if v and (k[0] == 'C' or k[0] == 'L')}
|
|
# Force test to report failure
|
|
sconf_force_failure = 'force-failure'
|
|
# Force test to report success, and modify flags like it
|
|
# succeeded
|
|
sconf_force_success = 'force-success'
|
|
# Force test to report success, do not modify flags
|
|
sconf_assume_success = 'assume-success'
|
|
_implicit_test = Collector()
|
|
_custom_test = Collector()
|
|
implicit_tests = _implicit_test.tests
|
|
custom_tests = _custom_test.tests
|
|
comment_not_supported = '/* not supported */'
|
|
__flags_Werror = {k:['-Werror'] for k in ['CXXFLAGS']}
|
|
_cxx_conformance_cxx11 = 11
|
|
_cxx_conformance_cxx14 = 14
|
|
def __init__(self,msgprefix,user_settings,platform_settings):
|
|
self.msgprefix = msgprefix
|
|
self.user_settings = user_settings
|
|
self.platform_settings = platform_settings
|
|
self.successful_flags = {}
|
|
self.__cxx_conformance = None
|
|
self.__automatic_compiler_tests = {
|
|
'.cpp': self.check_cxx_works,
|
|
}
|
|
def message(self,msg):
|
|
print "%s: %s" % (self.msgprefix, msg)
|
|
@classmethod
|
|
def describe(cls,name):
|
|
f = getattr(cls, name)
|
|
if f.__doc__:
|
|
lines = f.__doc__.rstrip().split('\n')
|
|
if lines[-1].startswith("help:"):
|
|
return lines[-1][5:]
|
|
return None
|
|
@staticmethod
|
|
def _quote_macro_value(v):
|
|
return v.strip().replace('\n', ' \\\n')
|
|
def _check_forced(self,context,name):
|
|
return getattr(self.user_settings, 'sconf_%s' % name)
|
|
def _check_macro(self,context,macro_name,macro_value,test,**kwargs):
|
|
macro_value = self._quote_macro_value(macro_value)
|
|
r = self.Compile(context, text="""
|
|
#define {macro_name} {macro_value}
|
|
{test}
|
|
""".format(macro_name=macro_name, macro_value=macro_value, test=test), **kwargs)
|
|
if r:
|
|
context.sconf.Define(macro_name, macro_value)
|
|
else:
|
|
context.sconf.Define(macro_name, self.comment_not_supported)
|
|
def __compiler_test_already_done(self,context):
|
|
pass
|
|
def _check_compiler_works(self,context,ext):
|
|
self.__automatic_compiler_tests.pop(ext, self.__compiler_test_already_done)(context)
|
|
def _extend_successflags(self,k,v):
|
|
self.successful_flags.setdefault(k, []).extend(v)
|
|
def Compile(self,context,**kwargs):
|
|
if self.user_settings.lto:
|
|
self.Compile = self.Link
|
|
return self.Link(context, **kwargs)
|
|
return self._Test(context,action=context.TryCompile, **kwargs)
|
|
def Link(self,context,**kwargs):
|
|
return self._Test(context,action=context.TryLink, **kwargs)
|
|
def _Test(self,context,text,msg,action,main='',ext='.cpp',testflags={},successflags={},skipped=None,successmsg=None,failuremsg=None,expect_failure=False):
|
|
self._check_compiler_works(context,ext)
|
|
context.Message('%s: checking %s...' % (self.msgprefix, msg))
|
|
if skipped is not None:
|
|
context.Result('(skipped){skipped}'.format(skipped=skipped))
|
|
return
|
|
env_flags = self.PreservedEnvironment(context.env, successflags.keys() + testflags.keys() + self.__flags_Werror.keys() + ['CPPDEFINES'])
|
|
context.env.MergeFlags(successflags)
|
|
frame = None
|
|
forced = None
|
|
try:
|
|
1//0
|
|
except ZeroDivisionError:
|
|
frame = sys.exc_info()[2].tb_frame.f_back
|
|
while frame is not None:
|
|
co_name = frame.f_code.co_name
|
|
if co_name[0:6] == 'check_':
|
|
forced = self._check_forced(context, co_name[6:])
|
|
break
|
|
frame = frame.f_back
|
|
caller_modified_env_flags = self.PreservedEnvironment(context.env, self.__flags_Werror.keys() + testflags.keys())
|
|
# Always pass -Werror
|
|
context.env.Append(**self.__flags_Werror)
|
|
context.env.Append(**testflags)
|
|
if forced is None:
|
|
cc_env_strings = self.ForceVerboseLog(context.env)
|
|
undef_SDL_main = '\n#undef main /* avoid -Dmain=SDL_main from libSDL */\n'
|
|
r = action(text + undef_SDL_main + 'int main(int argc,char**argv){(void)argc;(void)argv;' + main + ';}\n', ext)
|
|
if expect_failure:
|
|
r = not r
|
|
cc_env_strings.restore(context.env)
|
|
context.Result((successmsg if r else failuremsg) or r)
|
|
else:
|
|
choices = (self.sconf_force_failure, self.sconf_force_success, self.sconf_assume_success)
|
|
if forced not in choices:
|
|
try:
|
|
forced = choices[int(forced)]
|
|
except ValueError:
|
|
raise SCons.Errors.UserError("Unknown force value for sconf_%s: %s" % (co_name[6:], forced))
|
|
except IndexError:
|
|
raise SCons.Errors.UserError("Out of range force value for sconf_%s: %s" % (co_name[6:], forced))
|
|
if forced == self.sconf_force_failure:
|
|
r = False
|
|
elif forced == self.sconf_force_success or forced == self.sconf_assume_success:
|
|
r = True
|
|
else:
|
|
raise SCons.Errors.UserError("Unknown force value for sconf_%s: %s" % (co_name[6:], forced))
|
|
if expect_failure:
|
|
r = not r
|
|
context.Result('(forced){inverted}{forced}'.format(forced=forced, inverted='(inverted)' if expect_failure else ''))
|
|
# On success, revert to base flags + successflags
|
|
# On failure, revert to base flags
|
|
if r and forced != self.sconf_assume_success:
|
|
caller_modified_env_flags.restore(context.env)
|
|
context.env.Replace(CPPDEFINES=env_flags['CPPDEFINES'])
|
|
CPPDEFINES = []
|
|
for v in successflags.pop('CPPDEFINES', []):
|
|
d = v
|
|
if isinstance(d, str):
|
|
d = (d,None)
|
|
if d[0] in ('_REENTRANT',):
|
|
# Blacklist defines that must not be moved to the
|
|
# configuration header.
|
|
CPPDEFINES.append(v)
|
|
continue
|
|
context.sconf.Define(d[0], d[1])
|
|
successflags['CPPDEFINES'] = CPPDEFINES
|
|
for (k,v) in successflags.items():
|
|
self._extend_successflags(k, v)
|
|
else:
|
|
env_flags.restore(context.env)
|
|
return r
|
|
def _soft_check_system_library(self,context,header,main,lib,successflags={}):
|
|
include = '\n'.join(['#include <%s>' % h for h in header])
|
|
# Test library. On success, good. On failure, test header to
|
|
# give the user more help.
|
|
if self.Link(context, text=include, main=main, msg='for usable library ' + lib, successflags=successflags):
|
|
return
|
|
if self.Compile(context, text=include, main=main, msg='for usable header ' + header[-1], testflags=successflags):
|
|
return (0, "Header %s is usable, but library %s is not usable." % (header[-1], lib))
|
|
if self.Compile(context, text=include, main=main, msg='for parseable header ' + header[-1], testflags=successflags):
|
|
return (1, "Header %s is parseable, but cannot compile the test program." % (header[-1]))
|
|
return (2, "Header %s is missing or unusable." % (header[-1]))
|
|
def _check_system_library(self,*args,**kwargs):
|
|
e = self._soft_check_system_library(*args, **kwargs)
|
|
if e:
|
|
raise SCons.Errors.StopError(e[1])
|
|
@_custom_test
|
|
def check_libphysfs(self,context):
|
|
main = '''
|
|
PHYSFS_File *f;
|
|
char b[1] = {0};
|
|
PHYSFS_init("");
|
|
f = PHYSFS_openWrite("a");
|
|
PHYSFS_sint64 w = PHYSFS_write(f, b, 1, 1);
|
|
(void)w;
|
|
PHYSFS_close(f);
|
|
f = PHYSFS_openRead("a");
|
|
PHYSFS_sint64 r = PHYSFS_read(f, b, 1, 1);
|
|
(void)r;
|
|
PHYSFS_close(f);
|
|
'''
|
|
l = ['physfs']
|
|
successflags = {'LIBS' : l}
|
|
e = self._soft_check_system_library(context, header=['physfs.h'], main=main, lib='physfs', successflags=successflags)
|
|
if not e:
|
|
return
|
|
if e[0] == 0:
|
|
self.message("physfs header usable; adding zlib and retesting library")
|
|
l.append('z')
|
|
e = self._soft_check_system_library(context, header=['physfs.h'], main=main, lib='physfs', successflags=successflags)
|
|
if e:
|
|
raise SCons.Errors.StopError(e[1])
|
|
@_custom_test
|
|
def check_libSDL(self,context):
|
|
successflags = self.pkgconfig.merge(context, self.message, self.user_settings, 'sdl', 'SDL')
|
|
self._check_system_library(context,header=['SDL.h'],main='''
|
|
SDL_RWops *ops = reinterpret_cast<SDL_RWops *>(argv);
|
|
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_CDROM | SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
|
SDL_QuitSubSystem(SDL_INIT_CDROM);
|
|
SDL_FreeRW(ops);
|
|
SDL_Quit();
|
|
''',
|
|
lib='SDL', successflags=successflags
|
|
)
|
|
@_custom_test
|
|
def check_SDL_mixer(self,context):
|
|
msg = 'whether to use SDL_mixer'
|
|
context.Display('%s: checking %s...' % (self.msgprefix, msg))
|
|
# SDL_mixer support?
|
|
context.Result(self.user_settings.sdlmixer)
|
|
if not self.user_settings.sdlmixer:
|
|
return
|
|
self._extend_successflags('CPPDEFINES', ['USE_SDLMIXER'])
|
|
successflags = self.pkgconfig.merge(context, self.message, self.user_settings, 'SDL_mixer', 'SDL_mixer')
|
|
if self.user_settings.host_platform == 'darwin':
|
|
successflags['FRAMEWORKS'] = ['SDL_mixer']
|
|
successflags['CPPPATH'] = [os.path.join(os.getenv("HOME"), 'Library/Frameworks/SDL_mixer.framework/Headers'), '/Library/Frameworks/SDL_mixer.framework/Headers']
|
|
self._check_system_library(context,header=['SDL_mixer.h'],main='''
|
|
int i = Mix_Init(MIX_INIT_FLAC | MIX_INIT_OGG);
|
|
(void)i;
|
|
Mix_Pause(0);
|
|
Mix_ResumeMusic();
|
|
Mix_Quit();
|
|
''',
|
|
lib='SDL_mixer', successflags=successflags)
|
|
@_implicit_test
|
|
def check_cxx_works(self,context):
|
|
"""
|
|
help:assume C++ compiler works
|
|
"""
|
|
if self.Link(context, text='', msg='whether C++ compiler and linker work'):
|
|
return
|
|
if self.Compile(context, text='', msg='whether C++ compiler works'):
|
|
raise SCons.Errors.StopError("C++ compiler works, but C++ linker does not work.")
|
|
raise SCons.Errors.StopError("C++ compiler does not work.")
|
|
@_custom_test
|
|
def check_compiler_template_parentheses_warning(self,context):
|
|
# Test for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51064
|
|
text = '''
|
|
template <unsigned S1, unsigned S2 = ((S1 + 4 - 1) & ~(4 - 1))>
|
|
struct T {};
|
|
'''
|
|
main = 'T<3> t;(void)t;'
|
|
if self.Cxx11Compile(context, text=text, main=main, msg='whether C++ compiler accepts parenthesized template computations', testflags={'CXXFLAGS' : ['-Wparentheses']}) or \
|
|
self.Cxx11Compile(context, text=text, main=main, msg='whether C++ compiler understands -Wno-parentheses', successflags={'CXXFLAGS' : ['-Wno-parentheses']}):
|
|
return
|
|
raise SCons.Errors.StopError("C++ compiler errors on template computed expressions, even with -Wno-parentheses.")
|
|
@_custom_test
|
|
def check_compiler_missing_field_initializers(self,context):
|
|
f = {'CXXFLAGS' : ['-Wmissing-field-initializers']}
|
|
text = 'struct A{int a;};'
|
|
main = 'A a{};(void)a;'
|
|
if not self.Cxx11Compile(context, text=text, main=main, msg='whether C++ compiler warns for {} initialization', testflags=f, expect_failure=True) or \
|
|
self.Cxx11Compile(context, text=text, main=main, msg='whether C++ compiler understands -Wno-missing-field-initializers', successflags={'CXXFLAGS' : ['-Wno-missing-field-initializers']}) or \
|
|
not self.Cxx11Compile(context, text=text, main=main, msg='whether C++ compiler always errors for {} initialization', expect_failure=True):
|
|
return
|
|
raise SCons.Errors.StopError("C++ compiler errors on {} initialization, even with -Wno-missing-field-initializers.")
|
|
@_custom_test
|
|
def check_attribute_error(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((error))
|
|
"""
|
|
f = '''
|
|
void a()__attribute__((__error__("a called")));
|
|
'''
|
|
if self.Compile(context, text=f, main='if("0"[0]==\'1\')a();', msg='whether compiler optimizes function __attribute__((__error__))'):
|
|
context.sconf.Define('DXX_HAVE_ATTRIBUTE_ERROR')
|
|
context.sconf.Define('__attribute_error(M)', '__attribute__((__error__(M)))')
|
|
else:
|
|
self.Compile(context, text=f, msg='whether compiler accepts function __attribute__((__error__))') and \
|
|
self.Compile(context, text=f, main='a();', msg='whether compiler understands function __attribute__((__error__))', expect_failure=True)
|
|
context.sconf.Define('__attribute_error(M)', self.comment_not_supported)
|
|
@_custom_test
|
|
def check_builtin_bswap(self,context):
|
|
b = '(void)__builtin_bswap{bits}(static_cast<uint{bits}_t>(argc));'
|
|
include = '''
|
|
#include <cstdint>
|
|
'''
|
|
main = '''
|
|
{b64}
|
|
{b32}
|
|
#ifdef DXX_HAVE_BUILTIN_BSWAP16
|
|
{b16}
|
|
#endif
|
|
'''.format(
|
|
b64 = b.format(bits=64),
|
|
b32 = b.format(bits=32),
|
|
b16 = b.format(bits=16),
|
|
)
|
|
if self.Cxx11Compile(context, text=include, main=main, msg='whether compiler implements __builtin_bswap{16,32,64} functions', successflags={'CPPDEFINES' : ['DXX_HAVE_BUILTIN_BSWAP', 'DXX_HAVE_BUILTIN_BSWAP16']}):
|
|
return
|
|
if self.Cxx11Compile(context, text=include, main=main, msg='whether compiler implements __builtin_bswap{32,64} functions', successflags={'CPPDEFINES' : ['DXX_HAVE_BUILTIN_BSWAP']}):
|
|
return
|
|
@_custom_test
|
|
def check_builtin_constant_p(self,context):
|
|
"""
|
|
help:assume compiler supports compile-time __builtin_constant_p
|
|
"""
|
|
f = '''
|
|
int c(int);
|
|
static int a(int b){
|
|
return __builtin_constant_p(b) ? 1 : %s;
|
|
}
|
|
'''
|
|
main = 'return a(1) + a(2)'
|
|
if self.Link(context, text=f % 'c(b)', main=main, msg='whether compiler optimizes __builtin_constant_p'):
|
|
context.sconf.Define('DXX_HAVE_BUILTIN_CONSTANT_P')
|
|
context.sconf.Define('dxx_builtin_constant_p(A)', '__builtin_constant_p(A)')
|
|
else:
|
|
self.Compile(context, text=f % '2', main=main, msg='whether compiler accepts __builtin_constant_p')
|
|
context.sconf.Define('dxx_builtin_constant_p(A)', '((void)(A),0)')
|
|
@_custom_test
|
|
def check_builtin_expect(self,context):
|
|
main = '''
|
|
return __builtin_expect(argc == 1, 1) ? 1 : 0;
|
|
'''
|
|
if self.Compile(context, text='', main=main, msg='whether compiler accepts __builtin_expect'):
|
|
context.sconf.Define('likely(A)', '__builtin_expect(!!(A), 1)')
|
|
context.sconf.Define('unlikely(A)', '__builtin_expect(!!(A), 0)')
|
|
else:
|
|
macro_value = '(!!(A))'
|
|
context.sconf.Define('likely(A)', macro_value)
|
|
context.sconf.Define('unlikely(A)', macro_value)
|
|
@_custom_test
|
|
def check_builtin_object_size(self,context):
|
|
"""
|
|
help:assume compiler supports __builtin_object_size
|
|
"""
|
|
f = '''
|
|
int a();
|
|
static inline int a(char *c){
|
|
return __builtin_object_size(c,0) == 4 ? 1 : %s;
|
|
}
|
|
'''
|
|
main = '''
|
|
char c[4];
|
|
return a(c);
|
|
'''
|
|
if self.Link(context, text=f % 'a()', main=main, msg='whether compiler optimizes __builtin_object_size'):
|
|
context.sconf.Define('DXX_HAVE_BUILTIN_OBJECT_SIZE')
|
|
else:
|
|
self.Compile(context, text=f % '2', main=main, msg='whether compiler accepts __builtin_object_size')
|
|
@_custom_test
|
|
def check_embedded_compound_statement(self,context):
|
|
f = '''
|
|
return ({ 1 + 2; });
|
|
'''
|
|
if self.Compile(context, text='', main=f, msg='whether compiler understands embedded compound statements'):
|
|
context.sconf.Define('DXX_BEGIN_COMPOUND_STATEMENT', '')
|
|
context.sconf.Define('DXX_END_COMPOUND_STATEMENT', '')
|
|
else:
|
|
context.sconf.Define('DXX_BEGIN_COMPOUND_STATEMENT', '[&]')
|
|
context.sconf.Define('DXX_END_COMPOUND_STATEMENT', '()')
|
|
context.sconf.Define('DXX_ALWAYS_ERROR_FUNCTION(F,S)', r'( DXX_BEGIN_COMPOUND_STATEMENT { \
|
|
void F() __attribute_error(S); \
|
|
F(); \
|
|
} DXX_END_COMPOUND_STATEMENT )')
|
|
@_custom_test
|
|
def check_attribute_always_inline(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((always_inline))
|
|
"""
|
|
macro_name = '__attribute_always_inline()'
|
|
macro_value = '__attribute__((__always_inline__))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=macro_name + 'static inline void a(){}', main='a();', msg='for function __attribute__((always_inline))')
|
|
@_custom_test
|
|
def check_attribute_alloc_size(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((alloc_size))
|
|
"""
|
|
macro_name = '__attribute_alloc_size(A,...)'
|
|
macro_value = '__attribute__((alloc_size(A, ## __VA_ARGS__)))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
char*a(int)__attribute_alloc_size(1);
|
|
char*b(int,int)__attribute_alloc_size(1,2);
|
|
""", msg='for function __attribute__((alloc_size))')
|
|
@_custom_test
|
|
def check_attribute_cold(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((cold))
|
|
"""
|
|
macro_name = '__attribute_cold'
|
|
macro_value = '__attribute__((cold))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
__attribute_cold char*a(int);
|
|
""", msg='for function __attribute__((cold))')
|
|
@_custom_test
|
|
def check_attribute_format_arg(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((format_arg))
|
|
"""
|
|
macro_name = '__attribute_format_arg(A)'
|
|
macro_value = '__attribute__((format_arg(A)))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
char*a(char*)__attribute_format_arg(1);
|
|
""", msg='for function __attribute__((format_arg))')
|
|
@_custom_test
|
|
def check_attribute_format_printf(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((format(printf)))
|
|
"""
|
|
macro_name = '__attribute_format_printf(A,B)'
|
|
macro_value = '__attribute__((format(printf,A,B)))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
int a(char*,...)__attribute_format_printf(1,2);
|
|
int b(char*)__attribute_format_printf(1,0);
|
|
""", msg='for function __attribute__((format(printf)))')
|
|
@_custom_test
|
|
def check_attribute_malloc(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((malloc))
|
|
"""
|
|
macro_name = '__attribute_malloc()'
|
|
macro_value = '__attribute__((malloc))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
int *a()__attribute_malloc();
|
|
""", msg='for function __attribute__((malloc))')
|
|
@_custom_test
|
|
def check_attribute_nonnull(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((nonnull))
|
|
"""
|
|
macro_name = '__attribute_nonnull(...)'
|
|
macro_value = '__attribute__((nonnull __VA_ARGS__))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
int a(int*)__attribute_nonnull();
|
|
int b(int*)__attribute_nonnull((1));
|
|
""", msg='for function __attribute__((nonnull))')
|
|
@_custom_test
|
|
def check_attribute_noreturn(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((noreturn))
|
|
"""
|
|
macro_name = '__attribute_noreturn'
|
|
macro_value = '__attribute__((noreturn))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test=macro_name + ' void a();void a(){for(;;);}', main='a();', msg='for function __attribute__((noreturn))')
|
|
@_custom_test
|
|
def check_attribute_used(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((used))
|
|
"""
|
|
macro_name = '__attribute_used'
|
|
macro_value = '__attribute__((used))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
static void a()__attribute_used;
|
|
static void a(){}
|
|
""", msg='for function __attribute__((used))')
|
|
@_custom_test
|
|
def check_attribute_unused(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((unused))
|
|
"""
|
|
macro_name = '__attribute_unused'
|
|
macro_value = '__attribute__((unused))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
__attribute_unused
|
|
static void a(){}
|
|
""", msg='for function __attribute__((unused))')
|
|
@_custom_test
|
|
def check_attribute_warn_unused_result(self,context):
|
|
"""
|
|
help:assume compiler supports __attribute__((warn_unused_result))
|
|
"""
|
|
macro_name = '__attribute_warn_unused_result'
|
|
macro_value = '__attribute__((warn_unused_result))'
|
|
self._check_macro(context,macro_name=macro_name,macro_value=macro_value,test="""
|
|
int a()__attribute_warn_unused_result;
|
|
int a(){return 0;}
|
|
""", msg='for function __attribute__((warn_unused_result))')
|
|
@_implicit_test
|
|
def check_cxx11(self,context):
|
|
"""
|
|
help:assume C++ compiler supports C++11
|
|
"""
|
|
return self._check_cxx_std_flag(context, ('-std=gnu++0x', '-std=c++0x'), self._cxx_conformance_cxx11)
|
|
@_implicit_test
|
|
def check_cxx14(self,context):
|
|
"""
|
|
help:assume C++ compiler supports C++14
|
|
"""
|
|
return self._check_cxx_std_flag(context, ('-std=gnu++14', '-std=c++14'), self._cxx_conformance_cxx14)
|
|
def _check_cxx_std_flag(self,context,flags,level):
|
|
for f in flags:
|
|
r = self.Compile(context, text='', msg='whether C++ compiler accepts {f}'.format(f=f), successflags={'CXXFLAGS': [f]})
|
|
if r:
|
|
return level
|
|
return 0
|
|
def Cxx11Compile(self,context,*args,**kwargs):
|
|
kwargs.setdefault('skipped', self.__skip_missing_cxx_std(context, self._cxx_conformance_cxx11, 'no C++11 support'))
|
|
return self.Compile(context,*args,**kwargs)
|
|
def Cxx14Compile(self,context,*args,**kwargs):
|
|
kwargs.setdefault('skipped', self.__skip_missing_cxx_std(context, self._cxx_conformance_cxx14, 'no C++14 support'))
|
|
return self.Compile(context,*args,**kwargs)
|
|
def __skip_missing_cxx_std(self,context,level,text):
|
|
if self.__cxx_conformance is None:
|
|
self.__cxx_conformance = self.check_cxx14(context) or self.check_cxx11(context)
|
|
if self.__cxx_conformance < level:
|
|
return text
|
|
@_implicit_test
|
|
def check_boost_array(self,context,**kwargs):
|
|
"""
|
|
help:assume Boost.Array works
|
|
"""
|
|
return self.Compile(context, msg='for Boost.Array', successflags={'CPPDEFINES' : ['DXX_HAVE_BOOST_ARRAY']}, **kwargs)
|
|
@_implicit_test
|
|
def check_cxx_array(self,context,**kwargs):
|
|
"""
|
|
help:assume <array> works
|
|
"""
|
|
return self.Cxx11Compile(context, msg='for <array>', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX_ARRAY']}, **kwargs)
|
|
@_implicit_test
|
|
def check_cxx_tr1_array(self,context,**kwargs):
|
|
"""
|
|
help:assume <tr1/array> works
|
|
"""
|
|
return self.Compile(context, msg='for <tr1/array>', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX_TR1_ARRAY']}, **kwargs)
|
|
@_custom_test
|
|
def _check_cxx_array(self,context):
|
|
include = '''
|
|
#include "compiler-array.h"
|
|
'''
|
|
main = '''
|
|
array<int,2>b;
|
|
b[0]=1;
|
|
'''
|
|
how = self.check_cxx_array(context, text=include, main=main) or self.check_boost_array(context, text=include, main=main) or self.check_cxx_tr1_array(context, text=include, main=main)
|
|
if not how:
|
|
raise SCons.Errors.StopError("C++ compiler does not support <array> or Boost.Array or <tr1/array>.")
|
|
@_custom_test
|
|
def check_cxx11_function_auto(self,context):
|
|
"""
|
|
help:assume compiler supports C++11 function declarator syntax
|
|
"""
|
|
f = '''
|
|
auto f()->int;
|
|
'''
|
|
if not self.Cxx11Compile(context, text=f, msg='for C++11 function declarator syntax'):
|
|
raise SCons.Errors.StopError("C++ compiler does not support C++11 function declarator syntax.")
|
|
def _check_static_assert_method(self,context,msg,f,testflags={},_Compile=Compile,_tdict={'expr' : 'true&&true'},_fdict={'expr' : 'false||false'},**kwargs):
|
|
return _Compile(self, context, text=f % _tdict, main='f(A());', msg=msg % 'true', testflags=testflags, **kwargs) and \
|
|
_Compile(self, context, text=f % _fdict, main='f(A());', msg=msg % 'false', expect_failure=True, successflags=testflags, **kwargs)
|
|
@_implicit_test
|
|
def check_boost_static_assert(self,context,f):
|
|
"""
|
|
help:assume Boost.StaticAssert works
|
|
"""
|
|
return self._check_static_assert_method(context, 'for Boost.StaticAssert when %s', f, testflags={'CPPDEFINES' : ['DXX_HAVE_BOOST_STATIC_ASSERT']})
|
|
@_implicit_test
|
|
def check_c_typedef_static_assert(self,context,f):
|
|
"""
|
|
help:assume C typedef-based static assertion works
|
|
"""
|
|
return self._check_static_assert_method(context, 'for C typedef static assertion when %s', f, testflags={'CPPDEFINES' : ['DXX_HAVE_C_TYPEDEF_STATIC_ASSERT']})
|
|
@_implicit_test
|
|
def check_cxx11_static_assert(self,context,f):
|
|
"""
|
|
help:assume compiler supports C++ intrinsic static_assert
|
|
"""
|
|
return self._check_static_assert_method(context, 'for C++11 intrinsic static_assert when %s', f, testflags={'CPPDEFINES' : ['DXX_HAVE_CXX11_STATIC_ASSERT']}, _Compile=ConfigureTests.Cxx11Compile)
|
|
@_custom_test
|
|
def _check_static_assert(self,context):
|
|
f = '''
|
|
#include "compiler-static_assert.h"
|
|
static_assert(%(expr)s, "global");
|
|
struct A
|
|
{
|
|
static const bool value = %(expr)s;
|
|
static_assert(%(expr)s, "class literal");
|
|
static_assert(A::value, "class static");
|
|
A()
|
|
{
|
|
static_assert(%(expr)s, "constructor literal");
|
|
static_assert(value, "constructor static");
|
|
}
|
|
};
|
|
template <typename>
|
|
struct B
|
|
{
|
|
static const bool value = %(expr)s;
|
|
static_assert(%(expr)s, "template class literal");
|
|
static_assert(value, "template class static");
|
|
B(A a)
|
|
{
|
|
static_assert(%(expr)s, "constructor literal");
|
|
static_assert(value, "constructor self static");
|
|
static_assert(A::value, "constructor static");
|
|
static_assert(a.value, "constructor member");
|
|
}
|
|
template <typename R>
|
|
B(B<R> &&b)
|
|
{
|
|
static_assert(%(expr)s, "template constructor literal");
|
|
static_assert(value, "template constructor self static");
|
|
static_assert(B<R>::value, "template constructor static");
|
|
static_assert(b.value, "template constructor member");
|
|
}
|
|
};
|
|
template <typename T>
|
|
static void f(B<T> b)
|
|
{
|
|
static_assert(%(expr)s, "template function literal");
|
|
static_assert(B<T>::value, "template function static");
|
|
static_assert(b.value, "template function member");
|
|
}
|
|
void f(A a);
|
|
void f(A a)
|
|
{
|
|
static_assert(%(expr)s, "function literal");
|
|
static_assert(A::value, "function static");
|
|
static_assert(a.value, "function member");
|
|
f(B<long>(B<int>(a)));
|
|
}
|
|
'''
|
|
how = self.check_cxx11_static_assert(context,f) or self.check_boost_static_assert(context,f) or self.check_c_typedef_static_assert(context,f)
|
|
@_implicit_test
|
|
def check_boost_type_traits(self,context,f):
|
|
"""
|
|
help:assume Boost.TypeTraits works
|
|
"""
|
|
return self.Compile(context, text=f, msg='for Boost.TypeTraits', ext='.cpp', successflags={'CPPDEFINES' : ['DXX_HAVE_BOOST_TYPE_TRAITS']})
|
|
@_implicit_test
|
|
def check_cxx11_type_traits(self,context,f):
|
|
"""
|
|
help:assume <type_traits> works
|
|
"""
|
|
return self.Cxx11Compile(context, text=f, msg='for <type_traits>', ext='.cpp', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX11_TYPE_TRAITS']})
|
|
@_custom_test
|
|
def _check_type_traits(self,context):
|
|
f = '''
|
|
#include "compiler-type_traits.h"
|
|
typedef tt::conditional<true,int,long>::type a;
|
|
typedef tt::conditional<false,int,long>::type b;
|
|
'''
|
|
if self.check_cxx11_type_traits(context, f) or self.check_boost_type_traits(context, f):
|
|
context.sconf.Define('DXX_HAVE_TYPE_TRAITS')
|
|
@_implicit_test
|
|
def check_boost_foreach(self,context,**kwargs):
|
|
"""
|
|
help:assume Boost.Foreach works
|
|
"""
|
|
return self.Compile(context, msg='for Boost.Foreach', successflags={'CPPDEFINES' : ['DXX_HAVE_BOOST_FOREACH']}, **kwargs)
|
|
@_implicit_test
|
|
def check_cxx11_range_for(self,context,**kwargs):
|
|
return self.Cxx11Compile(context, msg='for C++11 range-based for', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX11_RANGE_FOR']}, **kwargs)
|
|
@_custom_test
|
|
def _check_range_based_for(self,context):
|
|
include = '''
|
|
#include "compiler-range_for.h"
|
|
'''
|
|
main = '''
|
|
int b[2];
|
|
range_for(int&c,b)c=0;
|
|
'''
|
|
if not self.check_cxx11_range_for(context, text=include, main=main) and not self.check_boost_foreach(context, text=include, main=main):
|
|
raise SCons.Errors.StopError("C++ compiler does not support range-based for or Boost.Foreach.")
|
|
@_custom_test
|
|
def check_cxx11_constexpr(self,context):
|
|
f = '''
|
|
struct A {};
|
|
constexpr A a(){return {};}
|
|
'''
|
|
if not self.Cxx11Compile(context, text=f, msg='for C++11 constexpr'):
|
|
raise SCons.Errors.StopError("C++ compiler does not support constexpr.")
|
|
@_custom_test
|
|
def check_constexpr_union_constructor(self,context):
|
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56583
|
|
# <=gcc-4.7.x ICE on constexpr union constructors with anonymous
|
|
# substructure.
|
|
# Works fine without the substructure.
|
|
# Works fine in >=gcc-4.8 regardless of substructure.
|
|
f = '''
|
|
union U {
|
|
struct {
|
|
int a;
|
|
};
|
|
constexpr U(int b) :
|
|
a(b)
|
|
{
|
|
}
|
|
};
|
|
U a{640};
|
|
'''
|
|
self.Cxx11Compile(context, text=f, msg='whether compiler supports constexpr union constructors', successflags={'CPPDEFINES' : ['DXX_HAVE_CONSTEXPR_UNION_CONSTRUCTOR']})
|
|
@_implicit_test
|
|
def check_pch(self,context):
|
|
for how in [{'CXXFLAGS' : ['-x', 'c++-header']}]:
|
|
result = self.Compile(context, text='', msg='whether compiler supports pre-compiled headers', testflags=how)
|
|
if result:
|
|
self.pch_flags = how
|
|
return result
|
|
@_custom_test
|
|
def _check_pch(self,context):
|
|
self.pch_flags = None
|
|
msg = 'when to pre-compile headers'
|
|
context.Display('%s: checking %s...' % (self.msgprefix, msg))
|
|
if self.user_settings.pch:
|
|
count = int(self.user_settings.pch)
|
|
else:
|
|
count = 0
|
|
if count <= 0:
|
|
context.Result('never')
|
|
return
|
|
context.Display('if used at least %u time%s\n' % (count, 's' if count > 1 else ''))
|
|
if not self.check_pch(context):
|
|
raise SCons.Errors.StopError("C++ compiler does not support pre-compiled headers.")
|
|
@_custom_test
|
|
def check_cxx11_explicit_bool(self,context):
|
|
"""
|
|
help:assume compiler supports explicit operator bool
|
|
"""
|
|
f = '''
|
|
struct A{explicit operator bool();};
|
|
'''
|
|
r = self.Cxx11Compile(context, text=f, msg='for explicit operator bool')
|
|
macro_name = 'dxx_explicit_operator_bool'
|
|
if r:
|
|
context.sconf.Define(macro_name, 'explicit')
|
|
context.sconf.Define('DXX_HAVE_EXPLICIT_OPERATOR_BOOL')
|
|
else:
|
|
context.sconf.Define(macro_name, self.comment_not_supported)
|
|
@_custom_test
|
|
def _check_cxx11_explicit_delete(self,context):
|
|
# clang 3.4 warns when a named parameter to a deleted function
|
|
# is not used, even though there is no body in which it could be
|
|
# used, so every named parameter to a deleted function is always
|
|
# unused.
|
|
f = 'int a(int %s)=delete;'
|
|
if self.check_cxx11_explicit_delete_named(context, f):
|
|
# No bug: named parameters with explicitly deleted functions
|
|
# work correctly.
|
|
return
|
|
if self.check_cxx11_explicit_delete_named_unused(context, f):
|
|
# Clang bug hit. Called function adds -Wno-unused-parameter
|
|
# to work around the bug, but affected users will not get
|
|
# warnings about parameters that are unused in regular
|
|
# functions.
|
|
return
|
|
if self.check_cxx11_explicit_delete_anonymous(context, f):
|
|
raise SCons.Errors.StopError("C++ compiler rejects explicitly deleted functions with named parameters, even with -Wno-unused-parameter.")
|
|
raise SCons.Errors.StopError("C++ compiler does not support explicitly deleted functions.")
|
|
@_implicit_test
|
|
def check_cxx11_explicit_delete_named(self,context,f):
|
|
"""
|
|
help:assume compiler supports explicitly deleted functions with named parameters
|
|
"""
|
|
return self.Cxx11Compile(context, text=f % 'b', msg='for explicitly deleted functions with named parameters')
|
|
@_implicit_test
|
|
def check_cxx11_explicit_delete_named_unused(self,context,f):
|
|
"""
|
|
help:assume compiler supports explicitly deleted functions with named parameters with -Wno-unused-parameter
|
|
"""
|
|
return self.Cxx11Compile(context, text=f % 'b', msg='for explicitly deleted functions with named parameters and -Wno-unused-parameter', successflags={'CXXFLAGS' : ['-Wno-unused-parameter']})
|
|
@_implicit_test
|
|
def check_cxx11_explicit_delete_anonymous(self,context,f):
|
|
"""
|
|
help:assume compiler supports explicitly deleted functions with anonymous parameters
|
|
"""
|
|
return self.Cxx11Compile(context, text=f % '', msg='for explicitly deleted functions with anonymous parameters')
|
|
@_implicit_test
|
|
def check_cxx11_free_begin(self,context,**kwargs):
|
|
return self.Cxx11Compile(context, msg='for C++11 functions begin(), end()', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX11_BEGIN']}, **kwargs)
|
|
@_implicit_test
|
|
def check_boost_free_begin(self,context,**kwargs):
|
|
return self.Compile(context, msg='for Boost.Range functions begin(), end()', successflags={'CPPDEFINES' : ['DXX_HAVE_BOOST_BEGIN']}, **kwargs)
|
|
@_custom_test
|
|
def _check_free_begin_function(self,context):
|
|
f = '''
|
|
#include "compiler-begin.h"
|
|
struct A {
|
|
typedef int *iterator;
|
|
typedef const int *const_iterator;
|
|
iterator begin(){return 0;}
|
|
iterator end(){return 0;}
|
|
const_iterator begin() const{return 0;}
|
|
const_iterator end() const{return 0;}
|
|
};
|
|
#define F(C){\
|
|
C int a[1]{0};\
|
|
C A b{};\
|
|
if(begin(a)||end(a)||begin(b)||end(b))return 1;\
|
|
}
|
|
'''
|
|
main = 'F()F(const)'
|
|
if not self.check_cxx11_free_begin(context, text=f, main=main) and not self.check_boost_free_begin(context, text=f, main=main):
|
|
raise SCons.Errors.StopError("C++ compiler does not support free functions begin() and end().")
|
|
@_implicit_test
|
|
def check_cxx11_addressof(self,context,**kwargs):
|
|
return self.Cxx11Compile(context, msg='for C++11 function addressof()', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX11_ADDRESSOF']}, **kwargs)
|
|
@_implicit_test
|
|
def check_boost_addressof(self,context,**kwargs):
|
|
return self.Compile(context, msg='for Boost.Utility function addressof()', successflags={'CPPDEFINES' : ['DXX_HAVE_BOOST_ADDRESSOF']}, **kwargs)
|
|
@_custom_test
|
|
def _check_free_addressof_function(self,context):
|
|
f = '''
|
|
#include "compiler-addressof.h"
|
|
struct A {
|
|
void operator&();
|
|
};
|
|
'''
|
|
main = '''
|
|
A b;
|
|
return addressof(b) != 0;
|
|
'''
|
|
if not self.check_cxx11_addressof(context, text=f, main=main) and not self.check_boost_addressof(context, text=f, main=main):
|
|
raise SCons.Errors.StopError("C++ compiler does not support free function addressof().")
|
|
@_custom_test
|
|
def check_cxx14_exchange(self,context):
|
|
f = '''
|
|
#include "compiler-exchange.h"
|
|
'''
|
|
self.Cxx14Compile(context, text=f, main='return exchange(argc, 5)', msg='for C++14 exchange', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX14_EXCHANGE']})
|
|
@_custom_test
|
|
def check_cxx14_integer_sequence(self,context):
|
|
f = '''
|
|
#include <utility>
|
|
using std::integer_sequence;
|
|
using std::index_sequence;
|
|
'''
|
|
self.Cxx14Compile(context, text=f, msg='for C++14 integer_sequence', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX14_INTEGER_SEQUENCE']})
|
|
@_custom_test
|
|
def check_cxx14_make_unique(self,context):
|
|
f = '''
|
|
#include "compiler-make_unique.h"
|
|
'''
|
|
main = '''
|
|
make_unique<int>(0);
|
|
make_unique<int[]>(1);
|
|
'''
|
|
self.Cxx14Compile(context, text=f, main=main, msg='for C++14 make_unique', successflags={'CPPDEFINES' : ['DXX_HAVE_CXX14_MAKE_UNIQUE']})
|
|
@_implicit_test
|
|
def check_cxx11_inherit_constructor(self,context,text,fmtargs,**kwargs):
|
|
"""
|
|
help:assume compiler supports inheriting constructors
|
|
"""
|
|
blacklist_clang_libcxx = '''
|
|
/* Test for bug where clang + libc++ + constructor inheritance causes a
|
|
* compilation failure when returning nullptr.
|
|
*
|
|
* Works: gcc
|
|
* Works: clang + gcc libstdc++
|
|
* Works: old clang + old libc++ (cutoff date unknown).
|
|
* Works: new clang + new libc++ + unique_ptr<T>
|
|
* Fails: new clang + new libc++ + unique_ptr<T[]> (v3.6.0 confirmed broken).
|
|
|
|
memory: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
|
|
typename enable_if<__same_or_less_cv_qualified<_Pp, pointer>::value, __nat>::type = __nat()) _NOEXCEPT
|
|
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
.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
|
|
using B::B;
|
|
^
|
|
.sconf_temp/conftest_43.cpp:30:2: note: while substituting deduced template arguments into function template 'I' [with _Pp = I]
|
|
return nullptr;
|
|
^
|
|
*/
|
|
#include <memory>
|
|
class I : std::unique_ptr<int[]>
|
|
{
|
|
public:
|
|
typedef std::unique_ptr<int[]> B;
|
|
using B::B;
|
|
};
|
|
I a();
|
|
I a()
|
|
{
|
|
return nullptr;
|
|
}
|
|
'''
|
|
macro_value = self._quote_macro_value('''
|
|
typedef B,##__VA_ARGS__ _dxx_constructor_base_type;
|
|
using _dxx_constructor_base_type::_dxx_constructor_base_type;''')
|
|
if self.Cxx11Compile(context, text=blacklist_clang_libcxx + text.format(macro_value=macro_value, **fmtargs), msg='for C++11 inherited constructors with good unique_ptr<T[]> support', **kwargs):
|
|
return macro_value
|
|
return None
|
|
@_implicit_test
|
|
def check_cxx11_variadic_forward_constructor(self,context,text,fmtargs,**kwargs):
|
|
"""
|
|
help:assume compiler supports variadic template-based constructor forwarding
|
|
"""
|
|
macro_value = self._quote_macro_value('''
|
|
template <typename... Args>
|
|
D(Args&&... args) :
|
|
B,##__VA_ARGS__(std::forward<Args>(args)...) {}
|
|
''')
|
|
if self.Cxx11Compile(context, text='#include <algorithm>\n' + text.format(macro_value=macro_value, **fmtargs), msg='for C++11 variadic templates on constructors', **kwargs):
|
|
return macro_value
|
|
return None
|
|
@_custom_test
|
|
def _check_forward_constructor(self,context):
|
|
text = '''
|
|
#define {macro_name}{macro_parameters} {macro_value}
|
|
struct A {{
|
|
A(int){{}}
|
|
}};
|
|
struct B:A {{
|
|
{macro_name}(B,A);
|
|
}};
|
|
'''
|
|
macro_name = 'DXX_INHERIT_CONSTRUCTORS'
|
|
macro_parameters = '(D,B,...)'
|
|
# C++03 support is possible with enumerated out template
|
|
# variations. If someone finds a worthwhile compiler without
|
|
# variadic templates, enumerated templates can be added.
|
|
for f in (self.check_cxx11_inherit_constructor, self.check_cxx11_variadic_forward_constructor):
|
|
macro_value = f(context, text=text, main='B(0)', fmtargs={'macro_name':macro_name, 'macro_parameters':macro_parameters})
|
|
if macro_value:
|
|
break
|
|
if not macro_value:
|
|
raise SCons.Errors.StopError("C++ compiler does not support constructor forwarding.")
|
|
context.sconf.Define(macro_name + macro_parameters, macro_value)
|
|
@_custom_test
|
|
def check_cxx11_template_alias(self,context):
|
|
text = '''
|
|
template <typename>
|
|
struct A;
|
|
template <typename T>
|
|
using B = A<T>;
|
|
'''
|
|
main = '''
|
|
A<int> *a = 0;
|
|
B<int> *b = a;
|
|
(void)b;
|
|
'''
|
|
if self.Cxx11Compile(context, text=text, main=main, msg='for C++11 template aliases'):
|
|
context.sconf.Define('DXX_HAVE_CXX11_TEMPLATE_ALIAS')
|
|
@_custom_test
|
|
def check_cxx11_ref_qualifier(self,context):
|
|
text = '''
|
|
struct A {
|
|
int a()&{return 1;}
|
|
int a()&&{return 2;}
|
|
};
|
|
'''
|
|
main = '''
|
|
A a;
|
|
return a.a() != A().a();
|
|
'''
|
|
if self.Cxx11Compile(context, text=text, main=main, msg='for C++11 reference qualified methods'):
|
|
context.sconf.Define('DXX_HAVE_CXX11_REF_QUALIFIER')
|
|
@_custom_test
|
|
def check_deep_tuple(self,context):
|
|
text = '''
|
|
#include <tuple>
|
|
static inline std::tuple<{type}> make() {{
|
|
return std::make_tuple({value});
|
|
}}
|
|
static void a(){{
|
|
std::tuple<{type}> t = make();
|
|
(void)t;
|
|
}}
|
|
'''
|
|
count = 20
|
|
if self.Compile(context, text=text.format(type=','.join(('int',)*count), value=','.join(('0',)*count)), main='a()', msg='whether compiler handles 20-element tuples'):
|
|
return
|
|
count = 2
|
|
if self.Compile(context, text=text.format(type=','.join(('int',)*count), value=','.join(('0',)*count)), main='a()', msg='whether compiler handles 2-element tuples'):
|
|
raise SCons.Errors.StopError("Compiler cannot handle tuples of 20 elements. Raise the template instantiation depth.")
|
|
raise SCons.Errors.StopError("Compiler cannot handle tuples of 2 elements.")
|
|
@_implicit_test
|
|
def check_poison_valgrind(self,context):
|
|
'''
|
|
help:add Valgrind annotations; wipe certain freed memory when running under Valgrind
|
|
'''
|
|
context.Message('%s: checking %s...' % (self.msgprefix, 'whether to use Valgrind poisoning'))
|
|
r = 'valgrind' in self.user_settings.poison
|
|
context.Result(r)
|
|
if not r:
|
|
return
|
|
text = '''
|
|
#include "poison.h"
|
|
'''
|
|
main = '''
|
|
DXX_MAKE_MEM_UNDEFINED(&argc, sizeof(argc));
|
|
'''
|
|
if self.Compile(context, text=text, main=main, msg='whether Valgrind memcheck header works', successflags={'CPPDEFINES' : ['DXX_HAVE_POISON_VALGRIND']}):
|
|
return True
|
|
raise SCons.Errors.StopError("Valgrind poison requested, but <valgrind/memcheck.h> does not work.")
|
|
@_implicit_test
|
|
def check_poison_overwrite(self,context):
|
|
'''
|
|
help:always wipe certain freed memory
|
|
'''
|
|
context.Message('%s: checking %s...' % (self.msgprefix, 'whether to use overwrite poisoning'))
|
|
r = 'overwrite' in self.user_settings.poison
|
|
context.Result(r)
|
|
if r:
|
|
context.sconf.Define('DXX_HAVE_POISON_OVERWRITE')
|
|
return r
|
|
@_custom_test
|
|
def _check_poison_method(self,context):
|
|
poison = None
|
|
for f in (
|
|
self.check_poison_valgrind,
|
|
self.check_poison_overwrite,
|
|
):
|
|
if f(context):
|
|
poison = True
|
|
if poison:
|
|
context.sconf.Define('DXX_HAVE_POISON')
|
|
@_custom_test
|
|
def check_strcasecmp_present(self,context):
|
|
main = '''
|
|
return !strcasecmp(argv[0], argv[0] + 1) && !strncasecmp(argv[0] + 1, argv[0], 1);
|
|
'''
|
|
self.Compile(context, text='#include <cstring>', main=main, msg='for strcasecmp', successflags={'CPPDEFINES' : ['DXX_HAVE_STRCASECMP']})
|
|
|
|
def add_compiler_option_tests():
|
|
def define_compiler_option_test(opt, doc=None):
|
|
def f(self, context):
|
|
self.Compile(context, text='', main='', msg='whether compiler accepts ' + opt, successflags={'CXXFLAGS' : [opt]})
|
|
f.__name__ = n = 'check_compiler_option_' + opt.replace('=', '')
|
|
f.__doc__ = doc or ('\nhelp:assume compiler accepts ' + opt + '\n')
|
|
setattr(ConfigureTests, n, custom_tests(f))
|
|
custom_tests = ConfigureTests._custom_test
|
|
for opt in (
|
|
('-fvisibility=hidden',),
|
|
('-Wsuggest-attribute=noreturn',),
|
|
('-Wlogical-op',),
|
|
):
|
|
define_compiler_option_test(*opt)
|
|
add_compiler_option_tests()
|
|
del add_compiler_option_tests
|
|
|
|
class LazyObjectConstructor:
|
|
def __get_lazy_object(self,srcname,transform_target):
|
|
env = self.env
|
|
o = env.StaticObject(target='%s%s%s' % (self.user_settings.builddir, transform_target(self, srcname), env["OBJSUFFIX"]), source=srcname)
|
|
if env._dxx_pch_node:
|
|
env.Depends(o, self.env._dxx_pch_node)
|
|
return o
|
|
def __lazy_objects(self,name,source):
|
|
try:
|
|
return self.__lazy_object_cache[name]
|
|
except KeyError as e:
|
|
def __strip_extension(self,name):
|
|
return os.path.splitext(name)[0]
|
|
value = []
|
|
for s in source:
|
|
if isinstance(s, str):
|
|
s = {'source': [s]}
|
|
transform_target = s.get('transform_target', __strip_extension)
|
|
value.extend([self.__get_lazy_object(srcname, transform_target) for srcname in s['source']])
|
|
self.__lazy_object_cache[name] = value
|
|
return value
|
|
|
|
@staticmethod
|
|
def create_lazy_object_getter(sources):
|
|
name = repr(sources)
|
|
return lambda s: s.__lazy_objects(name, sources)
|
|
|
|
@classmethod
|
|
def create_lazy_object_property(cls,sources):
|
|
return property(cls.create_lazy_object_getter(sources))
|
|
|
|
def __init__(self):
|
|
self.__lazy_object_cache = {}
|
|
|
|
class FilterHelpText:
|
|
def __init__(self):
|
|
self.visible_arguments = []
|
|
self._sconf_align = None
|
|
def FormatVariableHelpText(self, env, opt, help, default, actual, aliases):
|
|
if not opt in self.visible_arguments:
|
|
return ''
|
|
if not self._sconf_align:
|
|
self._sconf_align = len(max((s for s in self.visible_arguments if s[:6] == 'sconf_'), key=len))
|
|
l = []
|
|
if default is not None:
|
|
if isinstance(default, str) and not default.isalnum():
|
|
default = '"%s"' % default
|
|
l.append("default: {default}".format(default=default))
|
|
actual = getattr(self, opt, None)
|
|
if actual is not None:
|
|
if isinstance(actual, str) and not actual.isalnum():
|
|
actual = '"%s"' % actual
|
|
l.append("current: {current}".format(current=actual))
|
|
return (" {opt:%u} {help}" % (self._sconf_align if opt[:6] == 'sconf_' else 15)).format(opt=opt, help=help) + (" [" + "; ".join(l) + "]" if l else '') + '\n'
|
|
|
|
class DXXCommon(LazyObjectConstructor):
|
|
__shared_program_instance = [0]
|
|
__shared_header_file_list = []
|
|
__endian = checkEndian()
|
|
@property
|
|
def program_message_prefix(self):
|
|
return '%s.%d' % (self.PROGRAM_NAME, self.program_instance)
|
|
# Settings which affect how the files are compiled
|
|
class UserBuildSettings:
|
|
# Paths for the Videocore libs/includes on the Raspberry Pi
|
|
RPI_DEFAULT_VC_PATH='/opt/vc'
|
|
default_OGLES_LIB = 'GLES_CM'
|
|
_default_prefix = '/usr/local'
|
|
def default_builddir(self):
|
|
builddir_prefix = self.builddir_prefix
|
|
builddir_suffix = self.builddir_suffix
|
|
default_builddir = builddir_prefix or ''
|
|
if builddir_prefix is not None or builddir_suffix is not None:
|
|
fields = [
|
|
self.host_platform,
|
|
os.path.basename(self.CXX) if self.CXX else None,
|
|
]
|
|
compiler_flags = '\n'.join((getattr(self, attr) or '').strip() for attr in ['CPPFLAGS', 'CXXFLAGS'])
|
|
if compiler_flags:
|
|
# Mix in CRC of CXXFLAGS to get reasonable uniqueness
|
|
# when flags are changed. A full hash is
|
|
# unnecessary here.
|
|
crc = binascii.crc32(compiler_flags)
|
|
if crc < 0:
|
|
crc = crc + 0x100000000
|
|
fields.append('{:08x}'.format(crc))
|
|
if self.pch:
|
|
fields.append('p%s' % self.pch)
|
|
fields.append(''.join(a[1] if getattr(self, a[0]) else (a[2] if len(a) > 2 else '')
|
|
for a in (
|
|
('debug', 'dbg'),
|
|
('lto', 'lto'),
|
|
('profiler', 'prf'),
|
|
('editor', 'ed'),
|
|
('opengl', 'ogl', 'sdl'),
|
|
('opengles', 'es'),
|
|
('raspberrypi', 'rpi'),
|
|
)))
|
|
default_builddir += '-'.join([f for f in fields if f])
|
|
if builddir_suffix is not None:
|
|
default_builddir += builddir_prefix
|
|
return default_builddir
|
|
def default_memdebug(self):
|
|
return self.debug
|
|
# automatic setup for raspberrypi
|
|
def default_opengles(self):
|
|
if self.raspberrypi:
|
|
return True
|
|
return False
|
|
def selected_OGLES_LIB(self):
|
|
if self.raspberrypi:
|
|
return 'GLESv2'
|
|
return self.default_OGLES_LIB
|
|
def __default_DATA_DIR(self):
|
|
return self.prefix + '/share/games/' + self._program.target
|
|
@staticmethod
|
|
def _generic_variable(key,help,default):
|
|
return (key, help, default)
|
|
@staticmethod
|
|
def _enum_variable(key,help,default,allowed_values):
|
|
return EnumVariable(key, help, default, allowed_values)
|
|
def _options(self):
|
|
return (
|
|
{
|
|
'variable': self._enum_variable,
|
|
'arguments': [
|
|
('sconf_%s' % name[6:], None, ConfigureTests.describe(name) or ('assume result of %s' % name), {'allowed_values' : ['0', '1', '2', ConfigureTests.sconf_force_failure, ConfigureTests.sconf_force_success, ConfigureTests.sconf_assume_success]}) for name in ConfigureTests.implicit_tests + ConfigureTests.custom_tests if name[0] != '_'
|
|
],
|
|
},
|
|
{
|
|
'variable': BoolVariable,
|
|
'arguments': (
|
|
('raspberrypi', False, 'build for Raspberry Pi (automatically sets opengles and opengles_lib)'),
|
|
('git_describe_version', os.path.exists(os.environ.get('GIT_DIR', '.git')), 'include git --describe in extra_version'),
|
|
('git_status', True, 'include git status'),
|
|
('versid_depend_all', False, 'rebuild vers_id.cpp if any object file changes'),
|
|
),
|
|
},
|
|
{
|
|
'variable': self._generic_variable,
|
|
'arguments': (
|
|
('rpi_vc_path', self.RPI_DEFAULT_VC_PATH, 'directory for RPi VideoCore libraries'),
|
|
('opengles_lib', self.selected_OGLES_LIB, 'name of the OpenGL ES library to link against'),
|
|
('prefix', self._default_prefix, 'installation prefix directory (Linux only)'),
|
|
('sharepath', self.__default_DATA_DIR, 'directory for shared game data (Linux only)'),
|
|
('pch', None, 'pre-compile headers used this many times'),
|
|
('lto', 0, 'enable gcc link time optimization'),
|
|
),
|
|
},
|
|
{
|
|
'variable': BoolVariable,
|
|
'arguments': (
|
|
('check_header_includes', False, 'compile test each header (developer option)'),
|
|
('debug', False, 'build DEBUG binary which includes asserts, debugging output, cheats and more output'),
|
|
('memdebug', self.default_memdebug, 'build with malloc tracking'),
|
|
('profiler', False, 'profiler build'),
|
|
('opengl', True, 'build with OpenGL support'),
|
|
('opengles', self.default_opengles, 'build with OpenGL ES support'),
|
|
('editor', False, 'include editor into build (!EXPERIMENTAL!)'),
|
|
('sdlmixer', True, 'build with SDL_Mixer support for sound and music (includes external music support)'),
|
|
('ipv6', False, 'enable IPv6 compability'),
|
|
('use_udp', True, 'enable UDP support'),
|
|
('use_tracker', True, 'enable Tracker support (requires UDP)'),
|
|
('verbosebuild', False, 'print out all compiler/linker messages during building'),
|
|
),
|
|
},
|
|
{
|
|
'variable': self._generic_variable,
|
|
'arguments': (
|
|
('CHOST', os.environ.get('CHOST'), 'CHOST of output'),
|
|
('CXX', os.environ.get('CXX'), 'C++ compiler command'),
|
|
('PKG_CONFIG', os.environ.get('PKG_CONFIG'), 'PKG_CONFIG to run (Linux only)'),
|
|
('RC', os.environ.get('RC'), 'Windows resource compiler command'),
|
|
('extra_version', None, 'text to append to version, such as VCS identity'),
|
|
('ccache', None, 'path to ccache'),
|
|
('distcc', None, 'path to distcc'),
|
|
('distcc_hosts', os.environ.get('DISTCC_HOSTS'), 'hosts to distribute compilation'),
|
|
),
|
|
},
|
|
{
|
|
'variable': self._generic_variable,
|
|
'stack': ' ',
|
|
'arguments': (
|
|
('CPPFLAGS', os.environ.get('CPPFLAGS'), 'C preprocessor flags'),
|
|
('CXXFLAGS', os.environ.get('CXXFLAGS'), 'C++ compiler flags'),
|
|
('LDFLAGS', os.environ.get('LDFLAGS'), 'Linker flags'),
|
|
('LIBS', os.environ.get('LIBS'), 'Libraries to link'),
|
|
),
|
|
},
|
|
{
|
|
'variable': self._enum_variable,
|
|
'arguments': (
|
|
('host_platform', 'linux' if sys.platform == 'linux2' else sys.platform, 'cross-compile to specified platform', {'allowed_values' : ['win32', 'darwin', 'linux']}),
|
|
),
|
|
},
|
|
{
|
|
'variable': ListVariable,
|
|
'arguments': (
|
|
('poison', 'none', 'method for poisoning free memory', {'names' : ('valgrind', 'overwrite')}),
|
|
),
|
|
},
|
|
{
|
|
'variable': self._generic_variable,
|
|
'arguments': (
|
|
('builddir_prefix', None, 'prefix to generated build directory'),
|
|
('builddir_suffix', None, 'suffix to generated build directory'),
|
|
# This must be last so that default_builddir will
|
|
# have access to other properties.
|
|
('builddir', self.default_builddir, 'build in specified directory'),
|
|
),
|
|
},
|
|
)
|
|
@staticmethod
|
|
def _names(name,prefix):
|
|
return ['%s%s%s' % (p, '_' if p else '', name) for p in prefix]
|
|
def __init__(self,program=None):
|
|
self._program = program
|
|
def register_variables(self,prefix,variables):
|
|
self.known_variables = []
|
|
for grp in self._options():
|
|
variable = grp['variable']
|
|
stack = grp.get('stack', None)
|
|
for opt in grp['arguments']:
|
|
(name,value,help) = opt[0:3]
|
|
kwargs = opt[3] if len(opt) > 3 else {}
|
|
if name not in variables.keys():
|
|
filtered_help.visible_arguments.append(name)
|
|
variables.Add(variable(key=name, help=help, default=None if callable(value) else value, **kwargs))
|
|
names = self._names(name, prefix)
|
|
for n in names:
|
|
if n not in variables.keys():
|
|
variables.Add(variable(key=n, help=help, default=None, **kwargs))
|
|
if not name in names:
|
|
names.append(name)
|
|
self.known_variables.append((names, name, value, stack))
|
|
if stack:
|
|
for n in names:
|
|
variables.Add(self._generic_variable(key='%s_stop' % n, help=None, default=None))
|
|
def read_variables(self,variables,d):
|
|
for (namelist,cname,dvalue,stack) in self.known_variables:
|
|
value = None
|
|
found_value = False
|
|
for n in namelist:
|
|
try:
|
|
v = d[n]
|
|
found_value = True
|
|
if stack:
|
|
if callable(v):
|
|
value = v(dvalue=dvalue, value=value, stack=stack)
|
|
else:
|
|
if value:
|
|
value = stack.join([value, v])
|
|
else:
|
|
value = v
|
|
if d.get(n + '_stop', None):
|
|
break
|
|
continue
|
|
value = v
|
|
break
|
|
except KeyError as e:
|
|
pass
|
|
if not found_value:
|
|
value = dvalue
|
|
if callable(value):
|
|
value = value()
|
|
setattr(self, cname, value)
|
|
if self.builddir != '' and self.builddir[-1:] != '/':
|
|
self.builddir += '/'
|
|
def clone(self):
|
|
clone = DXXCommon.UserBuildSettings(None)
|
|
for grp in clone._options():
|
|
for o in grp['arguments']:
|
|
name = o[0]
|
|
value = getattr(self, name)
|
|
setattr(clone, name, value)
|
|
return clone
|
|
class UserInstallSettings:
|
|
def _options(self):
|
|
return (
|
|
{
|
|
'variable': self._generic_variable,
|
|
'arguments': (
|
|
('DESTDIR', None, 'installation stage directory'),
|
|
('program_name', None, 'name of built program'),
|
|
),
|
|
},
|
|
{
|
|
'variable': BoolVariable,
|
|
'arguments': (
|
|
('register_install_target', True, 'report install target to SCons core'),
|
|
),
|
|
},
|
|
)
|
|
class UserSettings(UserBuildSettings,UserInstallSettings):
|
|
def _options(self):
|
|
return DXXCommon.UserBuildSettings._options(self) + DXXCommon.UserInstallSettings._options(self)
|
|
# Base class for platform-specific settings processing
|
|
class _PlatformSettings:
|
|
tools = None
|
|
ogllibs = ''
|
|
platform_objects = []
|
|
def __init__(self,program,user_settings):
|
|
self.__program = program
|
|
self.user_settings = user_settings
|
|
@property
|
|
def env(self):
|
|
return self.__program.env
|
|
# Settings to apply to mingw32 builds
|
|
class Win32PlatformSettings(_PlatformSettings):
|
|
ogllibs = ('opengl32',)
|
|
tools = ['mingw']
|
|
def adjust_environment(self,program,env):
|
|
env.Append(CPPDEFINES = ['_WIN32', 'HAVE_STRUCT_TIMEVAL', 'WIN32_LEAN_AND_MEAN'])
|
|
class DarwinPlatformSettings(_PlatformSettings):
|
|
def __init__(self,program,user_settings):
|
|
DXXCommon._PlatformSettings.__init__(self,program,user_settings)
|
|
def adjust_environment(self,program,env):
|
|
env.Append(CPPDEFINES = ['HAVE_STRUCT_TIMESPEC', 'HAVE_STRUCT_TIMEVAL', '__unix__'])
|
|
env.Append(CPPPATH = [os.path.join(os.getenv("HOME"), 'Library/Frameworks/SDL.framework/Headers'), '/Library/Frameworks/SDL.framework/Headers'])
|
|
env.Append(FRAMEWORKS = ['ApplicationServices', 'Cocoa', 'SDL'])
|
|
if (self.user_settings.opengl == 1) or (self.user_settings.opengles == 1):
|
|
env.Append(FRAMEWORKS = ['OpenGL'])
|
|
env.Append(FRAMEWORKPATH = [os.path.join(os.getenv("HOME"), 'Library/Frameworks'), '/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks'])
|
|
# Settings to apply to Linux builds
|
|
class LinuxPlatformSettings(_PlatformSettings):
|
|
__opengl_libs = ['GL', 'GLU']
|
|
__pkg_config_sdl = {}
|
|
def __init__(self,program,user_settings):
|
|
DXXCommon._PlatformSettings.__init__(self,program,user_settings)
|
|
if (user_settings.opengles == 1):
|
|
self.ogllibs = [ user_settings.opengles_lib, 'EGL']
|
|
else:
|
|
self.ogllibs = self.__opengl_libs
|
|
def adjust_environment(self,program,env):
|
|
env.Append(CPPDEFINES = ['HAVE_STRUCT_TIMESPEC', 'HAVE_STRUCT_TIMEVAL'])
|
|
env.Append(CCFLAGS = ['-pthread'])
|
|
|
|
def __init__(self):
|
|
LazyObjectConstructor.__init__(self)
|
|
self.sources = []
|
|
self.__shared_program_instance[0] += 1
|
|
self.program_instance = self.__shared_program_instance[0]
|
|
|
|
@staticmethod
|
|
def _collect_pch_candidates(target,source,env):
|
|
for t in target:
|
|
scanner = t.get_source_scanner(source[0])
|
|
deps = scanner(source[0], env, scanner.path(env))
|
|
for d in deps:
|
|
ds = str(d)
|
|
env.__dxx_pch_candidates[ds] = env.__dxx_pch_candidates.get(ds, 0) + 1
|
|
return (target, source)
|
|
|
|
@staticmethod
|
|
def write_pch_inclusion_file(target, source, env):
|
|
with open(str(target[0]), 'wt') as f:
|
|
f.write('/* BEGIN PCH GENERATED FILE\n * Threshold=%u\n */\n' % env.__dxx_pch_inclusion_count)
|
|
for (name,count) in env.__dxx_pch_candidates.items():
|
|
if count >= env.__dxx_pch_inclusion_count:
|
|
f.write('#include "%s"\t/* %u */\n' % (name, count))
|
|
env.Depends(target, name)
|
|
f.write('/* END PCH GENERATED FILE */\n')
|
|
|
|
def create_header_targets(self):
|
|
fs = SCons.Node.FS.get_default_fs()
|
|
builddir = self.user_settings.builddir
|
|
check_header_includes = os.path.join(builddir, 'check_header_includes.cpp')
|
|
if not self.__shared_header_file_list:
|
|
open(check_header_includes, 'wt')
|
|
headers = Git.pcall(['ls-files', '-z', '--', '*.h'], stdout=subprocess.PIPE).out
|
|
excluded_directories = (
|
|
'common/arch/cocoa/',
|
|
'common/arch/carbon/',
|
|
)
|
|
self.__shared_header_file_list.extend([h for h in headers.split('\0') if h and not h.startswith(excluded_directories)])
|
|
dirname = os.path.join(builddir, self.srcdir)
|
|
kwargs = {
|
|
'CXXFLAGS' : self.env['CXXFLAGS'][:],
|
|
'source' : check_header_includes
|
|
}
|
|
Depends = self.env.Depends
|
|
StaticObject = self.env.StaticObject
|
|
OBJSUFFIX = self.env['OBJSUFFIX']
|
|
for name in self.__shared_header_file_list:
|
|
if not name:
|
|
continue
|
|
if self.srcdir == 'common' and not name.startswith('common/'):
|
|
# Skip game-specific headers when testing common
|
|
continue
|
|
if self.srcdir[0] == 'd' and name[0] == 'd' and not name.startswith(self.srcdir):
|
|
# Skip d1 in d2 and d2 in d1
|
|
continue
|
|
CPPFLAGS = self.env['CPPFLAGS'][:]
|
|
if name[:24] == 'common/include/compiler-':
|
|
CPPFLAGS.extend(['-include', 'dxxsconf.h'])
|
|
CPPFLAGS.extend(['-include', name])
|
|
if self.user_settings.verbosebuild:
|
|
kwargs['CXXCOMSTR'] = "Checking %s %s %s" % (self.target, builddir, name)
|
|
Depends(StaticObject(target=os.path.join('%s/chi/%s%s' % (dirname, name, OBJSUFFIX)), CPPFLAGS=CPPFLAGS, **kwargs), fs.File(name))
|
|
|
|
def create_pch_node(self,dirname,configure_pch_flags):
|
|
if self.user_settings.check_header_includes:
|
|
self.create_header_targets()
|
|
if not configure_pch_flags:
|
|
self.env._dxx_pch_node = None
|
|
return
|
|
dirname = os.path.join(self.user_settings.builddir, dirname)
|
|
target = os.path.join(dirname, 'pch.h.gch')
|
|
source = os.path.join(dirname, 'pch.cpp')
|
|
self.env._dxx_pch_node = self.env.StaticObject(target=target, source=source, CXXFLAGS=self.env['CXXFLAGS'] + configure_pch_flags['CXXFLAGS'])
|
|
self.env.Append(CXXFLAGS = ['-include', str(self.env._dxx_pch_node[0])[:-4], '-Winvalid-pch'])
|
|
self.env.__dxx_pch_candidates = {}
|
|
self.env.__dxx_pch_inclusion_count = int(self.user_settings.pch)
|
|
self.env['BUILDERS']['StaticObject'].add_emitter('.cpp', self._collect_pch_candidates)
|
|
self.env.Command(source, None, self.write_pch_inclusion_file)
|
|
|
|
def _quote_cppdefine(self,s,f=repr):
|
|
r = ''
|
|
prior = False
|
|
for c in f(s):
|
|
# No xdigit support in str
|
|
if c in ' ()*+,-./:=[]_' or (c.isalnum() and not (prior and (c.isdigit() or c in 'abcdefABCDEF'))):
|
|
r += c
|
|
elif c == '\n':
|
|
r += r'\n'
|
|
else:
|
|
r += '\\\\x' + binascii.b2a_hex(c)
|
|
prior = True
|
|
continue
|
|
prior = False
|
|
return '\\"' + r + '\\"'
|
|
|
|
def prepare_environment(self):
|
|
# Prettier build messages......
|
|
# Move target to end of C++ source command
|
|
target_string = ' -o $TARGET'
|
|
cxxcom = self.env['CXXCOM']
|
|
if target_string + ' ' in cxxcom:
|
|
cxxcom = cxxcom.replace(target_string, '') + target_string
|
|
# Add ccache/distcc only for compile, not link
|
|
if self.user_settings.ccache:
|
|
cxxcom = self.user_settings.ccache + ' ' + cxxcom
|
|
if self.user_settings.distcc:
|
|
self.env['ENV']['CCACHE_PREFIX'] = self.user_settings.distcc
|
|
elif self.user_settings.distcc:
|
|
cxxcom = self.user_settings.distcc + ' ' + cxxcom
|
|
self.env['CXXCOM'] = cxxcom
|
|
# Move target to end of link command
|
|
linkcom = self.env['LINKCOM']
|
|
if target_string + ' ' in linkcom:
|
|
linkcom = linkcom.replace(target_string, '') + target_string
|
|
# Add $CXXFLAGS to link command
|
|
cxxflags = '$CXXFLAGS '
|
|
if ' ' + cxxflags not in linkcom:
|
|
linkflags = '$LINKFLAGS'
|
|
linkcom = linkcom.replace(linkflags, cxxflags + linkflags)
|
|
self.env['LINKCOM'] = linkcom
|
|
# Custom DISTCC_HOSTS per target
|
|
distcc_hosts = self.user_settings.distcc_hosts
|
|
if distcc_hosts is not None:
|
|
self.env['ENV']['DISTCC_HOSTS'] = distcc_hosts
|
|
if (self.user_settings.verbosebuild == 0):
|
|
builddir = self.user_settings.builddir if self.user_settings.builddir != '' else '.'
|
|
self.env["CXXCOMSTR"] = "Compiling %s %s $SOURCE" % (self.target, builddir)
|
|
self.env["LINKCOMSTR"] = "Linking %s $TARGET" % self.target
|
|
|
|
# Use -Wundef to catch when a shared source file includes a
|
|
# shared header that misuses conditional compilation. Use
|
|
# -Werror=undef to make this fatal. Both are needed, since
|
|
# gcc 4.5 silently ignores -Werror=undef. On gcc 4.5, misuse
|
|
# produces a warning. On gcc 4.7, misuse produces an error.
|
|
Werror = get_Werror_string(self.user_settings.CXXFLAGS)
|
|
self.env.Prepend(CXXFLAGS = [
|
|
'-Wall',
|
|
Werror + 'extra',
|
|
Werror + 'format-security',
|
|
Werror + 'missing-braces',
|
|
Werror + 'missing-include-dirs',
|
|
Werror + 'unused',
|
|
Werror + 'undef',
|
|
Werror + 'pointer-arith',
|
|
Werror + 'cast-qual',
|
|
Werror + 'cast-align',
|
|
Werror + 'missing-declarations',
|
|
Werror + 'redundant-decls',
|
|
Werror + 'vla',
|
|
])
|
|
self.env.Append(CXXFLAGS = ['-funsigned-char'])
|
|
self.env.Append(CPPPATH = ['common/include', 'common/main', '.', self.user_settings.builddir])
|
|
self.env.Append(CPPFLAGS = SCons.Util.CLVar('-Wno-sign-compare'))
|
|
if (self.user_settings.editor == 1):
|
|
self.env.Append(CPPPATH = ['common/include/editor'])
|
|
# Get traditional compiler environment variables
|
|
for cc in ('CXX', 'RC',):
|
|
value = getattr(self.user_settings, cc)
|
|
if value is not None:
|
|
self.env[cc] = value
|
|
for flags in ['CPPFLAGS', 'CXXFLAGS', 'LIBS']:
|
|
value = getattr(self.user_settings, flags)
|
|
if value is not None:
|
|
self.env.Append(**{flags : SCons.Util.CLVar(value)})
|
|
if self.user_settings.LDFLAGS:
|
|
self.env.Append(LINKFLAGS = SCons.Util.CLVar(self.user_settings.LDFLAGS))
|
|
if self.user_settings.lto:
|
|
f = ['-flto=%s' % self.user_settings.lto, '-fno-fat-lto-objects']
|
|
self.env.Append(CXXFLAGS = f)
|
|
|
|
def check_endian(self):
|
|
# set endianess
|
|
if (self.__endian == "big"):
|
|
message(self, "BigEndian machine detected")
|
|
self.env.Append(CPPDEFINES = ['WORDS_BIGENDIAN'])
|
|
elif (self.__endian == "little"):
|
|
message(self, "LittleEndian machine detected")
|
|
|
|
def check_platform(self):
|
|
# windows or *nix?
|
|
platform_name = self.user_settings.host_platform
|
|
if self._argument_prefix_list:
|
|
prefix = ' with prefix list %s' % list(self._argument_prefix_list)
|
|
else:
|
|
prefix = ''
|
|
message(self, "compiling on %s for %s into %s%s" % (sys.platform, platform_name, self.user_settings.builddir or '.', prefix))
|
|
if platform_name == 'win32':
|
|
platform = self.Win32PlatformSettings
|
|
elif platform_name == 'darwin':
|
|
platform = self.DarwinPlatformSettings
|
|
else:
|
|
platform = self.LinuxPlatformSettings
|
|
self.platform_settings = platform(self, self.user_settings)
|
|
# Acquire environment object...
|
|
self.env = Environment(ENV = os.environ, tools = platform.tools)
|
|
self.platform_settings.adjust_environment(self, self.env)
|
|
|
|
def process_user_settings(self):
|
|
env = self.env
|
|
# opengl or software renderer?
|
|
if (self.user_settings.opengl == 1) or (self.user_settings.opengles == 1):
|
|
if (self.user_settings.opengles == 1):
|
|
message(self, "building with OpenGL ES")
|
|
env.Append(CPPDEFINES = ['OGLES'])
|
|
else:
|
|
message(self, "building with OpenGL")
|
|
env.Append(CPPDEFINES = ['OGL'])
|
|
|
|
# debug?
|
|
if not self.user_settings.debug:
|
|
env.Append(CPPDEFINES = ['NDEBUG', 'RELEASE'])
|
|
env.Prepend(CXXFLAGS = ['-g', '-O2'])
|
|
if self.user_settings.memdebug:
|
|
message(self, "including: MEMDEBUG")
|
|
env.Append(CPPDEFINES = ['DEBUG_MEMORY_ALLOCATIONS'])
|
|
|
|
# profiler?
|
|
if (self.user_settings.profiler == 1):
|
|
env.Append(CPPFLAGS = ['-pg'])
|
|
|
|
#editor build?
|
|
if (self.user_settings.editor == 1):
|
|
env.Append(CPPDEFINES = ['EDITOR'])
|
|
|
|
# IPv6 compability?
|
|
if (self.user_settings.ipv6 == 1):
|
|
env.Append(CPPDEFINES = ['IPv6'])
|
|
|
|
# UDP support?
|
|
if (self.user_settings.use_udp == 1):
|
|
env.Append(CPPDEFINES = ['USE_UDP'])
|
|
# Tracker support? (Relies on UDP)
|
|
if( self.user_settings.use_tracker == 1 ):
|
|
env.Append( CPPDEFINES = [ 'USE_TRACKER' ] )
|
|
|
|
# Raspberry Pi?
|
|
if (self.user_settings.raspberrypi == 1):
|
|
message(self, "Raspberry Pi: using VideoCore libs in \"%s\"" % self.user_settings.rpi_vc_path)
|
|
env.Append(CPPDEFINES = ['RPI', 'WORDS_NEED_ALIGNMENT'])
|
|
# use CPPFLAGS -isystem instead of CPPPATH because these those header files
|
|
# are not very clean and would trigger some warnings we usually consider as
|
|
# errors. Using them as system headers will make gcc ignoring any warnings.
|
|
env.Append(CPPFLAGS = [
|
|
'-isystem='+self.user_settings.rpi_vc_path+'/include',
|
|
'-isystem='+self.user_settings.rpi_vc_path+'/include/interface/vcos/pthreads',
|
|
'-isystem='+self.user_settings.rpi_vc_path+'/include/interface/vmcs_host/linux'])
|
|
env.Append(LIBPATH = self.user_settings.rpi_vc_path + '/lib')
|
|
env.Append(LIBS = ['bcm_host'])
|
|
|
|
class DXXArchive(DXXCommon):
|
|
srcdir = 'common'
|
|
target = 'dxx-common'
|
|
__objects_common = DXXCommon.create_lazy_object_property([os.path.join(srcdir, f) for f in [
|
|
'2d/2dsline.cpp',
|
|
'2d/bitblt.cpp',
|
|
'2d/bitmap.cpp',
|
|
'2d/box.cpp',
|
|
'2d/canvas.cpp',
|
|
'2d/circle.cpp',
|
|
'2d/disc.cpp',
|
|
'2d/gpixel.cpp',
|
|
'2d/line.cpp',
|
|
'2d/pixel.cpp',
|
|
'2d/rect.cpp',
|
|
'2d/rle.cpp',
|
|
'2d/scalec.cpp',
|
|
'3d/draw.cpp',
|
|
'3d/globvars.cpp',
|
|
'3d/instance.cpp',
|
|
'3d/matrix.cpp',
|
|
'3d/points.cpp',
|
|
'3d/rod.cpp',
|
|
'3d/setup.cpp',
|
|
'arch/sdl/joy.cpp',
|
|
'arch/sdl/rbaudio.cpp',
|
|
'arch/sdl/timer.cpp',
|
|
'arch/sdl/window.cpp',
|
|
'main/cli.cpp',
|
|
'main/cmd.cpp',
|
|
'main/cvar.cpp',
|
|
'maths/fixc.cpp',
|
|
'maths/rand.cpp',
|
|
'maths/tables.cpp',
|
|
'maths/vecmat.cpp',
|
|
'misc/error.cpp',
|
|
'misc/hmp.cpp',
|
|
'misc/ignorecase.cpp',
|
|
'misc/strutil.cpp',
|
|
'texmap/ntmap.cpp',
|
|
'texmap/scanline.cpp'
|
|
]
|
|
])
|
|
objects_editor = DXXCommon.create_lazy_object_property([os.path.join(srcdir, f) for f in [
|
|
'editor/func.cpp',
|
|
'ui/button.cpp',
|
|
'ui/checkbox.cpp',
|
|
'ui/dialog.cpp',
|
|
'ui/file.cpp',
|
|
'ui/gadget.cpp',
|
|
'ui/icon.cpp',
|
|
'ui/inputbox.cpp',
|
|
'ui/keypad.cpp',
|
|
'ui/keypress.cpp',
|
|
'ui/listbox.cpp',
|
|
'ui/menu.cpp',
|
|
'ui/menubar.cpp',
|
|
'ui/message.cpp',
|
|
'ui/radio.cpp',
|
|
'ui/scroll.cpp',
|
|
'ui/ui.cpp',
|
|
'ui/uidraw.cpp',
|
|
'ui/userbox.cpp'
|
|
]
|
|
])
|
|
# for non-ogl
|
|
objects_arch_sdl = DXXCommon.create_lazy_object_property([os.path.join(srcdir, f) for f in [
|
|
'3d/clipper.cpp',
|
|
'texmap/tmapflat.cpp'
|
|
]
|
|
])
|
|
# for ogl
|
|
objects_arch_ogl = DXXCommon.create_lazy_object_property([os.path.join(srcdir, f) for f in [
|
|
'arch/ogl/ogl_extensions.cpp',
|
|
'arch/ogl/ogl_sync.cpp'
|
|
]
|
|
])
|
|
objects_arch_sdlmixer = DXXCommon.create_lazy_object_property([os.path.join(srcdir, f) for f in [
|
|
'arch/sdl/digi_mixer_music.cpp',
|
|
]
|
|
])
|
|
class Win32PlatformSettings(LazyObjectConstructor, DXXCommon.Win32PlatformSettings):
|
|
platform_objects = LazyObjectConstructor.create_lazy_object_property([
|
|
'common/arch/win32/messagebox.cpp'
|
|
])
|
|
def __init__(self,program,user_settings):
|
|
LazyObjectConstructor.__init__(self)
|
|
DXXCommon.Win32PlatformSettings.__init__(self, program, user_settings)
|
|
self.user_settings = user_settings
|
|
class DarwinPlatformSettings(LazyObjectConstructor, DXXCommon.DarwinPlatformSettings):
|
|
platform_objects = LazyObjectConstructor.create_lazy_object_property([
|
|
'common/arch/cocoa/messagebox.mm',
|
|
'common/arch/cocoa/SDLMain.m'
|
|
])
|
|
|
|
def __init__(self, program, user_settings):
|
|
LazyObjectConstructor.__init__(self)
|
|
DXXCommon.DarwinPlatformSettings.__init__(self, program, user_settings)
|
|
self.user_settings = user_settings
|
|
@property
|
|
def objects_common(self):
|
|
objects_common = self.__objects_common
|
|
return objects_common + self.platform_settings.platform_objects
|
|
def __init__(self,user_settings):
|
|
self.PROGRAM_NAME = 'DXX-Archive'
|
|
self._argument_prefix_list = None
|
|
DXXCommon.__init__(self)
|
|
self.user_settings = user_settings.clone()
|
|
self.check_platform()
|
|
self.prepare_environment()
|
|
self.check_endian()
|
|
self.process_user_settings()
|
|
self.configure_environment()
|
|
self.create_pch_node(self.srcdir, self.configure_pch_flags)
|
|
|
|
def configure_environment(self):
|
|
fs = SCons.Node.FS.get_default_fs()
|
|
builddir = fs.Dir(self.user_settings.builddir or '.')
|
|
tests = ConfigureTests(self.program_message_prefix, self.user_settings, self.platform_settings)
|
|
log_file=fs.File('sconf.log', builddir)
|
|
conf = self.env.Configure(custom_tests = {
|
|
k:getattr(tests, k) for k in tests.custom_tests
|
|
},
|
|
conf_dir=fs.Dir('.sconf_temp', builddir),
|
|
log_file=log_file,
|
|
config_h=fs.File('dxxsconf.h', builddir),
|
|
clean=False,
|
|
help=False
|
|
)
|
|
self.configure_added_environment_flags = tests.successful_flags
|
|
self.configure_pch_flags = None
|
|
if not conf.env:
|
|
return
|
|
try:
|
|
for k in tests.custom_tests:
|
|
getattr(conf, k)()
|
|
except SCons.Errors.StopError as e:
|
|
raise SCons.Errors.StopError(e.args[0] + ' See {log_file} for details.'.format(log_file=log_file), *e.args[1:])
|
|
self.env = conf.Finish()
|
|
self.configure_pch_flags = tests.pch_flags
|
|
|
|
class DXXProgram(DXXCommon):
|
|
# version number
|
|
VERSION_MAJOR = 0
|
|
VERSION_MINOR = 58
|
|
VERSION_MICRO = 1
|
|
static_archive_construction = {}
|
|
# None when unset. Tuple of one once cached.
|
|
_computed_extra_version = None
|
|
def _apply_target_name(self,name):
|
|
return os.path.join(os.path.dirname(name), '.%s.%s' % (self.target, os.path.splitext(os.path.basename(name))[0]))
|
|
objects_similar_arch_ogl = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join('similar', f) for f in [
|
|
'arch/ogl/gr.cpp',
|
|
'arch/ogl/ogl.cpp',
|
|
]
|
|
],
|
|
'transform_target':_apply_target_name,
|
|
}])
|
|
objects_similar_arch_sdl = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join('similar', f) for f in [
|
|
'arch/sdl/gr.cpp',
|
|
]
|
|
],
|
|
'transform_target':_apply_target_name,
|
|
}])
|
|
objects_similar_arch_sdlmixer = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join('similar', f) for f in [
|
|
'arch/sdl/digi_mixer.cpp',
|
|
'arch/sdl/jukebox.cpp'
|
|
]
|
|
],
|
|
'transform_target':_apply_target_name,
|
|
}])
|
|
__objects_common = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join('similar', f) for f in [
|
|
'2d/font.cpp',
|
|
'2d/palette.cpp',
|
|
'2d/pcx.cpp',
|
|
'3d/interp.cpp',
|
|
'arch/sdl/digi.cpp',
|
|
'arch/sdl/digi_audio.cpp',
|
|
'arch/sdl/event.cpp',
|
|
'arch/sdl/init.cpp',
|
|
'arch/sdl/key.cpp',
|
|
'arch/sdl/mouse.cpp',
|
|
'main/ai.cpp',
|
|
'main/aipath.cpp',
|
|
'main/automap.cpp',
|
|
'main/bm.cpp',
|
|
'main/cntrlcen.cpp',
|
|
'main/collide.cpp',
|
|
'main/config.cpp',
|
|
'main/console.cpp',
|
|
'main/controls.cpp',
|
|
'main/credits.cpp',
|
|
'main/digiobj.cpp',
|
|
'main/effects.cpp',
|
|
'main/endlevel.cpp',
|
|
'main/fireball.cpp',
|
|
'main/fuelcen.cpp',
|
|
'main/fvi.cpp',
|
|
'main/game.cpp',
|
|
'main/gamecntl.cpp',
|
|
'main/gamefont.cpp',
|
|
'main/gamemine.cpp',
|
|
'main/gamerend.cpp',
|
|
'main/gamesave.cpp',
|
|
'main/gameseg.cpp',
|
|
'main/gameseq.cpp',
|
|
'main/gauges.cpp',
|
|
'main/hostage.cpp',
|
|
'main/hud.cpp',
|
|
'main/iff.cpp',
|
|
'main/inferno.cpp',
|
|
'main/kconfig.cpp',
|
|
'main/kmatrix.cpp',
|
|
'main/laser.cpp',
|
|
'main/lighting.cpp',
|
|
'main/menu.cpp',
|
|
'main/mglobal.cpp',
|
|
'main/mission.cpp',
|
|
'main/morph.cpp',
|
|
'main/multi.cpp',
|
|
'main/multibot.cpp',
|
|
'main/newdemo.cpp',
|
|
'main/newmenu.cpp',
|
|
'main/object.cpp',
|
|
'main/paging.cpp',
|
|
'main/physics.cpp',
|
|
'main/piggy.cpp',
|
|
'main/player.cpp',
|
|
'main/playsave.cpp',
|
|
'main/polyobj.cpp',
|
|
'main/powerup.cpp',
|
|
'main/render.cpp',
|
|
'main/robot.cpp',
|
|
'main/scores.cpp',
|
|
'main/segment.cpp',
|
|
'main/slew.cpp',
|
|
'main/songs.cpp',
|
|
'main/state.cpp',
|
|
'main/switch.cpp',
|
|
'main/terrain.cpp',
|
|
'main/texmerge.cpp',
|
|
'main/text.cpp',
|
|
'main/titles.cpp',
|
|
'main/vclip.cpp',
|
|
'main/wall.cpp',
|
|
'main/weapon.cpp',
|
|
'mem/mem.cpp',
|
|
'misc/args.cpp',
|
|
'misc/hash.cpp',
|
|
'misc/physfsx.cpp',
|
|
]
|
|
],
|
|
'transform_target':_apply_target_name,
|
|
}])
|
|
objects_editor = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join('similar', f) for f in [
|
|
'editor/autosave.cpp',
|
|
'editor/centers.cpp',
|
|
'editor/curves.cpp',
|
|
'main/dumpmine.cpp',
|
|
'editor/eglobal.cpp',
|
|
'editor/elight.cpp',
|
|
'editor/eobject.cpp',
|
|
'editor/eswitch.cpp',
|
|
'editor/group.cpp',
|
|
'editor/info.cpp',
|
|
'editor/kbuild.cpp',
|
|
'editor/kcurve.cpp',
|
|
'editor/kfuncs.cpp',
|
|
'editor/kgame.cpp',
|
|
'editor/khelp.cpp',
|
|
'editor/kmine.cpp',
|
|
'editor/ksegmove.cpp',
|
|
'editor/ksegsel.cpp',
|
|
'editor/ksegsize.cpp',
|
|
'editor/ktmap.cpp',
|
|
'editor/kview.cpp',
|
|
'editor/med.cpp',
|
|
'editor/meddraw.cpp',
|
|
'editor/medmisc.cpp',
|
|
'editor/medrobot.cpp',
|
|
'editor/medsel.cpp',
|
|
'editor/medwall.cpp',
|
|
'editor/mine.cpp',
|
|
'editor/objpage.cpp',
|
|
'editor/segment.cpp',
|
|
'editor/seguvs.cpp',
|
|
'editor/texpage.cpp',
|
|
'editor/texture.cpp',
|
|
]
|
|
],
|
|
'transform_target':_apply_target_name,
|
|
}])
|
|
|
|
objects_use_udp = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join('similar', f) for f in [
|
|
'main/net_udp.cpp',
|
|
]
|
|
],
|
|
'transform_target':_apply_target_name,
|
|
}])
|
|
class UserSettings(DXXCommon.UserSettings):
|
|
@property
|
|
def BIN_DIR(self):
|
|
# installation path
|
|
return self.prefix + '/bin'
|
|
# Settings to apply to mingw32 builds
|
|
class Win32PlatformSettings(DXXCommon.Win32PlatformSettings):
|
|
def __init__(self,program,user_settings):
|
|
DXXCommon.Win32PlatformSettings.__init__(self,program,user_settings)
|
|
user_settings.sharepath = ''
|
|
self.platform_objects = self.platform_objects[:]
|
|
def adjust_environment(self,program,env):
|
|
DXXCommon.Win32PlatformSettings.adjust_environment(self, program, env)
|
|
rcbasename = os.path.join(program.srcdir, 'arch/win32/%s' % program.target)
|
|
self.platform_objects.append(env.RES(target='%s%s%s' % (program.user_settings.builddir, rcbasename, env["OBJSUFFIX"]), source='%s.rc' % rcbasename))
|
|
env.Append(CPPPATH = [os.path.join(program.srcdir, 'arch/win32/include')])
|
|
env.Append(LINKFLAGS = '-mwindows')
|
|
env.Append(LIBS = ['glu32', 'wsock32', 'ws2_32', 'winmm', 'mingw32', 'SDLmain', 'SDL'])
|
|
# Settings to apply to Apple builds
|
|
class DarwinPlatformSettings(DXXCommon.DarwinPlatformSettings):
|
|
def __init__(self,program,user_settings):
|
|
DXXCommon.DarwinPlatformSettings.__init__(self,program,user_settings)
|
|
user_settings.sharepath = ''
|
|
self.platform_objects = self.platform_objects[:]
|
|
def adjust_environment(self,program,env):
|
|
DXXCommon.DarwinPlatformSettings.adjust_environment(self, program, env)
|
|
VERSION = str(program.VERSION_MAJOR) + '.' + str(program.VERSION_MINOR)
|
|
if (program.VERSION_MICRO):
|
|
VERSION += '.' + str(program.VERSION_MICRO)
|
|
env['VERSION_NUM'] = VERSION
|
|
env['VERSION_NAME'] = program.PROGRAM_NAME + ' v' + VERSION
|
|
# Settings to apply to Linux builds
|
|
class LinuxPlatformSettings(DXXCommon.LinuxPlatformSettings):
|
|
def __init__(self,program,user_settings):
|
|
DXXCommon.LinuxPlatformSettings.__init__(self,program,user_settings)
|
|
if user_settings.sharepath and user_settings.sharepath[-1] != '/':
|
|
user_settings.sharepath += '/'
|
|
|
|
@property
|
|
def objects_common(self):
|
|
objects_common = self.__objects_common
|
|
if (self.user_settings.use_udp == 1):
|
|
objects_common = objects_common + self.objects_use_udp
|
|
return objects_common + self.platform_settings.platform_objects
|
|
def __init__(self,prefix,variables):
|
|
self.variables = variables
|
|
self._argument_prefix_list = prefix
|
|
DXXCommon.__init__(self)
|
|
self.banner()
|
|
self.user_settings = self.UserSettings(program=self)
|
|
self.user_settings.register_variables(prefix=prefix, variables=self.variables)
|
|
|
|
def init(self,substenv):
|
|
self.user_settings.read_variables(self.variables, substenv)
|
|
if not DXXProgram.static_archive_construction.has_key(self.user_settings.builddir):
|
|
DXXProgram.static_archive_construction[self.user_settings.builddir] = DXXArchive(self.user_settings)
|
|
self.check_platform()
|
|
self.prepare_environment()
|
|
self.check_endian()
|
|
self.process_user_settings()
|
|
self.register_program()
|
|
|
|
def prepare_environment(self):
|
|
DXXCommon.prepare_environment(self)
|
|
archive = DXXProgram.static_archive_construction[self.user_settings.builddir]
|
|
self.env.MergeFlags(archive.configure_added_environment_flags)
|
|
self.create_pch_node(self.srcdir, archive.configure_pch_flags)
|
|
self.env.Append(CPPDEFINES = [('DXX_VERSION_SEQ', ','.join([str(self.VERSION_MAJOR), str(self.VERSION_MINOR), str(self.VERSION_MICRO)]))])
|
|
# For PRIi64
|
|
self.env.Append(CPPDEFINES = [('__STDC_FORMAT_MACROS',)])
|
|
self.env.Append(CPPPATH = [os.path.join(self.srcdir, 'main')])
|
|
|
|
def banner(self):
|
|
VERSION_STRING = ' v' + str(self.VERSION_MAJOR) + '.' + str(self.VERSION_MINOR) + '.' + str(self.VERSION_MICRO)
|
|
print '\n===== ' + self.PROGRAM_NAME + VERSION_STRING + ' =====\n'
|
|
|
|
def check_platform(self):
|
|
DXXCommon.check_platform(self)
|
|
env = self.env
|
|
# windows or *nix?
|
|
if sys.platform == 'darwin':
|
|
VERSION = str(self.VERSION_MAJOR) + '.' + str(self.VERSION_MINOR)
|
|
if (self.VERSION_MICRO):
|
|
VERSION += '.' + str(self.VERSION_MICRO)
|
|
env['VERSION_NUM'] = VERSION
|
|
env['VERSION_NAME'] = self.PROGRAM_NAME + ' v' + VERSION
|
|
env.Append(LIBS = ['m'])
|
|
|
|
def process_user_settings(self):
|
|
DXXCommon.process_user_settings(self)
|
|
env = self.env
|
|
# opengl or software renderer?
|
|
|
|
# profiler?
|
|
if (self.user_settings.profiler == 1):
|
|
env.Append(LINKFLAGS = '-pg')
|
|
|
|
env.Append(CPPDEFINES = [('SHAREPATH', '\\"' + str(self.user_settings.sharepath) + '\\"')])
|
|
|
|
def register_program(self):
|
|
self._register_program(self.shortname)
|
|
|
|
@classmethod
|
|
def compute_extra_version(cls):
|
|
c = cls._computed_extra_version
|
|
if c is None:
|
|
s = ds = None
|
|
v = cls._compute_extra_version()
|
|
if v:
|
|
s = Git.spcall(['status', '--short', '--branch'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
ds = Git.spcall(['diff', '--stat', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
cls._computed_extra_version = c = (v or '', s, ds)
|
|
return c
|
|
|
|
@classmethod
|
|
def _compute_extra_version(cls):
|
|
try:
|
|
g = Git.pcall(['describe', '--tags', '--abbrev=8'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
except OSError as e:
|
|
if e.errno == errno.ENOENT:
|
|
return None
|
|
raise
|
|
if g.returncode:
|
|
return None
|
|
c = Git.pcall(['diff', '--quiet', '--cached'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode
|
|
d = Git.pcall(['diff', '--quiet'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode
|
|
return g.out.split('\n')[0] + ('+' if c else '') + ('*' if d else '')
|
|
|
|
def _register_program(self,dxxstr,program_specific_objects=[]):
|
|
env = self.env
|
|
exe_target = os.path.join(self.srcdir, self.target)
|
|
static_archive_construction = self.static_archive_construction[self.user_settings.builddir]
|
|
objects = static_archive_construction.objects_common[:]
|
|
objects.extend(self.objects_common)
|
|
objects.extend(program_specific_objects)
|
|
if (self.user_settings.sdlmixer == 1):
|
|
objects.extend(static_archive_construction.objects_arch_sdlmixer)
|
|
objects.extend(self.objects_similar_arch_sdlmixer)
|
|
if (self.user_settings.opengl == 1) or (self.user_settings.opengles == 1):
|
|
env.Append(LIBS = self.platform_settings.ogllibs)
|
|
objects.extend(static_archive_construction.objects_arch_ogl)
|
|
objects.extend(self.objects_similar_arch_ogl)
|
|
else:
|
|
message(self, "building with Software Renderer")
|
|
objects.extend(static_archive_construction.objects_arch_sdl)
|
|
objects.extend(self.objects_similar_arch_sdl)
|
|
if (self.user_settings.editor == 1):
|
|
objects.extend(self.objects_editor)
|
|
objects.extend(static_archive_construction.objects_editor)
|
|
exe_target += '-editor'
|
|
if self.user_settings.program_name:
|
|
exe_target = self.user_settings.program_name
|
|
versid_build_environ = ['CXX', 'CPPFLAGS', 'CXXFLAGS', 'LINKFLAGS']
|
|
versid_cppdefines = env['CPPDEFINES'][:]
|
|
versid_cppdefines.extend([('DESCENT_%s' % k, self._quote_cppdefine(env.get(k, ''))) for k in versid_build_environ])
|
|
v = StaticSubprocess.pcall(env['CXX'].split(' ') + ['--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
if not v.returncode and (v.out or v.err):
|
|
v = (v.out or v.err).split('\n')[0]
|
|
versid_cppdefines.append(('DESCENT_%s' % 'CXX_version', self._quote_cppdefine(v)))
|
|
versid_build_environ.append('CXX_version')
|
|
extra_version = self.user_settings.extra_version
|
|
if extra_version is None:
|
|
extra_version = 'v%u.%u' % (self.VERSION_MAJOR, self.VERSION_MINOR)
|
|
if self.VERSION_MICRO:
|
|
extra_version += '.%u' % self.VERSION_MICRO
|
|
git_describe_version = (self.compute_extra_version() if self.user_settings.git_describe_version else ('', '', ''))
|
|
if git_describe_version[0] and not (extra_version and (extra_version == git_describe_version[0] or (extra_version[0] == 'v' and extra_version[1:] == git_describe_version[0]))):
|
|
# Suppress duplicate output
|
|
if extra_version:
|
|
extra_version += ' '
|
|
extra_version += git_describe_version[0]
|
|
if extra_version:
|
|
versid_cppdefines.append(('DESCENT_VERSION_EXTRA', self._quote_cppdefine(extra_version, f=str)))
|
|
versid_cppdefines.append(('DESCENT_git_status', self._quote_cppdefine(git_describe_version[1])))
|
|
versid_build_environ.append('git_status')
|
|
versid_cppdefines.append(('DESCENT_git_diffstat', self._quote_cppdefine(git_describe_version[2])))
|
|
versid_build_environ.append('git_diffstat')
|
|
versid_cppdefines.append(('DXX_RBE"(A)"', "'" + ''.join(['A(%s)' % k for k in versid_build_environ]) + "'"))
|
|
versid_environ = self.env['ENV'].copy()
|
|
# Direct mode conflicts with __TIME__
|
|
versid_environ['CCACHE_NODIRECT'] = 1
|
|
versid_objlist = [self.env.StaticObject(target='%s%s%s' % (self.user_settings.builddir, self._apply_target_name(s), self.env["OBJSUFFIX"]), source=s, CPPDEFINES=versid_cppdefines, ENV=versid_environ) for s in ['similar/main/vers_id.cpp']]
|
|
if self.user_settings.versid_depend_all:
|
|
env.Depends(versid_objlist[0], objects)
|
|
if env._dxx_pch_node:
|
|
env.Depends(versid_objlist[0], env._dxx_pch_node)
|
|
objects.extend(versid_objlist)
|
|
# finally building program...
|
|
exe_node = env.Program(target=os.path.join(self.user_settings.builddir, str(exe_target)), source = self.sources + objects)
|
|
if self.user_settings.host_platform != 'darwin':
|
|
if self.user_settings.register_install_target:
|
|
install_dir = (self.user_settings.DESTDIR or '') + self.user_settings.BIN_DIR
|
|
env.Install(install_dir, exe_node)
|
|
env.Alias('install', install_dir)
|
|
else:
|
|
syspath = sys.path[:]
|
|
cocoa = 'common/arch/cocoa'
|
|
sys.path += [cocoa]
|
|
import tool_bundle
|
|
sys.path = syspath
|
|
tool_bundle.TOOL_BUNDLE(env)
|
|
env.MakeBundle(os.path.join(self.user_settings.builddir, self.PROGRAM_NAME + '.app'), exe_node,
|
|
'free.%s-rebirth' % dxxstr, os.path.join(cocoa, 'Info.plist'),
|
|
typecode='APPL', creator='DCNT',
|
|
icon_file=os.path.join(cocoa, '%s-rebirth.icns' % dxxstr),
|
|
resources=[[os.path.join(self.srcdir, s), s] for s in ['English.lproj/InfoPlist.strings']])
|
|
|
|
def GenerateHelpText(self):
|
|
return self.variables.GenerateHelpText(self.env)
|
|
|
|
class D1XProgram(DXXProgram):
|
|
PROGRAM_NAME = 'D1X-Rebirth'
|
|
target = 'd1x-rebirth'
|
|
srcdir = 'd1x-rebirth'
|
|
shortname = 'd1x'
|
|
def prepare_environment(self):
|
|
DXXProgram.prepare_environment(self)
|
|
# Flags and stuff for all platforms...
|
|
self.env.Append(CPPDEFINES = ['DXX_BUILD_DESCENT_I'])
|
|
|
|
# general source files
|
|
__objects_common = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join(srcdir, f) for f in [
|
|
'main/bmread.cpp',
|
|
'main/custom.cpp',
|
|
'main/snddecom.cpp',
|
|
#'tracker/client/tracker_client.c'
|
|
]
|
|
],
|
|
}])
|
|
@property
|
|
def objects_common(self):
|
|
return self.__objects_common + DXXProgram.objects_common.fget(self)
|
|
|
|
# for editor
|
|
__objects_editor = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join(srcdir, f) for f in [
|
|
'main/hostage.cpp',
|
|
'editor/ehostage.cpp',
|
|
]
|
|
],
|
|
}])
|
|
@property
|
|
def objects_editor(self):
|
|
return self.__objects_editor + DXXProgram.objects_editor.fget(self)
|
|
|
|
class D2XProgram(DXXProgram):
|
|
PROGRAM_NAME = 'D2X-Rebirth'
|
|
target = 'd2x-rebirth'
|
|
srcdir = 'd2x-rebirth'
|
|
shortname = 'd2x'
|
|
def prepare_environment(self):
|
|
DXXProgram.prepare_environment(self)
|
|
# Flags and stuff for all platforms...
|
|
self.env.Append(CPPDEFINES = ['DXX_BUILD_DESCENT_II'])
|
|
|
|
# general source files
|
|
__objects_common = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join(srcdir, f) for f in [
|
|
'libmve/decoder8.cpp',
|
|
'libmve/decoder16.cpp',
|
|
'libmve/mve_audio.cpp',
|
|
'libmve/mvelib.cpp',
|
|
'libmve/mveplay.cpp',
|
|
'main/escort.cpp',
|
|
'main/gamepal.cpp',
|
|
'main/movie.cpp',
|
|
'misc/physfsrwops.cpp',
|
|
]
|
|
],
|
|
}])
|
|
@property
|
|
def objects_common(self):
|
|
return self.__objects_common + DXXProgram.objects_common.fget(self)
|
|
|
|
# for editor
|
|
__objects_editor = DXXCommon.create_lazy_object_property([{
|
|
'source':[os.path.join(srcdir, f) for f in [
|
|
'main/bmread.cpp',
|
|
]
|
|
],
|
|
}])
|
|
@property
|
|
def objects_editor(self):
|
|
return self.__objects_editor + DXXProgram.objects_editor.fget(self)
|
|
|
|
variables = Variables([v for (k,v) in ARGLIST if k == 'site'] or ['site-local.py'], ARGUMENTS)
|
|
filtered_help = FilterHelpText()
|
|
variables.FormatVariableHelpText = filtered_help.FormatVariableHelpText
|
|
def _filter_duplicate_prefix_elements(e,s):
|
|
r = e not in s
|
|
s.add(e)
|
|
return r
|
|
def register_program(program,other_program):
|
|
s = program.shortname
|
|
import itertools
|
|
l = [v for (k,v) in ARGLIST if k == s or k == 'dxx'] or [other_program.shortname not in ARGUMENTS]
|
|
# Fallback case: build the regular configuration.
|
|
if len(l) == 1:
|
|
try:
|
|
if int(l[0]):
|
|
return [program((s,''), variables)]
|
|
return []
|
|
except ValueError:
|
|
# If not an integer, treat this as a configuration profile.
|
|
pass
|
|
r = []
|
|
seen = set()
|
|
for e in l:
|
|
for prefix in itertools.product(*[v.split('+') for v in e.split(',')]):
|
|
duplicates = set()
|
|
prefix = tuple(p for p in prefix if _filter_duplicate_prefix_elements(p, duplicates))
|
|
if prefix in seen:
|
|
continue
|
|
seen.add(prefix)
|
|
prefix = ['%s%s%s' % (s, '_' if p else '', p) for p in prefix] + list(prefix)
|
|
r.append(program(prefix, variables))
|
|
return r
|
|
d1x = register_program(D1XProgram, D2XProgram)
|
|
d2x = register_program(D2XProgram, D1XProgram)
|
|
|
|
# show some help when running scons -h
|
|
h = 'DXX-Rebirth, SConstruct file help:' + """
|
|
|
|
Type 'scons' to build the binary.
|
|
Type 'scons install' to build (if it hasn't been done) and install.
|
|
Type 'scons -c' to clean up.
|
|
|
|
Extra options (add them to command line, like 'scons extraoption=value'):
|
|
d1x=[0/1] Disable/enable D1X-Rebirth
|
|
d1x=prefix-list Enable D1X-Rebirth with prefix-list modifiers
|
|
d2x=[0/1] Disable/enable D2X-Rebirth
|
|
d2x=prefix-list Enable D2X-Rebirth with prefix-list modifiers
|
|
dxx=VALUE Equivalent to d1x=VALUE d2x=VALUE
|
|
"""
|
|
substenv = SCons.Environment.SubstitutionEnvironment()
|
|
variables.Update(substenv)
|
|
for d in d1x + d2x:
|
|
d.init(substenv)
|
|
h += d.PROGRAM_NAME + ('.%d:\n' % d.program_instance) + d.GenerateHelpText()
|
|
Help(h)
|
|
unknown = variables.UnknownVariables()
|
|
# Delete known unregistered variables
|
|
unknown.pop('d1x', None)
|
|
unknown.pop('d2x', None)
|
|
unknown.pop('dxx', None)
|
|
unknown.pop('site', None)
|
|
ignore_unknown_variables = unknown.pop('ignore_unknown_variables', '0')
|
|
if unknown:
|
|
try:
|
|
ignore_unknown_variables = int(ignore_unknown_variables)
|
|
except ValueError:
|
|
ignore_unknown_variables = False
|
|
if not ignore_unknown_variables:
|
|
raise SCons.Errors.StopError('Unknown values specified on command line.' +
|
|
''.join(['\n\t%s' % k for k in unknown.keys()]) +
|
|
'\nRemove unknown values or set ignore_unknown_variables=1 to continue.')
|
|
|
|
#EOF
|