0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-30 00:56:44 +01:00
mongodb/site_scons/site_tools/icecream.py
2019-11-19 17:19:15 +00:00

218 lines
8.1 KiB
Python

# 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')
if 'ICECC_VERSION' in env:
# TODO:
#
# If ICECC_VERSION is a file, we are done. If it is a file
# URL, resolve it to a filesystem path. If it is a remote UTL,
# then fetch it to somewhere under $BUILD_ROOT/scons/icecc
# with its "correct" name (i.e. the md5 hash), and symlink it
# to some other deterministic name to use as icecc_version.
pass
else:
# Make a predictable name for the toolchain
icecc_version_target_filename = env.subst('$CC$CXX').replace('/', '_')
icecc_version_dir = env.Dir('$BUILD_ROOT/scons/icecc')
icecc_version = icecc_version_dir.File(icecc_version_target_filename)
# There is a weird ordering problem that occurs when the ninja generator
# is enabled with icecream. Because the modules system runs configure
# checks after the environment is setup and configure checks ignore our
# --no-exec from the Ninja tool they try to create the icecc_env file.
# But since the Ninja tool has reached into the internals of SCons to
# disabled as much of it as possible SCons never creates this directory,
# causing the icecc_create_env call to fail. So we explicitly
# force creation of the directory now so it exists in all
# circumstances.
env.Execute(SCons.Defaults.Mkdir(icecc_version_dir))
# 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",
],
)
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",
],
)
# 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.keys():
if not suffix in suffixes:
continue
base = emitterdict[suffix]
emitterdict[suffix] = SCons.Builder.ListEmitter([
base,
icecc_toolchain_dependency_emitter
])
# 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 env.ToolchainIs('clang'):
env['ENV']['ICECC_CLANG_REMOTE_CPP'] = 1
else:
env.AppendUnique(
CCFLAGS=[
'-fdirectives-only'
]
)
if 'ICECC_SCHEDULER' in env:
env['ENV']['USE_SCHEDULER'] = env['ICECC_SCHEDULER']
# Not all platforms have the readlink utility, so create our own
# generator for that.
def icecc_version_gen(target, source, env, for_signature):
f = env.File('$ICECC_VERSION')
if not f.islink():
return f
return env.File(os.path.realpath(f.abspath))
env['ICECC_VERSION_GEN'] = icecc_version_gen
def icecc_version_arch_gen(target, source, env, for_signature):
if 'ICECC_VERSION_ARCH' in env:
return "${ICECC_VERSION_ARCH}:"
return str()
env['ICECC_VERSION_ARCH_GEN'] = icecc_version_arch_gen
# Make compile jobs flow through icecc
env['CCCOM'] = '$( ICECC_VERSION=${ICECC_VERSION_ARCH_GEN}${ICECC_VERSION_GEN} $ICECC $) ' + env['CCCOM']
env['CXXCOM'] = '$( ICECC_VERSION=${ICECC_VERSION_ARCH_GEN}${ICECC_VERSION_GEN} $ICECC $) ' + env['CXXCOM']
env['SHCCCOM'] = '$( ICECC_VERSION=${ICECC_VERSION_ARCH_GEN}${ICECC_VERSION_GEN} $ICECC $) ' + env['SHCCCOM']
env['SHCXXCOM'] = '$( ICECC_VERSION=${ICECC_VERSION_ARCH_GEN}${ICECC_VERSION_GEN} $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:
line = line.decode('utf-8')
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