mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 18:45:52 +00:00
Improvements to scons build for beast.
* Common code extracted to Python directories. * Read ~/.scons file for scons environment defaults. * Override scons settings with shell environment variables. * New "tags" for debug, nodebug, optimize, nooptimize builds. * Universal platform detection. * Default value of environment variables set through prefix dictionaries. * Check for correct Boost value and fail otherwise. * Extract git describe --tags into a preprocesor variable, -DTIP_BRANCH * More colors - blue for unchanged defaults, green for changed defaults, red for error. * Contain unit tests for non-obvious stuff. * Check to see that boost libraries have been built. * Right now, we accept both .dylib and .a versions but it'd be easy to enforce .a only.
This commit is contained in:
committed by
Vinnie Falco
parent
04ea9ff74c
commit
524f41177c
@@ -1,160 +1,48 @@
|
|||||||
# Beast scons file
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
import ntpath
|
import copy
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
|
||||||
#import re
|
|
||||||
#import json
|
|
||||||
#import urlparse
|
|
||||||
#import posixpath
|
|
||||||
#import string
|
|
||||||
#import subprocess
|
|
||||||
#import platform
|
|
||||||
#import itertools
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
def add_beast_to_path():
|
||||||
|
python_home = os.path.join(os.getcwd(), 'python')
|
||||||
|
if python_home not in sys.path:
|
||||||
|
sys.path.append(python_home)
|
||||||
|
|
||||||
# Format a name value pair
|
add_beast_to_path()
|
||||||
def print_nv_pair(n, v):
|
|
||||||
name = ("%s" % n.rjust(10))
|
|
||||||
sys.stdout.write("%s \033[94m%s\033[0m\n" % (name, v))
|
|
||||||
|
|
||||||
# Pretty-print values as a build configuration
|
from beast.env.AddCommonFlags import add_common_flags
|
||||||
def print_build_vars(env,var):
|
from beast.env.AddUserEnv import add_user_env
|
||||||
val = env.get(var, '')
|
from beast.env import Print
|
||||||
|
from beast.platform import GetEnvironment
|
||||||
|
from beast.util import Boost
|
||||||
|
from beast.util import File
|
||||||
|
from beast.util import Tests
|
||||||
|
|
||||||
if val and val != '':
|
VARIANT_DIRECTORIES = {
|
||||||
name = ("%s" % var.rjust(10))
|
'beast': ('bin', 'beast'),
|
||||||
|
'modules': ('bin', 'modules'),
|
||||||
|
}
|
||||||
|
|
||||||
wrapper = textwrap.TextWrapper()
|
BOOST_LIBRARIES = 'boost_system',
|
||||||
wrapper.break_long_words = False
|
MAIN_PROGRAM_FILE = 'beast/unit_test/tests/main.cpp'
|
||||||
wrapper.break_on_hyphens = False
|
DOTFILE = '~/.scons'
|
||||||
wrapper.width = 69
|
|
||||||
|
|
||||||
if type(val) is str:
|
|
||||||
lines = wrapper.wrap(val)
|
|
||||||
else:
|
|
||||||
lines = wrapper.wrap(" ".join(str(x) for x in val))
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
print_nv_pair (name, line)
|
|
||||||
name = " "
|
|
||||||
|
|
||||||
def print_build_config(env):
|
|
||||||
config_vars = ['CC', 'CXX', 'CFLAGS', 'CCFLAGS', 'CPPFLAGS',
|
|
||||||
'CXXFLAGS', 'LIBPATH', 'LINKFLAGS', 'LIBS', 'BOOST_HOME']
|
|
||||||
sys.stdout.write("\nConfiguration:\n")
|
|
||||||
for var in config_vars:
|
|
||||||
print_build_vars(env,var)
|
|
||||||
print
|
|
||||||
|
|
||||||
def print_cmd_line(s, target, src, env):
|
|
||||||
target = (''.join([str(x) for x in target]))
|
|
||||||
source = (''.join([str(x) for x in src]))
|
|
||||||
name = target
|
|
||||||
print ' \033[94m' + name + '\033[0m'
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Returns the list of libraries needed by the test source file. This is
|
|
||||||
# accomplished by scanning the source file for a special comment line
|
|
||||||
# with this format, which must match exactly:
|
|
||||||
#
|
|
||||||
# // LIBS: <name>...
|
|
||||||
#
|
|
||||||
# path = path to source file
|
|
||||||
#
|
|
||||||
def get_libs(path):
|
|
||||||
prefix = '// LIBS:'
|
|
||||||
with open(path, 'rb') as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith(prefix):
|
|
||||||
items = line.split(prefix, 1)[1].strip()
|
|
||||||
return [x.strip() for x in items.split(' ')]
|
|
||||||
|
|
||||||
# Returns the list of source modules needed by the test source file. This
|
|
||||||
#
|
|
||||||
# // MODULES: <module>...
|
|
||||||
#
|
|
||||||
# path = path to source file
|
|
||||||
#
|
|
||||||
def get_mods(path):
|
|
||||||
prefix = '// MODULES:'
|
|
||||||
with open(path, 'rb') as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith(prefix):
|
|
||||||
items = line.split(prefix, 1)[1].strip()
|
|
||||||
items = [os.path.normpath(os.path.join(
|
|
||||||
os.path.dirname(path), x.strip())) for
|
|
||||||
x in items.split(' ')]
|
|
||||||
return items
|
|
||||||
|
|
||||||
# Build a stand alone executable that runs
|
|
||||||
# all the test suites in one source file
|
|
||||||
#
|
|
||||||
def build_test(env,path):
|
|
||||||
libs = get_libs(path)
|
|
||||||
mods = get_mods(path)
|
|
||||||
bin = os.path.basename(os.path.splitext(path)[0])
|
|
||||||
bin = os.path.join ("bin", bin)
|
|
||||||
srcs = ['beast/unit_test/tests/main.cpp']
|
|
||||||
srcs.append (path)
|
|
||||||
if mods:
|
|
||||||
srcs.extend (mods)
|
|
||||||
# All paths get normalized here, so we can use posix
|
|
||||||
# forward slashes for everything including on Windows
|
|
||||||
srcs = [os.path.normpath(os.path.join ('bin', x)) for x in srcs]
|
|
||||||
objs = [os.path.splitext(x)[0]+'.o' for x in srcs]
|
|
||||||
env_ = env
|
|
||||||
if libs:
|
|
||||||
env_.Append(LIBS = libs)
|
|
||||||
env_.Program (bin, srcs)
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
env = Environment()
|
File.validate_libraries(Boost.LIBPATH, BOOST_LIBRARIES)
|
||||||
|
defaults = GetEnvironment.get_environment(ARGUMENTS)
|
||||||
|
working = copy.deepcopy(defaults)
|
||||||
|
add_common_flags(defaults)
|
||||||
|
|
||||||
env['PRINT_CMD_LINE_FUNC'] = print_cmd_line
|
add_user_env(working, DOTFILE)
|
||||||
|
add_common_flags(working)
|
||||||
|
Print.print_build_config(working, defaults)
|
||||||
|
|
||||||
env.VariantDir (os.path.join ('bin', 'beast'), 'beast', duplicate=0)
|
env = Environment(**working)
|
||||||
env.VariantDir (os.path.join ('bin', 'modules'), 'modules', duplicate=0)
|
|
||||||
|
|
||||||
# Copy important os environment variables into env
|
for name, path in VARIANT_DIRECTORIES.items():
|
||||||
if os.environ.get ('CC', None):
|
env.VariantDir(os.path.join(*path), name, duplicate=0)
|
||||||
env.Replace (CC = os.environ['CC'])
|
env.Replace(PRINT_CMD_LINE_FUNC=Print.print_cmd_line)
|
||||||
if os.environ.get ('CXX', None):
|
Tests.run_tests(env, MAIN_PROGRAM_FILE, '.', '.test.cpp')
|
||||||
env.Replace (CXX = os.environ['CXX'])
|
|
||||||
if os.environ.get ('PATH', None):
|
|
||||||
env.Replace (PATH = os.environ['PATH'])
|
|
||||||
|
|
||||||
# Set up boost variables
|
|
||||||
home = os.environ.get("BOOST_HOME", None)
|
|
||||||
if home is not None:
|
|
||||||
env.Prepend (CPPPATH = home)
|
|
||||||
env.Append (LIBPATH = os.path.join (home, 'stage', 'lib'))
|
|
||||||
|
|
||||||
# Set up flags
|
|
||||||
env.Append(CXXFLAGS = [
|
|
||||||
'-std=c++11',
|
|
||||||
'-frtti',
|
|
||||||
'-O3',
|
|
||||||
'-fno-strict-aliasing',
|
|
||||||
'-g'
|
|
||||||
])
|
|
||||||
|
|
||||||
for root, dirs, files in os.walk('.'):
|
|
||||||
for path in files:
|
|
||||||
path = os.path.join(root,path)
|
|
||||||
if (path.endswith(".test.cpp")):
|
|
||||||
build_test(env,path)
|
|
||||||
|
|
||||||
print_build_config (env)
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifndef BEAST_BASIC_SECONDS_CLOCK_BOOST_WORKAROUND
|
#ifndef BEAST_BASIC_SECONDS_CLOCK_BOOST_WORKAROUND
|
||||||
# ifdef _MSC_VER
|
# ifdef _MSC_VER
|
||||||
|
|||||||
@@ -101,10 +101,6 @@
|
|||||||
#define BEAST_DEBUG 1
|
#define BEAST_DEBUG 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ! (defined (DEBUG) || defined (_DEBUG) || defined (NDEBUG) || defined (_NDEBUG))
|
|
||||||
// #warning "Neither NDEBUG or DEBUG has been defined - you should set one of these to make it clear whether this is a release build,"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __LITTLE_ENDIAN__
|
#ifdef __LITTLE_ENDIAN__
|
||||||
#define BEAST_LITTLE_ENDIAN 1
|
#define BEAST_LITTLE_ENDIAN 1
|
||||||
#else
|
#else
|
||||||
@@ -213,4 +209,3 @@
|
|||||||
#define BEAST_FILEANDLINE_ __FILE__ "(" BEAST_PP_STR1_(__LINE__) "): warning:"
|
#define BEAST_FILEANDLINE_ __FILE__ "(" BEAST_PP_STR1_(__LINE__) "): warning:"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
10
src/beast/python/README
Normal file
10
src/beast/python/README
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Python code for the beast and scons.
|
||||||
|
|
||||||
|
Scripts you can run from this directory.
|
||||||
|
|
||||||
|
./run-tests.sh
|
||||||
|
Runs the unit tests.
|
||||||
|
|
||||||
|
./clean-python.sh
|
||||||
|
If you remove or rename any Python files, you should run this script to
|
||||||
|
prevent old .pyc files from hiding bugs.
|
||||||
0
src/beast/python/beast/__init__.py
Normal file
0
src/beast/python/beast/__init__.py
Normal file
11
src/beast/python/beast/env/AddCommonFlags.py
vendored
Normal file
11
src/beast/python/beast/env/AddCommonFlags.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from beast.util import Boost
|
||||||
|
from beast.util import Git
|
||||||
|
|
||||||
|
def add_common_flags(env):
|
||||||
|
git_flag = '-DTIP_BRANCH="%s"' % Git.describe()
|
||||||
|
env['CPPFLAGS'] = '%s %s' % (env['CPPFLAGS'], git_flag)
|
||||||
|
env['CPPPATH'].insert(0, Boost.CPPPATH)
|
||||||
|
env['LIBPATH'].append(Boost.LIBPATH)
|
||||||
|
env['BOOST_HOME'] = Boost.CPPPATH
|
||||||
38
src/beast/python/beast/env/AddUserEnv.py
vendored
Normal file
38
src/beast/python/beast/env/AddUserEnv.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
from beast.env.ReadEnvFile import read_env_file
|
||||||
|
from beast.util.String import is_string
|
||||||
|
from beast.util.Terminal import warn
|
||||||
|
|
||||||
|
_BAD_VARS_ERROR = """
|
||||||
|
the following variables appearing in %s were not understood:
|
||||||
|
%s"""
|
||||||
|
|
||||||
|
def add_user_env(env, dotfile, print=print):
|
||||||
|
df = os.path.expanduser(dotfile)
|
||||||
|
try:
|
||||||
|
with open(df, 'r') as f:
|
||||||
|
dotvars = read_env_file(f.read())
|
||||||
|
except IOError:
|
||||||
|
if os.path.exists(df):
|
||||||
|
warn("Dotfile %s exists but can't be read." % dotfile, print)
|
||||||
|
dotvars = {}
|
||||||
|
|
||||||
|
bad_names = []
|
||||||
|
for name, value in dotvars.items():
|
||||||
|
if name in env:
|
||||||
|
if is_string(env[name]):
|
||||||
|
env[name] = value
|
||||||
|
else:
|
||||||
|
env[name] = shlex.split(value)
|
||||||
|
else:
|
||||||
|
bad_names.append(name)
|
||||||
|
if bad_names:
|
||||||
|
error = _BAD_VARS_ERROR % (dotfile, '\n '.join(bad_names))
|
||||||
|
warn(error, print)
|
||||||
|
|
||||||
|
for name, default in env.items():
|
||||||
|
env[name] = os.environ.get(name, default)
|
||||||
41
src/beast/python/beast/env/Print.py
vendored
Normal file
41
src/beast/python/beast/env/Print.py
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from beast.util import String
|
||||||
|
from beast.util import Terminal
|
||||||
|
|
||||||
|
FIELD_WIDTH = 10
|
||||||
|
LINE_WIDTH = 69
|
||||||
|
|
||||||
|
EMPTY_NAME = ' ' * FIELD_WIDTH
|
||||||
|
|
||||||
|
TEXT_WRAPPER = textwrap.TextWrapper(
|
||||||
|
break_long_words=False,
|
||||||
|
break_on_hyphens=False,
|
||||||
|
width=LINE_WIDTH,
|
||||||
|
)
|
||||||
|
|
||||||
|
DISPLAY_EMPTY_ENVS = True
|
||||||
|
|
||||||
|
def print_build_vars(name, value, same, print=print):
|
||||||
|
"""Pretty-print values as a build configuration."""
|
||||||
|
name = '%s' % name.rjust(FIELD_WIDTH)
|
||||||
|
color = Terminal.blue if same else Terminal.green
|
||||||
|
|
||||||
|
for line in TEXT_WRAPPER.wrap(String.stringify(value, ' ')):
|
||||||
|
print(' '.join([name, color(line)]))
|
||||||
|
name = EMPTY_NAME
|
||||||
|
|
||||||
|
def print_cmd_line(s, target, source, env):
|
||||||
|
print(EMPTY_NAME + Terminal.blue(String.stringify(target)))
|
||||||
|
|
||||||
|
def print_build_config(env, original, print=print):
|
||||||
|
print('\nConfiguration:')
|
||||||
|
for name, value in env.items():
|
||||||
|
if value or DISPLAY_EMPTY_ENVS:
|
||||||
|
same = (value == original[name])
|
||||||
|
if not same:
|
||||||
|
print('"%s" != "%s"' % (value, original[name]))
|
||||||
|
print_build_vars(name, value, same, print=print)
|
||||||
|
print()
|
||||||
34
src/beast/python/beast/env/ReadEnvFile.py
vendored
Normal file
34
src/beast/python/beast/env/ReadEnvFile.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from beast.util import String
|
||||||
|
from beast.util.Terminal import warn
|
||||||
|
|
||||||
|
ENV_LINE_MATCH = re.compile(r'(?: export \s+)? \s* ([^=\s]*) \s* = (.*)',
|
||||||
|
re.VERBOSE)
|
||||||
|
|
||||||
|
def read_env_file(data, print=print):
|
||||||
|
try:
|
||||||
|
return json.loads(data)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
bad_lines = []
|
||||||
|
results = {}
|
||||||
|
for number, raw_line in enumerate(data.splitlines()):
|
||||||
|
line = String.remove_comment(raw_line).strip()
|
||||||
|
if line:
|
||||||
|
match = ENV_LINE_MATCH.match(line)
|
||||||
|
if match:
|
||||||
|
name, value = match.groups()
|
||||||
|
results[name.strip()] = String.remove_quotes(value.strip())
|
||||||
|
else:
|
||||||
|
bad_lines.append([number, raw_line])
|
||||||
|
if bad_lines:
|
||||||
|
warn("Didn't understand the following environment file lines:", print)
|
||||||
|
for number, line in bad_lines:
|
||||||
|
print('%d. >>> %s' % (number + 1, line))
|
||||||
|
|
||||||
|
return results
|
||||||
51
src/beast/python/beast/env/ReadEnvFile_test.py
vendored
Normal file
51
src/beast/python/beast/env/ReadEnvFile_test.py
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
from beast.env.ReadEnvFile import read_env_file
|
||||||
|
|
||||||
|
from beast.util import Terminal
|
||||||
|
Terminal.CAN_CHANGE_COLOR = False
|
||||||
|
|
||||||
|
JSON = """
|
||||||
|
{
|
||||||
|
"FOO": "foo",
|
||||||
|
"BAR": "bar bar bar",
|
||||||
|
"CPPFLAGS": "-std=c++11 -frtti -fno-strict-aliasing -DWOMBAT"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
ENV = """
|
||||||
|
# An env file.
|
||||||
|
|
||||||
|
FOO=foo
|
||||||
|
export BAR="bar bar bar"
|
||||||
|
CPPFLAGS=-std=c++11 -frtti -fno-strict-aliasing -DWOMBAT
|
||||||
|
|
||||||
|
# export BAZ=baz should be ignored.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
RESULT = {
|
||||||
|
'FOO': 'foo',
|
||||||
|
'BAR': 'bar bar bar',
|
||||||
|
'CPPFLAGS': '-std=c++11 -frtti -fno-strict-aliasing -DWOMBAT',
|
||||||
|
}
|
||||||
|
|
||||||
|
BAD_ENV = ENV + """
|
||||||
|
This line isn't right.
|
||||||
|
NO SPACES IN NAMES="valid value"
|
||||||
|
"""
|
||||||
|
|
||||||
|
class test_ReadEnvFile(TestCase):
|
||||||
|
def test_read_json(self):
|
||||||
|
self.assertEqual(read_env_file(JSON), RESULT)
|
||||||
|
|
||||||
|
def test_read_env(self):
|
||||||
|
self.assertEqual(read_env_file(ENV), RESULT)
|
||||||
|
|
||||||
|
def test_read_env_error(self):
|
||||||
|
errors = []
|
||||||
|
self.assertEqual(read_env_file(BAD_ENV, errors.append), RESULT)
|
||||||
|
self.assertEqual(errors, [
|
||||||
|
"WARNING: Didn't understand the following environment file lines:",
|
||||||
|
"11. >>> This line isn't right.",
|
||||||
|
'12. >>> NO SPACES IN NAMES="valid value"'])
|
||||||
17
src/beast/python/beast/env/Tags.py
vendored
Normal file
17
src/beast/python/beast/env/Tags.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from beast.util.Terminal import warn
|
||||||
|
|
||||||
|
_TAGS = frozenset(['debug', 'optimize'])
|
||||||
|
|
||||||
|
def _to_tag(name, value):
|
||||||
|
return '%s%s' % ('' if value else 'no', name)
|
||||||
|
|
||||||
|
def get_tags(arguments, print=print):
|
||||||
|
result = {}
|
||||||
|
bad_tags = set(arguments) - _TAGS
|
||||||
|
if bad_tags:
|
||||||
|
warn("don't understand tags " + ' '.join(bad_tags), print=print)
|
||||||
|
debug = result.get('debug', True)
|
||||||
|
optimize = result.get('optimize', not debug)
|
||||||
|
return _to_tag('debug', debug), _to_tag('optimize', optimize)
|
||||||
0
src/beast/python/beast/env/__init__.py
vendored
Normal file
0
src/beast/python/beast/env/__init__.py
vendored
Normal file
59
src/beast/python/beast/platform/GetEnvironment.py
Normal file
59
src/beast/python/beast/platform/GetEnvironment.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from beast.platform import Platform
|
||||||
|
from beast.util import Dict
|
||||||
|
from beast.env import Tags
|
||||||
|
|
||||||
|
_DEFAULTS = {
|
||||||
|
'': {
|
||||||
|
'BOOST_HOME': None,
|
||||||
|
'CC': 'gcc',
|
||||||
|
'CCFLAGS': None,
|
||||||
|
'CFLAGS': None,
|
||||||
|
'CXX': 'g++',
|
||||||
|
'CPPFLAGS': '-std=c++11 -frtti -fno-strict-aliasing',
|
||||||
|
'CPPPATH': [],
|
||||||
|
'LIBPATH': [],
|
||||||
|
'LIBS': [],
|
||||||
|
'LINKFLAGS': '',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Darwin': {
|
||||||
|
'CC': 'clang',
|
||||||
|
'CXX': 'clang++',
|
||||||
|
'CPPFLAGS': '-x c++ -stdlib=libc++ -std=c++11 -frtti',
|
||||||
|
'LINKFLAGS': '-stdlib=libc++',
|
||||||
|
},
|
||||||
|
|
||||||
|
'FreeBSD': {
|
||||||
|
'CC': 'gcc46',
|
||||||
|
'CXX': 'g++46',
|
||||||
|
'CCFLAGS': '-Wl,-rpath=/usr/local/lib/gcc46',
|
||||||
|
'LINKFLAGS': '-Wl,-rpath=/usr/local/lib/gcc46',
|
||||||
|
'LIBS': ['kvm'],
|
||||||
|
},
|
||||||
|
|
||||||
|
# TODO: specific flags for Windows, Linux platforms.
|
||||||
|
}
|
||||||
|
|
||||||
|
TAGS = {
|
||||||
|
'debug': {
|
||||||
|
'CPPFLAGS': '-g -DDEBUG'
|
||||||
|
},
|
||||||
|
|
||||||
|
'optimize': {
|
||||||
|
'CPPFLAGS': '-O3',
|
||||||
|
},
|
||||||
|
|
||||||
|
'nooptimize': {
|
||||||
|
'CPPFLAGS': '-O0',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_environment(arguments):
|
||||||
|
tags = Tags.get_tags(arguments)
|
||||||
|
env = Dict.compose_prefix_dicts(Platform.PLATFORM, _DEFAULTS)
|
||||||
|
for tag in tags or []:
|
||||||
|
for k, v in TAGS.get(tag, {}).items():
|
||||||
|
env[k] = '%s %s' % (env[k], v)
|
||||||
|
return env
|
||||||
27
src/beast/python/beast/platform/Platform.py
Normal file
27
src/beast/python/beast/platform/Platform.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import platform
|
||||||
|
|
||||||
|
def _get_platform_string():
|
||||||
|
system = platform.system()
|
||||||
|
parts = [system]
|
||||||
|
linux = system == 'Linux'
|
||||||
|
if linux:
|
||||||
|
flavor, version, _ = platform.linux_distribution()
|
||||||
|
# Arch still has issues with the platform module
|
||||||
|
parts[0] = flavor.capitalize() or 'Archlinux'
|
||||||
|
parts.extend(version.split('.'))
|
||||||
|
elif system == 'Darwin':
|
||||||
|
ten, major, minor = platform.mac_ver()[0].split('.')
|
||||||
|
parts.extend([ten, major, minor])
|
||||||
|
elif system == 'Windows':
|
||||||
|
release, version, csd, ptype = platform.win32_ver()
|
||||||
|
parts.extend([release, version, csd, ptype])
|
||||||
|
elif system == 'FreeBSD':
|
||||||
|
# No other variables to pass with FreeBSD that Python provides and I could find
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise Exception("Don't understand how to build for platform " + system)
|
||||||
|
return '.'.join(parts), linux
|
||||||
|
|
||||||
|
PLATFORM, IS_LINUX = _get_platform_string()
|
||||||
0
src/beast/python/beast/platform/__init__.py
Normal file
0
src/beast/python/beast/platform/__init__.py
Normal file
61
src/beast/python/beast/util/Boost.py
Normal file
61
src/beast/python/beast/util/Boost.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
ROOT_ENV_VARIABLE = 'BOOST_ROOT'
|
||||||
|
MINIMUM_VERSION = 1, 55, 0
|
||||||
|
VERSION_FILE = 'boost', 'version.hpp'
|
||||||
|
LIBRARY_PATH_SEGMENT = 'stage', 'lib'
|
||||||
|
VERSION_MATCHER = re.compile(r'#define\s+BOOST_VERSION\s+(\d+)')
|
||||||
|
|
||||||
|
CANT_OPEN_VERSION_FILE_ERROR = """Unable to open boost version file %s.
|
||||||
|
You have set the environment variable BOOST_ROOT to be %s.
|
||||||
|
Please check to make sure that this points to a valid installation of boost."""
|
||||||
|
|
||||||
|
CANT_UNDERSTAND_VERSION_ERROR = (
|
||||||
|
"Didn't understand version string '%s' from file %s'")
|
||||||
|
VERSION_TOO_OLD_ERROR = ('Your version of boost, %s, is older than the minimum '
|
||||||
|
'required, %s.')
|
||||||
|
|
||||||
|
def _text(major, minor, release):
|
||||||
|
return '%d.%02d.%02d' % (major, minor, release)
|
||||||
|
|
||||||
|
def _raw_boost_path():
|
||||||
|
try:
|
||||||
|
path = os.environ[ROOT_ENV_VARIABLE]
|
||||||
|
if path:
|
||||||
|
return os.path.normpath(path)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
raise KeyError('%s environment variable is not set.' % ROOT_ENV_VARIABLE)
|
||||||
|
|
||||||
|
def _get_version_number(path):
|
||||||
|
version_file = os.path.join(path, *VERSION_FILE)
|
||||||
|
try:
|
||||||
|
with open(version_file) as f:
|
||||||
|
for line in f:
|
||||||
|
match = VERSION_MATCHER.match(line)
|
||||||
|
if match:
|
||||||
|
version = match.group(1)
|
||||||
|
try:
|
||||||
|
return int(version)
|
||||||
|
except ValueError:
|
||||||
|
raise Exception(CANT_UNDERSTAND_VERSION_ERROR %
|
||||||
|
(version, version_file))
|
||||||
|
except IOError:
|
||||||
|
raise Exception(CANT_OPEN_VERSION_FILE_ERROR % (version_file, path))
|
||||||
|
|
||||||
|
def _validate_version(v):
|
||||||
|
version = v // 100000, (v // 100) % 100, v % 100
|
||||||
|
if version < MINIMUM_VERSION:
|
||||||
|
raise Exception(VERSION_TOO_OLD_ERROR % (
|
||||||
|
_text(*version), _text(*MINIMUM_VERSION)))
|
||||||
|
|
||||||
|
def _boost_path():
|
||||||
|
path = _raw_boost_path()
|
||||||
|
_validate_version(_get_version_number(path))
|
||||||
|
return path
|
||||||
|
|
||||||
|
CPPPATH = _boost_path()
|
||||||
|
LIBPATH = os.path.join(CPPPATH, *LIBRARY_PATH_SEGMENT)
|
||||||
17
src/beast/python/beast/util/Dict.py
Normal file
17
src/beast/python/beast/util/Dict.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
def compose(*dicts):
|
||||||
|
result = {}
|
||||||
|
for d in dicts:
|
||||||
|
result.update(**d)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_items_with_prefix(key, mapping):
|
||||||
|
"""Get all elements from the mapping whose keys are a prefix of the given
|
||||||
|
key, sorted by increasing key length."""
|
||||||
|
for k, v in sorted(mapping.items()):
|
||||||
|
if key.startswith(k):
|
||||||
|
yield v
|
||||||
|
|
||||||
|
def compose_prefix_dicts(key, mapping):
|
||||||
|
return compose(*get_items_with_prefix(key, mapping))
|
||||||
56
src/beast/python/beast/util/Dict_test.py
Normal file
56
src/beast/python/beast/util/Dict_test.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from beast.util import Dict
|
||||||
|
|
||||||
|
DICT = {
|
||||||
|
'': {
|
||||||
|
'foo': 'foo-default',
|
||||||
|
'bar': 'bar-default',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Darwin': {
|
||||||
|
'foo': 'foo-darwin',
|
||||||
|
'baz': 'baz-darwin',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Darwin.10.8': {
|
||||||
|
'foo': 'foo-darwin-10.8',
|
||||||
|
'bing': 'bing-darwin-10.8',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class test_Dict(TestCase):
|
||||||
|
def computeMapValue(self, config, key):
|
||||||
|
return Dict.compose(*Dict.get_items_with_prefix(config, DICT))[key]
|
||||||
|
|
||||||
|
def assertMapValue(self, config, key, result):
|
||||||
|
self.assertEquals(self.computeMapValue(config, key), result)
|
||||||
|
|
||||||
|
def testDefault1(self):
|
||||||
|
self.assertMapValue('', 'foo', 'foo-default')
|
||||||
|
|
||||||
|
def testDefault2(self):
|
||||||
|
self.assertMapValue('Darwin.10.8', 'bar', 'bar-default')
|
||||||
|
|
||||||
|
def testPrefix1(self):
|
||||||
|
self.assertMapValue('Darwin', 'foo', 'foo-darwin')
|
||||||
|
|
||||||
|
def testPrefix2(self):
|
||||||
|
self.assertMapValue('Darwin.10.8', 'foo', 'foo-darwin-10.8')
|
||||||
|
|
||||||
|
def testPrefix3(self):
|
||||||
|
self.assertMapValue('Darwin', 'baz', 'baz-darwin')
|
||||||
|
|
||||||
|
def testPrefix4(self):
|
||||||
|
self.assertMapValue('Darwin.10.8', 'bing', 'bing-darwin-10.8')
|
||||||
|
|
||||||
|
def testFailure1(self):
|
||||||
|
self.assertRaises(KeyError, self.computeMapValue, '', 'baz')
|
||||||
|
|
||||||
|
def testFailure2(self):
|
||||||
|
self.assertRaises(KeyError, self.computeMapValue, '', 'bing')
|
||||||
|
|
||||||
|
def testFailure2(self):
|
||||||
|
self.assertRaises(KeyError, self.computeMapValue, 'Darwin', 'bing')
|
||||||
14
src/beast/python/beast/util/Execute.py
Normal file
14
src/beast/python/beast/util/Execute.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from beast.util import String
|
||||||
|
|
||||||
|
def execute(args, include_errors=True, **kwds):
|
||||||
|
"""Execute a shell command and return the value. If args is a string,
|
||||||
|
it's split on spaces - if some of your arguments contain spaces, args should
|
||||||
|
instead be a list of arguments."""
|
||||||
|
if String.is_string(args):
|
||||||
|
args = args.split()
|
||||||
|
stderr = subprocess.STDOUT if include_errors else None
|
||||||
|
return subprocess.check_output(args, stderr=stderr, **kwds)
|
||||||
42
src/beast/python/beast/util/File.py
Normal file
42
src/beast/python/beast/util/File.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from beast.util import String
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
LIBRARY_PATTERNS = 'lib%s.a', 'lib%s.dylib'
|
||||||
|
|
||||||
|
def first_fields_after_prefix(filename, prefix):
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
return String.first_fields_after_prefix(prefix, f)
|
||||||
|
|
||||||
|
def find_files_with_suffix(base, suffix):
|
||||||
|
for parent, _, files in os.walk(base):
|
||||||
|
for path in files:
|
||||||
|
path = os.path.join(parent, path)
|
||||||
|
if path.endswith(suffix):
|
||||||
|
yield os.path.normpath(path)
|
||||||
|
|
||||||
|
def child_files(parent, files):
|
||||||
|
return [os.path.normpath(os.path.join(parent, f)) for f in files]
|
||||||
|
|
||||||
|
def sibling_files(path, files):
|
||||||
|
return child_files(os.path.dirname(path), files)
|
||||||
|
|
||||||
|
def replace_extension(file, ext):
|
||||||
|
return os.path.splitext(file)[0] + ext
|
||||||
|
|
||||||
|
def validate_libraries(path, libraries):
|
||||||
|
bad = []
|
||||||
|
for lib in libraries:
|
||||||
|
found = False
|
||||||
|
for pat in LIBRARY_PATTERNS:
|
||||||
|
libfile = os.path.join(path, pat % lib)
|
||||||
|
if os.path.isfile(libfile):
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
bad.append(libfile)
|
||||||
|
if bad:
|
||||||
|
libs = 'library' if len(bad) == 1 else 'libraries'
|
||||||
|
raise Exception('Missing %s: %s' % (libs, ', '.join(bad)))
|
||||||
9
src/beast/python/beast/util/Git.py
Normal file
9
src/beast/python/beast/util/Git.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from beast.util import Execute
|
||||||
|
from beast.util import String
|
||||||
|
|
||||||
|
def describe(**kwds):
|
||||||
|
return String.single_line(Execute.execute('git describe --tags', **kwds))
|
||||||
7
src/beast/python/beast/util/Iter.py
Normal file
7
src/beast/python/beast/util/Iter.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
def first(condition, sequence):
|
||||||
|
for i in sequence:
|
||||||
|
result = condition(i)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
60
src/beast/python/beast/util/String.py
Normal file
60
src/beast/python/beast/util/String.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from beast.util import Iter
|
||||||
|
from beast.util.Terminal import warn
|
||||||
|
|
||||||
|
def is_string(s):
|
||||||
|
"""Is s a string? - in either Python 2.x or 3.x."""
|
||||||
|
return isinstance(s, (str, unicode))
|
||||||
|
|
||||||
|
def stringify(item, joiner=''):
|
||||||
|
"""If item is not a string, stringify its members and join them."""
|
||||||
|
try:
|
||||||
|
len(item)
|
||||||
|
except:
|
||||||
|
return str(item)
|
||||||
|
if not item or is_string(item):
|
||||||
|
return item or ''
|
||||||
|
else:
|
||||||
|
return joiner.join(str(i) for i in item)
|
||||||
|
|
||||||
|
def single_line(line, report_errors=True, joiner='+'):
|
||||||
|
"""Force a string to be a single line with no carriage returns, and report
|
||||||
|
a warning if there was more than one line."""
|
||||||
|
lines = line.strip().splitlines()
|
||||||
|
if report_errors and len(lines) > 1:
|
||||||
|
print('multiline result:', lines)
|
||||||
|
return joiner.join(lines)
|
||||||
|
|
||||||
|
# Copied from
|
||||||
|
# https://github.com/lerugray/pickett/blob/master/pickett/ParseScript.py
|
||||||
|
def remove_comment(line):
|
||||||
|
"""Remove trailing comments from one line."""
|
||||||
|
start = 0
|
||||||
|
while True:
|
||||||
|
loc = line.find('#', start)
|
||||||
|
if loc == -1:
|
||||||
|
return line.replace('\\#', '#')
|
||||||
|
elif not (loc and line[loc - 1] == '\\'):
|
||||||
|
return line[:loc].replace('\\#', '#')
|
||||||
|
start = loc + 1
|
||||||
|
|
||||||
|
def remove_quotes(line, quote='"', print=print):
|
||||||
|
if not line.startswith(quote):
|
||||||
|
return line
|
||||||
|
if line.endswith(quote):
|
||||||
|
return line[1:-1]
|
||||||
|
|
||||||
|
warn('line started with %s but didn\'t end with one:' % quote, print)
|
||||||
|
print(line)
|
||||||
|
return line[1:]
|
||||||
|
|
||||||
|
def fields_after_prefix(prefix, line):
|
||||||
|
line = line.strip()
|
||||||
|
return line.startswith(prefix) and line[len(prefix):].split()
|
||||||
|
|
||||||
|
def first_fields_after_prefix(prefix, sequence):
|
||||||
|
condition = functools.partial(fields_after_prefix, prefix)
|
||||||
|
return Iter.first(condition, sequence) or []
|
||||||
36
src/beast/python/beast/util/String_test.py
Normal file
36
src/beast/python/beast/util/String_test.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from beast.util import String
|
||||||
|
from beast.util import Terminal
|
||||||
|
|
||||||
|
Terminal.CAN_CHANGE_COLOR = False
|
||||||
|
|
||||||
|
class String_test(TestCase):
|
||||||
|
def test_comments(self):
|
||||||
|
self.assertEqual(String.remove_comment(''), '')
|
||||||
|
self.assertEqual(String.remove_comment('#'), '')
|
||||||
|
self.assertEqual(String.remove_comment('# a comment'), '')
|
||||||
|
self.assertEqual(String.remove_comment('hello # a comment'), 'hello ')
|
||||||
|
self.assertEqual(String.remove_comment(
|
||||||
|
r'hello \# not a comment # a comment'),
|
||||||
|
'hello # not a comment ')
|
||||||
|
|
||||||
|
def test_remove_quotes(self):
|
||||||
|
errors = []
|
||||||
|
self.assertEqual(String.remove_quotes('hello', print=errors.append),
|
||||||
|
'hello')
|
||||||
|
self.assertEqual(String.remove_quotes('"hello"', print=errors.append),
|
||||||
|
'hello')
|
||||||
|
self.assertEqual(String.remove_quotes('hello"', print=errors.append),
|
||||||
|
'hello"')
|
||||||
|
self.assertEqual(errors, [])
|
||||||
|
|
||||||
|
def test_remove_quotes_error(self):
|
||||||
|
errors = []
|
||||||
|
self.assertEqual(String.remove_quotes('"hello', print=errors.append),
|
||||||
|
'hello')
|
||||||
|
self.assertEqual(errors,
|
||||||
|
['WARNING: line started with " but didn\'t end with one:',
|
||||||
|
'"hello'])
|
||||||
36
src/beast/python/beast/util/Terminal.py
Normal file
36
src/beast/python/beast/util/Terminal.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from beast.platform.Platform import PLATFORM
|
||||||
|
|
||||||
|
# See https://stackoverflow.com/questions/7445658/how-to-detect-if-the-console-does-support-ansi-escape-codes-in-python
|
||||||
|
CAN_CHANGE_COLOR = (
|
||||||
|
hasattr(sys.stderr, "isatty")
|
||||||
|
and sys.stderr.isatty()
|
||||||
|
and not PLATFORM.startswith('Windows'))
|
||||||
|
|
||||||
|
|
||||||
|
# See https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
RED = 91
|
||||||
|
GREEN = 92
|
||||||
|
BLUE = 94
|
||||||
|
|
||||||
|
def add_mode(text, *modes):
|
||||||
|
if CAN_CHANGE_COLOR:
|
||||||
|
modes = ';'.join(str(m) for m in modes)
|
||||||
|
return '\033[%sm%s\033[0m' % (modes, text)
|
||||||
|
else:
|
||||||
|
return text
|
||||||
|
|
||||||
|
def blue(text):
|
||||||
|
return add_mode(text, BLUE)
|
||||||
|
|
||||||
|
def green(text):
|
||||||
|
return add_mode(text, GREEN)
|
||||||
|
|
||||||
|
def red(text):
|
||||||
|
return add_mode(text, RED)
|
||||||
|
|
||||||
|
def warn(text, print=print):
|
||||||
|
print('%s %s' % (red('WARNING:'), text))
|
||||||
31
src/beast/python/beast/util/Tests.py
Normal file
31
src/beast/python/beast/util/Tests.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from beast.util import File
|
||||||
|
|
||||||
|
LIBS_PREFIX = '// LIBS:'
|
||||||
|
MODS_PREFIX = '// MODULES:'
|
||||||
|
|
||||||
|
def build_executable(env, path, main_program_file):
|
||||||
|
"""Build a stand alone executable that runs
|
||||||
|
all the test suites in one source file."""
|
||||||
|
libs = File.first_fields_after_prefix(path, LIBS_PREFIX)
|
||||||
|
source_modules = File.first_fields_after_prefix(path, MODS_PREFIX)
|
||||||
|
source_modules = File.sibling_files(path, source_modules)
|
||||||
|
|
||||||
|
bin = os.path.basename(os.path.splitext(path)[0])
|
||||||
|
bin = os.path.join('bin', bin)
|
||||||
|
|
||||||
|
# All paths get normalized here, so we can use posix
|
||||||
|
# forward slashes for everything including on Windows
|
||||||
|
srcs = File.child_files('bin', [main_program_file, path] + source_modules)
|
||||||
|
objs = [File.replace_extension(f, '.o') for f in srcs]
|
||||||
|
if libs:
|
||||||
|
env.Append(LIBS=libs) # DANGER: will append the file over and over.
|
||||||
|
env.Program(bin, srcs)
|
||||||
|
|
||||||
|
def run_tests(env, main_program_file, root, suffix):
|
||||||
|
root = os.path.normpath(root)
|
||||||
|
for path in File.find_files_with_suffix(root, suffix):
|
||||||
|
build_executable(env, path, main_program_file)
|
||||||
0
src/beast/python/beast/util/__init__.py
Normal file
0
src/beast/python/beast/util/__init__.py
Normal file
5
src/beast/python/clean-python.py
Executable file
5
src/beast/python/clean-python.py
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Remove all the compiled .pyc files at or below this directory.
|
||||||
|
|
||||||
|
find . -name \*.pyc | xargs rm
|
||||||
5
src/beast/python/run-tests.sh
Executable file
5
src/beast/python/run-tests.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Run all the beast Python unit tests.
|
||||||
|
|
||||||
|
python -m unittest discover -p \*_test.py
|
||||||
Reference in New Issue
Block a user