Skip to content

Commit 57cd933

Browse files
committed
Add SCons-based build system.
1 parent 5233f45 commit 57cd933

File tree

14 files changed

+1005
-0
lines changed

14 files changed

+1005
-0
lines changed

SConscript

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- python -*-
2+
3+
Import("env")
4+
5+
env.Append(CPPPATH = "#/include",CPPDEFINES = ["BOOST_ALL_NO_LIB=1"])
6+
7+
env.AppendUnique(CPPDEFINES = ["${LINK_DYNAMIC and 'BOOST_PYTHON_DYN_LINK=1' or []}"])
8+
for variant in env["variant"]:
9+
env["current_variant"] = variant
10+
env.SetProperty(profile = False)
11+
if variant == "release":
12+
env.SetProperty(optimize = "speed", debug = False)
13+
elif variant == "debug":
14+
env.SetProperty(optimize = "no", debug = True)
15+
elif variant == "profile":
16+
env.SetProperty(optimize = "speed", profile = True, debug = True)
17+
for linking in env["link"]:
18+
env["linking"] = linking
19+
if linking == "dynamic":
20+
env["LINK_DYNAMIC"] = True
21+
else:
22+
env["LINK_DYNAMIC"] = False
23+
for threading in env["threading"]:
24+
env["current_threading"] = threading
25+
env.SetProperty(threading = threading)
26+
variant_dir=env.subst("$BOOST_CURRENT_VARIANT_DIR")
27+
28+
env.SConscript("src/SConscript", variant_dir=variant_dir + '/src',
29+
exports = { "env" : env.Clone(BOOST_LIB = 'python') })
30+
if GetOption("test"):
31+
test_env = env.Clone(BOOST_LIB = 'python', BOOST_TEST = True)
32+
test_env.BoostUseLib('python')
33+
env.SConscript("test/SConscript", variant_dir=variant_dir + '/test',
34+
exports = { "env" : test_env })

SConstruct

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# -*- python -*-
2+
#
3+
# Copyright (c) 2016 Stefan Seefeld
4+
# All rights reserved.
5+
#
6+
# Distributed under the Boost Software License, Version 1.0.
7+
# (See accompanying file LICENSE_1_0.txt or copy at
8+
# http://www.boost.org/LICENSE_1_0.txt)
9+
10+
import SCons.Script.Main
11+
import config
12+
import config.ui
13+
import platform
14+
import os
15+
16+
17+
#
18+
# We try to mimic the typical autotools-workflow.
19+
#
20+
# * In a 'configure' step all the essential build parameters are established
21+
# (either by explicit command-line arguments or from configure checks)
22+
# * A subsequent build step can then simply read the cached variables, so
23+
# users don't have to memorize and re-issue the arguments on each subsequent
24+
# invocation, and neither do the config checks need to be re-run.
25+
#
26+
# The essential part here is to define a 'config' target, which removes any
27+
# caches that may still be lingering around, then runs the checks.
28+
29+
if 'config' in COMMAND_LINE_TARGETS:
30+
# Clear the cache
31+
try: os.remove('bin.SCons/config.py')
32+
except: pass
33+
if not os.path.exists('bin.SCons/'):
34+
os.mkdir('bin.SCons/')
35+
vars = Variables('bin.SCons/config.py', ARGUMENTS)
36+
config.add_options(vars)
37+
arch = ARGUMENTS.get('arch', platform.machine())
38+
env = Environment(toolpath=['config/tools'],
39+
tools=['default', 'libs', 'tests'],
40+
variables=vars,
41+
TARGET_ARCH=arch)
42+
43+
Help(config.ui.help(vars, env) + """
44+
Variables are saved in bin.SCons/config.py and persist between scons invocations.
45+
""")
46+
47+
if GetOption('help'):
48+
Return()
49+
50+
build_dir = config.prepare_build_dir(env)
51+
config_log = '{}/config.log'.format(build_dir)
52+
53+
# configure
54+
SConsignFile('{}/.sconsign'.format(build_dir))
55+
#env.Decider('MD5-timestamp')
56+
env.Decider('timestamp-newer')
57+
checks = config.get_checks()
58+
if 'config' in COMMAND_LINE_TARGETS:
59+
conf=env.Configure(custom_tests=checks, log_file=config_log, conf_dir=build_dir)
60+
if False in (getattr(conf, c)() for c in checks):
61+
Exit(1)
62+
env = conf.Finish()
63+
vars.Save('bin.SCons/config.py', env)
64+
65+
if not os.path.exists(config_log):
66+
print('Please run `scons config` first. (See `scons -h` for available options.)')
67+
Exit(1)
68+
69+
if not GetOption('verbose'):
70+
config.ui.pretty_output(env)
71+
72+
# build
73+
env['BPL_VERSION'] = '1.61'
74+
for e in config.variants(env):
75+
variant_dir=e.subst("$BOOST_CURRENT_VARIANT_DIR")
76+
e.SConscript('src/SConscript', variant_dir=variant_dir + '/src',
77+
exports = { 'env' : e.Clone(BOOST_LIB = 'python') })
78+
if 'test' in COMMAND_LINE_TARGETS:
79+
test_env = e.Clone(BOOST_LIB = 'python', BOOST_TEST = True)
80+
test_env.BoostUseLib('python')
81+
e.SConscript('test/SConscript', variant_dir=variant_dir + '/test',
82+
exports = { 'env' : test_env })

