Save errno for logging before potentially overwriting it.
[chromium-blink-merge.git] / build / landmines.py
blob753ad3bddf3e3a4ed9eb4ea3d2e660a24423996d
1 #!/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.
6 """
7 This file holds a list of reasons why a particular build needs to be clobbered
8 (or a list of 'landmines').
10 This script runs every build as a hook. If it detects that the build should
11 be clobbered, it will touch the file <build_dir>/.landmine_triggered. The
12 various build scripts will then check for the presence of this file and clobber
13 accordingly. The script will also emit the reasons for the clobber to stdout.
15 A landmine is tripped when a builder checks out a different revision, and the
16 diff between the new landmines and the old ones is non-null. At this point, the
17 build is clobbered.
18 """
20 import difflib
21 import functools
22 import gyp_helper
23 import logging
24 import optparse
25 import os
26 import shlex
27 import sys
28 import time
30 SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
32 def 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)
41 if logging.getLogger().isEnabledFor(logging.INFO):
42 print '%s -> %r' % (func.__name__, val[0])
43 return val[0]
44 return inner
45 return memoizer
48 @memoize()
49 def IsWindows():
50 return sys.platform in ['win32', 'cygwin']
53 @memoize()
54 def IsLinux():
55 return sys.platform.startswith('linux')
58 @memoize()
59 def IsMac():
60 return sys.platform == 'darwin'
63 @memoize()
64 def 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', '')))
69 @memoize()
70 def gyp_msvs_version():
71 return os.environ.get('GYP_MSVS_VERSION', '')
73 @memoize()
74 def distributor():
75 """
76 Returns a string which is the distributed build engine in use (if any).
77 Possible values: 'goma', 'ib', ''
78 """
79 if 'goma' in gyp_defines():
80 return 'goma'
81 elif IsWindows():
82 if 'CHROME_HEADLESS' in os.environ:
83 return 'ib' # use (win and !goma and headless) as approximation of ib
86 @memoize()
87 def platform():
88 """
89 Returns a string representing the platform this build is targetted for.
90 Possible values: 'win', 'mac', 'linux', 'ios', 'android'
91 """
92 if 'OS' in gyp_defines():
93 if 'android' in gyp_defines()['OS']:
94 return 'android'
95 else:
96 return gyp_defines()['OS']
97 elif IsWindows():
98 return 'win'
99 elif IsLinux():
100 return 'linux'
101 else:
102 return 'mac'
105 @memoize()
106 def builder():
108 Returns a string representing the build engine (not compiler) to use.
109 Possible values: 'make', 'ninja', 'xcode', 'msvs', 'scons'
111 if 'GYP_GENERATORS' in os.environ:
112 # for simplicity, only support the first explicit generator
113 generator = os.environ['GYP_GENERATORS'].split(',')[0]
114 if generator.endswith('-android'):
115 return generator.split('-')[0]
116 elif generator.endswith('-ninja'):
117 return 'ninja'
118 else:
119 return generator
120 else:
121 if platform() == 'android':
122 # Good enough for now? Do any android bots use make?
123 return 'ninja'
124 elif platform() == 'ios':
125 return 'xcode'
126 elif IsWindows():
127 return 'msvs'
128 elif IsLinux():
129 return 'ninja'
130 elif IsMac():
131 return 'xcode'
132 else:
133 assert False, 'Don\'t know what builder we\'re using!'
136 def get_landmines(target):
138 ALL LANDMINES ARE DEFINED HERE.
139 target is 'Release' or 'Debug'
141 landmines = []
142 add = lambda item: landmines.append(item + '\n')
144 if (distributor() == 'goma' and platform() == 'win32' and
145 builder() == 'ninja'):
146 add('Need to clobber winja goma due to backend cwd cache fix.')
147 if platform() == 'android':
148 add('Clobber: Resources removed in r195014 require clobber.')
149 if platform() == 'win' and builder() == 'ninja':
150 add('Compile on cc_unittests fails due to symbols removed in r185063.')
151 if platform() == 'linux' and builder() == 'ninja':
152 add('Builders switching from make to ninja will clobber on this.')
153 if platform() == 'mac':
154 add('Switching from bundle to unbundled dylib (issue 14743002).')
155 if (platform() == 'win' and builder() == 'ninja' and
156 gyp_msvs_version() == '2012' and
157 gyp_defines().get('target_arch') == 'x64' and
158 gyp_defines().get('dcheck_always_on') == '1'):
159 add("Switched win x64 trybots from VS2010 to VS2012.")
161 return landmines
164 def get_target_build_dir(build_tool, target, is_iphone=False):
166 Returns output directory absolute path dependent on build and targets.
167 Examples:
168 r'c:\b\build\slave\win\build\src\out\Release'
169 '/mnt/data/b/build/slave/linux/build/src/out/Debug'
170 '/b/build/slave/ios_rel_device/build/src/xcodebuild/Release-iphoneos'
172 Keep this function in sync with tools/build/scripts/slave/compile.py
174 ret = None
175 if build_tool == 'xcode':
176 ret = os.path.join(SRC_DIR, 'xcodebuild',
177 target + ('-iphoneos' if is_iphone else ''))
178 elif build_tool in ['make', 'ninja', 'ninja-ios']: # TODO: Remove ninja-ios.
179 ret = os.path.join(SRC_DIR, 'out', target)
180 elif build_tool in ['msvs', 'vs', 'ib']:
181 ret = os.path.join(SRC_DIR, 'build', target)
182 elif build_tool == 'scons':
183 ret = os.path.join(SRC_DIR, 'sconsbuild', target)
184 else:
185 raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool)
186 return os.path.abspath(ret)
189 def set_up_landmines(target):
190 """Does the work of setting, planting, and triggering landmines."""
191 out_dir = get_target_build_dir(builder(), target, platform() == 'ios')
193 landmines_path = os.path.join(out_dir, '.landmines')
194 if not os.path.exists(out_dir):
195 os.makedirs(out_dir)
197 new_landmines = get_landmines(target)
199 if not os.path.exists(landmines_path):
200 with open(landmines_path, 'w') as f:
201 f.writelines(new_landmines)
202 else:
203 triggered = os.path.join(out_dir, '.landmines_triggered')
204 with open(landmines_path, 'r') as f:
205 old_landmines = f.readlines()
206 if old_landmines != new_landmines:
207 old_date = time.ctime(os.stat(landmines_path).st_ctime)
208 diff = difflib.unified_diff(old_landmines, new_landmines,
209 fromfile='old_landmines', tofile='new_landmines',
210 fromfiledate=old_date, tofiledate=time.ctime(), n=0)
212 with open(triggered, 'w') as f:
213 f.writelines(diff)
214 elif os.path.exists(triggered):
215 # Remove false triggered landmines.
216 os.remove(triggered)
219 def main():
220 parser = optparse.OptionParser()
221 parser.add_option('-v', '--verbose', action='store_true',
222 default=('LANDMINES_VERBOSE' in os.environ),
223 help=('Emit some extra debugging information (default off). This option '
224 'is also enabled by the presence of a LANDMINES_VERBOSE environment '
225 'variable.'))
226 options, args = parser.parse_args()
228 if args:
229 parser.error('Unknown arguments %s' % args)
231 logging.basicConfig(
232 level=logging.DEBUG if options.verbose else logging.ERROR)
234 gyp_helper.apply_chromium_gyp_env()
236 for target in ('Debug', 'Release', 'Debug_x64', 'Release_x64'):
237 set_up_landmines(target)
239 return 0
242 if __name__ == '__main__':
243 sys.exit(main())