• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
7This script runs every build as the first hook (See DEPS). If it detects that
8the build should be clobbered, it will delete the contents of the build
9directory.
10
11A landmine is tripped when a builder checks out a different revision, and the
12diff between the new landmines and the old ones is non-null. At this point, the
13build is clobbered.
14
15Before adding or changing a landmine consider the consequences of doing so.
16Doing so will wipe out every output directory on every Chrome developer's
17machine. This can be particularly problematic on Windows where the directory
18deletion may well fail (locked files, command prompt in the directory, etc.),
19and generated .sln and .vcxproj files will be deleted.
20
21This output directory deletion will be repated when going back and forth across
22the change that added the landmine, adding to the cost. There are usually less
23troublesome alternatives.
24"""
25
26import difflib
27import errno
28import logging
29import optparse
30import os
31import sys
32import subprocess
33import time
34
35import clobber
36import landmine_utils
37
38
39def get_build_dir(src_dir):
40  """
41  Returns output directory absolute path dependent on build and targets.
42  Examples:
43    r'c:\b\build\slave\win\build\src\out'
44    '/mnt/data/b/build/slave/linux/build/src/out'
45    '/b/build/slave/ios_rel_device/build/src/out'
46
47  Keep this function in sync with tools/build/scripts/slave/compile.py
48  """
49  if 'CHROMIUM_OUT_DIR' in os.environ:
50    output_dir = os.environ.get('CHROMIUM_OUT_DIR').strip()
51    if not output_dir:
52      raise Error('CHROMIUM_OUT_DIR environment variable is set but blank!')
53  else:
54    output_dir = 'out'
55  return os.path.abspath(os.path.join(src_dir, output_dir))
56
57
58def clobber_if_necessary(new_landmines, src_dir):
59  """Does the work of setting, planting, and triggering landmines."""
60  out_dir = get_build_dir(src_dir)
61  landmines_path = os.path.normpath(os.path.join(src_dir, '.landmines'))
62  try:
63    os.makedirs(out_dir)
64  except OSError as e:
65    if e.errno == errno.EEXIST:
66      pass
67
68  if os.path.exists(landmines_path):
69    with open(landmines_path, 'r') as f:
70      old_landmines = f.readlines()
71    if old_landmines != new_landmines:
72      old_date = time.ctime(os.stat(landmines_path).st_ctime)
73      diff = difflib.unified_diff(old_landmines, new_landmines,
74          fromfile='old_landmines', tofile='new_landmines',
75          fromfiledate=old_date, tofiledate=time.ctime(), n=0)
76      sys.stdout.write('Clobbering due to:\n')
77      sys.stdout.writelines(diff)
78      sys.stdout.flush()
79
80      clobber.clobber(out_dir)
81
82  # Save current set of landmines for next time.
83  with open(landmines_path, 'w') as f:
84    f.writelines(new_landmines)
85
86
87def process_options():
88  """Returns an options object containing the configuration for this script."""
89  parser = optparse.OptionParser()
90  parser.add_option(
91      '-s', '--landmine-scripts', action='append',
92      help='Path to the script which emits landmines to stdout. The target '
93           'is passed to this script via option -t. Note that an extra '
94           'script can be specified via an env var EXTRA_LANDMINES_SCRIPT.')
95  parser.add_option('-d', '--src-dir',
96      help='Path of the source root dir. Overrides the default location of the '
97           'source root dir when calculating the build directory.')
98  parser.add_option('-v', '--verbose', action='store_true',
99      default=('LANDMINES_VERBOSE' in os.environ),
100      help=('Emit some extra debugging information (default off). This option '
101          'is also enabled by the presence of a LANDMINES_VERBOSE environment '
102          'variable.'))
103
104  options, args = parser.parse_args()
105
106  if args:
107    parser.error('Unknown arguments %s' % args)
108
109  logging.basicConfig(
110      level=logging.DEBUG if options.verbose else logging.ERROR)
111
112  if options.src_dir:
113    if not os.path.isdir(options.src_dir):
114      parser.error('Cannot find source root dir at %s' % options.src_dir)
115    logging.debug('Overriding source root dir. Using: %s', options.src_dir)
116  else:
117    options.src_dir = \
118        os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
119
120  if not options.landmine_scripts:
121    options.landmine_scripts = [os.path.join(options.src_dir, 'build',
122                                             'get_landmines.py')]
123
124  extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT')
125  if extra_script:
126    options.landmine_scripts += [extra_script]
127
128  return options
129
130
131def main():
132  options = process_options()
133
134  landmines = []
135  for s in options.landmine_scripts:
136    proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE)
137    output, _ = proc.communicate()
138    landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()])
139  clobber_if_necessary(landmines, options.src_dir)
140
141  return 0
142
143
144if __name__ == '__main__':
145  sys.exit(main())
146