config/__init__.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#
2+
# Copyright (c) 2016 Stefan Seefeld
3+
# All rights reserved.
4+
#
5+
# Distributed under the Boost Software License, Version 1.0.
6+
# (See accompanying file LICENSE_1_0.txt or copy at
7+
# http://www.boost.org/LICENSE_1_0.txt)
8+
9+
from SCons.Variables import *
10+
from SCons.Script import AddOption
11+
from collections import OrderedDict
12+
import platform
13+
from . import ui
14+
from . import python
15+
from . import boost
16+
17+
def add_options(vars):
18+
ui.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose mode: print full commands.')
19+
python.add_options(vars)
20+
boost.add_options(vars)
21+
22+
vars.Add('CPPPATH', converter=lambda v:v.split())
23+
vars.Add('CCFLAGS', converter=lambda v:v.split())
24+
vars.Add('LIBPATH', converter=lambda v:v.split())
25+
vars.Add('LIBS', converter=lambda v:v.split())
26+
vars.Add('PYTHON')
27+
vars.Add('PYTHONLIBS')
28+
29+
ui.add_variable(vars, ("arch", "target architeture", platform.machine()))
30+
ui.add_variable(vars, ("toolchain", "toolchain to use", 'gcc'))
31+
ui.add_variable(vars, ListVariable("variant", "Build configuration", "release", ["release", "debug", "profile"]))
32+
ui.add_variable(vars, ListVariable("link", "Library linking", "dynamic", ["static", "dynamic"]))
33+
ui.add_variable(vars, ListVariable("threading", "Multi-threading support", "multi", ["single", "multi"]))
34+
ui.add_variable(vars, EnumVariable("layout", "Layout of library names and header locations", "versioned", ["versioned", "system"]))
35+
ui.add_variable(vars, PathVariable("stagedir", "If --stage is passed install only compiled library files in this location", "stage", PathVariable.PathAccept))
36+
ui.add_variable(vars, PathVariable("prefix", "Install prefix", "/usr/local", PathVariable.PathAccept))
37+
38+
39+
def get_checks():
40+
checks = OrderedDict()
41+
checks['python'] = python.check
42+
checks['boost'] = boost.check
43+
return checks
44+
45+
46+
def set_property(env, **kw):
47+
48+
from toolchains.gcc import features as gcc_features
49+
from toolchains.msvc import features as msvc_features
50+
51+
if 'gcc' in env['TOOLS']: features = gcc_features
52+
elif 'msvc' in env['TOOLS']: features = msvc_features
53+
else: raise Error('unknown toolchain')
54+
features.init_once(env)
55+
for (prop,value) in kw.items():
56+
getattr(features, prop, lambda x, y : None)(env, value)
57+
env[prop.upper()] = value
58+
59+
60+
def boost_suffix(env):
61+
suffix = str()
62+
63+
if env["layout"] == "versioned":
64+
if "gcc" in env["TOOLS"]:
65+
suffix += "-gcc" + "".join(env["CCVERSION"].split(".")[0:2])
66+
if env["THREADING"] == "multi":
67+
suffix += "-mt"
68+
if env["DEBUG"]:
69+
suffix += "-d"
70+
if env["layout"] == "versioned":
71+
suffix += "-" + "_".join(env["BPL_VERSION"].split("."))
72+
73+
return suffix
74+
75+
76+
def prepare_build_dir(env):
77+
78+
vars = {}
79+
env["boost_suffix"] = boost_suffix
80+
build_dir="bin.SCons"
81+
if "gcc" in env["TOOLS"]:
82+
build_dir+="/gcc-%s"%env["CCVERSION"]
83+
vars['CXXFLAGS'] = ['-ftemplate-depth-128', '-Wall']
84+
85+
elif "msvc" in env["TOOLS"]:
86+
build_dir+="/msvc-%s"%env["MSVS_VERSION"]
87+
vars['BOOST_BUILD_DIR'] = build_dir
88+
vars['BOOST_SUFFIX'] = "${boost_suffix(__env__)}"
89+
env.Replace(**vars)
90+
return build_dir
91+
92+
93+
def variants(env):
94+
95+
env.Append(CPPPATH = "#/include", CPPDEFINES = ["BOOST_ALL_NO_LIB=1"])
96+
set_property(env, architecture = env['TARGET_ARCH'])
97+
for variant in env["variant"]:
98+
e = env.Clone()
99+
e["current_variant"] = variant
100+
set_property(env, profile = False)
101+
if variant == "release":
102+
set_property(e, optimize = "speed", debug = False)
103+
elif variant == "debug":
104+
set_property(e, optimize = "no", debug = True)
105+
elif variant == "profile":
106+
set_property(e, optimize = "speed", profile = True, debug = True)
107+
for linking in env["link"]:
108+
e["linking"] = linking
109+
if linking == "dynamic":
110+
e["LINK_DYNAMIC"] = True
111+
else:
112+
e["LINK_DYNAMIC"] = False
113+
for threading in e["threading"]:
114+
e["current_threading"] = threading
115+
set_property(e, threading = threading)
116+
yield e

