From f1884ccb8bae49ee915b5aa065058e5cf9dacb1a Mon Sep 17 00:00:00 2001 From: Andrew Morrow Date: Sat, 15 Jul 2017 18:23:12 -0400 Subject: SERVER-30278 SCons icecream integration --- site_scons/site_tools/icecream.py | 161 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 site_scons/site_tools/icecream.py (limited to 'site_scons') diff --git a/site_scons/site_tools/icecream.py b/site_scons/site_tools/icecream.py new file mode 100644 index 00000000000..9838b633490 --- /dev/null +++ b/site_scons/site_tools/icecream.py @@ -0,0 +1,161 @@ +# Copyright 2017 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import SCons + +import os +import re +import subprocess + +from pkg_resources import parse_version + +icecream_version_min = '1.1rc2' + +def generate(env): + + if not exists(env): + return + + # Absoluteify, so we can derive ICERUN + env['ICECC'] = env.WhereIs('$ICECC') + + if not 'ICERUN' in env: + env['ICERUN'] = env.File('$ICECC').File('icerun') + + # Absoluteify, for parity with ICECC + env['ICERUN'] = env.WhereIs('$ICERUN') + + # We can't handle sanitizer blacklist files, so disable icecc then, and just flow through + # icerun to prevent slamming the local system with a huge -j value. + if any(f.startswith("-fsanitize-blacklist=") for fs in ['CCFLAGS', 'CFLAGS', 'CXXFLAGS'] for f in env[fs]): + env['ICECC'] = '$ICERUN' + + # Make CC and CXX absolute paths too. It is better for icecc. + env['CC'] = env.WhereIs('$CC') + env['CXX'] = env.WhereIs('$CXX') + + # Make a predictable name for the toolchain + icecc_version_target_filename=env.subst('$CC$CXX').replace('/', '_') + icecc_version = env.Dir('$BUILD_ROOT/scons/icecc').File(icecc_version_target_filename) + + # Make an isolated environment so that our setting of ICECC_VERSION in the environment + # doesn't appear when executing icecc_create_env + toolchain_env = env.Clone() + if toolchain_env.ToolchainIs('clang'): + toolchain = env.Command( + target=icecc_version, + source=['$ICECC_CREATE_ENV', '$CC', '$CXX'], + action=[ + "${SOURCES[0]} --clang ${SOURCES[1].abspath} /bin/true $TARGET", + ], + ) + env['ENV']['ICECC_CLANG_REMOTE_CPP'] = 1 + else: + toolchain = toolchain_env.Command( + target=icecc_version, + source=['$ICECC_CREATE_ENV', '$CC', '$CXX'], + action=[ + "${SOURCES[0]} --gcc ${SOURCES[1].abspath} ${SOURCES[2].abspath} $TARGET", + ] + ) + env.AppendUnique(CCFLAGS=['-fdirectives-only']) + + # Add ICECC_VERSION to the environment, pointed at the generated + # file so that we can expand it in the realpath expressions for + # CXXCOM and friends below. + env['ICECC_VERSION'] = icecc_version + + if 'ICECC_SCHEDULER' in env: + env['ENV']['USE_SCHEDULER'] = env['ICECC_SCHEDULER'] + + # Create an emitter that makes all of the targets depend on the + # icecc_version_target (ensuring that we have read the link), which in turn + # depends on the toolchain (ensuring that we have packaged it). + def icecc_toolchain_dependency_emitter(target, source, env): + env.Requires(target, toolchain) + return target, source + + # Cribbed from Tool/cc.py and Tool/c++.py. It would be better if + # we could obtain this from SCons. + _CSuffixes = ['.c'] + if not SCons.Util.case_sensitive_suffixes('.c', '.C'): + _CSuffixes.append('.C') + + _CXXSuffixes = ['.cpp', '.cc', '.cxx', '.c++', '.C++'] + if SCons.Util.case_sensitive_suffixes('.c', '.C'): + _CXXSuffixes.append('.C') + + suffixes = _CSuffixes + _CXXSuffixes + for object_builder in SCons.Tool.createObjBuilders(env): + emitterdict = object_builder.builder.emitter + for suffix in emitterdict.iterkeys(): + if not suffix in suffixes: + continue + base = emitterdict[suffix] + emitterdict[suffix] = SCons.Builder.ListEmitter([ + base, + icecc_toolchain_dependency_emitter + ]) + + # Make compile jobs flow through icecc + env['CCCOM'] = '$( ICECC_VERSION=$$(realpath $ICECC_VERSION) $ICECC $) ' + env['CCCOM'] + env['CXXCOM'] = '$( ICECC_VERSION=$$(realpath $ICECC_VERSION) $ICECC $) ' + env['CXXCOM'] + env['SHCCCOM'] = '$( ICECC_VERSION=$$(realpath $ICECC_VERSION) $ICECC $) ' + env['SHCCCOM'] + env['SHCXXCOM'] = '$( ICECC_VERSION=$$(realpath $ICECC_VERSION) $ICECC $) ' + env['SHCXXCOM'] + + # Make link like jobs flow through icerun so we don't kill the + # local machine. + # + # TODO: Should we somehow flow SPAWN or other universal shell launch through + # ICERUN to avoid saturating the local machine, and build something like + # ninja pools? + env['ARCOM'] = '$( $ICERUN $) ' + env['ARCOM'] + env['LINKCOM'] = '$( $ICERUN $) ' + env['LINKCOM'] + env['SHLINKCOM'] = '$( $ICERUN $) ' + env['SHLINKCOM'] + + # Uncomment these to debug your icecc integration + # env['ENV']['ICECC_DEBUG'] = 'debug' + # env['ENV']['ICECC_LOGFILE'] = 'icecc.log' + +def exists(env): + + icecc = env.get('ICECC', False) + if not icecc: + return False + icecc = env.WhereIs(icecc) + + pipe = SCons.Action._subproc(env, SCons.Util.CLVar(icecc) + ['--version'], + stdin = 'devnull', + stderr = 'devnull', + stdout = subprocess.PIPE) + + if pipe.wait() != 0: + return False + + validated = False + for line in pipe.stdout: + if validated: + continue # consume all data + version_banner = re.search(r'^ICECC ', line) + if not version_banner: + continue + icecc_version = re.split('ICECC (.+)', line) + if len(icecc_version) < 2: + continue + icecc_version = parse_version(icecc_version[1]) + needed_version = parse_version(icecream_version_min) + if icecc_version >= needed_version: + validated = True + + return validated -- cgit v1.2.1