blob: 4a894b57d904533555b573663085459dbfa35117 [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"""
7This file holds a list of reasons why a particular build needs to be clobbered
8(or a list of 'landmines').
9
10This script runs every build as a hook. If it detects that the build should
11be clobbered, it will touch the file <build_dir>/.landmine_triggered. The
12various build scripts will then check for the presence of this file and clobber
13accordingly. The script will also emit the reasons for the clobber to stdout.
14
15A landmine is tripped when a builder checks out a different revision, and the
16diff between the new landmines and the old ones is non-null. At this point, the
17build is clobbered.
18"""
19
20import difflib
21import functools
22import gyp_helper
[email protected]c45227b2012-11-15 02:53:0323import logging
24import optparse
[email protected]abab8d72012-11-14 04:59:4825import os
26import shlex
27import sys
28import time
29
30SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
31
32def memoize(default=None):
33 """This decorator caches the return value of a parameterless pure function"""
34 def memoizer(func):
35 val = []
36 @functools.wraps(func)
37 def inner():
38 if not val:
39 ret = func()
40 val.append(ret if ret is not None else default)
[email protected]c45227b2012-11-15 02:53:0341 if logging.getLogger().isEnabledFor(logging.INFO):
42 print '%s -> %r' % (func.__name__, val[0])
[email protected]abab8d72012-11-14 04:59:4843 return val[0]
44 return inner
45 return memoizer
46
47
48@memoize()
49def IsWindows():
50 return sys.platform.startswith('win') or sys.platform == 'cygwin'
51
52
53@memoize()
54def IsLinux():
55 return sys.platform.startswith('linux')
56
57
58@memoize()
59def IsMac():
60 return sys.platform.startswith('darwin')
61
62
63@memoize()
64def gyp_defines():
65 """Parses and returns GYP_DEFINES env var as a dictionary."""
66 return dict(arg.split('=', 1)
67 for arg in shlex.split(os.environ.get('GYP_DEFINES', '')))
68
69
70@memoize()
71def distributor():
72 """
73 Returns a string which is the distributed build engine in use (if any).
74 Possible values: 'goma', 'ib', ''
75 """
76 if 'goma' in gyp_defines():
77 return 'goma'
78 elif IsWindows():
79 if 'CHROME_HEADLESS' in os.environ:
80 return 'ib' # use (win and !goma and headless) as approximation of ib
81
82
83@memoize()
84def platform():
85 """
86 Returns a string representing the platform this build is targetted for.
87 Possible values: 'win', 'mac', 'linux', 'ios', 'android'
88 """
89 if 'OS' in gyp_defines():
90 if 'android' in gyp_defines()['OS']:
91 return 'android'
92 else:
93 return gyp_defines()['OS']
94 elif IsWindows():
95 return 'win'
96 elif IsLinux():
97 return 'linux'
98 else:
99 return 'mac'
100
101
102@memoize()
103def builder():
104 """
105 Returns a string representing the build engine (not compiler) to use.
106 Possible values: 'make', 'ninja', 'xcode', 'msvs', 'scons'
107 """
108 if 'GYP_GENERATORS' in os.environ:
109 # for simplicity, only support the first explicit generator
110 generator = os.environ['GYP_GENERATORS'].split(',')[0]
111 if generator.endswith('-android'):
112 return generator.split('-')[0]
113 else:
114 return generator
115 else:
116 if platform() == 'android':
117 # Good enough for now? Do any android bots use make?
118 return 'ninja'
119 elif platform() == 'ios':
120 return 'xcode'
121 elif IsWindows():
122 return 'msvs'
123 elif IsLinux():
124 return 'make'
125 elif IsMac():
126 return 'xcode'
127 else:
128 assert False, 'Don\'t know what builder we\'re using!'
129
130
131def get_landmines(target):
132 """
133 ALL LANDMINES ARE DEFINED HERE.
134 target is 'Release' or 'Debug'
135 """
136 landmines = []
137 add = lambda item: landmines.append(item + '\n')
138
139 if (distributor() == 'goma' and platform() == 'win32' and
140 builder() == 'ninja'):
141 add('Need to clobber winja goma due to backend cwd cache fix.')
[email protected]a3078d82012-12-01 01:59:36142 if platform() == 'android':
[email protected]2759bae2013-03-25 18:04:01143 add('Clobber: java files renamed in crrev.com/12880022')
[email protected]95795dd92013-03-04 19:17:42144 if platform() == 'win' and builder() == 'ninja':
145 add('Compile on cc_unittests fails due to symbols removed in r185063.')
[email protected]4ae6e842013-03-09 19:40:31146 if platform() == 'linux' and builder() == 'ninja':
147 add('Builders switching from make to ninja will clobber on this.')
[email protected]abab8d72012-11-14 04:59:48148
149 return landmines
150
151
152def get_target_build_dir(build_tool, target, is_iphone=False):
153 """
154 Returns output directory absolute path dependent on build and targets.
155 Examples:
156 r'c:\b\build\slave\win\build\src\out\Release'
157 '/mnt/data/b/build/slave/linux/build/src/out/Debug'
158 '/b/build/slave/ios_rel_device/build/src/xcodebuild/Release-iphoneos'
159
160 Keep this function in sync with tools/build/scripts/slave/compile.py
161 """
162 ret = None
163 if build_tool == 'xcode':
164 ret = os.path.join(SRC_DIR, 'xcodebuild',
165 target + ('-iphoneos' if is_iphone else ''))
166 elif build_tool == 'make':
167 ret = os.path.join(SRC_DIR, 'out', target)
168 elif build_tool == 'ninja':
169 ret = os.path.join(SRC_DIR, 'out', target)
[email protected]d074d472012-11-15 01:52:07170 elif build_tool in ['msvs', 'vs', 'ib']:
[email protected]abab8d72012-11-14 04:59:48171 ret = os.path.join(SRC_DIR, 'build', target)
172 elif build_tool == 'scons':
173 ret = os.path.join(SRC_DIR, 'sconsbuild', target)
174 else:
[email protected]68d80692013-01-17 23:50:02175 raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool)
[email protected]abab8d72012-11-14 04:59:48176 return os.path.abspath(ret)
177
178
[email protected]c45227b2012-11-15 02:53:03179def set_up_landmines(target):
180 """Does the work of setting, planting, and triggering landmines."""
181 out_dir = get_target_build_dir(builder(), target, platform() == 'ios')
182
183 landmines_path = os.path.join(out_dir, '.landmines')
184 if not os.path.exists(out_dir):
185 os.makedirs(out_dir)
186
187 new_landmines = get_landmines(target)
188
189 if not os.path.exists(landmines_path):
190 with open(landmines_path, 'w') as f:
191 f.writelines(new_landmines)
192 else:
193 triggered = os.path.join(out_dir, '.landmines_triggered')
194 with open(landmines_path, 'r') as f:
195 old_landmines = f.readlines()
196 if old_landmines != new_landmines:
197 old_date = time.ctime(os.stat(landmines_path).st_ctime)
198 diff = difflib.unified_diff(old_landmines, new_landmines,
199 fromfile='old_landmines', tofile='new_landmines',
200 fromfiledate=old_date, tofiledate=time.ctime(), n=0)
201
202 with open(triggered, 'w') as f:
203 f.writelines(diff)
204 elif os.path.exists(triggered):
205 # Remove false triggered landmines.
206 os.remove(triggered)
207
208
209def main():
210 parser = optparse.OptionParser()
211 parser.add_option('-v', '--verbose', action='store_true',
212 default=('LANDMINES_VERBOSE' in os.environ),
213 help=('Emit some extra debugging information (default off). This option '
214 'is also enabled by the presence of a LANDMINES_VERBOSE environment '
215 'variable.'))
216 options, args = parser.parse_args()
217
218 if args:
219 parser.error('Unknown arguments %s' % args)
220
221 logging.basicConfig(
222 level=logging.DEBUG if options.verbose else logging.ERROR)
[email protected]abab8d72012-11-14 04:59:48223
224 gyp_helper.apply_chromium_gyp_env()
225
[email protected]31e8a852013-03-29 21:17:44226 for target in ('Debug', 'Release', 'Debug_x64', 'Release_x64'):
[email protected]c45227b2012-11-15 02:53:03227 set_up_landmines(target)
[email protected]abab8d72012-11-14 04:59:48228
229 return 0
230
231
232if __name__ == '__main__':
[email protected]c45227b2012-11-15 02:53:03233 sys.exit(main())