[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 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. |
| 5 | |
| 6 | """ |
| 7 | This file holds a list of reasons why a particular build needs to be clobbered |
| 8 | (or a list of 'landmines'). |
| 9 | |
| 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. |
| 14 | |
| 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 | """ |
| 19 | |
| 20 | import difflib |
| 21 | import functools |
| 22 | import gyp_helper |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 23 | import logging |
| 24 | import optparse |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 25 | import os |
| 26 | import shlex |
| 27 | import sys |
| 28 | import time |
| 29 | |
| 30 | SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
| 31 | |
| 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) |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 41 | if logging.getLogger().isEnabledFor(logging.INFO): |
| 42 | print '%s -> %r' % (func.__name__, val[0]) |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 43 | return val[0] |
| 44 | return inner |
| 45 | return memoizer |
| 46 | |
| 47 | |
| 48 | @memoize() |
| 49 | def IsWindows(): |
[email protected] | ddca2f7 | 2013-05-11 19:13:21 | [diff] [blame^] | 50 | return sys.platform in ['win32', 'cygwin'] |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 51 | |
| 52 | |
| 53 | @memoize() |
| 54 | def IsLinux(): |
| 55 | return sys.platform.startswith('linux') |
| 56 | |
| 57 | |
| 58 | @memoize() |
| 59 | def IsMac(): |
[email protected] | ddca2f7 | 2013-05-11 19:13:21 | [diff] [blame^] | 60 | return sys.platform == 'darwin' |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 61 | |
| 62 | |
| 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', ''))) |
| 68 | |
| 69 | |
| 70 | @memoize() |
| 71 | def 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() |
| 84 | def 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() |
| 103 | def 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] |
[email protected] | df4226d | 2013-04-30 20:39:16 | [diff] [blame] | 113 | elif generator.endswith('-ninja'): |
| 114 | return 'ninja' |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 115 | else: |
| 116 | return generator |
| 117 | else: |
| 118 | if platform() == 'android': |
| 119 | # Good enough for now? Do any android bots use make? |
| 120 | return 'ninja' |
| 121 | elif platform() == 'ios': |
| 122 | return 'xcode' |
| 123 | elif IsWindows(): |
| 124 | return 'msvs' |
| 125 | elif IsLinux(): |
[email protected] | ddca2f7 | 2013-05-11 19:13:21 | [diff] [blame^] | 126 | return 'ninja' |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 127 | elif IsMac(): |
| 128 | return 'xcode' |
| 129 | else: |
| 130 | assert False, 'Don\'t know what builder we\'re using!' |
| 131 | |
| 132 | |
| 133 | def get_landmines(target): |
| 134 | """ |
| 135 | ALL LANDMINES ARE DEFINED HERE. |
| 136 | target is 'Release' or 'Debug' |
| 137 | """ |
| 138 | landmines = [] |
| 139 | add = lambda item: landmines.append(item + '\n') |
| 140 | |
| 141 | if (distributor() == 'goma' and platform() == 'win32' and |
| 142 | builder() == 'ninja'): |
| 143 | add('Need to clobber winja goma due to backend cwd cache fix.') |
[email protected] | a3078d8 | 2012-12-01 01:59:36 | [diff] [blame] | 144 | if platform() == 'android': |
[email protected] | 491657e | 2013-04-19 01:55:24 | [diff] [blame] | 145 | add('Clobber: Resources removed in r195014 require clobber.') |
[email protected] | 95795dd9 | 2013-03-04 19:17:42 | [diff] [blame] | 146 | if platform() == 'win' and builder() == 'ninja': |
| 147 | add('Compile on cc_unittests fails due to symbols removed in r185063.') |
[email protected] | 4ae6e84 | 2013-03-09 19:40:31 | [diff] [blame] | 148 | if platform() == 'linux' and builder() == 'ninja': |
| 149 | add('Builders switching from make to ninja will clobber on this.') |
[email protected] | 19a904b | 2013-05-03 22:12:29 | [diff] [blame] | 150 | if platform() == 'mac': |
| 151 | add('Switching from bundle to unbundled dylib (issue 14743002).') |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 152 | |
| 153 | return landmines |
| 154 | |
| 155 | |
| 156 | def get_target_build_dir(build_tool, target, is_iphone=False): |
| 157 | """ |
| 158 | Returns output directory absolute path dependent on build and targets. |
| 159 | Examples: |
| 160 | r'c:\b\build\slave\win\build\src\out\Release' |
| 161 | '/mnt/data/b/build/slave/linux/build/src/out/Debug' |
| 162 | '/b/build/slave/ios_rel_device/build/src/xcodebuild/Release-iphoneos' |
| 163 | |
| 164 | Keep this function in sync with tools/build/scripts/slave/compile.py |
| 165 | """ |
| 166 | ret = None |
| 167 | if build_tool == 'xcode': |
| 168 | ret = os.path.join(SRC_DIR, 'xcodebuild', |
| 169 | target + ('-iphoneos' if is_iphone else '')) |
[email protected] | ddca2f7 | 2013-05-11 19:13:21 | [diff] [blame^] | 170 | elif build_tool in ['make', 'ninja', 'ninja-ios']: # TODO: Remove ninja-ios. |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 171 | ret = os.path.join(SRC_DIR, 'out', target) |
[email protected] | d074d47 | 2012-11-15 01:52:07 | [diff] [blame] | 172 | elif build_tool in ['msvs', 'vs', 'ib']: |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 173 | ret = os.path.join(SRC_DIR, 'build', target) |
| 174 | elif build_tool == 'scons': |
| 175 | ret = os.path.join(SRC_DIR, 'sconsbuild', target) |
| 176 | else: |
[email protected] | 68d8069 | 2013-01-17 23:50:02 | [diff] [blame] | 177 | raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool) |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 178 | return os.path.abspath(ret) |
| 179 | |
| 180 | |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 181 | def set_up_landmines(target): |
| 182 | """Does the work of setting, planting, and triggering landmines.""" |
| 183 | out_dir = get_target_build_dir(builder(), target, platform() == 'ios') |
| 184 | |
| 185 | landmines_path = os.path.join(out_dir, '.landmines') |
| 186 | if not os.path.exists(out_dir): |
| 187 | os.makedirs(out_dir) |
| 188 | |
| 189 | new_landmines = get_landmines(target) |
| 190 | |
| 191 | if not os.path.exists(landmines_path): |
| 192 | with open(landmines_path, 'w') as f: |
| 193 | f.writelines(new_landmines) |
| 194 | else: |
| 195 | triggered = os.path.join(out_dir, '.landmines_triggered') |
| 196 | with open(landmines_path, 'r') as f: |
| 197 | old_landmines = f.readlines() |
| 198 | if old_landmines != new_landmines: |
| 199 | old_date = time.ctime(os.stat(landmines_path).st_ctime) |
| 200 | diff = difflib.unified_diff(old_landmines, new_landmines, |
| 201 | fromfile='old_landmines', tofile='new_landmines', |
| 202 | fromfiledate=old_date, tofiledate=time.ctime(), n=0) |
| 203 | |
| 204 | with open(triggered, 'w') as f: |
| 205 | f.writelines(diff) |
| 206 | elif os.path.exists(triggered): |
| 207 | # Remove false triggered landmines. |
| 208 | os.remove(triggered) |
| 209 | |
| 210 | |
| 211 | def main(): |
| 212 | parser = optparse.OptionParser() |
| 213 | parser.add_option('-v', '--verbose', action='store_true', |
| 214 | default=('LANDMINES_VERBOSE' in os.environ), |
| 215 | help=('Emit some extra debugging information (default off). This option ' |
| 216 | 'is also enabled by the presence of a LANDMINES_VERBOSE environment ' |
| 217 | 'variable.')) |
| 218 | options, args = parser.parse_args() |
| 219 | |
| 220 | if args: |
| 221 | parser.error('Unknown arguments %s' % args) |
| 222 | |
| 223 | logging.basicConfig( |
| 224 | level=logging.DEBUG if options.verbose else logging.ERROR) |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 225 | |
| 226 | gyp_helper.apply_chromium_gyp_env() |
| 227 | |
[email protected] | 31e8a85 | 2013-03-29 21:17:44 | [diff] [blame] | 228 | for target in ('Debug', 'Release', 'Debug_x64', 'Release_x64'): |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 229 | set_up_landmines(target) |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 230 | |
| 231 | return 0 |
| 232 | |
| 233 | |
| 234 | if __name__ == '__main__': |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 235 | sys.exit(main()) |