diff --git a/SConstruct b/SConstruct index 140ab6a..3f336e3 100644 --- a/SConstruct +++ b/SConstruct @@ -6,122 +6,10 @@ # # Eryn Wells - -# -# DEFAULT CONFIGURATION VALUES -# - -# Settings for the default toolchain binaries. Setting these overrides the -# defaults, provided your the given binary exists. -CC = None -CXX = None -AS = None -LINK = 'clang++' - -CCFLAGS = ['-Wall', '-Wextra', '-pedantic'] - - -# -# BUILD STUFF BELOW HERE -# - import os -import os.path -import sys -import SCons.Errors +import erw -def which(program): - def is_executable(path): - return os.path.exists(path) and os.access(path, os.X_OK) - - path, name = os.path.split(program) - if path: - if is_executable(program): - return program - else: - pathext = [''] + os.environ.get('PATHEXT', '').split(os.pathsep) - for path in os.environ.get('PATH', '').split(os.pathsep): - exe = os.path.join(path, program) - for ext in pathext: - candidate = exe + ext - if is_executable(candidate): - return candidate - return None - - -def get_bool_argument(arg): - try: - return bool(int(arg)) - except ValueError: - pass - if arg in ('False', 'FALSE', 'false', ''): - return False - return True - - -def set_toolchain_binary(env, var, user_binary, binaries=()): - if user_binary and which(user_binary): - env[var] = user_binary - return - for c in binaries: - if which(c): - env[var] = c - break - -common_env = Environment() -set_toolchain_binary(common_env, 'CC', CC, ('clang', 'gcc')) -set_toolchain_binary(common_env, 'CXX', CXX, ('clang++', 'g++')) -set_toolchain_binary(common_env, 'AS', AS) -set_toolchain_binary(common_env, 'LINK', LINK) - - -def verbose_build_cmds(): - def generate_comstr(action): - return '{:>25}: $TARGET'.format(action) - common_env['ARCOMSTR'] = generate_comstr('Archiving') - common_env['ASCOMSTR'] = generate_comstr('Assembling') - common_env['ASPPCOMSTR'] = generate_comstr('Assembling') - common_env['CCCOMSTR'] = generate_comstr('Building (C)') - common_env['CXXCOMSTR'] = generate_comstr('Building (C++)') - common_env['LINKCOMSTR'] = generate_comstr('Linking') - common_env['RANLIBCOMSTR'] = generate_comstr('Indexing') - common_env['SHCCCOMSTR'] = generate_comstr('Building (C, Shared)') - common_env['SHCXXCOMSTR'] = generate_comstr('Building (C++, Shared)') - common_env['SHLINKCOMSTR'] = generate_comstr('Linking (Shared)') - - -def succinct_build_cmds(): - def generate_comstr(action): - return ' [{:^6}] $TARGET'.format(action) - common_env['ARCOMSTR'] = generate_comstr('AR') - common_env['ASCOMSTR'] = generate_comstr('AS') - common_env['ASPPCOMSTR'] = generate_comstr('AS') - common_env['CCCOMSTR'] = generate_comstr('CC') - common_env['CXXCOMSTR'] = generate_comstr('CXX') - common_env['LINKCOMSTR'] = generate_comstr('LINK') - common_env['RANLIBCOMSTR'] = generate_comstr('RANLIB') - common_env['SHCCCOMSTR'] = generate_comstr('SHCC') - common_env['SHCXXCOMSTR'] = generate_comstr('SHCXX') - common_env['SHLINKCOMSTR'] = generate_comstr('SHLINK') - - -BUILD_CMDS = get_bool_argument(ARGUMENTS.get('BUILD_CMDS', False)) -if not BUILD_CMDS: - verbose_build_cmds() - -# Separate environment for building libraries because they often don't use the -# same CCFLAGS I do. -lib_env = common_env.Clone() - -common_env.Append(CCFLAGS=CCFLAGS) -common_env.Append(CFLAGS=['-std=c99']) -common_env.Append(CXXFLAGS=['-std=c++11']) - -# Add color error messages for clang -if sys.stdout.isatty(): - if 'clang' in common_env['CC'] or 'clang' in common_env['CXX']: - common_env.Append(CCFLAGS=['-fcolor-diagnostics']) BUILD_DIR = Dir('#build') LIB_DIR = Dir('#lib') @@ -129,82 +17,44 @@ SRC_DIR = Dir('#src') TEST_DIR = Dir('#test') -def create_env(name, appends=None): - output_dir = BUILD_DIR.Dir(name) - env = common_env.Clone() - # Standard env extensions. - env.Append(CPPPATH=[SRC_DIR]) - # Custom env stuff. - env['__name'] = name - if appends: - for k, v in appends.iteritems(): - if k.startswith('='): - env[k[1:]] = v - else: - env.Append(**{k: v}) - return env - - -def do_sconscript(env, build_env, src_dir, out_dir): +def do_sconscript(env, src_dir, out_dir): sconscript = src_dir.File('SConscript') print 'Reading {}'.format(sconscript) # Swapping env and build_env here is a bit wacky. Doing so means that env is # always the Environment that the SConscript should be building with, while # build_env is the Environment we're using to put everything together. env.SConscript(sconscript, - {'env': build_env, 'build_env': env}, + {'env': env.Clone(), 'build_env': env}, variant_dir=out_dir) - -debug_env = create_env('debug', { - 'CPPDEFINES': ['NDEBUG'], - 'CCFLAGS': ['-O0', '-g'], -}) - -beta_env = create_env('beta', { - 'CPPDEFINES': ['NDEBUG'], - 'CCFLAGS': ['-O3', '-g'], -}) - -release_env = create_env('release', { - 'CPPDEFINES': ['NRELEASE'], - 'CCFLAGS': ['-O3'] -}) - +BUILD_CMDS = get_bool_argument(ARGUMENTS.get('BUILD_CMDS', False)) +MODE = ARGUMENTS.get('MODE', None) modes = { - 'debug': debug_env, - 'beta': beta_env, - 'release': release_env, + 'debug': erw.DebugEnvironment(succinct=not BUILD_CMDS), + 'beta': erw.BetaEnvironment(succinct=not BUILD_CMDS), + 'release': erw.ReleaseEnvironment(succinct=not BUILD_CMDS), } -mode = ARGUMENTS.get('MODE', None) -build_modes = [] -if mode: - # If MODE=foo is specified, build only that mode. - build_modes.append(mode) -else: - build_modes = ['debug'] - -for mode in build_modes: +for mode in (MODE.split(',') if MODE else ['debug']): try: env = modes[mode] except KeyError: print 'Skipping invalid mode: {}'.format(mode) break - out_dir = BUILD_DIR.Dir(env['__name']) + out_dir = BUILD_DIR.Dir(env['_name']) # Process all lib dirs. for lib in os.listdir(LIB_DIR.abspath): lib_out_dir = out_dir.Dir('lib').Dir(lib) - do_sconscript(env, lib_env, LIB_DIR.Dir(lib), lib_out_dir) + do_sconscript(env, LIB_DIR.Dir(lib), lib_out_dir) env.Append(LIBPATH=[lib_out_dir]) # Get source files. src_out_dir = out_dir.Dir('src') - do_sconscript(env, env, SRC_DIR, src_out_dir) + do_sconscript(env, SRC_DIR, src_out_dir) env.Append(LIBPATH=[src_out_dir]) # Get test binaries. - do_sconscript(env, env, TEST_DIR, out_dir.Dir('test')) + do_sconscript(env, TEST_DIR, out_dir.Dir('test')) diff --git a/site_scons/.gitignore b/site_scons/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/site_scons/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/site_scons/erw.py b/site_scons/erw.py new file mode 100644 index 0000000..4742080 --- /dev/null +++ b/site_scons/erw.py @@ -0,0 +1,84 @@ +# erw.py +# Eryn Wells + +import sys + +import SCons.Environment + +import paths + +# +# Environments +# + +class Environment(SCons.Environment.Environment): + ''' + Default SCons environment for building things. + ''' + + def __init__(self, name, modern=True, paranoid=True, colorful=True, succinct=True, **kwargs): + # Use clang if its available. + kwargs.setdefault('CC', self._toolchain_binary(('clang', 'gcc'))) + kwargs.setdefault('CXX', self._toolchain_binary(('clang++', 'g++'))) + kwargs.setdefault('LINK', self._toolchain_binary(('clang++'))) + + super(Environment, self).__init__(**kwargs) + + self['_name'] = name + + # Modern C/C++ + if modern: + self.Append(CFLAGS=['-std=c99']) + self.Append(CXXFLAGS=['-std=c++11']) + + # Paranoid C/C++ + if paranoid: + self.Append(CCFLAGS=['-Wall', '-Wextra', '-pedantic']) + + # Colorful C/C++ + if colorful and sys.stdout.isatty(): + if 'clang' in self['CC'] or 'clang' in self['CXX']: + self.Append(CCFLAGS=['-fcolor-diagnostics']) + + self['ARCOMSTR'] = self._comstr('Archiving', succinct) + self['ASCOMSTR'] = self._comstr('Assembling', succinct) + self['ASPPCOMSTR'] = self._comstr('Assembling', succinct) + self['CCCOMSTR'] = self._comstr('Building (C)', succinct) + self['CXXCOMSTR'] = self._comstr('Building (C++)', succinct) + self['LINKCOMSTR'] = self._comstr('Linking', succinct) + self['RANLIBCOMSTR'] = self._comstr('Indexing', succinct) + self['SHCCCOMSTR'] = self._comstr('Building (C, Shared)', succinct) + self['SHCXXCOMSTR'] = self._comstr('Building (C++, Shared)', succinct) + self['SHLINKCOMSTR'] = self._comstr('Linking (Shared)', succinct) + + def _toolchain_binary(self, binaries): + for b in binaries: + if b and paths.which(b): + return b + + def _comstr(self, action, succinct=True): + if succinct: + return '{:>25}: $TARGET'.format(action) + else: + return ' [{:^6}] $TARGET'.format(action) + + +class DebugEnvironment(Environment): + def __init__(self, name='debug', **kwargs): + super(DebugEnvironment, self).__init__(name, **kwargs) + self.Append(CPPDEFINES=['NDEBUG']) + self.Append(CCFLAGS=['-O0', '-g']) + + +class BetaEnvironment(Environment): + def __init__(self, name='beta', **kwargs): + super(BetaEnvironment, self).__init__(name, **kwargs) + self.Append(CPPDEFINES=['NDEBUG']) + self.Append(CCFLAGS=['-O3', '-g']) + + +class ReleaseEnvironment(Environment): + def __init__(self, name='release', **kwargs): + super(ReleaseEnvironment, self).__init__(name, **kwargs) + self.Append(CPPDEFINES=['NRELEASE']) + self.Append(CCFLAGS=['-O3']) diff --git a/site_scons/paths.py b/site_scons/paths.py new file mode 100644 index 0000000..c2b37b9 --- /dev/null +++ b/site_scons/paths.py @@ -0,0 +1,30 @@ +# paths.py +# Eryn Wells + +import os +import os.path + + +def is_executable(path): + return os.path.exists(path) and os.access(path, os.X_OK) + + +def which(program): + ''' + Look for `program` in system path and return the full path to that binary if + it is found. Otherwise, return `None`. + ''' + path, name = os.path.split(program) + if path: + if is_executable(program): + return program + else: + pathext = [''] + os.environ.get('PATHEXT', '').split(os.pathsep) + for path in os.environ.get('PATH', '').split(os.pathsep): + exe = os.path.join(path, program) + for ext in pathext: + candidate = exe + ext + if is_executable(candidate): + return candidate + return None + diff --git a/site_scons/site_init.py b/site_scons/site_init.py new file mode 100644 index 0000000..1c64046 --- /dev/null +++ b/site_scons/site_init.py @@ -0,0 +1,20 @@ +# site_init.py +# Eryn Wells + +import erw + +# +# Argument utils +# + +def get_bool_argument(arg): + ''' + Convert the given argument value to a bool. True values are any integer that + is considered true by Python, and any string value that isn't a + capitalization variant of the word "false". + ''' + try: + return bool(int(arg)) + except ValueError: + pass + return str(arg).lower() != 'false'