diff --git a/SConscript b/SConscript new file mode 100644 index 00000000..a773cde3 --- /dev/null +++ b/SConscript @@ -0,0 +1,34 @@ +# -*- python -*- + +Import("env") + +env.Append(CPPPATH = "#/include",CPPDEFINES = ["BOOST_ALL_NO_LIB=1"]) + +env.AppendUnique(CPPDEFINES = ["${LINK_DYNAMIC and 'BOOST_PYTHON_DYN_LINK=1' or []}"]) +for variant in env["variant"]: + env["current_variant"] = variant + env.SetProperty(profile = False) + if variant == "release": + env.SetProperty(optimize = "speed", debug = False) + elif variant == "debug": + env.SetProperty(optimize = "no", debug = True) + elif variant == "profile": + env.SetProperty(optimize = "speed", profile = True, debug = True) + for linking in env["link"]: + env["linking"] = linking + if linking == "dynamic": + env["LINK_DYNAMIC"] = True + else: + env["LINK_DYNAMIC"] = False + for threading in env["threading"]: + env["current_threading"] = threading + env.SetProperty(threading = threading) + variant_dir=env.subst("$BOOST_CURRENT_VARIANT_DIR") + + env.SConscript("src/SConscript", variant_dir=variant_dir + '/src', + exports = { "env" : env.Clone(BOOST_LIB = 'python') }) + if GetOption("test"): + test_env = env.Clone(BOOST_LIB = 'python', BOOST_TEST = True) + test_env.BoostUseLib('python') + env.SConscript("test/SConscript", variant_dir=variant_dir + '/test', + exports = { "env" : test_env }) diff --git a/SConstruct b/SConstruct new file mode 100644 index 00000000..1e60db7a --- /dev/null +++ b/SConstruct @@ -0,0 +1,82 @@ +# -*- python -*- +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import SCons.Script.Main +import config +import config.ui +import platform +import os + + +# +# We try to mimic the typical autotools-workflow. +# +# * In a 'configure' step all the essential build parameters are established +# (either by explicit command-line arguments or from configure checks) +# * A subsequent build step can then simply read the cached variables, so +# users don't have to memorize and re-issue the arguments on each subsequent +# invocation, and neither do the config checks need to be re-run. +# +# The essential part here is to define a 'config' target, which removes any +# caches that may still be lingering around, then runs the checks. + +if 'config' in COMMAND_LINE_TARGETS: + # Clear the cache + try: os.remove('bin.SCons/config.py') + except: pass +if not os.path.exists('bin.SCons/'): + os.mkdir('bin.SCons/') +vars = Variables('bin.SCons/config.py', ARGUMENTS) +config.add_options(vars) +arch = ARGUMENTS.get('arch', platform.machine()) +env = Environment(toolpath=['config/tools'], + tools=['default', 'libs', 'tests'], + variables=vars, + TARGET_ARCH=arch) + +Help(config.ui.help(vars, env) + """ +Variables are saved in bin.SCons/config.py and persist between scons invocations. +""") + +if GetOption('help'): + Return() + +build_dir = config.prepare_build_dir(env) +config_log = '{}/config.log'.format(build_dir) + +# configure +SConsignFile('{}/.sconsign'.format(build_dir)) +#env.Decider('MD5-timestamp') +env.Decider('timestamp-newer') +checks = config.get_checks() +if 'config' in COMMAND_LINE_TARGETS: + conf=env.Configure(custom_tests=checks, log_file=config_log, conf_dir=build_dir) + if False in (getattr(conf, c)() for c in checks): + Exit(1) + env = conf.Finish() + vars.Save('bin.SCons/config.py', env) + +if not os.path.exists(config_log): + print('Please run `scons config` first. (See `scons -h` for available options.)') + Exit(1) + +if not GetOption('verbose'): + config.ui.pretty_output(env) + +# build +env['BPL_VERSION'] = '1.61' +for e in config.variants(env): + variant_dir=e.subst("$BOOST_CURRENT_VARIANT_DIR") + e.SConscript('src/SConscript', variant_dir=variant_dir + '/src', + exports = { 'env' : e.Clone(BOOST_LIB = 'python') }) + if 'test' in COMMAND_LINE_TARGETS: + test_env = e.Clone(BOOST_LIB = 'python', BOOST_TEST = True) + test_env.BoostUseLib('python') + e.SConscript('test/SConscript', variant_dir=variant_dir + '/test', + exports = { 'env' : test_env }) diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 00000000..b03127ec --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,116 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from SCons.Variables import * +from SCons.Script import AddOption +from collections import OrderedDict +import platform +from . import ui +from . import python +from . import boost + +def add_options(vars): + ui.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose mode: print full commands.') + python.add_options(vars) + boost.add_options(vars) + + vars.Add('CPPPATH', converter=lambda v:v.split()) + vars.Add('CCFLAGS', converter=lambda v:v.split()) + vars.Add('LIBPATH', converter=lambda v:v.split()) + vars.Add('LIBS', converter=lambda v:v.split()) + vars.Add('PYTHON') + vars.Add('PYTHONLIBS') + + ui.add_variable(vars, ("arch", "target architeture", platform.machine())) + ui.add_variable(vars, ("toolchain", "toolchain to use", 'gcc')) + ui.add_variable(vars, ListVariable("variant", "Build configuration", "release", ["release", "debug", "profile"])) + ui.add_variable(vars, ListVariable("link", "Library linking", "dynamic", ["static", "dynamic"])) + ui.add_variable(vars, ListVariable("threading", "Multi-threading support", "multi", ["single", "multi"])) + ui.add_variable(vars, EnumVariable("layout", "Layout of library names and header locations", "versioned", ["versioned", "system"])) + ui.add_variable(vars, PathVariable("stagedir", "If --stage is passed install only compiled library files in this location", "stage", PathVariable.PathAccept)) + ui.add_variable(vars, PathVariable("prefix", "Install prefix", "/usr/local", PathVariable.PathAccept)) + + +def get_checks(): + checks = OrderedDict() + checks['python'] = python.check + checks['boost'] = boost.check + return checks + + +def set_property(env, **kw): + + from toolchains.gcc import features as gcc_features + from toolchains.msvc import features as msvc_features + + if 'gcc' in env['TOOLS']: features = gcc_features + elif 'msvc' in env['TOOLS']: features = msvc_features + else: raise Error('unknown toolchain') + features.init_once(env) + for (prop,value) in kw.items(): + getattr(features, prop, lambda x, y : None)(env, value) + env[prop.upper()] = value + + +def boost_suffix(env): + suffix = str() + + if env["layout"] == "versioned": + if "gcc" in env["TOOLS"]: + suffix += "-gcc" + "".join(env["CCVERSION"].split(".")[0:2]) + if env["THREADING"] == "multi": + suffix += "-mt" + if env["DEBUG"]: + suffix += "-d" + if env["layout"] == "versioned": + suffix += "-" + "_".join(env["BPL_VERSION"].split(".")) + + return suffix + + +def prepare_build_dir(env): + + vars = {} + env["boost_suffix"] = boost_suffix + build_dir="bin.SCons" + if "gcc" in env["TOOLS"]: + build_dir+="/gcc-%s"%env["CCVERSION"] + vars['CXXFLAGS'] = ['-ftemplate-depth-128', '-Wall'] + + elif "msvc" in env["TOOLS"]: + build_dir+="/msvc-%s"%env["MSVS_VERSION"] + vars['BOOST_BUILD_DIR'] = build_dir + vars['BOOST_SUFFIX'] = "${boost_suffix(__env__)}" + env.Replace(**vars) + return build_dir + + +def variants(env): + + env.Append(CPPPATH = "#/include", CPPDEFINES = ["BOOST_ALL_NO_LIB=1"]) + set_property(env, architecture = env['TARGET_ARCH']) + for variant in env["variant"]: + e = env.Clone() + e["current_variant"] = variant + set_property(env, profile = False) + if variant == "release": + set_property(e, optimize = "speed", debug = False) + elif variant == "debug": + set_property(e, optimize = "no", debug = True) + elif variant == "profile": + set_property(e, optimize = "speed", profile = True, debug = True) + for linking in env["link"]: + e["linking"] = linking + if linking == "dynamic": + e["LINK_DYNAMIC"] = True + else: + e["LINK_DYNAMIC"] = False + for threading in e["threading"]: + e["current_threading"] = threading + set_property(e, threading = threading) + yield e diff --git a/config/boost.py b/config/boost.py new file mode 100644 index 00000000..7548bf89 --- /dev/null +++ b/config/boost.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from . import ui +import os + +def add_options(vars): + + ui.add_option("--boost-prefix", dest="boost_prefix", type="string", nargs=1, action="store", + metavar="DIR", default=os.environ.get("BOOST_DIR"), + help="prefix for Boost libraries; should have 'include' and 'lib' subdirectories, 'boost' and 'stage\\lib' subdirectories on Windows") + ui.add_option("--boost-include", dest="boost_include", type="string", nargs=1, action="store", + metavar="DIR", help="location of Boost header files") + + +def check(context): + + boost_source_file = r"#include " + + context.Message('Checking for Boost...') + + boost_prefix = context.env.GetOption('boost_prefix') + boost_include = context.env.GetOption('boost_include') + incpath=None + if boost_include: + incpath=boost_include + elif boost_prefix: + incpath=boost_prefix + if incpath: + context.env.AppendUnique(CPPPATH=[incpath]) + if not context.TryCompile(boost_source_file, '.cpp'): + context.Result(0) + return False + context.Result(1) + return True diff --git a/config/python.py b/config/python.py new file mode 100644 index 00000000..0aff24ee --- /dev/null +++ b/config/python.py @@ -0,0 +1,98 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from . import ui + +def add_options(vars): + + ui.add_option('--python', help='the python executable') + + +def check(context): + + python_source_file = r""" +// If defined, enforces linking againg PythonXXd.lib, which +// is usually not included in Python environments. +#undef _DEBUG +#include "Python.h" +int main() +{ + Py_Initialize(); + Py_Finalize(); + return 0; +} +""" + + import platform + import subprocess + import re, os + + def check_python(cmd): + return subprocess.check_output([python, '-c', cmd]).strip() + + def check_sysconfig(cmd): + r = check_python('import distutils.sysconfig as c; print(c.%s)'%cmd) + return r if r != 'None' else '' + + context.Message('Checking for Python...') + python = context.env.GetOption('python') or 'python' + context.env['PYTHON'] = python + incpath = check_sysconfig('get_python_inc()') + context.env.AppendUnique(CPPPATH=[incpath]) + if platform.system() == 'Windows': + version = check_python('import sys; print("%d%d"%sys.version_info[0:2])') + prefix = check_python('import sys; print(sys.prefix)') + libfile = os.path.join(prefix, 'libs', 'python%s.lib'%version) + libpath = os.path.join(prefix, 'libs') + lib = 'python%s'%version + context.env.AppendUnique(LIBS=[lib]) + else: + libpath = check_sysconfig('get_config_var("LIBDIR")') + libfile = check_sysconfig('get_config_var("LIBRARY")') + match = re.search('(python.*)\.(a|so|dylib)', libfile) + lib = None + if match: + lib = match.group(1) + context.env.AppendUnique(PYTHONLIBS=[lib]) + if match.group(2) == 'a': + flags = check_sysconfig('get_config_var("LINKFORSHARED")') + if flags is not None: + context.env.AppendUnique(LINKFLAGS=flags.split()) + context.env.AppendUnique(LIBPATH=[libpath]) + oldlibs = context.AppendLIBS([lib]) + flags = check_sysconfig('get_config_var("MODLIBS")') + flags += ' ' + check_sysconfig('get_config_var("SHLIBS")') + flags = [f[2:] for f in flags.strip().split() if f.startswith('-l')] + if flags: + context.AppendLIBS([flags]) + result = context.TryLink(python_source_file,'.cpp') + if not result and context.env['PLATFORM'] == 'darwin': + # Sometimes we need some extra stuff on Mac OS + frameworkDir = libpath # search up the libDir tree for the proper home for frameworks + while frameworkDir and frameworkDir != "/": + frameworkDir, d2 = os.path.split(frameworkDir) + if d2 == "Python.framework": + if not "Python" in os.listdir(os.path.join(frameworkDir, d2)): + context.Result(0) + print(( + "Expected to find Python in framework directory %s, but it isn't there" + % frameworkDir)) + return False + break + context.env.AppendUnique(LINKFLAGS="-F%s" % frameworkDir) + result = context.TryLink(python_source_file,'.cpp') + if not result: + context.Result(0) + print("Cannot link program with Python.") + return False + if context.env['PLATFORM'] == 'darwin': + context.env['LDMODULESUFFIX'] = '.so' + context.Result(1) + context.SetLIBS(oldlibs) + context.env.AppendUnique(PYTHONLIBS=[lib] + flags) + return True diff --git a/config/toolchains/__init__.py b/config/toolchains/__init__.py new file mode 100644 index 00000000..af38285a --- /dev/null +++ b/config/toolchains/__init__.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import traceback + +def append_feature_flag(env, **kw): + stack = traceback.extract_stack(limit = 3) + feature = stack[0][2].upper() + for (key, val) in kw.items(): + feature_var = feature + "_" + key + env.AppendUnique(**{ key : "$" + feature_var }) + env[feature_var] = val + diff --git a/config/toolchains/gcc.py b/config/toolchains/gcc.py new file mode 100644 index 00000000..200ecb6a --- /dev/null +++ b/config/toolchains/gcc.py @@ -0,0 +1,55 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from . import append_feature_flag + +class features: + + @classmethod + def init_once(cls, env): + pass + + @staticmethod + def architecture(env, arch): + if arch: + flag = {'x86' : '-m32', + 'x86_64' : '-m64',}.get(arch) + if flag: + append_feature_flag(env, CCFLAGS = flag) + + @staticmethod + def optimize(env, optimize): + if not optimize or optimize == "no": + append_feature_flag(env, CCFLAGS = "-O0 -fno-inline") + elif optimize == "speed": + append_feature_flag(env, CCFLAGS = "-O3 -finline-functions -Wno-inline") + elif optimize == "space": + append_feature_flag(env, CCFLAGS = "-Os") + else: + append_feature_flag(env, CCFLAGS = "") + + @staticmethod + def profile(env, profile): + if profile: + append_feature_flag(env, CCFLAGS = "-pg", LINKFLAGS = "-pg") + else: + append_feature_flag(env, CCFLAGS = "", LINKFLAGS = "") + + @staticmethod + def threading(env, threading): + if threading == "multi": + append_feature_flag(env, CCFLAGS = "-pthread", LINKFLAGS = "-pthread") + else: + append_feature_flag(env, CCFLAGS = "", LINKFLAGS = "") + + @staticmethod + def debug(env, debug): + if debug: + append_feature_flag(env, CCFLAGS = "-g", CPPDEFINES = []) + else: + append_feature_flag(env, CCFLAGS = "", CPPDEFINES = "NDEBUG") diff --git a/config/toolchains/msvc.py b/config/toolchains/msvc.py new file mode 100644 index 00000000..e2ff52fc --- /dev/null +++ b/config/toolchains/msvc.py @@ -0,0 +1,57 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from . import append_feature_flag + +class features: + + @classmethod + def init_once(cls, env): + env.AppendUnique(CCFLAGS = ['-TP', '/Z7', '/W3' ,'/GR', '/MDd', '/Zc:forScope', '/Zc:wchar_t', '/wd4675', '/EHs']) + env.AppendUnique(LINKFLAGS = ['/subsystem:console']) + + @staticmethod + def architecture(env, arch): + if arch: + flag = {'x86' : '/MACHINE:X32', + 'x86_64' : '/MACHINE:X64',}.get(arch) + if flag: + append_feature_flag(env, LINKFLAGS = flag) + + @staticmethod + def optimize(env, optimize): + #if not optimize or optimize == "no": + # append_feature_flag(env, CCFLAGS = "-O0 -fno-inline") + #elif optimize == "speed": + # append_feature_flag(env, CCFLAGS = "-O3 -finline-functions -Wno-inline") + #elif optimize == "space": + # append_feature_flag(env, CCFLAGS = "-Os") + #else: + append_feature_flag(env, CCFLAGS = "") + + @staticmethod + def profile(env, profile): + #if profile: + # append_feature_flag(env, CCFLAGS = "-pg", LINKFLAGS = "-pg") + #else: + append_feature_flag(env, CCFLAGS = "", LINKFLAGS = "") + + @staticmethod + def threading(env, threading): + #if threading == "multi": + # append_feature_flag(env, CCFLAGS = "/MT") + #else: + # append_feature_flag(env, CCFLAGS = "", LINKFLAGS = "") + pass + + @staticmethod + def debug(env, debug): + #if debug: + # append_feature_flag(env, CCFLAGS = "-g", CPPDEFINES = []) + #else: + append_feature_flag(env, CCFLAGS = "", CPPDEFINES = "NDEBUG") diff --git a/config/tools/clang.py b/config/tools/clang.py new file mode 100644 index 00000000..9af06708 --- /dev/null +++ b/config/tools/clang.py @@ -0,0 +1,44 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Based on SCons/Tool/gcc.py + +import os +import re +import subprocess + +import SCons.Util +import SCons.Tool.cc + +compilers = ['clang'] + +def generate(env): + """Add Builders and construction variables for clang to an Environment.""" + SCons.Tool.cc.generate(env) + + env['CC'] = env.Detect(compilers) or 'clang' + if env['PLATFORM'] in ['cygwin', 'win32']: + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') + else: + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC') + # determine compiler version + if env['CC']: + #pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'], + pipe = SCons.Action._subproc(env, [env['CC'], '--version'], + stdin = 'devnull', + stderr = 'devnull', + stdout = subprocess.PIPE) + if pipe.wait() != 0: return + # clang -dumpversion is of no use + line = pipe.stdout.readline() + match = re.search(r'clang +version +([0-9]+(?:\.[0-9]+)+)', line) + if match: + env['CCVERSION'] = match.group(1) + +def exists(env): + return env.Detect(compilers) diff --git a/config/tools/libs.py b/config/tools/libs.py new file mode 100644 index 00000000..09eed5e6 --- /dev/null +++ b/config/tools/libs.py @@ -0,0 +1,84 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import distutils.sysconfig +from SCons.Script import AddOption, COMMAND_LINE_TARGETS, BUILD_TARGETS + + +def BoostLibrary(env, lib, sources, make_aliases = True, **kw): + if env["LINK_DYNAMIC"]: + lib_node = env.SharedLibrary("boost_" + lib + env["BOOST_SUFFIX"], sources, **kw) + else: + lib_node = env.StaticLibrary("boost_" + lib + env["BOOST_SUFFIX"], sources, **kw) + + if make_aliases: + if env.GetOption("stage"): + env.Alias(lib, env.Install(env.Dir("$stagedir", "#"), lib_node)) + env.Default(env.Alias(lib, lib_node)) + + if env.GetOption("install"): + env.Alias(lib, env.Install("$prefix/lib", lib_node)) + return lib_node + + +def BoostUseLib(env, lib): + env.AppendUnique( + LIBPATH = [env.Dir("$BOOST_CURRENT_VARIANT_DIR/src")], + LIBS = ["boost_" + lib + env["BOOST_SUFFIX"]] + ) + if env.get("BOOST_TEST"): + env.AppendUnique(RPATH = [env.Dir("$BOOST_CURRENT_VARIANT_DIR/src")]) + + +def PythonExtension(env, lib, sources, **kw): + if env["LINK_DYNAMIC"]: + #env.AppendUnique(CPPDEFINES = ['BOOST_PYTHON_DYN_LINK=1']) + ext = env.SharedLibrary(lib, sources, SHLIBPREFIX='', SHLIBSUFFIX=distutils.sysconfig.get_config_var("SO"), **kw) + #env.Alias(lib, ext) + return ext + + +def boost_copy_func(dest, source, env): + import os, stat, shutil + + if os.path.isdir(source): + if os.path.exists(dest): + if not os.path.isdir(dest): + raise SCons.Errors.UserError, "cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source)) + else: + os.makedirs(dest) + for file in os.listdir(source): + if file == ".svn": continue + boost_copy_func(os.path.join(dest, file), os.path.join(source, file), env) + else: + shutil.copy2(source, dest) + st = os.stat(source) + os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + + return 0 + + +def exists(env): + return True + + +def generate(env): + env.AddMethod(BoostLibrary) + env.AddMethod(BoostUseLib) + env.AddMethod(PythonExtension) + + env.Replace( + INSTALL = boost_copy_func, + BOOST_CURRENT_VARIANT_DIR = "$BOOST_BUILD_DIR/$current_variant/$linking/threading-$current_threading" + ) + + AddOption('--stage', dest='stage', action="store_true") + AddOption('--install', dest='install', action="store_true") + + #if env.GetOption("install"): + # BUILD_TARGETS.extend(env.Alias("install-headers")) diff --git a/config/tools/tests.py b/config/tools/tests.py new file mode 100644 index 00000000..9a872f8f --- /dev/null +++ b/config/tools/tests.py @@ -0,0 +1,105 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from SCons.Script import AddOption, Flatten +from SCons.Script import Builder +from SCons.Action import Action +from subprocess import check_output, STDOUT, CalledProcessError +import sys + + +def BoostCompileTest(env, test, source = None, **kw): + obj = env.Object(test, source if source is not None else test + '.cpp') + return obj + +def BoostRun(env, prog, target, command = '$SOURCE'): + + def call(target, source, env=env): + cmd = env.subst(command, target=target, source=source) + result_file = env.subst('$TARGET', target=target) + output='' + try: + output=check_output(cmd, stderr=STDOUT, shell=True) + success=True + except CalledProcessError as e: + output=e.output + success=False + with open(result_file, 'w+') as result: + result.write('Result: {}\n'.format(success and 'pass' or 'fail')) + result.write('Output: {}\n'.format(output)) + if sys.stdout.isatty(): + env['RESULT']=success and '\033[92mPASS\033[0m' or '\033[91mFAIL\033[0m' + else: + env['RESULT']=success and 'PASS' or 'FAIL' + + testcomstr = env.get('TESTCOMSTR') + if testcomstr: + run = env.Command(target, prog, Action(call, cmdstr=testcomstr)) + else: + run = env.Command(target, prog, Action(call, cmdstr=command)) + env.AddPostAction(target, Action('@echo $RESULT')) + return run + + +def BoostRunPythonScript(env, script): + return env.BoostRun(env.File(script), script.replace('.py', '.result'), '"${PYTHON}" $SOURCE') + + +def BoostRunTest(env, test, source = None, command = '$SOURCE', command_sources = [], **kw): + test_prog = env.Program(test, (source is None) and (test + ".cpp") or source, **kw) + command += '> $TARGET' + run = env.BoostRun([test_prog, command_sources], test + '.result', command) + return run + + +def BoostRunTests(env, tests, **kw): + run = [] + for test in Flatten(tests): + run += env.BoostRunTest(test, **kw) + return run + +def BoostCompileTests(env, tests, **kw): + comp = [] + for test in Flatten(tests): + comp += env.BoostCompileTest(test, **kw) + return comp + + +def BoostTestSummary(env, tests, **kw): + + def print_summary(target, source, **kw): + results = tests + failures = [r for r in results + if r.get_path().endswith('.result') and not 'Result: pass' in r.get_contents()] + print('%s tests; %s pass; %s fails'%(len(results), len(results)-len(failures), len(failures))) + for f in failures: + print('%s\n%s'%(f.get_path(), f.get_contents())) + testsumcomstr = env.get('TESTSUMCOMSTR') + if testsumcomstr: + run = env.Command('summary', tests, Action(print_summary, cmdstr=testsumcomstr)) + else: + run = env.Command('summary', tests, print_summary, cmdstr='') + + + + + +def exists(env): + return True + + +def generate(env): + AddOption('--test', dest='test', action="store_true") + + env.AddMethod(BoostCompileTest) + env.AddMethod(BoostRun) + env.AddMethod(BoostRunPythonScript) + env.AddMethod(BoostRunTest) + env.AddMethod(BoostRunTests) + env.AddMethod(BoostCompileTests) + env.AddMethod(BoostTestSummary) diff --git a/config/ui.py b/config/ui.py new file mode 100644 index 00000000..e6e4f24f --- /dev/null +++ b/config/ui.py @@ -0,0 +1,96 @@ +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from SCons.Script import AddOption +import sys + +variables=[] # remember 'public' variables +options=[] + +def add_option(*args, **kwds): + """Capture the help messages so we can produce a helpful usage text.""" + options.append('{:25} {}'.format(', '.join(args), kwds.get('help', ''))) + AddOption(*args, **kwds) + +def add_variable(vars, var): + variables.append(var[0]) + vars.Add(var) + + +def options_help(env): + + return '\n '.join(options) + + +def variables_help(vars, env): + """This is cloned from SCons' Variables.GenerateHelpText, to only report 'public' variables.""" + + opts = [o for o in vars.options if o.key in variables] + + def format(opt): + if opt.key in env: + actual = env.subst('${%s}' % opt.key) + else: + actual = None + return vars.FormatVariableHelpText(env, opt.key, opt.help, opt.default, actual, opt.aliases) + text = ''.join([f for f in map(format, opts) if f]) + lines = [' %s'%l for l in text.split('\n')] # Add some indentation + return '\n'.join(lines) + + + +def help(vars, env): + + return """Usage: scons [--option...] [variable=value...] [target...] + +available options: + + {} + +available variables: + {} +""".format(options_help(env), variables_help(vars, env)) + +def pretty_output(env): + + colors = {} + colors['red'] = '\033[31m' + colors['green'] = '\033[32m' + colors['blue'] = '\033[34m' + colors['yellow'] = '\033[93m' + colors['Red'] = '\033[91m' + colors['Green'] = '\033[92m' + colors['Blue'] = '\033[94m' + colors['Purple'] = '\033[95m' + colors['Cyan'] = '\033[96m' + colors['end'] = '\033[0m' + + #If the output is not a terminal, remove the colors + if not sys.stdout.isatty(): + for key, value in colors.iteritems(): + colors[key] = '' + + compile_source_message = '{green}Compiling $TARGET{end}'.format(**colors) + compile_shared_source_message = '{green}Compiling $TARGET{end}'.format(**colors) + link_program_message = '{blue}Linking $TARGET{end}'.format(**colors) + link_library_message = '{blue}Linking $TARGET{end}'.format(**colors) + ranlib_library_message = '{blue}Ranlib $TARGET{end}'.format(**colors) + link_shared_library_message = '{blue}Linking $TARGET{end}'.format(**colors) + test_message = '{blue}Testing $SOURCE{end}'.format(**colors) + testsum_message = '{Blue}Test Summary{end}'.format(**colors) + + env.Replace(CXXCOMSTR = compile_source_message, + CCCOMSTR = compile_source_message, + SHCCCOMSTR = compile_shared_source_message, + SHCXXCOMSTR = compile_shared_source_message, + ARCOMSTR = link_library_message, + RANLIBCOMSTR = ranlib_library_message, + SHLINKCOMSTR = link_shared_library_message, + LINKCOMSTR = link_program_message, + TESTCOMSTR = test_message, + TESTSUMCOMSTR = testsum_message) diff --git a/src/SConscript b/src/SConscript new file mode 100644 index 00000000..a1d3de6b --- /dev/null +++ b/src/SConscript @@ -0,0 +1,44 @@ +# -*- python -*- +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +Import('env') + +env.AppendUnique(CPPDEFINES = ["${LINK_DYNAMIC and 'BOOST_PYTHON_DYN_LINK=1' or ''}"]) +env.AppendUnique(CPPDEFINES = ['BOOST_PYTHON_SOURCE']) + +env.BoostLibrary( + 'python', + ['numeric.cpp', + 'list.cpp', + 'long.cpp', + 'dict.cpp', + 'tuple.cpp', + 'str.cpp', + 'slice.cpp', + 'converter/from_python.cpp', + 'converter/registry.cpp', + 'converter/type_id.cpp', + 'object/enum.cpp', + 'object/class.cpp', + 'object/function.cpp', + 'object/inheritance.cpp', + 'object/life_support.cpp', + 'object/pickle_support.cpp', + 'errors.cpp', + 'module.cpp', + 'converter/builtin_converters.cpp', + 'converter/arg_to_python_base.cpp', + 'object/iterator.cpp', + 'object/stl_iterator.cpp', + 'object_protocol.cpp', + 'object_operators.cpp', + 'wrapper.cpp', + 'import.cpp', + 'exec.cpp', + 'object/function_doc_signature.cpp']) diff --git a/test/SConscript b/test/SConscript new file mode 100644 index 00000000..30ba644b --- /dev/null +++ b/test/SConscript @@ -0,0 +1,132 @@ +# -*- python -*- +# +# Copyright (c) 2016 Stefan Seefeld +# All rights reserved. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +Import('env') + +# libs needed for embedding +ELIBS=env['LIBS'] + env['PYTHONLIBS'] + +def BPLTest(env, name, sources = None, script = None): + run = env.BoostRunPythonScript(name + '.py') + if sources: + for source in sources: + Depends(run, + env.PythonExtension(source != name and source or (source + '_ext'), source + '.cpp') + ) + else: + Depends(run, env.PythonExtension(name + '_ext', name + '.cpp')) + return run + +env.AddMethod(BPLTest) + +env.AppendENVPath('PYTHONPATH', Dir('.').path) + +tests=[] +tests+=env.BPLTest('crossmod_exception', ['crossmod_exception_a', 'crossmod_exception_b']) + +for test in ['injected', + 'properties', + 'return_arg', + 'staticmethod', + 'shared_ptr', + 'enable_shared_from_this', + 'andreas_beyer', + 'polymorphism', + 'polymorphism2', + 'wrapper_held_type', + 'polymorphism2_auto_ptr', + 'auto_ptr', + 'minimal', + 'args', + 'raw_ctor', + #'numpy', + 'enum', + 'exception_translator']: + tests+=env.BPLTest(test) + +tests+=env.BPLTest('test_cltree', ['cltree']) +tests+=env.BPLTest('newtest', ['m1', 'm2']) +tests+=env.BPLTest('const_argument') +tests+=env.BPLTest('keywords_test', ['keywords']) +Depends( + env.BoostRunPythonScript('test_builtin_converters.py'), + env.PythonExtension('builtin_converters_ext', 'test_builtin_converters.cpp') + ) + +for test in ['test_pointer_adoption', + 'operators', + 'operators_wrapper', + 'callbacks', + 'defaults', + 'object', + 'list', + 'long', + 'dict', + 'tuple', + 'str', + 'slice', + 'virtual_functions', + 'back_reference', + 'implicit', + 'data_members', + 'ben_scott1', + 'bienstman1', + 'bienstman2', + 'bienstman3', + 'multi_arg_constructor']: + tests+=env.BPLTest(test) + +tests+=env.BPLTest('iterator', ['iterator', 'input_iterator']) +tests+=env.BPLTest('stl_iterator') +tests+=env.BPLTest('extract') +tests+=env.BPLTest('crossmod_opaque', ['crossmod_opaque_a', 'crossmod_opaque_b']) + +for test in ['opaque', + 'voidptr', + 'pickle1', + 'pickle2', + 'pickle3', + 'pickle4', + 'nested', + 'docstring', + 'pytype_function', + 'vector_indexing_suite', + 'pointer_vector']: + tests+=env.BPLTest(test) + +Depends( + env.BoostRunPythonScript('map_indexing_suite.py'), + env.PythonExtension('map_indexing_suite_ext', [ + 'map_indexing_suite.cpp', + 'int_map_indexing_suite.cpp', + 'a_map_indexing_suite.cpp']) + ) + +tests+=env.BoostRunTest('import_', 'import_.cpp', '${SOURCES[0]} ${SOURCES[1]}', 'import_.py', LIBS=ELIBS) + +tests+=env.BoostCompileTest('indirect_traits_test') +tests+=env.BoostRunTests(['destroy_test', + 'pointer_type_id_test', + 'bases', + 'if_else', + 'pointee', + 'result'], LIBS=ELIBS) + +tests+=env.BoostCompileTests(['string_literal', + 'borrowed', + 'object_manager', + 'copy_ctor_mutates_rhs']) + +tests+=env.BoostRunTest('upcast', LIBS=ELIBS) +tests+=env.BoostCompileTest('select_holder') +tests+=env.BoostRunTest('select_from_python_test', LIBS=ELIBS) +tests+=env.BoostCompileTest('select_arg_to_python_test') + +env.BoostTestSummary(tests) +AlwaysBuild(tests)