diff options
Diffstat (limited to 'deps/v8/gypfiles/landmines.py')
-rwxr-xr-x | deps/v8/gypfiles/landmines.py | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/deps/v8/gypfiles/landmines.py b/deps/v8/gypfiles/landmines.py new file mode 100755 index 0000000000..2a81c66d1a --- /dev/null +++ b/deps/v8/gypfiles/landmines.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python +# Copyright 2014 the V8 project 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 script runs every build as the first hook (See DEPS). If it detects that +the build should be clobbered, it will delete the contents of the build +directory. + +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 errno +import gyp_environment +import logging +import optparse +import os +import re +import shutil +import sys +import subprocess +import time + +import landmine_utils + + +SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + +def get_build_dir(build_tool, is_iphone=False): + """ + Returns output directory absolute path dependent on build and targets. + Examples: + r'c:\b\build\slave\win\build\src\out' + '/mnt/data/b/build/slave/linux/build/src/out' + '/b/build/slave/ios_rel_device/build/src/xcodebuild' + + 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') + elif build_tool in ['make', 'ninja', 'ninja-ios']: # TODO: Remove ninja-ios. + if 'CHROMIUM_OUT_DIR' in os.environ: + output_dir = os.environ.get('CHROMIUM_OUT_DIR').strip() + if not output_dir: + raise Error('CHROMIUM_OUT_DIR environment variable is set but blank!') + else: + output_dir = landmine_utils.gyp_generator_flags().get('output_dir', 'out') + ret = os.path.join(SRC_DIR, output_dir) + elif build_tool in ['msvs', 'vs', 'ib']: + ret = os.path.join(SRC_DIR, 'build') + else: + raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool) + return os.path.abspath(ret) + + +def extract_gn_build_commands(build_ninja_file): + """Extracts from a build.ninja the commands to run GN. + + The commands to run GN are the gn rule and build.ninja build step at the + top of the build.ninja file. We want to keep these when deleting GN builds + since we want to preserve the command-line flags to GN. + + On error, returns the empty string.""" + result = "" + with open(build_ninja_file, 'r') as f: + # Read until the second blank line. The first thing GN writes to the file + # is the "rule gn" and the second is the section for "build build.ninja", + # separated by blank lines. + num_blank_lines = 0 + while num_blank_lines < 2: + line = f.readline() + if len(line) == 0: + return '' # Unexpected EOF. + result += line + if line[0] == '\n': + num_blank_lines = num_blank_lines + 1 + return result + +def delete_build_dir(build_dir): + # GN writes a build.ninja.d file. Note that not all GN builds have args.gn. + build_ninja_d_file = os.path.join(build_dir, 'build.ninja.d') + if not os.path.exists(build_ninja_d_file): + shutil.rmtree(build_dir) + return + + # GN builds aren't automatically regenerated when you sync. To avoid + # messing with the GN workflow, erase everything but the args file, and + # write a dummy build.ninja file that will automatically rerun GN the next + # time Ninja is run. + build_ninja_file = os.path.join(build_dir, 'build.ninja') + build_commands = extract_gn_build_commands(build_ninja_file) + + try: + gn_args_file = os.path.join(build_dir, 'args.gn') + with open(gn_args_file, 'r') as f: + args_contents = f.read() + except IOError: + args_contents = '' + + shutil.rmtree(build_dir) + + # Put back the args file (if any). + os.mkdir(build_dir) + if args_contents != '': + with open(gn_args_file, 'w') as f: + f.write(args_contents) + + # Write the build.ninja file sufficiently to regenerate itself. + with open(os.path.join(build_dir, 'build.ninja'), 'w') as f: + if build_commands != '': + f.write(build_commands) + else: + # Couldn't parse the build.ninja file, write a default thing. + f.write('''rule gn +command = gn -q gen //out/%s/ +description = Regenerating ninja files + +build build.ninja: gn +generator = 1 +depfile = build.ninja.d +''' % (os.path.split(build_dir)[1])) + + # Write a .d file for the build which references a nonexistant file. This + # will make Ninja always mark the build as dirty. + with open(build_ninja_d_file, 'w') as f: + f.write('build.ninja: nonexistant_file.gn\n') + + +def needs_clobber(landmines_path, new_landmines): + if os.path.exists(landmines_path): + 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) + sys.stdout.write('Clobbering due to:\n') + sys.stdout.writelines(diff) + return True + else: + sys.stdout.write('Clobbering due to missing landmines file.\n') + return True + return False + + +def clobber_if_necessary(new_landmines): + """Does the work of setting, planting, and triggering landmines.""" + out_dir = get_build_dir(landmine_utils.builder()) + landmines_path = os.path.normpath(os.path.join(out_dir, '..', '.landmines')) + try: + os.makedirs(out_dir) + except OSError as e: + if e.errno == errno.EEXIST: + pass + + if needs_clobber(landmines_path, new_landmines): + # Clobber contents of build directory but not directory itself: some + # checkouts have the build directory mounted. + for f in os.listdir(out_dir): + path = os.path.join(out_dir, f) + if os.path.basename(out_dir) == 'build': + # Only delete build directories and files for MSVS builds as the folder + # shares some checked out files and directories. + if (os.path.isdir(path) and + re.search(r'(?:[Rr]elease)|(?:[Dd]ebug)', f)): + delete_build_dir(path) + elif (os.path.isfile(path) and + (path.endswith('.sln') or + path.endswith('.vcxproj') or + path.endswith('.vcxproj.user'))): + os.unlink(path) + else: + if os.path.isfile(path): + os.unlink(path) + elif os.path.isdir(path): + delete_build_dir(path) + if os.path.basename(out_dir) == 'xcodebuild': + # Xcodebuild puts an additional project file structure into build, + # while the output folder is xcodebuild. + project_dir = os.path.join(SRC_DIR, 'build', 'all.xcodeproj') + if os.path.exists(project_dir) and os.path.isdir(project_dir): + delete_build_dir(project_dir) + + # Save current set of landmines for next time. + with open(landmines_path, 'w') as f: + f.writelines(new_landmines) + + +def process_options(): + """Returns a list of landmine emitting scripts.""" + parser = optparse.OptionParser() + parser.add_option( + '-s', '--landmine-scripts', action='append', + default=[os.path.join(SRC_DIR, 'gypfiles', 'get_landmines.py')], + help='Path to the script which emits landmines to stdout. The target ' + 'is passed to this script via option -t. Note that an extra ' + 'script can be specified via an env var EXTRA_LANDMINES_SCRIPT.') + parser.add_option('-v', '--verbose', action='store_true', + default=('LANDMINES_VERBOSE' in os.environ), + help=('Emit some extra debugging information (default off). This option ' + 'is also enabled by the presence of a LANDMINES_VERBOSE environment ' + 'variable.')) + + options, args = parser.parse_args() + + if args: + parser.error('Unknown arguments %s' % args) + + logging.basicConfig( + level=logging.DEBUG if options.verbose else logging.ERROR) + + extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT') + if extra_script: + return options.landmine_scripts + [extra_script] + else: + return options.landmine_scripts + + +def main(): + landmine_scripts = process_options() + + if landmine_utils.builder() in ('dump_dependency_json', 'eclipse'): + return 0 + + gyp_environment.set_environment() + + landmines = [] + for s in landmine_scripts: + proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE) + output, _ = proc.communicate() + landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()]) + clobber_if_necessary(landmines) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) |