diff --git a/SConstruct b/SConstruct index 48b0d84..5b7d355 100644 --- a/SConstruct +++ b/SConstruct @@ -1,21 +1,175 @@ # SConstruct # vim: set ft=python: +# +# Toplevel Scons build script. This should be mostly complete and generic enough +# for most builds. +# # Eryn Wells -''' -Toplevel Scons build script. This should be mostly complete and generic enough -for most builds. -''' -import logging +# +# DEFAULT CONFIGURATION VALUES +# -setup_logging() +# Settings for the default toolchain binaries. Setting these overrides the +# defaults, provided your the given binary exists. +CC = None +CXX = None +AS = None +LINK = None + +# Same as above but for default CFLAGS. These are appended to both CFLAGS and +# CXXFLAGS. +CFLAGS = '-Wall -Wextra -pedantic' + + +# +# BUILD STUFF BELOW HERE +# + +import os +import os.path +import SCons.Errors + + +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) +common_env.Append(CFLAGS='{} -std=c99'.format(CFLAGS)) +common_env.Append(CXXFLAGS='{} -std=c++11'.format(CFLAGS)) + +# Add color error messages for clang +if 'clang' in common_env['CC']: + common_env.Append(CFLAGS=' -fcolor-diagnostics') +if 'clang' in common_env['CXX']: + common_env.Append(CXXFLAGS=' -fcolor-diagnostics') BUILD_CMDS = get_bool_argument(ARGUMENTS.get('BUILD_CMDS', False)) -MODE = ARGUMENTS.get('MODE', None) +if not 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)') -for mode in (MODE.split(',') if MODE else ['debug']): - env = Environment.for_mode(mode) - env.process_lib_dirs() - env.process_src() - env.process_tests() +build_dir = Dir('#build') +lib_dir = Dir('#lib') +src_dir = Dir('#src') +test_dir = Dir('#test') + + +def create_env(name, src_dirs, appends=None): + output_dir = build_dir.Dir(name) + env = common_env.Clone() + env['__name'] = name + env['__build_dir'] = output_dir + env['__src_dirs'] = [] + env['__output_dirs'] = [] + for d in src_dirs: + out_dir = output_dir.Dir(d.path) + env['__src_dirs'].append(d) + env['__output_dirs'].append(out_dir) + env.VariantDir(out_dir, d.path, duplicate=0) + env.Clean('.', out_dir) + if appends: + for k, v in appends.iteritems(): + if k.startswith('='): + env[k[1:]] = v + else: + env.Append(**{k: v}) + return env + + +debug_cflags = ' -O0 -g' +debug_env = create_env('debug', [src_dir], { + 'CPPDEFINES': ['DEBUG'], + 'CFLAGS': debug_cflags, + 'CXXFLAGS': debug_cflags, +}) + +release_cflags = ' -O2' +release_env = create_env('release', [src_dir], { + 'CPPDEFINES': ['RELEASE'], + 'CFLAGS': release_cflags, + 'CXXFLAGS': release_cflags, +}) + +test_gtest_dir = Dir('#lib/gtest') +test_cpppath = test_gtest_dir.Dir('include') +test_env = create_env('test', [src_dir, test_dir, test_gtest_dir], { + 'CPPDEFINES': ['DEBUG'], + 'CPPPATH': [test_cpppath], + 'LIBPATH': [test_gtest_dir], + 'CFLAGS': debug_cflags, + 'CXXFLAGS': debug_cflags, +}) + + +modes = { + 'debug': debug_env, + 'release': release_env, + 'test': test_env, +} + +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 = modes.keys() + +for mode in build_modes: + try: + env = modes[mode] + except KeyError: + print 'Skipping invalid mode: {}'.format(mode) + for d in env['__output_dirs']: + env.SConscript(d.File('SConscript'), {'env': env}) diff --git a/lib/gtest/SConscript b/lib/gtest/SConscript index 5d2e1c7..42a81b0 100644 --- a/lib/gtest/SConscript +++ b/lib/gtest/SConscript @@ -1,8 +1,14 @@ # SConscript # vim: set ft=python: +# # Eryn Wells -Library('gtest', [ +import os.path + +Import('env') + + +files = [ 'gtest-all.cc', 'gtest-death-test.cc', 'gtest-filepath.cc', @@ -11,5 +17,12 @@ Library('gtest', [ 'gtest-test-part.cc', 'gtest-typed-test.cc', 'gtest.cc', - 'gtest_main.cc' -]) +] + +objs = [] +for f in files: + objs.append(env.Object(f)) + +env.Append(CPPPATH=[Dir('include').srcnode()]) + +gtest = env.Library('gtest', objs) diff --git a/site_scons/.gitignore b/site_scons/.gitignore deleted file mode 100644 index 0d20b64..0000000 --- a/site_scons/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc diff --git a/site_scons/site_init.py b/site_scons/site_init.py deleted file mode 100644 index 0c2f2ba..0000000 --- a/site_scons/site_init.py +++ /dev/null @@ -1,210 +0,0 @@ -# site_init.py -# Eryn Wells - -import logging -import sys -import SCons.Environment -import SCons.Errors - - -def setup_logging(level=logging.DEBUG): - '''Configure global logging for the SCons system.''' - root = logging.getLogger() - root.setLevel(logging.DEBUG) - root.addHandler(logging.StreamHandler()) - -# -# 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' - -# -# Builders -# - -def program_builder(env): - original_builder = env.Program - def builder(env, prog_name, sources, *args, **kwargs): - return original_builder(prog_name, sources, *args, **kwargs) - return builder - -# -# Environments -# - -class Environment(SCons.Environment.Environment): - ''' - Default SCons environment for building things. - ''' - - @classmethod - def _comstr(cls, action, succinct=True): - if succinct: - return '{:>25}: $TARGET'.format(action) - # Use the default *COMSTR. - return None - - @classmethod - def for_mode(cls, mode, name=None, succinct=True): - kwargs = {'succinct': succinct} - if name: - kwargs['name'] = name - mode_lower = mode.lower() - if mode_lower == 'debug': - return DebugEnvironment(**kwargs) - elif mode_lower == 'beta': - return BetaEnvironment(**kwargs) - elif mode_lower == 'release': - return ReleaseEnvironment(**kwargs) - raise SCons.Errors.UserError('Invalid mode: {}'.format(mode)) - - def __init__(self, name, modern=True, paranoid=True, colorful=True, succinct=True, **kwargs): - super(Environment, self).__init__(**self._append_custom_tools(kwargs)) - - self.SetDefault(NAME=name) - - self.SetDefault(LOGGER=logging.getLogger(name)) - - self.SetDefault(BUILD_ROOT=self.Dir('#build')) - self.SetDefault(LIB_ROOT=self.Dir('#lib')) - self.SetDefault(SRC_ROOT=self.Dir('#src')) - self.SetDefault(TEST_ROOT=self.Dir('#test')) - self.SetDefault(INCLUDE_ROOT=self.Dir('#include')) - - # Allow same directory includes. - self.AppendUnique(CPPPATH=['.']) - - self['CC'] = self.Detect(['clang', 'gcc']) - self['CXX'] = self.Detect(['clang++', 'g++']) - self['LINK'] = self.Detect(['clang++', 'clang', 'ld']) - - # Modern C/C++ - if modern: - self.AppendUnique(CFLAGS=['-std=c99']) - self.AppendUnique(CXXFLAGS=['-std=c++11']) - - # Paranoid C/C++ - if paranoid: - self.AppendUnique(CCFLAGS=['-Wall', '-Wextra', '-pedantic']) - - # Colorful C/C++ - if colorful and sys.stdout.isatty(): - if 'clang' in self['CC'] or 'clang' in self['CXX']: - self.AppendUnique(CCFLAGS=['-fcolor-diagnostics']) - elif 'gcc' in self['CC'] or 'g++' in self['CXX']: - # TODO: Also set a GCC_COLORS variable in the system environment? - self.AppendUnique(CCFLAGS=['-fdiagnostics-color=always']) - - # Pretty printing - self.SetDefault(ARCOMSTR=Environment._comstr('Archiving', succinct)) - self.SetDefault(ASCOMSTR=Environment._comstr('Assembling', succinct)) - self.SetDefault(ASPPCOMSTR=Environment._comstr('Assembling', succinct)) - self.SetDefault(CCCOMSTR=Environment._comstr('Building (C)', succinct)) - self.SetDefault(CXXCOMSTR=Environment._comstr('Building (C++)', succinct)) - self.SetDefault(LINKCOMSTR=Environment._comstr('Linking', succinct)) - self.SetDefault(RANLIBCOMSTR=Environment._comstr('Indexing', succinct)) - self.SetDefault(SHCCCOMSTR=Environment._comstr('Building (C, Shared)', succinct)) - self.SetDefault(SHCXXCOMSTR=Environment._comstr('Building (C++, Shared)', succinct)) - self.SetDefault(SHLINKCOMSTR=Environment._comstr('Linking (Shared)', succinct)) - - @property - def build_root(self): - '''Return the build output directory for this environment.''' - return self['BUILD_ROOT'].Dir(self['NAME']) - - @property - def lib_root(self): - '''Return the root directory for libraries for this environment.''' - return self['LIB_ROOT'] - - @property - def src_root(self): - '''Return the main source root directory for this environment.''' - return self['SRC_ROOT'] - - def process_src(self): - out_dir = self.build_root.Dir('src') - self.SConscript(self.src_root.File('SConscript'), - variant_dir=out_dir) - self.Append(CPPPATH=[self.src_root]) - - # - # Test processing - # - - def test_objects(self, name): - self._ensure_test_structure(name) - return self['TESTS'][name]['objects'] - - def test_program(self, name): - self._ensure_test_structure(name) - return self['TESTS'][name]['program'] - - def register_test_program(self, name, prog): - self._ensure_test_structure(name) - self['TESTS'][name]['program'] = prog - - def process_tests(self): - tests = [] - for test, struct in self['TESTS'].iteritems(): - if not struct['program']: - continue - tests.append(self.TestRun(test)) - self.Alias('test', tests) - - def _ensure_test_structure(self, name): - self['TESTS'].setdefault(name, {'program': None, 'objects': []}) - - # - # Logging - # - - def log(self, msg, *args, **kwargs): - self['LOGGER'].info(msg, *args, **kwargs) - - def log_debug(self, msg, *args, **kwargs): - self['LOGGER'].debug(msg, *args, **kwargs) - - def log_error(self, msg, *args, **kwargs): - self['LOGGER'].error(msg, *args, **kwargs) - - def _append_custom_tools(self, kwargs): - '''Add custom tools to the `kwargs`.''' - tools = kwargs.setdefault('tools', ['default']) - for tool in ['lib', 'test', 'program', 'sconscript', 'swiftc']: - if tool in tools: - continue - tools.append(tool) - return kwargs - - -class DebugEnvironment(Environment): - def __init__(self, name='debug', **kwargs): - super(DebugEnvironment, self).__init__(name, **kwargs) - self.Append(CPPDEFINES=['DEBUG']) - self.Append(CCFLAGS=['-O0', '-g']) - - -class BetaEnvironment(Environment): - def __init__(self, name='beta', **kwargs): - super(BetaEnvironment, self).__init__(name, **kwargs) - self.Append(CPPDEFINES=['DEBUG']) - self.Append(CCFLAGS=['-O3', '-g']) - - -class ReleaseEnvironment(Environment): - def __init__(self, name='release', **kwargs): - super(ReleaseEnvironment, self).__init__(name, **kwargs) - self.Append(CPPDEFINES=['NDEBUG', 'RELEASE']) - self.Append(CCFLAGS=['-O3']) diff --git a/site_scons/site_tools/lib.py b/site_scons/site_tools/lib.py deleted file mode 100644 index dd47f61..0000000 --- a/site_scons/site_tools/lib.py +++ /dev/null @@ -1,81 +0,0 @@ -# lib.py -# Eryn Wells - -''' -SCons builder for a lib directory. -''' - -import os - -import SCons.Errors -import SCons.Script - - -def _lib(env, name): - return env['LOCAL_LIBS'].get(name) - - -def _register_lib(env, name, lib): - if name in env['LOCAL_LIBS']: - env.log_error('Library has already been built: {}'.format(name)) - env['LOCAL_LIBS'][name] = lib - - -def _lib_dirs(env): - for lib in os.listdir(env.lib_root.abspath): - lib_dir = env.lib_root.Dir(lib) - if not lib_dir.isdir(): - continue - yield (lib, lib_dir) - - -def _process_lib_dirs(env): - for name, _ in _lib_dirs(env): - env.LibDir(name) - - -def _process_lib_dir(env, lib, src_dir=None, out_dir=None, inc_dir=None): - if not src_dir: - src_dir = env.lib_root.Dir(lib) - if not src_dir.isdir(): - err = 'Invalid library source directory: {}'.format(src_dir) - env.log_error(err) - raise SCons.Errors.UserError(err) - if not out_dir: - out_dir = env.build_root.Dir('lib').Dir(lib) - if not inc_dir: - include_dir = src_dir.Dir('include') - if include_dir.isdir(): - inc_dir = [include_dir] - env.Append(CPPPATH=inc_dir) - out = env.SConscript(src_dir.File('SConscript'), - clone=True, - variant_dir=out_dir) - return out - - -def _build_library(env, lib_func): - original_builder = lib_func - - def builder(env, lib_name, sources, *args, **kwargs): - lib = original_builder(lib_name, sources, *args, **kwargs) - _register_lib(env, lib_name, lib) - return lib - - return builder - -# -# SCons tool interface -# - -def generate(env): - env.SetDefault(LOCAL_LIBS={}) - env.AddMethod(_process_lib_dir, 'LibDir') - env.AddMethod(_build_library(env, env.Library), 'Library') - env.AddMethod(_build_library(env, env.StaticLibrary), 'StaticLibrary') - env.AddMethod(_build_library(env, env.SharedLibrary), 'SharedLibrary') - env.AddMethod(_lib, 'lib') - env.AddMethod(_process_lib_dirs, 'process_lib_dirs') - -def exists(env): - return True diff --git a/site_scons/site_tools/osxapp.py b/site_scons/site_tools/osxapp.py deleted file mode 100644 index e56a795..0000000 --- a/site_scons/site_tools/osxapp.py +++ /dev/null @@ -1,36 +0,0 @@ -# osxapp.py -# Eryn Wells - -import os.path - -_BUNDLE_SUFFIX = '.app' - - -def _assemble_app_bundle(env, bundle_name, binary, info_plist, resources_dir): - # TODO: Create directory structure, copy binary, plist, and resources. Sign the binary? app bundle? - - if not bundle_name.endswith('.app'): - bundle_name += '.app' - - bundle_dir = env.Dir(bundle_name) - contents_dir = bundle_dir.Dir('Contents') - macos_dir = contents_dir.Dir('MacOS') - resources_dir = contents_dir.Dir('Resources') - - return [env.Mkdir(contents_dir), - env.Copy(contents_dir.File('Info.plist'), info_list), - env.Mkdir(macos_dir), - env.Copy(macos_dir.File(bundle_name), binary) - - -_app_builder = Builder(action=_assemble_app_bundle, - suffix=_BUNDLE_SUFFIX) - - -def generate(env): - env['OSXAPPBUNDLESUFFIX'] = _BUNDLE_SUFFIX - env['BUILDERS']['OSXApp'] = _app_builder - - -def exists(env): - return True diff --git a/site_scons/site_tools/program.py b/site_scons/site_tools/program.py deleted file mode 100644 index 21bbddf..0000000 --- a/site_scons/site_tools/program.py +++ /dev/null @@ -1,46 +0,0 @@ -# program.py -# Eryn Wells - -''' -SCons tool for working with Programs. -''' - -def _program(env, name): - return env['PROGRAMS'].get(name) - - -def _register_program(env, name, program): - env['PROGRAMS'][name] = program - - -def _build_program(env): - original_builder = env.Program - - def builder(env, name, sources, local_libs=None, *args, **kwargs): - # local_libs is an array of names of libs built in the local project. - # These will be looked up in the environment and added to the LIBS - # array, if present. -# if local_libs: -# local_libs = map(lambda lib: env.lib(lib), local_libs) -# try: -# kwargs['LIBS'].extend(local_libs) -# except KeyError: -# kwargs['LIBS'] = local_libs - prog = original_builder(name, sources, *args, **kwargs) - _register_program(env, name, prog) - return prog - - return builder - -# -# SCons tool interface -# - -def generate(env): - env.SetDefault(PROGRAMS={}) - env.AddMethod(_build_program(env), 'Program') - env.AddMethod(_program, 'program') - - -def exists(env): - return True diff --git a/site_scons/site_tools/sconscript.py b/site_scons/site_tools/sconscript.py deleted file mode 100644 index 3e9b597..0000000 --- a/site_scons/site_tools/sconscript.py +++ /dev/null @@ -1,38 +0,0 @@ -# sconscript.py -# Eryn Wells - -''' -SCons tool for working with SConscripts. -''' - -import SCons.Script - -def _do_sconscript(env): - original_sconscript = env.SConscript - - def sconscript(env, sconscript, clone=False, *args, **kwargs): - exports = { - 'Library': env.Library, - 'Object': env.Object, - 'SharedObject': env.SharedObject, - 'StaticLibrary': env.StaticLibrary, - 'SharedLibrary': env.SharedLibrary, - 'Program': env.Program, - 'env': env.Clone() if clone else env, - } - SCons.Script._SConscript.GlobalDict.update(exports) - env.log('Reading {}'.format(sconscript)) - return original_sconscript(sconscript, {}, *args, **kwargs) - - return sconscript - -# -# SCons tool interface -# - -def generate(env): - env.AddMethod(_do_sconscript(env), 'SConscript') - - -def exists(env): - return True diff --git a/site_scons/site_tools/swiftc.py b/site_scons/site_tools/swiftc.py deleted file mode 100644 index 5d2766d..0000000 --- a/site_scons/site_tools/swiftc.py +++ /dev/null @@ -1,33 +0,0 @@ -# swiftc.py -# vim: set ft=python: -# Eryn Wells - -''' -SCons plugin for building Swift files with swiftc. -''' - -import SCons.Action -import SCons.Tool -import SCons.Util - -SwiftSuffix = '.swift' -SwiftAction = SCons.Action.Action("$SWIFTCCOM", "$SWIFTCCOMSTR") - -compilers = ['swiftc'] - -def generate(env): - static_obj, shared_obj = SCons.Tool.createObjBuilders(env) - static_obj.add_action(SwiftSuffix, SwiftAction) - static_obj.add_emitter(SwiftSuffix, SCons.Defaults.SharedObjectEmitter) - shared_obj.add_action(SwiftSuffix, SwiftAction) - shared_obj.add_emitter(SwiftSuffix, SCons.Defaults.SharedObjectEmitter) - - if 'SWIFTC' not in env: - compiler = env.Detect(compilers) - env['SWIFTC'] = compiler if compiler else compilers[0] - env['SWIFTFLAGS'] = SCons.Util.CLVar('') - env['SWIFTCCOM'] = '$SWIFTC -o $TARGET -c $SWIFTFLAGS $SOURCES' - env['SWIFTFILESUFFIX'] = SwiftSuffix - -def exists(env): - return env.Detect(compilers) diff --git a/site_scons/site_tools/test.py b/site_scons/site_tools/test.py deleted file mode 100644 index df8e924..0000000 --- a/site_scons/site_tools/test.py +++ /dev/null @@ -1,51 +0,0 @@ -# test.py -# Eryn Wells - -''' -Test builder for SCons. Test files are compiled to objects and stored in the -environment. -''' - -def _process_test_dir(env, dir, program=None): - # TODO: Builder for test directories? - pass - - -def _build_test_object(env, source, program=None): - obj = env.Object(source) - if not program: - program = 'test' - try: - env.test_objects(program).extend(obj) - except TypeError: - env.test_objects(program).append(obj) - return obj - - -def _build_test_program(env, name=None): - if not name: - name = 'test' - prog = env.Program(name, env.test_objects(name), LIBS=[env.lib('gtest')]) - env.register_test_program(name, prog) - return - - -def _run_tests(env, name=None): - if not name: - name = 'test' - cmd = env.Command(env.test_program(name), None, '$SOURCE --gtest_color=yes') - env.AlwaysBuild(cmd) - return cmd - -# -# SCons tool interface -# - -def generate(env): - env.SetDefault(TESTS={}) - env.AddMethod(_build_test_object, 'Test') - env.AddMethod(_build_test_program, 'TestProgram') - env.AddMethod(_run_tests, 'TestRun') - -def exists(env): - return 'Object' in env diff --git a/src/SConscript b/src/SConscript index 5d9a22e..f0639e5 100644 --- a/src/SConscript +++ b/src/SConscript @@ -1,5 +1,25 @@ # SConscript # vim: set ft=python: +# # Eryn Wells -Program('hello', ['hello.cc']) +import os.path + +Import('env') + + +subdirs = [ + # TODO: Put subdirectories here. +] + +for d in subdirs: + env.SConscript(os.path.join(d, 'SConscript'), {'env': env}) + + +files = [ + # TODO: Put files here. +] + +objs = [] +for f in files: + objs.append(env.Object(f)) diff --git a/src/hello.cc b/src/hello.cc deleted file mode 100644 index b02d2e0..0000000 --- a/src/hello.cc +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int -main() -{ - std::cout << "Hello world!" << std::endl; - return 0; -} diff --git a/test/SConscript b/test/SConscript new file mode 100644 index 0000000..f0639e5 --- /dev/null +++ b/test/SConscript @@ -0,0 +1,25 @@ +# SConscript +# vim: set ft=python: +# +# Eryn Wells + +import os.path + +Import('env') + + +subdirs = [ + # TODO: Put subdirectories here. +] + +for d in subdirs: + env.SConscript(os.path.join(d, 'SConscript'), {'env': env}) + + +files = [ + # TODO: Put files here. +] + +objs = [] +for f in files: + objs.append(env.Object(f))