blob: 624c60788025214cdc4f3dff4a44ff25579c9fd3 [file] [log] [blame]
[email protected]abab8d72012-11-14 04:59:481#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium 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"""
[email protected]9372bec2014-08-14 14:03:307This script runs every build as the first hook (See DEPS). If it detects that
scherkusbb04e1052014-09-03 21:28:238the build should be clobbered, it will delete the contents of the build
9directory.
[email protected]abab8d72012-11-14 04:59:4810
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
[email protected]d99e6bf2014-04-23 04:19:2317import errno
[email protected]9372bec2014-08-14 14:03:3018import gyp_environment
[email protected]c45227b2012-11-15 02:53:0319import logging
20import optparse
[email protected]abab8d72012-11-14 04:59:4821import os
[email protected]9372bec2014-08-14 14:03:3022import shutil
[email protected]abab8d72012-11-14 04:59:4823import sys
[email protected]ec069f72013-08-21 02:44:5824import subprocess
[email protected]abab8d72012-11-14 04:59:4825import time
26
[email protected]ec069f72013-08-21 02:44:5827import landmine_utils
28
29
[email protected]abab8d72012-11-14 04:59:4830SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
31
[email protected]abab8d72012-11-14 04:59:4832
[email protected]9372bec2014-08-14 14:03:3033def get_build_dir(build_tool, is_iphone=False):
[email protected]abab8d72012-11-14 04:59:4834 """
35 Returns output directory absolute path dependent on build and targets.
36 Examples:
[email protected]9372bec2014-08-14 14:03:3037 r'c:\b\build\slave\win\build\src\out'
38 '/mnt/data/b/build/slave/linux/build/src/out'
39 '/b/build/slave/ios_rel_device/build/src/xcodebuild'
[email protected]abab8d72012-11-14 04:59:4840
41 Keep this function in sync with tools/build/scripts/slave/compile.py
42 """
43 ret = None
44 if build_tool == 'xcode':
[email protected]9372bec2014-08-14 14:03:3045 ret = os.path.join(SRC_DIR, 'xcodebuild')
[email protected]ddca2f72013-05-11 19:13:2146 elif build_tool in ['make', 'ninja', 'ninja-ios']: # TODO: Remove ninja-ios.
oetuaho6bad0d42014-11-03 09:09:5347 if ('CHROMIUM_OUT_DIR' not in os.environ and
48 'output_dir' in landmine_utils.gyp_generator_flags()):
49 output_dir = landmine_utils.gyp_generator_flags()['output_dir']
50 else:
51 output_dir = os.environ.get('CHROMIUM_OUT_DIR', 'out')
52 ret = os.path.join(SRC_DIR, output_dir)
[email protected]abab8d72012-11-14 04:59:4853 else:
[email protected]68d80692013-01-17 23:50:0254 raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool)
[email protected]abab8d72012-11-14 04:59:4855 return os.path.abspath(ret)
56
57
brettwf5b2cda2014-11-10 23:08:0658def extract_gn_build_commands(build_ninja_file):
59 """Extracts from a build.ninja the commands to run GN.
60
61 The commands to run GN are the gn rule and build.ninja build step at the
62 top of the build.ninja file. We want to keep these when deleting GN builds
63 since we want to preserve the command-line flags to GN.
64
65 On error, returns the empty string."""
66 result = ""
67 with open(build_ninja_file, 'r') as f:
68 # Read until the second blank line. The first thing GN writes to the file
69 # is the "rule gn" and the second is the section for "build build.ninja",
70 # separated by blank lines.
71 num_blank_lines = 0
72 while num_blank_lines < 2:
73 line = f.readline()
74 if len(line) == 0:
75 return '' # Unexpected EOF.
76 result += line
77 if line[0] == '\n':
78 num_blank_lines = num_blank_lines + 1
79 return result
80
81def delete_build_dir(build_dir):
82 # GN writes a build.ninja.d file. Note that not all GN builds have args.gn.
83 build_ninja_d_file = os.path.join(build_dir, 'build.ninja.d')
84 if not os.path.exists(build_ninja_d_file):
85 shutil.rmtree(build_dir)
86 return
87
88 # GN builds aren't automatically regenerated when you sync. To avoid
89 # messing with the GN workflow, erase everything but the args file, and
90 # write a dummy build.ninja file that will automatically rerun GN the next
91 # time Ninja is run.
92 build_ninja_file = os.path.join(build_dir, 'build.ninja')
93 build_commands = extract_gn_build_commands(build_ninja_file)
94
95 try:
96 gn_args_file = os.path.join(build_dir, 'args.gn')
97 with open(gn_args_file, 'r') as f:
98 args_contents = f.read()
99 except IOError:
100 args_contents = ''
101
102 shutil.rmtree(build_dir)
103
104 # Put back the args file (if any).
105 os.mkdir(build_dir)
106 if args_contents != '':
107 with open(gn_args_file, 'w') as f:
108 f.write(args_contents)
109
110 # Write the build.ninja file sufficiently to regenerate itself.
111 with open(os.path.join(build_dir, 'build.ninja'), 'w') as f:
112 if build_commands != '':
113 f.write(build_commands)
114 else:
115 # Couldn't parse the build.ninja file, write a default thing.
116 f.write('''rule gn
117command = gn -q gen //out/%s/
118description = Regenerating ninja files
119
120build build.ninja: gn
121generator = 1
122depfile = build.ninja.d
123''' % (os.path.split(build_dir)[1]))
124
125 # Write a .d file for the build which references a nonexistant file. This
126 # will make Ninja always mark the build as dirty.
127 with open(build_ninja_d_file, 'w') as f:
128 f.write('build.ninja: nonexistant_file.gn\n')
129
130
[email protected]9372bec2014-08-14 14:03:30131def clobber_if_necessary(new_landmines):
[email protected]c45227b2012-11-15 02:53:03132 """Does the work of setting, planting, and triggering landmines."""
[email protected]9372bec2014-08-14 14:03:30133 out_dir = get_build_dir(landmine_utils.builder())
134 landmines_path = os.path.normpath(os.path.join(out_dir, '..', '.landmines'))
[email protected]d99e6bf2014-04-23 04:19:23135 try:
[email protected]c45227b2012-11-15 02:53:03136 os.makedirs(out_dir)
[email protected]d99e6bf2014-04-23 04:19:23137 except OSError as e:
138 if e.errno == errno.EEXIST:
139 pass
[email protected]c45227b2012-11-15 02:53:03140
[email protected]f3d0d1c32014-05-22 22:12:48141 if os.path.exists(landmines_path):
[email protected]c45227b2012-11-15 02:53:03142 with open(landmines_path, 'r') as f:
143 old_landmines = f.readlines()
144 if old_landmines != new_landmines:
145 old_date = time.ctime(os.stat(landmines_path).st_ctime)
146 diff = difflib.unified_diff(old_landmines, new_landmines,
147 fromfile='old_landmines', tofile='new_landmines',
148 fromfiledate=old_date, tofiledate=time.ctime(), n=0)
[email protected]9372bec2014-08-14 14:03:30149 sys.stdout.write('Clobbering due to:\n')
150 sys.stdout.writelines(diff)
[email protected]c45227b2012-11-15 02:53:03151
scherkusbb04e1052014-09-03 21:28:23152 # Clobber contents of build directory but not directory itself: some
153 # checkouts have the build directory mounted.
154 for f in os.listdir(out_dir):
155 path = os.path.join(out_dir, f)
156 if os.path.isfile(path):
157 os.unlink(path)
158 elif os.path.isdir(path):
brettwf5b2cda2014-11-10 23:08:06159 delete_build_dir(path)
[email protected]9372bec2014-08-14 14:03:30160
161 # Save current set of landmines for next time.
[email protected]f3d0d1c32014-05-22 22:12:48162 with open(landmines_path, 'w') as f:
163 f.writelines(new_landmines)
[email protected]c45227b2012-11-15 02:53:03164
165
[email protected]c36d62932013-09-02 21:51:18166def process_options():
167 """Returns a list of landmine emitting scripts."""
[email protected]c45227b2012-11-15 02:53:03168 parser = optparse.OptionParser()
[email protected]ec069f72013-08-21 02:44:58169 parser.add_option(
170 '-s', '--landmine-scripts', action='append',
171 default=[os.path.join(SRC_DIR, 'build', 'get_landmines.py')],
172 help='Path to the script which emits landmines to stdout. The target '
[email protected]c36d62932013-09-02 21:51:18173 'is passed to this script via option -t. Note that an extra '
174 'script can be specified via an env var EXTRA_LANDMINES_SCRIPT.')
[email protected]c45227b2012-11-15 02:53:03175 parser.add_option('-v', '--verbose', action='store_true',
176 default=('LANDMINES_VERBOSE' in os.environ),
177 help=('Emit some extra debugging information (default off). This option '
178 'is also enabled by the presence of a LANDMINES_VERBOSE environment '
179 'variable.'))
[email protected]ec069f72013-08-21 02:44:58180
[email protected]c45227b2012-11-15 02:53:03181 options, args = parser.parse_args()
182
183 if args:
184 parser.error('Unknown arguments %s' % args)
185
186 logging.basicConfig(
187 level=logging.DEBUG if options.verbose else logging.ERROR)
[email protected]abab8d72012-11-14 04:59:48188
[email protected]c36d62932013-09-02 21:51:18189 extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT')
190 if extra_script:
191 return options.landmine_scripts + [extra_script]
192 else:
193 return options.landmine_scripts
194
195
196def main():
197 landmine_scripts = process_options()
[email protected]07e55632014-02-15 05:23:29198
[email protected]a403a3d2014-04-12 01:13:21199 if landmine_utils.builder() in ('dump_dependency_json', 'eclipse'):
[email protected]07e55632014-02-15 05:23:29200 return 0
[email protected]abab8d72012-11-14 04:59:48201
[email protected]9372bec2014-08-14 14:03:30202 gyp_environment.SetEnvironment()
203
204 landmines = []
205 for s in landmine_scripts:
206 proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE)
207 output, _ = proc.communicate()
208 landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()])
209 clobber_if_necessary(landmines)
[email protected]abab8d72012-11-14 04:59:48210
211 return 0
212
213
214if __name__ == '__main__':
[email protected]c45227b2012-11-15 02:53:03215 sys.exit(main())