Selective build clobbering feature (landmines.py and android build scripts).

Adds the ability for devs/troopers/etc. to set 'landmines' in the tree so that
the build will selectively clobber when a builder moves over a revision with such
a change.

This cl has an basis landmines.py, and hooks the clobber mechanism to the android
build scripts.

The relevant cl which implements this for
compile.py is here: https://chromiumcodereview.appspot.com/11234013/

I'm planning to also implement an informational invocation for gclient to let devs know
about any potential landmines so they can decide if they need to clobber.

This previously attempted to land as: https://chromiumcodereview.appspot.com/11175016

R=ilevy@chromium.org,maruel@chromium.org
BUG=121897


Review URL: https://chromiumcodereview.appspot.com/11377141

git-svn-id: http://src.chromium.org/svn/trunk/src/build@167595 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
iannucci@chromium.org 2012-11-14 04:59:48 +00:00
Родитель 64562a877b
Коммит 693c711ddb
4 изменённых файлов: 293 добавлений и 50 удалений

Просмотреть файл

@ -47,18 +47,6 @@ function bb_baseline_setup {
shift
cd $SRC_ROOT
if [[ $BUILDBOT_CLOBBER ]]; then
echo "@@@BUILD_STEP Clobber@@@"
# Sdk key expires, delete android folder.
# crbug.com/145860
rm -rf ~/.android
rm -rf "${SRC_ROOT}"/out
if [ -e "${SRC_ROOT}"/out ] ; then
echo "Clobber appeared to fail? ${SRC_ROOT}/out still exists."
echo "@@@STEP_WARNINGS@@@"
fi
fi
echo "@@@BUILD_STEP Environment setup@@@"
bb_parse_args "$@"
@ -68,17 +56,44 @@ function bb_baseline_setup {
fi
export GOMA_DIR=/b/build/goma
. build/android/envsetup.sh
adb kill-server
adb start-server
}
function bb_compile_setup {
local extra_gyp_defines="$(bb_get_json_prop "$FACTORY_PROPERTIES" \
extra_gyp_defines)"
export GYP_DEFINES+=" fastbuild=1 $extra_gyp_defines"
if echo $extra_gyp_defines | grep -q clang; then
unset CXX_target
fi
adb kill-server
adb start-server
local build_path="${SRC_ROOT}/out/${BUILDTYPE}"
local landmines_triggered_path="$build_path/.landmines_triggered"
python "$SRC_ROOT/build/landmines.py"
if [[ $BUILDBOT_CLOBBER || -f "$landmines_triggered_path" ]]; then
echo "@@@BUILD_STEP Clobber@@@"
if [[ -z $BUILDBOT_CLOBBER ]]; then
echo "Clobbering due to triggered landmines: "
cat "$landmines_triggered_path"
else
# Also remove all the files under out/ on an explicit clobber
find "${SRC_ROOT}/out" -maxdepth 1 -type f -exec rm -f {} +
fi
# Sdk key expires, delete android folder.
# crbug.com/145860
rm -rf ~/.android
rm -rf "$build_path"
if [[ -e $build_path ]] ; then
echo "Clobber appeared to fail? $build_path still exists."
echo "@@@STEP_WARNINGS@@@"
fi
fi
}
function bb_compile_setup {
bb_setup_goma_internal
# Should be called only after envsetup is done.
gclient runhooks

Просмотреть файл

@ -8,6 +8,7 @@
# is invoked by Chromium beyond what can be done in the gclient hooks.
import glob
import gyp_helper
import os
import shlex
import subprocess
@ -44,36 +45,6 @@ if sys.platform == 'win32':
else:
psyco = None
def apply_gyp_environment(file_path=None):
"""
Reads in a *.gyp_env file and applies the valid keys to os.environ.
"""
if not file_path or not os.path.exists(file_path):
return
file_contents = open(file_path).read()
try:
file_data = eval(file_contents, {'__builtins__': None}, None)
except SyntaxError, e:
e.filename = os.path.abspath(file_path)
raise
supported_vars = ( 'CC',
'CHROMIUM_GYP_FILE',
'CHROMIUM_GYP_SYNTAX_CHECK',
'CXX',
'GYP_DEFINES',
'GYP_GENERATOR_FLAGS',
'GYP_GENERATOR_OUTPUT',
'GYP_GENERATORS', )
for var in supported_vars:
val = file_data.get(var)
if val:
if var in os.environ:
print 'INFO: Environment value for "%s" overrides value in %s.' % (
var, os.path.abspath(file_path)
)
else:
os.environ[var] = val
def additional_include_files(args=[]):
"""
Returns a list of additional (.gypi) files to include, without
@ -124,10 +95,7 @@ if __name__ == '__main__':
p.communicate()
sys.exit(p.returncode)
if 'SKIP_CHROMIUM_GYP_ENV' not in os.environ:
# Update the environment based on chromium.gyp_env
gyp_env_path = os.path.join(os.path.dirname(chrome_src), 'chromium.gyp_env')
apply_gyp_environment(gyp_env_path)
gyp_helper.apply_chromium_gyp_env()
# This could give false positives since it doesn't actually do real option
# parsing. Oh well.

50
gyp_helper.py Normal file
Просмотреть файл

@ -0,0 +1,50 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This file helps gyp_chromium and landmines correctly set up the gyp
# environment from chromium.gyp_env on disk
import os
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
CHROME_SRC = os.path.dirname(SCRIPT_DIR)
def apply_gyp_environment_from_file(file_path):
"""Reads in a *.gyp_env file and applies the valid keys to os.environ."""
if not os.path.exists(file_path):
return
with open(file_path) as f:
file_contents = f.read()
try:
file_data = eval(file_contents, {'__builtins__': None}, None)
except SyntaxError, e:
e.filename = os.path.abspath(file_path)
raise
supported_vars = (
'CC',
'CHROMIUM_GYP_FILE',
'CHROMIUM_GYP_SYNTAX_CHECK',
'CXX',
'GYP_DEFINES',
'GYP_GENERATOR_FLAGS',
'GYP_GENERATOR_OUTPUT',
'GYP_GENERATORS',
)
for var in supported_vars:
file_val = file_data.get(var)
if file_val:
if var in os.environ:
print 'INFO: Environment value for "%s" overrides value in %s.' % (
var, os.path.abspath(file_path)
)
else:
os.environ[var] = file_val
def apply_chromium_gyp_env():
if 'SKIP_CHROMIUM_GYP_ENV' not in os.environ:
# Update the environment based on chromium.gyp_env
path = os.path.join(os.path.dirname(CHROME_SRC), 'chromium.gyp_env')
apply_gyp_environment_from_file(path)

210
landmines.py Executable file
Просмотреть файл

@ -0,0 +1,210 @@
#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
This file holds a list of reasons why a particular build needs to be clobbered
(or a list of 'landmines').
This script runs every build as a hook. If it detects that the build should
be clobbered, it will touch the file <build_dir>/.landmine_triggered. The
various build scripts will then check for the presence of this file and clobber
accordingly. The script will also emit the reasons for the clobber to stdout.
A landmine is tripped when a builder checks out a different revision, and the
diff between the new landmines and the old ones is non-null. At this point, the
build is clobbered.
"""
import difflib
import functools
import gyp_helper
import os
import shlex
import sys
import time
SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
def memoize(default=None):
"""This decorator caches the return value of a parameterless pure function"""
def memoizer(func):
val = []
@functools.wraps(func)
def inner():
if not val:
ret = func()
val.append(ret if ret is not None else default)
print '%s -> %r' % (func.__name__, val[0])
return val[0]
return inner
return memoizer
@memoize()
def IsWindows():
return sys.platform.startswith('win') or sys.platform == 'cygwin'
@memoize()
def IsLinux():
return sys.platform.startswith('linux')
@memoize()
def IsMac():
return sys.platform.startswith('darwin')
@memoize()
def gyp_defines():
"""Parses and returns GYP_DEFINES env var as a dictionary."""
return dict(arg.split('=', 1)
for arg in shlex.split(os.environ.get('GYP_DEFINES', '')))
@memoize()
def distributor():
"""
Returns a string which is the distributed build engine in use (if any).
Possible values: 'goma', 'ib', ''
"""
if 'goma' in gyp_defines():
return 'goma'
elif IsWindows():
if 'CHROME_HEADLESS' in os.environ:
return 'ib' # use (win and !goma and headless) as approximation of ib
@memoize()
def platform():
"""
Returns a string representing the platform this build is targetted for.
Possible values: 'win', 'mac', 'linux', 'ios', 'android'
"""
if 'OS' in gyp_defines():
if 'android' in gyp_defines()['OS']:
return 'android'
else:
return gyp_defines()['OS']
elif IsWindows():
return 'win'
elif IsLinux():
return 'linux'
else:
return 'mac'
@memoize()
def builder():
"""
Returns a string representing the build engine (not compiler) to use.
Possible values: 'make', 'ninja', 'xcode', 'msvs', 'scons'
"""
if 'GYP_GENERATORS' in os.environ:
# for simplicity, only support the first explicit generator
generator = os.environ['GYP_GENERATORS'].split(',')[0]
if generator.endswith('-android'):
return generator.split('-')[0]
else:
return generator
else:
if platform() == 'android':
# Good enough for now? Do any android bots use make?
return 'ninja'
elif platform() == 'ios':
return 'xcode'
elif IsWindows():
return 'msvs'
elif IsLinux():
return 'make'
elif IsMac():
return 'xcode'
else:
assert False, 'Don\'t know what builder we\'re using!'
def get_landmines(target):
"""
ALL LANDMINES ARE DEFINED HERE.
target is 'Release' or 'Debug'
"""
landmines = []
add = lambda item: landmines.append(item + '\n')
if (distributor() == 'goma' and platform() == 'win32' and
builder() == 'ninja'):
add('Need to clobber winja goma due to backend cwd cache fix.')
return landmines
def get_target_build_dir(build_tool, target, is_iphone=False):
"""
Returns output directory absolute path dependent on build and targets.
Examples:
r'c:\b\build\slave\win\build\src\out\Release'
'/mnt/data/b/build/slave/linux/build/src/out/Debug'
'/b/build/slave/ios_rel_device/build/src/xcodebuild/Release-iphoneos'
Keep this function in sync with tools/build/scripts/slave/compile.py
"""
ret = None
if build_tool == 'xcode':
ret = os.path.join(SRC_DIR, 'xcodebuild',
target + ('-iphoneos' if is_iphone else ''))
elif build_tool == 'make':
ret = os.path.join(SRC_DIR, 'out', target)
elif build_tool == 'ninja':
ret = os.path.join(SRC_DIR, 'out', target)
elif build_tool == 'msvs':
ret = os.path.join(SRC_DIR, 'build', target)
elif build_tool == 'scons':
ret = os.path.join(SRC_DIR, 'sconsbuild', target)
else:
raise NotImplementedError()
return os.path.abspath(ret)
def main(argv):
if len(argv) > 1:
print('Unknown arguments %s' % argv[1:])
return 1
gyp_helper.apply_chromium_gyp_env()
for target in ('Debug', 'Release'):
out_dir = get_target_build_dir(builder(), target,
platform() == 'ios')
landmines_path = os.path.join(out_dir, '.landmines')
if not os.path.exists(out_dir):
os.makedirs(out_dir)
new_landmines = get_landmines(target)
if not os.path.exists(landmines_path):
with open(landmines_path, 'w') as f:
f.writelines(new_landmines)
else:
triggered = os.path.join(out_dir, '.landmines_triggered')
with open(landmines_path, 'r') as f:
old_landmines = f.readlines()
if old_landmines != new_landmines:
old_date = time.ctime(os.stat(landmines_path).st_ctime)
diff = difflib.unified_diff(old_landmines, new_landmines,
fromfile='old_landmines', tofile='new_landmines',
fromfiledate=old_date, tofiledate=time.ctime(), n=0)
with open(triggered, 'w') as f:
f.writelines(diff)
elif os.path.exists(triggered):
# Remove false triggered landmines.
os.remove(triggered)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))