[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(): |
| 50 | return sys.platform.startswith('win') or sys.platform == 'cygwin' |
| 51 | |
| 52 | |
| 53 | @memoize() |
| 54 | def IsLinux(): |
| 55 | return sys.platform.startswith('linux') |
| 56 | |
| 57 | |
| 58 | @memoize() |
| 59 | def IsMac(): |
| 60 | return sys.platform.startswith('darwin') |
| 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] |
| 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 | |
| 131 | def 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] | a3078d8 | 2012-12-01 01:59:36 | [diff] [blame] | 142 | if platform() == 'android': |
[email protected] | 2759bae | 2013-03-25 18:04:01 | [diff] [blame] | 143 | add('Clobber: java files renamed in crrev.com/12880022') |
[email protected] | 95795dd9 | 2013-03-04 19:17:42 | [diff] [blame] | 144 | if platform() == 'win' and builder() == 'ninja': |
| 145 | add('Compile on cc_unittests fails due to symbols removed in r185063.') |
[email protected] | 4ae6e84 | 2013-03-09 19:40:31 | [diff] [blame] | 146 | if platform() == 'linux' and builder() == 'ninja': |
| 147 | add('Builders switching from make to ninja will clobber on this.') |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 148 | |
| 149 | return landmines |
| 150 | |
| 151 | |
| 152 | def 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] | d074d47 | 2012-11-15 01:52:07 | [diff] [blame] | 170 | elif build_tool in ['msvs', 'vs', 'ib']: |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 171 | 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] | 68d8069 | 2013-01-17 23:50:02 | [diff] [blame] | 175 | raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool) |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 176 | return os.path.abspath(ret) |
| 177 | |
| 178 | |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 179 | def 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 | |
| 209 | def 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] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 223 | |
| 224 | gyp_helper.apply_chromium_gyp_env() |
| 225 | |
[email protected] | 31e8a85 | 2013-03-29 21:17:44 | [diff] [blame] | 226 | for target in ('Debug', 'Release', 'Debug_x64', 'Release_x64'): |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 227 | set_up_landmines(target) |
[email protected] | abab8d7 | 2012-11-14 04:59:48 | [diff] [blame] | 228 | |
| 229 | return 0 |
| 230 | |
| 231 | |
| 232 | if __name__ == '__main__': |
[email protected] | c45227b | 2012-11-15 02:53:03 | [diff] [blame] | 233 | sys.exit(main()) |