diff --git a/SConstruct b/SConstruct index 5b7d355..bbd54b4 100644 --- a/SConstruct +++ b/SConstruct @@ -27,149 +27,54 @@ CFLAGS = '-Wall -Wextra -pedantic' # BUILD STUFF BELOW HERE # -import os -import os.path -import SCons.Errors +if not GetOption('build_cmds'): + def comstr(action): + return '{:>18}: $TARGET'.format(action) + default_env = DefaultEnvironment() + default_env['ARCOMSTR'] = comstr('Archiving') + default_env['ASCOMSTR'] = comstr('Assembling') + default_env['ASPPCOMSTR'] = comstr('Assembling') + default_env['CCCOMSTR'] = comstr('Building (C)') + default_env['CXXCOMSTR'] = comstr('Building (C++)') + default_env['LINKCOMSTR'] = comstr('Linking') + default_env['RANLIBCOMSTR'] = comstr('Indexing') + default_env['SHCCCOMSTR'] = comstr('Building (C)') + default_env['SHCXXCOMSTR'] = comstr('Building (C++)') + default_env['SHLINKCOMSTR'] = comstr('Linking (Shared)') -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 +#test_gtest_dir = Dir('#lib/gtest') +#test_gtest_include = test_gtest_dir.Dir('include') +#test_env = create_env('test', [src_dir, test_dir, test_gtest_dir], { +# 'CPPDEFINES': ['DEBUG'], +# 'CPPPATH': [test_gtest_include], +# 'LIBPATH': [test_gtest_dir], +# 'CFLAGS': debug_cflags, +# 'CXXFLAGS': debug_cflags, +#}) -def get_bool_argument(arg): +for mode in GetOption('modes'): 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)) -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)') - -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] + env = MODES[mode] except KeyError: print 'Skipping invalid mode: {}'.format(mode) - for d in env['__output_dirs']: - env.SConscript(d.File('SConscript'), {'env': env}) + + # Process libraries + env.SConscript(LIB_DIR.File('SConscript'), { + 'env': env, + }, variant_dir=BUILD_DIR.Dir(env['MODE']).Dir('lib'), duplicate=0) + + # Process source + library, binary = env.SConscript(SRC_DIR.File('SConscript'), { + 'env': env + }, variant_dir=BUILD_DIR.Dir(env['MODE']).Dir('src'), duplicate=0) + env.Alias('lib', library) + env.Alias('bin', binary) + + env.SConscript(TEST_DIR.File('SConscript'), { + 'env': env, + }, variant_dir=BUILD_DIR.Dir(env['MODE']).Dir('test'), duplicate=0) + +Import('LIBS') +print LIBS diff --git a/lib/SConscript b/lib/SConscript new file mode 100644 index 0000000..1e223b9 --- /dev/null +++ b/lib/SConscript @@ -0,0 +1,24 @@ +# SConscript +# +# SCons build script for libs in base. Aggregates static and shared libraries in +# these directories and exports them in the 'LIBS' variable. +# +# Eryn Wells + + +Import('env') + +dirs = ( + 'gtest', +) + +env['LIBS'] = {} +for d in dirs: + static, dynamic = env.SConscript(Dir(d).File('SConscript'), { + 'env': env, + }) + env['LIBS'][d] = {} + if static: + env['LIBS'][d]['static'] = static + if dynamic: + env['LIBS'][d]['dynamic'] = dynamic diff --git a/lib/gtest/SConscript b/lib/gtest/SConscript index 42a81b0..a86241e 100644 --- a/lib/gtest/SConscript +++ b/lib/gtest/SConscript @@ -25,4 +25,6 @@ for f in files: env.Append(CPPPATH=[Dir('include').srcnode()]) -gtest = env.Library('gtest', objs) +gtest_static = env.Library('gtest', [env.StaticObject(f) for f in files]) +gtest_dynamic = None +Return('gtest_static gtest_dynamic') 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/binaries.py b/site_scons/binaries.py new file mode 100644 index 0000000..b858376 --- /dev/null +++ b/site_scons/binaries.py @@ -0,0 +1,42 @@ +# files.py +# +# Utilities for working with files and paths in SCons. +# +# Eryn Wells + +import os + + +def which(program): + ''' + Given a program name, search the system environment's $PATH for a binary of + that name. If one exists, return its name. If not, return None. This + function will also use the system environment's $PATHEXT to find binaries + with appropriate extensions (i.e., .exe on Windows). + ''' + is_executable = lambda path: ( 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 first(binaries): + ''' + Given a list of binaries, return the first one found. + ''' + for binary in binaries: + if which(binary): + return binary + return None diff --git a/site_scons/dirs.py b/site_scons/dirs.py new file mode 100644 index 0000000..7aa8751 --- /dev/null +++ b/site_scons/dirs.py @@ -0,0 +1,9 @@ +# dirs.py +# Eryn Wells + +from SCons.Script import Dir + +BUILD_DIR = Dir('#build') +LIB_DIR = Dir('#lib') +SRC_DIR = Dir('#src') +TEST_DIR = Dir('#test') diff --git a/site_scons/site_init.py b/site_scons/site_init.py new file mode 100644 index 0000000..d296109 --- /dev/null +++ b/site_scons/site_init.py @@ -0,0 +1,71 @@ +# site_init.py +# +# This file is read before every SConstruct and SConscript. So, anything that +# should be available to every build script should go here. +# +# Eryn Wells + +import os.path +import SCons.Defaults +from dirs import * + + +# +# Environment Configuration +# + +def has_clang(env): + _, cc_tail = os.path.split(env['CC']) + _, cxx_tail = os.path.split(env['CXX']) + return all([cc_tail.startswith('clang'), cxx_tail.startswith('clang')]) + +default_env = SCons.Defaults.DefaultEnvironment() +default_env.Append(TOOLS=['gtest']) +print default_env.Dump() + +default_env.Replace( + CC=default_env.WhereIs('clang') or default_env.WhereIs('gcc'), + CXX=default_env.WhereIs('clang++') or default_env.WhereIs('gcc++')) + +default_env.Append(CCFLAGS=['-Wall', '-Wextra', '-pedantic'], + CFLAGS=['-std=c99'], + CXXFLAGS=['-std=c++11']) +if has_clang(default_env): + # Only clang supports color. + default_env.Append(CCFLAGS=['-fcolor-diagnostics']) + + +debug_env = default_env.Clone(MODE='debug', + CCFLAGS=['-O0', '-g'], + CPPDEFINES=['DEBUG']) +release_env = default_env.Clone(MODE='release', + CCFLAGS=['-O2'], + CPPDEFINES=['RELEASE']) + +MODES = { + 'debug': debug_env, + 'release': release_env +} + +# +# Command Line Options +# + +def process_modes_option(option, opt, value, parser): + modes = value.split(',') + for m in modes: + setattr(parser.values, 'modes', set(modes)) + +AddOption('--show-build-cmds', + dest='build_cmds', + action='store_true', + help='Show build commands instead of friendly build messages') +AddOption('--modes', + type='string', + action='callback', + dest='modes', + metavar='MODES', + default=set(['debug']), + callback=process_modes_option, + help=('A comma separated list of modes. Choose from: {}. Default is ' + 'debug.').format(', '.join(MODES.keys()))) diff --git a/site_scons/site_tools/gtest/__init__.py b/site_scons/site_tools/gtest/__init__.py new file mode 100644 index 0000000..4bb21ae --- /dev/null +++ b/site_scons/site_tools/gtest/__init__.py @@ -0,0 +1,28 @@ +# gtestprogram.py +# Eryn Wells + +import SCons.Util +import dirs + + +def build_gtest_program(env, target, source=None, *args, **kwargs): + if not SCons.Util.is_List(source): + source = [source] + source.insert(0, dirs.LIB_DIR.Dir('gtest').File('gtest_main.cc')) + source.append(env['LIBS']['gtest']['static']) + return env.Program(target, source, *args, **kwargs) + + +def generate(env): + print 'gtestprogram generate()' + try: + env.AddMethod(build_gtest_program, 'GTestProgram') + except AttributeError: + # Old version of SCons + from SCons.Script.SConscript import SConsEnvironment + SConsEnvironment.GTestProgram = build_gtest_program + + +def exists(env): + print 'gtestprogram exists()' + return 'gtest' in env['LIBS'] diff --git a/src/SConscript b/src/SConscript index f0639e5..9e2c37e 100644 --- a/src/SConscript +++ b/src/SConscript @@ -18,8 +18,15 @@ for d in subdirs: files = [ # TODO: Put files here. + #'hello.cc', ] objs = [] for f in files: objs.append(env.Object(f)) + +#lib = env.Library('hello', files) +#prog = env.Program('hello', lib) +lib = None +prog = None +Return('prog lib') diff --git a/test/SConscript b/test/SConscript index f0639e5..e46f7bc 100644 --- a/test/SConscript +++ b/test/SConscript @@ -23,3 +23,5 @@ files = [ objs = [] for f in files: objs.append(env.Object(f)) + +env.GTestProgram('test_hello', 'test_hello.cc')