config/boost.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#
2+
# Copyright (c) 2016 Stefan Seefeld
3+
# All rights reserved.
4+
#
5+
# Distributed under the Boost Software License, Version 1.0.
6+
# (See accompanying file LICENSE_1_0.txt or copy at
7+
# http://www.boost.org/LICENSE_1_0.txt)
8+
9+
from . import ui
10+
import os
11+
12+
def add_options(vars):
13+
14+
ui.add_option("--boost-prefix", dest="boost_prefix", type="string", nargs=1, action="store",
15+
metavar="DIR", default=os.environ.get("BOOST_DIR"),
16+
help="prefix for Boost libraries; should have 'include' and 'lib' subdirectories, 'boost' and 'stage\\lib' subdirectories on Windows")
17+
ui.add_option("--boost-include", dest="boost_include", type="string", nargs=1, action="store",
18+
metavar="DIR", help="location of Boost header files")
19+
20+
21+
def check(context):
22+
23+
boost_source_file = r"#include <boost/config.hpp>"
24+
25+
context.Message('Checking for Boost...')
26+
27+
boost_prefix = context.env.GetOption('boost_prefix')
28+
boost_include = context.env.GetOption('boost_include')
29+
incpath=None
30+
if boost_include:
31+
incpath=boost_include
32+
elif boost_prefix:
33+
incpath=boost_prefix
34+
if incpath:
35+
context.env.AppendUnique(CPPPATH=[incpath])
36+
if not context.TryCompile(boost_source_file, '.cpp'):
37+
context.Result(0)
38+
return False
39+
context.Result(1)
40+
return True

