blob: 2a81c66d1a65290b4fbcf519026599e93d980baf [file] [log] [blame]
Ben Murdochb8a8cc12014-11-26 15:28:44 +00001#!/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"""
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00007This script runs every build as the first hook (See DEPS). If it detects that
8the build should be clobbered, it will delete the contents of the build
9directory.
Ben Murdochb8a8cc12014-11-26 15:28:44 +000010
11A landmine is tripped when a builder checks out a different revision, and the
12diff between the new landmines and the old ones is non-null. At this point, the
13build is clobbered.
14"""
15
16import difflib
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000017import errno
18import gyp_environment
Ben Murdochb8a8cc12014-11-26 15:28:44 +000019import logging
20import optparse
21import os
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000022import re
23import shutil
Ben Murdochb8a8cc12014-11-26 15:28:44 +000024import sys
25import subprocess
26import time
27
28import landmine_utils
29
30
31SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
32
33
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000034def get_build_dir(build_tool, is_iphone=False):
Ben Murdochb8a8cc12014-11-26 15:28:44 +000035 """
36 Returns output directory absolute path dependent on build and targets.
37 Examples:
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000038 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'
Ben Murdochb8a8cc12014-11-26 15:28:44 +000041
42 Keep this function in sync with tools/build/scripts/slave/compile.py
43 """
44 ret = None
45 if build_tool == 'xcode':
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000046 ret = os.path.join(SRC_DIR, 'xcodebuild')
Ben Murdochb8a8cc12014-11-26 15:28:44 +000047 elif build_tool in ['make', 'ninja', 'ninja-ios']: # TODO: Remove ninja-ios.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000048 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)
Ben Murdochb8a8cc12014-11-26 15:28:44 +000055 elif build_tool in ['msvs', 'vs', 'ib']:
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000056 ret = os.path.join(SRC_DIR, 'build')
Ben Murdochb8a8cc12014-11-26 15:28:44 +000057 else:
58 raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool)
59 return os.path.abspath(ret)
60
61
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000062def extract_gn_build_commands(build_ninja_file):
63 """Extracts from a build.ninja the commands to run GN.
Ben Murdochb8a8cc12014-11-26 15:28:44 +000064
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000065 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
85def 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)
Ben Murdochb8a8cc12014-11-26 15:28:44 +000090 return
91
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000092 # 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)
Ben Murdochb8a8cc12014-11-26 15:28:44 +000098
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000099 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
121command = gn -q gen //out/%s/
122description = Regenerating ninja files
123
124build build.ninja: gn
125generator = 1
126depfile = 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
135def needs_clobber(landmines_path, new_landmines):
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000136 if os.path.exists(landmines_path):
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000137 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)
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000144 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
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000151
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000152
153def 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.
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000192 with open(landmines_path, 'w') as f:
193 f.writelines(new_landmines)
194
195
196def process_options():
197 """Returns a list of landmine emitting scripts."""
198 parser = optparse.OptionParser()
199 parser.add_option(
200 '-s', '--landmine-scripts', action='append',
Ben Murdochc5610432016-08-08 18:44:38 +0100201 default=[os.path.join(SRC_DIR, 'gypfiles', 'get_landmines.py')],
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000202 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
226def main():
227 landmine_scripts = process_options()
228
229 if landmine_utils.builder() in ('dump_dependency_json', 'eclipse'):
230 return 0
231
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000232 gyp_environment.set_environment()
233
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000234 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()])
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000239 clobber_if_necessary(landmines)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000240
241 return 0
242
243
244if __name__ == '__main__':
245 sys.exit(main())