Compare commits

..

1 commit

Author SHA1 Message Date
0b58285ea9 Add beta environment -- debug, with optimization 2015-12-05 09:50:34 -08:00
13 changed files with 262 additions and 520 deletions

View file

@ -1,21 +1,209 @@
# SConstruct
# vim: set ft=python:
#
# Toplevel Scons build script. This should be mostly complete and generic enough
# for most builds.
#
# Eryn Wells <eryn@erynwells.me>
'''
Toplevel Scons build script. This should be mostly complete and generic enough
for most builds.
'''
import logging
#
# 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
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')
setup_logging()
BUILD_CMDS = get_bool_argument(ARGUMENTS.get('BUILD_CMDS', False))
MODE = ARGUMENTS.get('MODE', None)
if not BUILD_CMDS:
verbose_build_cmds()
for mode in (MODE.split(',') if MODE else ['debug']):
env = Environment.for_mode(mode)
env.process_lib_dirs()
env.process_src()
env.process_tests()
# 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')
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):
sconscript = src_dir.File('SConscript')
print 'Reading {}'.format(sconscript)
env.SConscript(sconscript,
{'env': build_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': ['-O2']
})
modes = {
'debug': debug_env,
'beta': beta_env,
'release': release_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 = ['debug']
for mode in build_modes:
try:
env = modes[mode]
except KeyError:
print 'Skipping invalid mode: {}'.format(mode)
break
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)
if not os.path.isdir(lib_out_dir.abspath):
continue
do_sconscript(env, lib_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)
env.Append(LIBPATH=[src_out_dir])
# Get test binaries.
do_sconscript(env, env, TEST_DIR, out_dir.Dir('test'))

View file

@ -1,8 +1,14 @@
# SConscript
# vim: set ft=python:
#
# Eryn Wells <eryn@erynwells.me>
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)

View file

@ -1 +0,0 @@
*.pyc

View file

@ -1,210 +0,0 @@
# site_init.py
# Eryn Wells <eryn@erynwells.me>
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'])

View file

@ -1,81 +0,0 @@
# lib.py
# Eryn Wells <eryn@erynwells.me>
'''
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

View file

@ -1,36 +0,0 @@
# osxapp.py
# Eryn Wells <eryn@erynwells.me>
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

View file

@ -1,46 +0,0 @@
# program.py
# Eryn Wells <eryn@erynwells.me>
'''
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

View file

@ -1,38 +0,0 @@
# sconscript.py
# Eryn Wells <eryn@erynwells.me>
'''
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

View file

@ -1,33 +0,0 @@
# swiftc.py
# vim: set ft=python:
# Eryn Wells <eryn@erynwells.me>
'''
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)

View file

@ -1,51 +0,0 @@
# test.py
# Eryn Wells <eryn@erynwells.me>
'''
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

View file

@ -1,5 +1,25 @@
# SConscript
# vim: set ft=python:
#
# Eryn Wells <eryn@erynwells.me>
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))

View file

@ -1,8 +0,0 @@
#include <iostream>
int
main()
{
std::cout << "Hello world!" << std::endl;
return 0;
}

25
test/SConscript Normal file
View file

@ -0,0 +1,25 @@
# SConscript
# vim: set ft=python:
#
# Eryn Wells <eryn@erynwells.me>
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))