config/python.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#
2+
# Copyright (c) 2016 Stefan Seefeld
3+
# All rights reserved.
4+
#
5+
# Distributed under the Boost Software License, Version 1.0.
6+
# (See accompanying file LICENSE_1_0.txt or copy at
7+
# http://www.boost.org/LICENSE_1_0.txt)
8+
9+
from . import ui
10+
11+
def add_options(vars):
12+
13+
ui.add_option('--python', help='the python executable')
14+
15+
16+
def check(context):
17+
18+
python_source_file = r"""
19+
// If defined, enforces linking againg PythonXXd.lib, which
20+
// is usually not included in Python environments.
21+
#undef _DEBUG
22+
#include "Python.h"
23+
int main()
24+
{
25+
Py_Initialize();
26+
Py_Finalize();
27+
return 0;
28+
}
29+
"""
30+
31+
import platform
32+
import subprocess
33+
import re, os
34+
35+
def check_python(cmd):
36+
return subprocess.check_output([python, '-c', cmd]).strip()
37+
38+
def check_sysconfig(cmd):
39+
r = check_python('import distutils.sysconfig as c; print(c.%s)'%cmd)
40+
return r if r != 'None' else ''
41+
42+
context.Message('Checking for Python...')
43+
python = context.env.GetOption('python') or 'python'
44+
context.env['PYTHON'] = python
45+
incpath = check_sysconfig('get_python_inc()')
46+
context.env.AppendUnique(CPPPATH=[incpath])
47+
if platform.system() == 'Windows':
48+
version = check_python('import sys; print("%d%d"%sys.version_info[0:2])')
49+
prefix = check_python('import sys; print(sys.prefix)')
50+
libfile = os.path.join(prefix, 'libs', 'python%s.lib'%version)
51+
libpath = os.path.join(prefix, 'libs')
52+
lib = 'python%s'%version
53+
context.env.AppendUnique(LIBS=[lib])
54+
else:
55+
libpath = check_sysconfig('get_config_var("LIBDIR")')
56+
libfile = check_sysconfig('get_config_var("LIBRARY")')
57+
match = re.search('(python.*)\.(a|so|dylib)', libfile)
58+
lib = None
59+
if match:
60+
lib = match.group(1)
61+
context.env.AppendUnique(PYTHONLIBS=[lib])
62+
if match.group(2) == 'a':
63+
flags = check_sysconfig('get_config_var("LINKFORSHARED")')
64+
if flags is not None:
65+
context.env.AppendUnique(LINKFLAGS=flags.split())
66+
context.env.AppendUnique(LIBPATH=[libpath])
67+
oldlibs = context.AppendLIBS([lib])
68+
flags = check_sysconfig('get_config_var("MODLIBS")')
69+
flags += ' ' + check_sysconfig('get_config_var("SHLIBS")')
70+
flags = [f[2:] for f in flags.strip().split() if f.startswith('-l')]
71+
if flags:
72+
context.AppendLIBS([flags])
73+
result = context.TryLink(python_source_file,'.cpp')
74+
if not result and context.env['PLATFORM'] == 'darwin':
75+
# Sometimes we need some extra stuff on Mac OS
76+
frameworkDir = libpath # search up the libDir tree for the proper home for frameworks
77+
while frameworkDir and frameworkDir != "/":
78+
frameworkDir, d2 = os.path.split(frameworkDir)
79+
if d2 == "Python.framework":
80+
if not "Python" in os.listdir(os.path.join(frameworkDir, d2)):
81+
context.Result(0)
82+
print((
83+
"Expected to find Python in framework directory %s, but it isn't there"
84+
% frameworkDir))
85+
return False
86+
break
87+
context.env.AppendUnique(LINKFLAGS="-F%s" % frameworkDir)
88+
result = context.TryLink(python_source_file,'.cpp')
89+
if not result:
90+
context.Result(0)
91+
print("Cannot link program with Python.")
92+
return False
93+
if context.env['PLATFORM'] == 'darwin':
94+
context.env['LDMODULESUFFIX'] = '.so'
95+
context.Result(1)
96+
context.SetLIBS(oldlibs)
97+
context.env.AppendUnique(PYTHONLIBS=[lib] + flags)
98+
return True

config/toolchains/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#
2+
# Copyright (c) 2016 Stefan Seefeld
3+
# All rights reserved.
4+
#
5+
# Distributed under the Boost Software License, Version 1.0.
6+
# (See accompanying file LICENSE_1_0.txt or copy at
7+
# http://www.boost.org/LICENSE_1_0.txt)
8+
9+
import traceback
10+
11+
def append_feature_flag(env, **kw):
12+
stack = traceback.extract_stack(limit = 3)
13+
feature = stack[0][2].upper()
14+
for (key, val) in kw.items():
15+
feature_var = feature + "_" + key
16+
env.AppendUnique(**{ key : "$" + feature_var })
17+
env[feature_var] = val
18+

0 commit comments

Comments
 (0)