| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 #!/usr/bin/env python |  | 
| 2 # Copyright 2014 the V8 project authors. All rights reserved. |  | 
| 3 # Use of this source code is governed by a BSD-style license that can be |  | 
| 4 # found in the LICENSE file. |  | 
| 5 |  | 
| 6 """ |  | 
| 7 This script runs every build as the first hook (See DEPS). If it detects that |  | 
| 8 the build should be clobbered, it will delete the contents of the build |  | 
| 9 directory. |  | 
| 10 |  | 
| 11 A landmine is tripped when a builder checks out a different revision, and the |  | 
| 12 diff between the new landmines and the old ones is non-null. At this point, the |  | 
| 13 build is clobbered. |  | 
| 14 """ |  | 
| 15 |  | 
| 16 import difflib |  | 
| 17 import errno |  | 
| 18 import gyp_environment |  | 
| 19 import logging |  | 
| 20 import optparse |  | 
| 21 import os |  | 
| 22 import re |  | 
| 23 import shutil |  | 
| 24 import sys |  | 
| 25 import subprocess |  | 
| 26 import time |  | 
| 27 |  | 
| 28 import landmine_utils |  | 
| 29 |  | 
| 30 |  | 
| 31 SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |  | 
| 32 |  | 
| 33 |  | 
| 34 def get_build_dir(build_tool, is_iphone=False): |  | 
| 35   """ |  | 
| 36   Returns output directory absolute path dependent on build and targets. |  | 
| 37   Examples: |  | 
| 38     r'c:\b\build\slave\win\build\src\out' |  | 
| 39     '/mnt/data/b/build/slave/linux/build/src/out' |  | 
| 40     '/b/build/slave/ios_rel_device/build/src/xcodebuild' |  | 
| 41 |  | 
| 42   Keep this function in sync with tools/build/scripts/slave/compile.py |  | 
| 43   """ |  | 
| 44   ret = None |  | 
| 45   if build_tool == 'xcode': |  | 
| 46     ret = os.path.join(SRC_DIR, 'xcodebuild') |  | 
| 47   elif build_tool in ['make', 'ninja', 'ninja-ios']:  # TODO: Remove ninja-ios. |  | 
| 48     if 'CHROMIUM_OUT_DIR' in os.environ: |  | 
| 49       output_dir = os.environ.get('CHROMIUM_OUT_DIR').strip() |  | 
| 50       if not output_dir: |  | 
| 51         raise Error('CHROMIUM_OUT_DIR environment variable is set but blank!') |  | 
| 52     else: |  | 
| 53       output_dir = landmine_utils.gyp_generator_flags().get('output_dir', 'out') |  | 
| 54     ret = os.path.join(SRC_DIR, output_dir) |  | 
| 55   elif build_tool in ['msvs', 'vs', 'ib']: |  | 
| 56     ret = os.path.join(SRC_DIR, 'build') |  | 
| 57   else: |  | 
| 58     raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool) |  | 
| 59   return os.path.abspath(ret) |  | 
| 60 |  | 
| 61 |  | 
| 62 def extract_gn_build_commands(build_ninja_file): |  | 
| 63   """Extracts from a build.ninja the commands to run GN. |  | 
| 64 |  | 
| 65   The commands to run GN are the gn rule and build.ninja build step at the |  | 
| 66   top of the build.ninja file. We want to keep these when deleting GN builds |  | 
| 67   since we want to preserve the command-line flags to GN. |  | 
| 68 |  | 
| 69   On error, returns the empty string.""" |  | 
| 70   result = "" |  | 
| 71   with open(build_ninja_file, 'r') as f: |  | 
| 72     # Read until the second blank line. The first thing GN writes to the file |  | 
| 73     # is the "rule gn" and the second is the section for "build build.ninja", |  | 
| 74     # separated by blank lines. |  | 
| 75     num_blank_lines = 0 |  | 
| 76     while num_blank_lines < 2: |  | 
| 77       line = f.readline() |  | 
| 78       if len(line) == 0: |  | 
| 79         return ''  # Unexpected EOF. |  | 
| 80       result += line |  | 
| 81       if line[0] == '\n': |  | 
| 82         num_blank_lines = num_blank_lines + 1 |  | 
| 83   return result |  | 
| 84 |  | 
| 85 def delete_build_dir(build_dir): |  | 
| 86   # GN writes a build.ninja.d file. Note that not all GN builds have args.gn. |  | 
| 87   build_ninja_d_file = os.path.join(build_dir, 'build.ninja.d') |  | 
| 88   if not os.path.exists(build_ninja_d_file): |  | 
| 89     shutil.rmtree(build_dir) |  | 
| 90     return |  | 
| 91 |  | 
| 92   # GN builds aren't automatically regenerated when you sync. To avoid |  | 
| 93   # messing with the GN workflow, erase everything but the args file, and |  | 
| 94   # write a dummy build.ninja file that will automatically rerun GN the next |  | 
| 95   # time Ninja is run. |  | 
| 96   build_ninja_file = os.path.join(build_dir, 'build.ninja') |  | 
| 97   build_commands = extract_gn_build_commands(build_ninja_file) |  | 
| 98 |  | 
| 99   try: |  | 
| 100     gn_args_file = os.path.join(build_dir, 'args.gn') |  | 
| 101     with open(gn_args_file, 'r') as f: |  | 
| 102       args_contents = f.read() |  | 
| 103   except IOError: |  | 
| 104     args_contents = '' |  | 
| 105 |  | 
| 106   shutil.rmtree(build_dir) |  | 
| 107 |  | 
| 108   # Put back the args file (if any). |  | 
| 109   os.mkdir(build_dir) |  | 
| 110   if args_contents != '': |  | 
| 111     with open(gn_args_file, 'w') as f: |  | 
| 112       f.write(args_contents) |  | 
| 113 |  | 
| 114   # Write the build.ninja file sufficiently to regenerate itself. |  | 
| 115   with open(os.path.join(build_dir, 'build.ninja'), 'w') as f: |  | 
| 116     if build_commands != '': |  | 
| 117       f.write(build_commands) |  | 
| 118     else: |  | 
| 119       # Couldn't parse the build.ninja file, write a default thing. |  | 
| 120       f.write('''rule gn |  | 
| 121 command = gn -q gen //out/%s/ |  | 
| 122 description = Regenerating ninja files |  | 
| 123 |  | 
| 124 build build.ninja: gn |  | 
| 125 generator = 1 |  | 
| 126 depfile = build.ninja.d |  | 
| 127 ''' % (os.path.split(build_dir)[1])) |  | 
| 128 |  | 
| 129   # Write a .d file for the build which references a nonexistant file. This |  | 
| 130   # will make Ninja always mark the build as dirty. |  | 
| 131   with open(build_ninja_d_file, 'w') as f: |  | 
| 132     f.write('build.ninja: nonexistant_file.gn\n') |  | 
| 133 |  | 
| 134 |  | 
| 135 def needs_clobber(landmines_path, new_landmines): |  | 
| 136   if os.path.exists(landmines_path): |  | 
| 137     with open(landmines_path, 'r') as f: |  | 
| 138       old_landmines = f.readlines() |  | 
| 139     if old_landmines != new_landmines: |  | 
| 140       old_date = time.ctime(os.stat(landmines_path).st_ctime) |  | 
| 141       diff = difflib.unified_diff(old_landmines, new_landmines, |  | 
| 142           fromfile='old_landmines', tofile='new_landmines', |  | 
| 143           fromfiledate=old_date, tofiledate=time.ctime(), n=0) |  | 
| 144       sys.stdout.write('Clobbering due to:\n') |  | 
| 145       sys.stdout.writelines(diff) |  | 
| 146       return True |  | 
| 147   else: |  | 
| 148     sys.stdout.write('Clobbering due to missing landmines file.\n') |  | 
| 149     return True |  | 
| 150   return False |  | 
| 151 |  | 
| 152 |  | 
| 153 def clobber_if_necessary(new_landmines): |  | 
| 154   """Does the work of setting, planting, and triggering landmines.""" |  | 
| 155   out_dir = get_build_dir(landmine_utils.builder()) |  | 
| 156   landmines_path = os.path.normpath(os.path.join(out_dir, '..', '.landmines')) |  | 
| 157   try: |  | 
| 158     os.makedirs(out_dir) |  | 
| 159   except OSError as e: |  | 
| 160     if e.errno == errno.EEXIST: |  | 
| 161       pass |  | 
| 162 |  | 
| 163   if needs_clobber(landmines_path, new_landmines): |  | 
| 164     # Clobber contents of build directory but not directory itself: some |  | 
| 165     # checkouts have the build directory mounted. |  | 
| 166     for f in os.listdir(out_dir): |  | 
| 167       path = os.path.join(out_dir, f) |  | 
| 168       if os.path.basename(out_dir) == 'build': |  | 
| 169         # Only delete build directories and files for MSVS builds as the folder |  | 
| 170         # shares some checked out files and directories. |  | 
| 171         if (os.path.isdir(path) and |  | 
| 172             re.search(r'(?:[Rr]elease)|(?:[Dd]ebug)', f)): |  | 
| 173           delete_build_dir(path) |  | 
| 174         elif (os.path.isfile(path) and |  | 
| 175               (path.endswith('.sln') or |  | 
| 176                path.endswith('.vcxproj') or |  | 
| 177                path.endswith('.vcxproj.user'))): |  | 
| 178           os.unlink(path) |  | 
| 179       else: |  | 
| 180         if os.path.isfile(path): |  | 
| 181           os.unlink(path) |  | 
| 182         elif os.path.isdir(path): |  | 
| 183           delete_build_dir(path) |  | 
| 184     if os.path.basename(out_dir) == 'xcodebuild': |  | 
| 185       # Xcodebuild puts an additional project file structure into build, |  | 
| 186       # while the output folder is xcodebuild. |  | 
| 187       project_dir = os.path.join(SRC_DIR, 'build', 'all.xcodeproj') |  | 
| 188       if os.path.exists(project_dir) and os.path.isdir(project_dir): |  | 
| 189         delete_build_dir(project_dir) |  | 
| 190 |  | 
| 191   # Save current set of landmines for next time. |  | 
| 192   with open(landmines_path, 'w') as f: |  | 
| 193     f.writelines(new_landmines) |  | 
| 194 |  | 
| 195 |  | 
| 196 def process_options(): |  | 
| 197   """Returns a list of landmine emitting scripts.""" |  | 
| 198   parser = optparse.OptionParser() |  | 
| 199   parser.add_option( |  | 
| 200       '-s', '--landmine-scripts', action='append', |  | 
| 201       default=[os.path.join(SRC_DIR, 'build', 'get_landmines.py')], |  | 
| 202       help='Path to the script which emits landmines to stdout. The target ' |  | 
| 203            'is passed to this script via option -t. Note that an extra ' |  | 
| 204            'script can be specified via an env var EXTRA_LANDMINES_SCRIPT.') |  | 
| 205   parser.add_option('-v', '--verbose', action='store_true', |  | 
| 206       default=('LANDMINES_VERBOSE' in os.environ), |  | 
| 207       help=('Emit some extra debugging information (default off). This option ' |  | 
| 208           'is also enabled by the presence of a LANDMINES_VERBOSE environment ' |  | 
| 209           'variable.')) |  | 
| 210 |  | 
| 211   options, args = parser.parse_args() |  | 
| 212 |  | 
| 213   if args: |  | 
| 214     parser.error('Unknown arguments %s' % args) |  | 
| 215 |  | 
| 216   logging.basicConfig( |  | 
| 217       level=logging.DEBUG if options.verbose else logging.ERROR) |  | 
| 218 |  | 
| 219   extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT') |  | 
| 220   if extra_script: |  | 
| 221     return options.landmine_scripts + [extra_script] |  | 
| 222   else: |  | 
| 223     return options.landmine_scripts |  | 
| 224 |  | 
| 225 |  | 
| 226 def main(): |  | 
| 227   landmine_scripts = process_options() |  | 
| 228 |  | 
| 229   if landmine_utils.builder() in ('dump_dependency_json', 'eclipse'): |  | 
| 230     return 0 |  | 
| 231 |  | 
| 232   gyp_environment.set_environment() |  | 
| 233 |  | 
| 234   landmines = [] |  | 
| 235   for s in landmine_scripts: |  | 
| 236     proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE) |  | 
| 237     output, _ = proc.communicate() |  | 
| 238     landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()]) |  | 
| 239   clobber_if_necessary(landmines) |  | 
| 240 |  | 
| 241   return 0 |  | 
| 242 |  | 
| 243 |  | 
| 244 if __name__ == '__main__': |  | 
| 245   sys.exit(main()) |  | 
| OLD | NEW | 
|---|