# rippled SConstruct # ''' Target Builds ---------------------------------------------------------------------------- Same as 'install' install Default target and copies it to build/rippled (default) all All available variants debug All available debug variants release All available release variants profile All available profile variants clang All clang variants clang.debug clang debug variant clang.coverage clang coverage variant clang.release clang release variant clang.profile clang profile variant gcc All gcc variants gcc.debug gcc debug variant gcc.coverage gcc coverage variant gcc.release gcc release variant gcc.profile gcc profile variant msvc All msvc variants msvc.debug MSVC debug variant msvc.release MSVC release variant vcxproj Generate Visual Studio 2013 project file count Show line count metrics Any individual target can also have ".nounity" appended for a classic, non unity build. Example: scons gcc.debug.nounity If the clang toolchain is detected, then the default target will use it, else the gcc toolchain will be used. On Windows environments, the MSVC toolchain is also detected. The following environment variables modify the build environment: CLANG_CC CLANG_CXX CLANG_LINK If set, a clang toolchain will be used. These must all be set together. GNU_CC GNU_CXX GNU_LINK If set, a gcc toolchain will be used (unless a clang toolchain is detected first). These must all be set together. CXX If set, used to detect a toolchain. BOOST_ROOT Path to the boost directory. OPENSSL_ROOT Path to the openssl directory. PROTOBUF_ROOT Path to the protobuf directory. CLANG_PROTOBUF_ROOT Override the path to the protobuf directory for the clang toolset. This is usually only needed when the installed protobuf library uses a different ABI than clang (as with ubuntu 15.10). CLANG_BOOST_ROOT Override the path to the boost directory for the clang toolset. This is usually only needed when the installed protobuf library uses a different ABI than clang (as with ubuntu 15.10). The following extra options may be used: --ninja Generate a `build.ninja` build file for the specified target (see: https://martine.github.io/ninja/). Only gcc and clang targets are supported. --static On linux, link protobuf, openssl, libc++, and boost statically --sanitize=[address, thread] On gcc & clang, add sanitizer instrumentation --assert Enable asserts, even in release builds. GCC 5: If the gcc toolchain is used, gcc version 5 or better is required. On linux distros that ship with gcc 4 (ubuntu < 15.10), rippled will force gcc to use gcc4's ABI (there was an ABI change between versions). This allows us to use the package manager to install rippled dependencies. It also means if the user builds C++ dependencies themselves - such as boost - they must either be built with gcc 4 or with the preprocessor flag `_GLIBCXX_USE_CXX11_ABI` set to zero. Clang on linux: Clang cannot use the new gcc 5 ABI (clang does not know about the `abi_tag` attribute). On linux distros that ship with the gcc 5 ABI (ubuntu >= 15.10), building with clang requires building boost and protobuf with the old ABI (best to build them with clang). It is best to statically link rippled in this scenario (use the `--static` with scons), as dynamic linking may use a library with the incorrect ABI. ''' # ''' TODO - Fix git-describe support - Fix printing exemplar command lines - Fix toolchain detection ''' #------------------------------------------------------------------------------- import collections import os import subprocess import sys import textwrap import time import SCons.Action sys.path.append(os.path.join('src', 'ripple', 'beast', 'site_scons')) sys.path.append(os.path.join('src', 'ripple', 'site_scons')) import Beast import scons_to_ninja #------------------------------------------------------------------------------ AddOption('--ninja', dest='ninja', action='store_true', help='generate ninja build file build.ninja') AddOption('--sanitize', dest='sanitize', choices=['address', 'thread'], help='Build with sanitizer support (gcc and clang only).') AddOption('--static', dest='static', action='store_true', help='On linux, link protobuf, openssl, libc++, and boost statically') AddOption('--assert', dest='assert', action='store_true', help='Enable asserts, even in release mode') def parse_time(t): l = len(t.split()) if l==5: return time.strptime(t, '%a %b %d %H:%M:%S %Y') elif l==3: return time.strptime(t, '%d %b %Y') else: return time.strptime(t, '%a %b %d %H:%M:%S %Z %Y') UNITY_BUILD_DIRECTORY = 'src/ripple/unity/' def memoize(function): memo = {} def wrapper(*args): if args in memo: return memo[args] else: rv = function(*args) memo[args] = rv return rv return wrapper def check_openssl(): if Beast.system.platform not in ['Debian', 'Ubuntu']: return line = subprocess.check_output('openssl version -b'.split()).strip() check_line = 'built on: ' if not line.startswith(check_line): raise Exception("Didn't find any '%s' line in '$ %s'" % (check_line, 'openssl version -b')) d = line[len(check_line):] if 'date unspecified' in d: words = subprocess.check_output('openssl version'.split()).split() if len(words)!=5: raise Exception("Didn't find version date in '$ openssl version'") d = ' '.join(words[-3:]) build_time = 'Mon Apr 7 20:33:19 UTC 2014' if parse_time(d) < parse_time(build_time): raise Exception('Your openSSL was built on %s; ' 'rippled needs a version built on or after %s.' % (line, build_time)) def set_implicit_cache(): '''Use implicit_cache on some targets to improve build times. By default, scons scans each file for include dependecies. The implicit cache flag lets you cache these dependencies for later builds, and will only rescan files that change. Failure cases are: 1) If the include search paths are changed (i.e. CPPPATH), then a file may not be rebuilt. 2) If a same-named file has been added to a directory that is earlier in the search path than the directory in which the file was found. Turn on if this build is for a specific debug target (i.e. clang.debug) If one of the failure cases applies, you can force a rescan of dependencies using the command line option `--implicit-deps-changed` ''' if len(COMMAND_LINE_TARGETS) == 1: s = COMMAND_LINE_TARGETS[0].split('.') if len(s) > 1 and 'debug' in s: SetOption('implicit_cache', 1) def import_environ(env): '''Imports environment settings into the construction environment''' def set(keys): if type(keys) == list: for key in keys: set(key) return if keys in os.environ: value = os.environ[keys] env[keys] = value set(['GNU_CC', 'GNU_CXX', 'GNU_LINK']) set(['CLANG_CC', 'CLANG_CXX', 'CLANG_LINK']) def detect_toolchains(env): def is_compiler(comp_from, comp_to): return comp_from and comp_to in comp_from def detect_clang(env): n = sum(x in env for x in ['CLANG_CC', 'CLANG_CXX', 'CLANG_LINK']) if n > 0: if n == 3: return True raise ValueError('CLANG_CC, CLANG_CXX, and CLANG_LINK must be set together') cc = env.get('CC') cxx = env.get('CXX') link = env.subst(env.get('LINK')) if (cc and cxx and link and is_compiler(cc, 'clang') and is_compiler(cxx, 'clang') and is_compiler(link, 'clang')): env['CLANG_CC'] = cc env['CLANG_CXX'] = cxx env['CLANG_LINK'] = link return True cc = env.WhereIs('clang') cxx = env.WhereIs('clang++') link = cxx if (is_compiler(cc, 'clang') and is_compiler(cxx, 'clang') and is_compiler(link, 'clang')): env['CLANG_CC'] = cc env['CLANG_CXX'] = cxx env['CLANG_LINK'] = link return True env['CLANG_CC'] = 'clang' env['CLANG_CXX'] = 'clang++' env['CLANG_LINK'] = env['LINK'] return False def detect_gcc(env): n = sum(x in env for x in ['GNU_CC', 'GNU_CXX', 'GNU_LINK']) if n > 0: if n == 3: return True raise ValueError('GNU_CC, GNU_CXX, and GNU_LINK must be set together') cc = env.get('CC') cxx = env.get('CXX') link = env.subst(env.get('LINK')) if (cc and cxx and link and is_compiler(cc, 'gcc') and is_compiler(cxx, 'g++') and is_compiler(link, 'g++')): env['GNU_CC'] = cc env['GNU_CXX'] = cxx env['GNU_LINK'] = link return True cc = env.WhereIs('gcc') cxx = env.WhereIs('g++') link = cxx if (is_compiler(cc, 'gcc') and is_compiler(cxx, 'g++') and is_compiler(link, 'g++')): env['GNU_CC'] = cc env['GNU_CXX'] = cxx env['GNU_LINK'] = link return True env['GNU_CC'] = 'gcc' env['GNU_CXX'] = 'g++' env['GNU_LINK'] = env['LINK'] return False toolchains = [] if detect_clang(env): toolchains.append('clang') if detect_gcc(env): toolchains.append('gcc') if env.Detect('cl'): toolchains.append('msvc') return toolchains def files(base): def _iter(base): for parent, _, files in os.walk(base): for path in files: path = os.path.join(parent, path) yield os.path.normpath(path) return list(_iter(base)) def print_coms(target, source, env): '''Display command line exemplars for an environment''' print ('Target: ' + Beast.yellow(str(target[0]))) # TODO Add 'PROTOCCOM' to this list and make it work Beast.print_coms(['CXXCOM', 'CCCOM', 'LINKCOM'], env) def is_debug_variant(variant): return variant in ('debug', 'coverage') @memoize def is_ubuntu(): try: return "Ubuntu" == subprocess.check_output(['lsb_release', '-si'], stderr=subprocess.STDOUT).strip() except: return False @memoize def use_gcc4_abi(cc_cmd): if os.getenv('RIPPLED_OLD_GCC_ABI'): return True gcc_ver = '' ubuntu_ver = None try: if 'gcc' in cc_cmd: gcc_ver = subprocess.check_output([cc_cmd, '-dumpversion'], stderr=subprocess.STDOUT).strip() else: gcc_ver = '5' # assume gcc 5 for ABI purposes for clang if is_ubuntu(): ubuntu_ver = float( subprocess.check_output(['lsb_release', '-sr'], stderr=subprocess.STDOUT).strip()) except: print("Unable to determine gcc version. Assuming native ABI.") return False if ubuntu_ver and ubuntu_ver < 15.1 and gcc_ver.startswith('5'): return True return False #------------------------------------------------------------------------------- # Set construction variables for the base environment def config_base(env): if False: env.Replace( CCCOMSTR='Compiling ' + Beast.blue('$SOURCES'), CXXCOMSTR='Compiling ' + Beast.blue('$SOURCES'), LINKCOMSTR='Linking ' + Beast.blue('$TARGET'), ) check_openssl() env.Append(CPPDEFINES=[ 'OPENSSL_NO_SSL2' ,'DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER' ,{'HAVE_USLEEP' : '1'} ,{'SOCI_CXX_C11' : '1'} ,'_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS' ,'BOOST_NO_AUTO_PTR' ]) if Beast.system.windows: try: OPENSSL_ROOT = os.path.normpath(os.environ['OPENSSL_ROOT']) env.Append(CPPPATH=[ os.path.join(OPENSSL_ROOT, 'include'), ]) env.Append(LIBPATH=[ os.path.join(OPENSSL_ROOT, 'lib'), ]) except KeyError: pass elif Beast.system.osx: OSX_OPENSSL_ROOT = '/usr/local/Cellar/openssl/' most_recent = sorted(os.listdir(OSX_OPENSSL_ROOT))[-1] openssl = os.path.join(OSX_OPENSSL_ROOT, most_recent) env.Prepend(CPPPATH='%s/include' % openssl) env.Prepend(LIBPATH=['%s/lib' % openssl]) # handle command-line arguments profile_jemalloc = ARGUMENTS.get('profile-jemalloc') if profile_jemalloc: env.Append(CPPDEFINES={'PROFILE_JEMALLOC' : profile_jemalloc}) env.Append(LIBS=['jemalloc']) env.Append(LIBPATH=[os.path.join(profile_jemalloc, 'lib')]) env.Append(CPPPATH=[os.path.join(profile_jemalloc, 'include')]) env.Append(LINKFLAGS=['-Wl,-rpath,' + os.path.join(profile_jemalloc, 'lib')]) def add_static_libs(env, static_libs, dyn_libs=None): if not 'HASSTATICLIBS' in env: env['HASSTATICLIBS'] = True env.Replace(LINKCOM=env['LINKCOM'] + " -Wl,-Bstatic $STATICLIBS -Wl,-Bdynamic $DYNAMICLIBS") for k,l in [('STATICLIBS', static_libs or []), ('DYNAMICLIBS', dyn_libs or [])]: c = env.get(k, '') for f in l: c += ' -l' + f env[k] = c def get_libs(lib, static): '''Returns a tuple of lists. The first element is the static libs, the second element is the dynamic libs ''' always_dynamic = ['dl', 'pthread', 'z', # for ubuntu 'gssapi_krb5', 'krb5', 'com_err', 'k5crypto', # for fedora ] try: cmd = ['pkg-config', '--static', '--libs', lib] libs = subprocess.check_output(cmd, stderr=subprocess.STDOUT).strip() all_libs = [l[2:] for l in libs.split() if l.startswith('-l')] if not static: return ([], all_libs) static_libs = [] dynamic_libs = [] for l in all_libs: if l in always_dynamic: dynamic_libs.append(l) else: static_libs.append(l) return (static_libs, dynamic_libs) except Exception as e: raise Exception('pkg-config failed for ' + lib + '; Exception: ' + str(e)) def add_sanitizer (toolchain, env): san = GetOption('sanitize') if not san: return san_to_lib = {'address': 'asan', 'thread': 'tsan'} if toolchain not in Split('clang gcc'): raise Exception("Sanitizers are only supported for gcc and clang") env.Append(CCFLAGS=['-fsanitize='+san, '-fno-omit-frame-pointer']) env.Append(LINKFLAGS=['-fsanitize='+san]) add_static_libs(env, [san_to_lib[san]]) env.Append(CPPDEFINES=['SANITIZER='+san_to_lib[san].upper()]) def add_boost_and_protobuf(toolchain, env): def get_environ_value(candidates): for c in candidates: try: return os.environ[c] except KeyError: pass raise KeyError('Environment variable not set') try: br_cands = ['CLANG_BOOST_ROOT'] if toolchain == 'clang' else [] br_cands.append('BOOST_ROOT') BOOST_ROOT = os.path.normpath(get_environ_value(br_cands)) env.Append(LIBPATH=[ os.path.join(BOOST_ROOT, 'stage', 'lib'), ]) env['BOOST_ROOT'] = BOOST_ROOT if toolchain in ['gcc', 'clang']: env.Append(CCFLAGS=['-isystem' + env['BOOST_ROOT']]) else: env.Append(CPPPATH=[ env['BOOST_ROOT'], ]) except KeyError: pass try: pb_cands = ['CLANG_PROTOBUF_ROOT'] if toolchain == 'clang' else [] pb_cands.append('PROTOBUF_ROOT') PROTOBUF_ROOT = os.path.normpath(get_environ_value(pb_cands)) env.Append(LIBPATH=[PROTOBUF_ROOT + '/src/.libs']) if not should_link_static() and toolchain in['clang', 'gcc']: env.Append(LINKFLAGS=['-Wl,-rpath,' + PROTOBUF_ROOT + '/src/.libs']) env['PROTOBUF_ROOT'] = PROTOBUF_ROOT env.Append(CPPPATH=[env['PROTOBUF_ROOT'] + '/src',]) except KeyError: pass def enable_asserts (): return GetOption('assert') # Set toolchain and variant specific construction variables def config_env(toolchain, variant, env): add_boost_and_protobuf(toolchain, env) if is_debug_variant(variant): env.Append(CPPDEFINES=['DEBUG', '_DEBUG']) elif (variant == 'release' or variant == 'profile') and (not enable_asserts()): env.Append(CPPDEFINES=['NDEBUG']) if should_link_static() and not Beast.system.linux: raise Exception("Static linking is only implemented for linux.") add_sanitizer(toolchain, env) if toolchain in Split('clang gcc'): if Beast.system.linux: link_static = should_link_static() for l in ['openssl', 'protobuf']: static, dynamic = get_libs(l, link_static) if link_static: add_static_libs(env, static, dynamic) else: env.Append(LIBS=dynamic) env.ParseConfig('pkg-config --static --cflags ' + l) env.Prepend(CFLAGS=['-Wall']) env.Prepend(CXXFLAGS=['-Wall']) env.Append(CCFLAGS=[ '-Wno-sign-compare', '-Wno-char-subscripts', '-Wno-format', '-g' # generate debug symbols ]) env.Append(LINKFLAGS=[ '-rdynamic', '-g', ]) if variant == 'profile': env.Append(CCFLAGS=[ '-p', '-pg', ]) env.Append(LINKFLAGS=[ '-p', '-pg', ]) if toolchain == 'clang': env.Append(CCFLAGS=['-Wno-redeclared-class-member']) env.Append(CPPDEFINES=['BOOST_ASIO_HAS_STD_ARRAY']) env.Append(CXXFLAGS=[ '-frtti', '-std=c++14', '-Wno-invalid-offsetof']) env.Append(CPPDEFINES=['_FILE_OFFSET_BITS=64']) if Beast.system.osx: env.Append(CPPDEFINES={ 'BEAST_COMPILE_OBJECTIVE_CPP': 1, }) # These should be the same regardless of platform... if Beast.system.osx: env.Append(CCFLAGS=[ '-Wno-deprecated', '-Wno-deprecated-declarations', '-Wno-unused-variable', '-Wno-unused-function', ]) else: if should_link_static(): env.Append(LINKFLAGS=[ '-static-libstdc++', ]) if use_gcc4_abi(env['CC'] if 'CC' in env else 'gcc'): env.Append(CPPDEFINES={ '-D_GLIBCXX_USE_CXX11_ABI' : 0 }) if toolchain == 'gcc': env.Append(CCFLAGS=[ '-Wno-unused-but-set-variable', '-Wno-deprecated', ]) boost_libs = [ 'boost_coroutine', 'boost_context', 'boost_date_time', 'boost_filesystem', 'boost_program_options', 'boost_regex', 'boost_system', 'boost_thread' ] env.Append(LIBS=['dl']) if should_link_static(): add_static_libs(env, boost_libs) else: # We prefer static libraries for boost if env.get('BOOST_ROOT'): static_libs = ['%s/stage/lib/lib%s.a' % (env['BOOST_ROOT'], l) for l in boost_libs] if all(os.path.exists(f) for f in static_libs): boost_libs = [File(f) for f in static_libs] env.Append(LIBS=boost_libs) if Beast.system.osx: env.Append(LIBS=[ 'crypto', 'protobuf', 'ssl', ]) env.Append(FRAMEWORKS=[ 'AppKit', 'Foundation' ]) else: env.Append(LIBS=['rt']) if variant == 'release': env.Append(CCFLAGS=[ '-O3', '-fno-strict-aliasing' ]) if variant == 'coverage': env.Append(CXXFLAGS=[ '-fprofile-arcs', '-ftest-coverage']) env.Append(LINKFLAGS=[ '-fprofile-arcs', '-ftest-coverage']) if toolchain == 'clang': if Beast.system.osx: env.Replace(CC='clang', CXX='clang++', LINK='clang++') elif 'CLANG_CC' in env and 'CLANG_CXX' in env and 'CLANG_LINK' in env: env.Replace(CC=env['CLANG_CC'], CXX=env['CLANG_CXX'], LINK=env['CLANG_LINK']) # C and C++ # Add '-Wshorten-64-to-32' env.Append(CCFLAGS=[]) # C++ only env.Append(CXXFLAGS=[ '-Wno-mismatched-tags', '-Wno-deprecated-register', '-Wno-unused-local-typedefs', '-Wno-unknown-warning-option', ]) elif toolchain == 'gcc': if 'GNU_CC' in env and 'GNU_CXX' in env and 'GNU_LINK' in env: env.Replace(CC=env['GNU_CC'], CXX=env['GNU_CXX'], LINK=env['GNU_LINK']) # Why is this only for gcc?! env.Append(CCFLAGS=['-Wno-unused-local-typedefs']) # If we are in debug mode, use GCC-specific functionality to add # extra error checking into the code (e.g. std::vector will throw # for out-of-bounds conditions) if is_debug_variant(variant): env.Append(CPPDEFINES={ '_FORTIFY_SOURCE': 2 }) env.Append(CCFLAGS=[ '-O0' ]) elif toolchain == 'msvc': env.Append (CPPPATH=[ os.path.join('src', 'protobuf', 'src'), os.path.join('src', 'protobuf', 'vsprojects'), ]) env.Append(CCFLAGS=[ '/bigobj', # Increase object file max size '/EHa', # ExceptionHandling all '/fp:precise', # Floating point behavior '/Gd', # __cdecl calling convention '/Gm-', # Minimal rebuild: disabled '/GR', # Enable RTTI '/Gy-', # Function level linking: disabled '/FS', '/MP', # Multiprocessor compilation '/openmp-', # pragma omp: disabled '/Zc:forScope', # Language extension: for scope '/Zi', # Generate complete debug info '/errorReport:none', # No error reporting to Internet '/nologo', # Suppress login banner #'/Fd${TARGET}.pdb', # Path: Program Database (.pdb) '/W3', # Warning level 3 '/WX-', # Disable warnings as errors '/wd"4018"', '/wd"4244"', '/wd"4267"', '/wd"4800"', # Disable C4800 (int to bool performance) ]) env.Append(CPPDEFINES={ '_WIN32_WINNT' : '0x6000', }) env.Append(CPPDEFINES=[ '_SCL_SECURE_NO_WARNINGS', '_CRT_SECURE_NO_WARNINGS', 'WIN32_CONSOLE', 'NOMINMAX' ]) if variant == 'debug': env.Append(LIBS=[ 'VC/static/ssleay32MTd.lib', 'VC/static/libeay32MTd.lib', ]) else: env.Append(LIBS=[ 'VC/static/ssleay32MT.lib', 'VC/static/libeay32MT.lib', ]) env.Append(LIBS=[ 'legacy_stdio_definitions.lib', 'Shlwapi.lib', 'kernel32.lib', 'user32.lib', 'gdi32.lib', 'winspool.lib', 'comdlg32.lib', 'advapi32.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib', 'uuid.lib', 'odbc32.lib', 'odbccp32.lib', ]) env.Append(LINKFLAGS=[ '/DEBUG', '/DYNAMICBASE', '/ERRORREPORT:NONE', #'/INCREMENTAL', '/MACHINE:X64', '/MANIFEST', #'''/MANIFESTUAC:"level='asInvoker' uiAccess='false'"''', '/nologo', '/NXCOMPAT', '/SUBSYSTEM:CONSOLE', '/TLBID:1', ]) if variant == 'debug': env.Append(CCFLAGS=[ '/GS', # Buffers security check: enable '/MTd', # Language: Multi-threaded Debug CRT '/Od', # Optimization: Disabled '/RTC1', # Run-time error checks: ]) env.Append(CPPDEFINES=[ '_CRTDBG_MAP_ALLOC' ]) else: env.Append(CCFLAGS=[ '/MT', # Language: Multi-threaded CRT '/Ox', # Optimization: Full ]) else: raise SCons.UserError('Unknown toolchain == "%s"' % toolchain) #------------------------------------------------------------------------------- # Configure the base construction environment root_dir = Dir('#').srcnode().get_abspath() # Path to this SConstruct file build_dir = os.path.join('build') base = Environment( toolpath=[os.path.join ('src', 'ripple', 'beast', 'site_scons', 'site_tools')], tools=['default', 'Protoc', 'VSProject'], ENV=os.environ, TARGET_ARCH='x86_64') import_environ(base) config_base(base) base.Append(CPPPATH=[ 'src', os.path.join('src', 'beast'), os.path.join('src', 'beast', 'include'), os.path.join('src', 'beast', 'extras'), os.path.join(build_dir, 'proto'), os.path.join('src','soci','src'), os.path.join('src','soci','include'), ]) base.Decider('MD5-timestamp') set_implicit_cache() # Configure the toolchains, variants, default toolchain, and default target variants = ['debug', 'coverage', 'release', 'profile'] all_toolchains = ['clang', 'gcc', 'msvc'] if Beast.system.osx: toolchains = ['clang'] default_toolchain = 'clang' else: toolchains = detect_toolchains(base) if not toolchains: raise ValueError('No toolchains detected!') if 'msvc' in toolchains: default_toolchain = 'msvc' elif 'gcc' in toolchains: if 'clang' in toolchains: cxx = os.environ.get('CXX', 'g++') default_toolchain = 'clang' if 'clang' in cxx else 'gcc' else: default_toolchain = 'gcc' elif 'clang' in toolchains: default_toolchain = 'clang' else: raise ValueError("Don't understand toolchains in " + str(toolchains)) default_tu_style = 'unity' default_variant = 'release' default_target = None for source in [ 'src/ripple/proto/ripple.proto', ]: base.Protoc([], source, PROTOCPROTOPATH=[os.path.dirname(source)], PROTOCOUTDIR=os.path.join(build_dir, 'proto'), PROTOCPYTHONOUTDIR=None) #------------------------------------------------------------------------------- class ObjectBuilder(object): def __init__(self, env, variant_dirs): self.env = env self.variant_dirs = variant_dirs self.objects = [] self.child_envs = [] def add_source_files(self, *filenames, **kwds): for filename in filenames: env = self.env if kwds: env = env.Clone() env.Prepend(**kwds) self.child_envs.append(env) o = env.Object(Beast.variantFile(filename, self.variant_dirs)) self.objects.append(o) def list_sources(base, suffixes): def _iter(base): for parent, dirs, files in os.walk(base): files = [f for f in files if not f[0] == '.'] dirs[:] = [d for d in dirs if not d[0] == '.'] for path in files: path = os.path.join(parent, path) r = os.path.splitext(path) if r[1] and r[1] in suffixes: yield os.path.normpath(path) return list(_iter(base)) def append_sources(result, *filenames, **kwds): result.append([filenames, kwds]) def get_soci_sources(style): result = [] cpp_path = [ 'src/soci/src/core', 'src/soci/include/private', 'src/sqlite', ] append_sources(result, 'src/ripple/unity/soci.cpp', CPPPATH=cpp_path) if style == 'unity': append_sources(result, 'src/ripple/unity/soci_ripple.cpp', CPPPATH=cpp_path) return result def use_shp(toolchain): ''' Return True if we want to use the --system-header-prefix command-line switch ''' if toolchain != 'clang': return False if use_shp.cache is None: try: ver = subprocess.check_output( ['clang', '--version'], stderr=subprocess.STDOUT).strip() use_shp.cache = 'version 3.4' not in ver and 'version 3.0' not in ver except: use_shp.cache = False return use_shp.cache use_shp.cache = None def get_common_sources(toolchain): result = [] if toolchain == 'msvc': warning_flags = {} else: warning_flags = {'CCFLAGS': ['-Wno-unused-function']} append_sources( result, 'src/ripple/unity/secp256k1.cpp', CPPPATH=['src/secp256k1'], **warning_flags) return result def get_classic_sources(toolchain): result = [] append_sources( result, *list_sources('src/ripple/core', '.cpp'), CPPPATH=[ 'src/soci/src/core', 'src/sqlite'] ) append_sources(result, *list_sources('src/ripple/beast/clock', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/container', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/insight', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/net', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/nudb', '.cpp')) append_sources(result, *list_sources('src/ripple/beast/utility', '.cpp')) append_sources(result, *list_sources('src/ripple/app', '.cpp')) append_sources(result, *list_sources('src/ripple/basics', '.cpp')) append_sources(result, *list_sources('src/ripple/crypto', '.cpp')) append_sources(result, *list_sources('src/ripple/json', '.cpp')) append_sources(result, *list_sources('src/ripple/ledger', '.cpp')) append_sources(result, *list_sources('src/ripple/legacy', '.cpp')) append_sources(result, *list_sources('src/ripple/net', '.cpp')) append_sources(result, *list_sources('src/ripple/overlay', '.cpp')) append_sources(result, *list_sources('src/ripple/peerfinder', '.cpp')) append_sources(result, *list_sources('src/ripple/protocol', '.cpp')) append_sources(result, *list_sources('src/ripple/rpc', '.cpp')) append_sources(result, *list_sources('src/ripple/shamap', '.cpp')) append_sources(result, *list_sources('src/ripple/server', '.cpp')) append_sources(result, *list_sources('src/ripple/test', '.cpp')) if use_shp(toolchain): cc_flags = {'CCFLAGS': ['--system-header-prefix=rocksdb2']} else: cc_flags = {} append_sources( result, *list_sources('src/ripple/nodestore', '.cpp'), CPPPATH=[ 'src/rocksdb2/include', 'src/snappy/snappy', 'src/snappy/config', ], **cc_flags) result += get_soci_sources('classic') result += get_common_sources(toolchain) return result def get_unity_sources(toolchain): result = [] append_sources( result, 'src/ripple/beast/unity/beast_clock_unity.cpp', 'src/ripple/beast/unity/beast_container_unity.cpp', 'src/ripple/beast/unity/beast_insight_unity.cpp', 'src/ripple/beast/unity/beast_net_unity.cpp', 'src/ripple/beast/unity/beast_nudb_unity.cpp', 'src/ripple/beast/unity/beast_utility_unity.cpp', 'src/ripple/unity/app_ledger.cpp', 'src/ripple/unity/app_main.cpp', 'src/ripple/unity/app_misc.cpp', 'src/ripple/unity/app_paths.cpp', 'src/ripple/unity/app_tests.cpp', 'src/ripple/unity/app_tx.cpp', 'src/ripple/unity/core.cpp', 'src/ripple/unity/basics.cpp', 'src/ripple/unity/crypto.cpp', 'src/ripple/unity/ledger.cpp', 'src/ripple/unity/net.cpp', 'src/ripple/unity/overlay.cpp', 'src/ripple/unity/peerfinder.cpp', 'src/ripple/unity/json.cpp', 'src/ripple/unity/protocol.cpp', 'src/ripple/unity/rpcx.cpp', 'src/ripple/unity/shamap.cpp', 'src/ripple/unity/server.cpp', 'src/ripple/unity/test.cpp', ) if use_shp(toolchain): cc_flags = {'CCFLAGS': ['--system-header-prefix=rocksdb2']} else: cc_flags = {} append_sources( result, 'src/ripple/unity/nodestore.cpp', CPPPATH=[ 'src/rocksdb2/include', 'src/snappy/snappy', 'src/snappy/config', ], **cc_flags) result += get_soci_sources('unity') result += get_common_sources(toolchain) return result # Declare the targets aliases = collections.defaultdict(list) msvc_configs = [] def should_prepare_target(cl_target, style, toolchain, variant): if not cl_target: # default target return (style == default_tu_style and toolchain == default_toolchain and variant == default_variant) if 'vcxproj' in cl_target: return toolchain == 'msvc' s = cl_target.split('.') if style == 'unity' and 'nounity' in s: return False if len(s) == 1: return ('all' in cl_target or variant in cl_target or toolchain in cl_target) if len(s) == 2 or len(s) == 3: return s[0] == toolchain and s[1] == variant return True # A target we don't know about, better prepare to build it def should_prepare_targets(style, toolchain, variant): if not COMMAND_LINE_TARGETS: return should_prepare_target(None, style, toolchain, variant) for t in COMMAND_LINE_TARGETS: if should_prepare_target(t, style, toolchain, variant): return True def should_link_static(): """ Return True if libraries should be linked statically """ return GetOption('static') def should_build_ninja(style, toolchain, variant): """ Return True if a ninja build file should be generated. Typically, scons will be called as follows to generate a ninja build file: `scons ninja=1 gcc.debug` where `gcc.debug` may be replaced with any of our non-visual studio targets. Raise an exception if we cannot generate the requested ninja build file (for example, if multiple targets are requested). """ if not GetOption('ninja'): return False if len(COMMAND_LINE_TARGETS) != 1: raise Exception('Can only generate a ninja file for a single target') cl_target = COMMAND_LINE_TARGETS[0] if 'vcxproj' in cl_target: raise Exception('Cannot generate a ninja file for a vcxproj') s = cl_target.split('.') if ( style == 'unity' and 'nounity' in s or style == 'classic' and 'nounity' not in s or len(s) == 1 ): return False if len(s) == 2 or len(s) == 3: return s[0] == toolchain and s[1] == variant return False for tu_style in ['classic', 'unity']: for toolchain in all_toolchains: for variant in variants: if not should_prepare_targets(tu_style, toolchain, variant): continue if variant in ['profile', 'coverage'] and toolchain == 'msvc': continue # Configure this variant's construction environment env = base.Clone() config_env(toolchain, variant, env) variant_name = '%s.%s' % (toolchain, variant) if tu_style == 'classic': variant_name += '.nounity' variant_dir = os.path.join(build_dir, variant_name) variant_dirs = { os.path.join(variant_dir, 'src') : 'src', os.path.join(variant_dir, 'proto') : os.path.join (build_dir, 'proto'), } for dest, source in variant_dirs.iteritems(): env.VariantDir(dest, source, duplicate=0) object_builder = ObjectBuilder(env, variant_dirs) if tu_style == 'classic': sources = get_classic_sources(toolchain) else: sources = get_unity_sources(toolchain) for s, k in sources: object_builder.add_source_files(*s, **k) git_commit_tag = {} if toolchain != 'msvc': git = Beast.Git(env) if git.exists: id = '%s+%s.%s' % (git.tags, git.user, git.branch) git_commit_tag = {'CPPDEFINES': {'GIT_COMMIT_ID' : '\'"%s"\'' % id }} object_builder.add_source_files( 'src/ripple/unity/git_id.cpp', **git_commit_tag) if use_shp(toolchain): cc_flags = {'CCFLAGS': ['--system-header-prefix=rocksdb2']} else: cc_flags = {} object_builder.add_source_files( 'src/ripple/beast/unity/beast_hash_unity.cpp', 'src/ripple/unity/beast.cpp', 'src/ripple/unity/lz4.c', 'src/ripple/unity/protobuf.cpp', 'src/ripple/unity/ripple.proto.cpp', 'src/ripple/unity/resource.cpp', 'src/ripple/unity/websocket02.cpp', **cc_flags ) object_builder.add_source_files( 'src/sqlite/sqlite_unity.c', CCFLAGS = ([] if toolchain == 'msvc' else ['-Wno-array-bounds'])) if 'gcc' in toolchain: cc_flags = {'CCFLAGS': ['-Wno-maybe-uninitialized']} elif use_shp(toolchain): cc_flags = {'CCFLAGS': ['--system-header-prefix=rocksdb2']} else: cc_flags = {} object_builder.add_source_files( 'src/ripple/unity/ed25519.c', CPPPATH=[ 'src/ed25519-donna', ] ) object_builder.add_source_files( 'src/ripple/unity/rocksdb.cpp', CPPPATH=[ 'src/rocksdb2', 'src/rocksdb2/include', 'src/snappy/snappy', 'src/snappy/config', ], **cc_flags ) object_builder.add_source_files( 'src/ripple/unity/snappy.cpp', CCFLAGS=([] if toolchain == 'msvc' else ['-Wno-unused-function']), CPPPATH=[ 'src/snappy/snappy', 'src/snappy/config', ] ) if toolchain == "clang" and Beast.system.osx: object_builder.add_source_files('src/ripple/unity/beastobjc.mm') target = env.Program( target=os.path.join(variant_dir, 'rippled'), source=object_builder.objects ) if tu_style == default_tu_style: if toolchain == default_toolchain and ( variant == default_variant): default_target = target install_target = env.Install (build_dir, source=default_target) env.Alias ('install', install_target) env.Default (install_target) aliases['all'].extend(install_target) if toolchain == 'msvc': config = env.VSProjectConfig(variant, 'x64', target, env) msvc_configs.append(config) if toolchain in toolchains: aliases['all'].extend(target) aliases[toolchain].extend(target) elif toolchain == 'msvc': config = env.VSProjectConfig(variant + ".classic", 'x64', target, env) msvc_configs.append(config) if toolchain in toolchains: aliases[variant].extend(target) env.Alias(variant_name, target) # ninja support if should_build_ninja(tu_style, toolchain, variant): print('Generating ninja: {}:{}:{}'.format(tu_style, toolchain, variant)) scons_to_ninja.GenerateNinjaFile( [object_builder.env] + object_builder.child_envs, dest_file='build.ninja') for key, value in aliases.iteritems(): env.Alias(key, value) vcxproj = base.VSProject( os.path.join('Builds', 'VisualStudio2015', 'RippleD'), source = [], VSPROJECT_ROOT_DIRS = ['src/beast', 'src/beast/include', 'src/beast/extras', 'src', '.'], VSPROJECT_CONFIGS = msvc_configs) base.Alias('vcxproj', vcxproj) #------------------------------------------------------------------------------- # Adds a phony target to the environment that always builds # See: http://www.scons.org/wiki/PhonyTargets def PhonyTargets(env = None, **kw): if not env: env = DefaultEnvironment() for target, action in kw.items(): env.AlwaysBuild(env.Alias(target, [], action)) # Build the list of rippled source files that hold unit tests def do_count(target, source, env): def list_testfiles(base, suffixes): def _iter(base): for parent, _, files in os.walk(base): for path in files: path = os.path.join(parent, path) r = os.path.splitext(path) if r[1] in suffixes: if r[0].endswith('.test'): yield os.path.normpath(path) return list(_iter(base)) testfiles = list_testfiles(os.path.join('src', 'ripple'), env.get('CPPSUFFIXES')) lines = 0 for f in testfiles: lines = lines + sum(1 for line in open(f)) print "Total unit test lines: %d" % lines PhonyTargets(env, count = do_count)