blob: 4cd5722f33254962482851b6dc5e79031e3b9d3a [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':
143 add('Clean android out directories to reduce zip size.')
[email protected]abab8d72012-11-14 04:59:48144
145 return landmines
146
147
148def get_target_build_dir(build_tool, target, is_iphone=False):
149 """
150 Returns output directory absolute path dependent on build and targets.
151 Examples:
152 r'c:\b\build\slave\win\build\src\out\Release'
153 '/mnt/data/b/build/slave/linux/build/src/out/Debug'
154 '/b/build/slave/ios_rel_device/build/src/xcodebuild/Release-iphoneos'
155
156 Keep this function in sync with tools/build/scripts/slave/compile.py
157 """
158 ret = None
159 if build_tool == 'xcode':
160 ret = os.path.join(SRC_DIR, 'xcodebuild',
161 target + ('-iphoneos' if is_iphone else ''))
162 elif build_tool == 'make':
163 ret = os.path.join(SRC_DIR, 'out', target)
164 elif build_tool == 'ninja':
165 ret = os.path.join(SRC_DIR, 'out', target)
[email protected]d074d472012-11-15 01:52:07166 elif build_tool in ['msvs', 'vs', 'ib']:
[email protected]abab8d72012-11-14 04:59:48167 ret = os.path.join(SRC_DIR, 'build', target)
168 elif build_tool == 'scons':
169 ret = os.path.join(SRC_DIR, 'sconsbuild', target)
170 else:
171 raise NotImplementedError()
172 return os.path.abspath(ret)
173
174
[email protected]c45227b2012-11-15 02:53:03175def set_up_landmines(target):
176 """Does the work of setting, planting, and triggering landmines."""
177 out_dir = get_target_build_dir(builder(), target, platform() == 'ios')
178
179 landmines_path = os.path.join(out_dir, '.landmines')
180 if not os.path.exists(out_dir):
181 os.makedirs(out_dir)
182
183 new_landmines = get_landmines(target)
184
185 if not os.path.exists(landmines_path):
186 with open(landmines_path, 'w') as f:
187 f.writelines(new_landmines)
188 else:
189 triggered = os.path.join(out_dir, '.landmines_triggered')
190 with open(landmines_path, 'r') as f:
191 old_landmines = f.readlines()
192 if old_landmines != new_landmines:
193 old_date = time.ctime(os.stat(landmines_path).st_ctime)
194 diff = difflib.unified_diff(old_landmines, new_landmines,
195 fromfile='old_landmines', tofile='new_landmines',
196 fromfiledate=old_date, tofiledate=time.ctime(), n=0)
197
198 with open(triggered, 'w') as f:
199 f.writelines(diff)
200 elif os.path.exists(triggered):
201 # Remove false triggered landmines.
202 os.remove(triggered)
203
204
205def main():
206 parser = optparse.OptionParser()
207 parser.add_option('-v', '--verbose', action='store_true',
208 default=('LANDMINES_VERBOSE' in os.environ),
209 help=('Emit some extra debugging information (default off). This option '
210 'is also enabled by the presence of a LANDMINES_VERBOSE environment '
211 'variable.'))
212 options, args = parser.parse_args()
213
214 if args:
215 parser.error('Unknown arguments %s' % args)
216
217 logging.basicConfig(
218 level=logging.DEBUG if options.verbose else logging.ERROR)
[email protected]abab8d72012-11-14 04:59:48219
220 gyp_helper.apply_chromium_gyp_env()
221
222 for target in ('Debug', 'Release'):
[email protected]c45227b2012-11-15 02:53:03223 set_up_landmines(target)
[email protected]abab8d72012-11-14 04:59:48224
225 return 0
226
227
228if __name__ == '__main__':
[email protected]c45227b2012-11-15 02:53:03229 sys.exit(main())