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"""
15
16import difflib
17import errno
18import gyp_environment
19import logging
20import optparse
21import os
22import shutil
23import sys
24import subprocess
25import time
26
27import landmine_utils
28
29
30SRC_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
31
32
33def get_build_dir(build_tool, is_iphone=False):
34  """
35  Returns output directory absolute path dependent on build and targets.
36  Examples:
37    r'c:\b\build\slave\win\build\src\out'
38    '/mnt/data/b/build/slave/linux/build/src/out'
39    '/b/build/slave/ios_rel_device/build/src/xcodebuild'
40
41  Keep this function in sync with tools/build/scripts/slave/compile.py
42  """
43  ret = None
44  if build_tool == 'xcode':
45    ret = os.path.join(SRC_DIR, 'xcodebuild')
46  elif build_tool in ['make', 'ninja', 'ninja-ios']:  # TODO: Remove ninja-ios.
47    ret = os.path.join(SRC_DIR, os.environ.get('CHROMIUM_OUT_DIR', 'out'))
48  else:
49    raise NotImplementedError('Unexpected GYP_GENERATORS (%s)' % build_tool)
50  return os.path.abspath(ret)
51
52
53def clobber_if_necessary(new_landmines):
54  """Does the work of setting, planting, and triggering landmines."""
55  out_dir = get_build_dir(landmine_utils.builder())
56  landmines_path = os.path.normpath(os.path.join(out_dir, '..', '.landmines'))
57  try:
58    os.makedirs(out_dir)
59  except OSError as e:
60    if e.errno == errno.EEXIST:
61      pass
62
63  if os.path.exists(landmines_path):
64    with open(landmines_path, 'r') as f:
65      old_landmines = f.readlines()
66    if old_landmines != new_landmines:
67      old_date = time.ctime(os.stat(landmines_path).st_ctime)
68      diff = difflib.unified_diff(old_landmines, new_landmines,
69          fromfile='old_landmines', tofile='new_landmines',
70          fromfiledate=old_date, tofiledate=time.ctime(), n=0)
71      sys.stdout.write('Clobbering due to:\n')
72      sys.stdout.writelines(diff)
73
74      # Clobber contents of build directory but not directory itself: some
75      # checkouts have the build directory mounted.
76      for f in os.listdir(out_dir):
77        path = os.path.join(out_dir, f)
78        if os.path.isfile(path):
79          os.unlink(path)
80        elif os.path.isdir(path):
81          shutil.rmtree(path)
82
83  # Save current set of landmines for next time.
84  with open(landmines_path, 'w') as f:
85    f.writelines(new_landmines)
86
87
88def process_options():
89  """Returns a list of landmine emitting scripts."""
90  parser = optparse.OptionParser()
91  parser.add_option(
92      '-s', '--landmine-scripts', action='append',
93      default=[os.path.join(SRC_DIR, 'build', 'get_landmines.py')],
94      help='Path to the script which emits landmines to stdout. The target '
95           'is passed to this script via option -t. Note that an extra '
96           'script can be specified via an env var EXTRA_LANDMINES_SCRIPT.')
97  parser.add_option('-v', '--verbose', action='store_true',
98      default=('LANDMINES_VERBOSE' in os.environ),
99      help=('Emit some extra debugging information (default off). This option '
100          'is also enabled by the presence of a LANDMINES_VERBOSE environment '
101          'variable.'))
102
103  options, args = parser.parse_args()
104
105  if args:
106    parser.error('Unknown arguments %s' % args)
107
108  logging.basicConfig(
109      level=logging.DEBUG if options.verbose else logging.ERROR)
110
111  extra_script = os.environ.get('EXTRA_LANDMINES_SCRIPT')
112  if extra_script:
113    return options.landmine_scripts + [extra_script]
114  else:
115    return options.landmine_scripts
116
117
118def main():
119  landmine_scripts = process_options()
120
121  if landmine_utils.builder() in ('dump_dependency_json', 'eclipse'):
122    return 0
123
124  gyp_environment.SetEnvironment()
125
126  landmines = []
127  for s in landmine_scripts:
128    proc = subprocess.Popen([sys.executable, s], stdout=subprocess.PIPE)
129    output, _ = proc.communicate()
130    landmines.extend([('%s\n' % l.strip()) for l in output.splitlines()])
131  clobber_if_necessary(landmines)
132
133  return 0
134
135
136if __name__ == '__main__':
137  sys.exit(main())
138