Remove no longer needed toolbar layer method.
[chromium-blink-merge.git] / tools / auto_bisect / bisect_utils.py
blob8d7dbd5357f0259eb107382d3a170fa2fb17744e
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Utility functions used by the bisect tool.
7 This includes functions related to checking out the depot and outputting
8 annotations for the Buildbot waterfall.
9 """
11 import errno
12 import imp
13 import os
14 import stat
15 import subprocess
16 import sys
18 DEFAULT_GCLIENT_CUSTOM_DEPS = {
19 'src/data/page_cycler': 'https://chrome-internal.googlesource.com/'
20 'chrome/data/page_cycler/.git',
21 'src/data/dom_perf': 'https://chrome-internal.googlesource.com/'
22 'chrome/data/dom_perf/.git',
23 'src/data/mach_ports': 'https://chrome-internal.googlesource.com/'
24 'chrome/data/mach_ports/.git',
25 'src/tools/perf/data': 'https://chrome-internal.googlesource.com/'
26 'chrome/tools/perf/data/.git',
27 'src/third_party/adobe/flash/binaries/ppapi/linux':
28 'https://chrome-internal.googlesource.com/'
29 'chrome/deps/adobe/flash/binaries/ppapi/linux/.git',
30 'src/third_party/adobe/flash/binaries/ppapi/linux_x64':
31 'https://chrome-internal.googlesource.com/'
32 'chrome/deps/adobe/flash/binaries/ppapi/linux_x64/.git',
33 'src/third_party/adobe/flash/binaries/ppapi/mac':
34 'https://chrome-internal.googlesource.com/'
35 'chrome/deps/adobe/flash/binaries/ppapi/mac/.git',
36 'src/third_party/adobe/flash/binaries/ppapi/mac_64':
37 'https://chrome-internal.googlesource.com/'
38 'chrome/deps/adobe/flash/binaries/ppapi/mac_64/.git',
39 'src/third_party/adobe/flash/binaries/ppapi/win':
40 'https://chrome-internal.googlesource.com/'
41 'chrome/deps/adobe/flash/binaries/ppapi/win/.git',
42 'src/third_party/adobe/flash/binaries/ppapi/win_x64':
43 'https://chrome-internal.googlesource.com/'
44 'chrome/deps/adobe/flash/binaries/ppapi/win_x64/.git',
45 'src/chrome/tools/test/reference_build/chrome_win': None,
46 'src/chrome/tools/test/reference_build/chrome_mac': None,
47 'src/chrome/tools/test/reference_build/chrome_linux': None,
48 'src/third_party/WebKit/LayoutTests': None,
49 'src/tools/valgrind': None,
52 GCLIENT_SPEC_DATA = [
54 'name': 'src',
55 'url': 'https://chromium.googlesource.com/chromium/src.git',
56 'deps_file': '.DEPS.git',
57 'managed': True,
58 'custom_deps': {},
59 'safesync_url': '',
62 GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']"
63 GCLIENT_CUSTOM_DEPS_V8 = {
64 'src/v8_bleeding_edge': 'https://chromium.googlesource.com/v8/v8.git'
66 FILE_DEPS_GIT = '.DEPS.git'
67 FILE_DEPS = 'DEPS'
69 # Bisect working directory.
70 BISECT_DIR = 'bisect'
72 # The percentage at which confidence is considered high.
73 HIGH_CONFIDENCE = 95
75 # Below is the map of "depot" names to information about each depot. Each depot
76 # is a repository, and in the process of bisecting, revision ranges in these
77 # repositories may also be bisected.
79 # Each depot information dictionary may contain:
80 # src: Path to the working directory.
81 # recurse: True if this repository will get bisected.
82 # svn: URL of SVN repository. Needed for git workflow to resolve hashes to
83 # SVN revisions.
84 # from: Parent depot that must be bisected before this is bisected.
85 # deps_var: Key name in vars variable in DEPS file that has revision
86 # information.
87 DEPOT_DEPS_NAME = {
88 'chromium': {
89 'src': 'src',
90 'recurse': True,
91 'from': ['android-chrome'],
92 'viewvc': 'https://chromium.googlesource.com/chromium/src/+/',
93 'deps_var': 'chromium_rev'
95 'webkit': {
96 'src': 'src/third_party/WebKit',
97 'recurse': True,
98 'from': ['chromium'],
99 'viewvc': 'https://chromium.googlesource.com/chromium/blink/+/',
100 'deps_var': 'webkit_revision'
102 'angle': {
103 'src': 'src/third_party/angle',
104 'src_old': 'src/third_party/angle_dx11',
105 'recurse': True,
106 'from': ['chromium'],
107 'platform': 'nt',
108 'viewvc': 'https://chromium.googlesource.com/angle/angle/+/',
109 'deps_var': 'angle_revision'
111 'v8': {
112 'src': 'src/v8',
113 'recurse': True,
114 'from': ['chromium'],
115 'custom_deps': GCLIENT_CUSTOM_DEPS_V8,
116 'viewvc': 'https://chromium.googlesource.com/v8/v8.git/+/',
117 'deps_var': 'v8_revision'
119 'v8_bleeding_edge': {
120 'src': 'src/v8_bleeding_edge',
121 'recurse': True,
122 'svn': 'https://v8.googlecode.com/svn/branches/bleeding_edge',
123 'from': ['v8'],
124 'viewvc': 'https://chromium.googlesource.com/v8/v8.git/+/',
125 'deps_var': 'v8_revision'
127 'skia/src': {
128 'src': 'src/third_party/skia/src',
129 'recurse': True,
130 'from': ['chromium'],
131 'viewvc': 'https://chromium.googlesource.com/skia/+/',
132 'deps_var': 'skia_revision'
136 DEPOT_NAMES = DEPOT_DEPS_NAME.keys()
138 # The possible values of the --bisect_mode flag, which determines what to
139 # use when classifying a revision as "good" or "bad".
140 BISECT_MODE_MEAN = 'mean'
141 BISECT_MODE_STD_DEV = 'std_dev'
142 BISECT_MODE_RETURN_CODE = 'return_code'
145 def AddAdditionalDepotInfo(depot_info):
146 """Adds additional depot info to the global depot variables."""
147 global DEPOT_DEPS_NAME
148 global DEPOT_NAMES
149 DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + depot_info.items())
150 DEPOT_NAMES = DEPOT_DEPS_NAME.keys()
153 def OutputAnnotationStepStart(name):
154 """Outputs annotation to signal the start of a step to a try bot.
156 Args:
157 name: The name of the step.
159 print
160 print '@@@SEED_STEP %s@@@' % name
161 print '@@@STEP_CURSOR %s@@@' % name
162 print '@@@STEP_STARTED@@@'
163 print
164 sys.stdout.flush()
167 def OutputAnnotationStepClosed():
168 """Outputs annotation to signal the closing of a step to a try bot."""
169 print
170 print '@@@STEP_CLOSED@@@'
171 print
172 sys.stdout.flush()
175 def OutputAnnotationStepText(text):
176 """Outputs appropriate annotation to print text.
178 Args:
179 name: The text to print.
181 print
182 print '@@@STEP_TEXT@%s@@@' % text
183 print
184 sys.stdout.flush()
187 def OutputAnnotationStepWarning():
188 """Outputs appropriate annotation to signal a warning."""
189 print
190 print '@@@STEP_WARNINGS@@@'
191 print
194 def OutputAnnotationStepLink(label, url):
195 """Outputs appropriate annotation to print a link.
197 Args:
198 label: The name to print.
199 url: The URL to print.
201 print
202 print '@@@STEP_LINK@%s@%s@@@' % (label, url)
203 print
204 sys.stdout.flush()
207 def LoadExtraSrc(path_to_file):
208 """Attempts to load an extra source file, and overrides global values.
210 If the extra source file is loaded successfully, then it will use the new
211 module to override some global values, such as gclient spec data.
213 Args:
214 path_to_file: File path.
216 Returns:
217 The loaded module object, or None if none was imported.
219 try:
220 global GCLIENT_SPEC_DATA
221 global GCLIENT_SPEC_ANDROID
222 extra_src = imp.load_source('data', path_to_file)
223 GCLIENT_SPEC_DATA = extra_src.GetGClientSpec()
224 GCLIENT_SPEC_ANDROID = extra_src.GetGClientSpecExtraParams()
225 return extra_src
226 except ImportError:
227 return None
230 def IsTelemetryCommand(command):
231 """Attempts to discern whether or not a given command is running telemetry."""
232 return 'tools/perf/run_' in command or 'tools\\perf\\run_' in command
235 def _CreateAndChangeToSourceDirectory(working_directory):
236 """Creates a directory 'bisect' as a subdirectory of |working_directory|.
238 If successful, the current working directory will be changed to the new
239 'bisect' directory.
241 Args:
242 working_directory: The directory to create the new 'bisect' directory in.
244 Returns:
245 True if the directory was successfully created (or already existed).
247 cwd = os.getcwd()
248 os.chdir(working_directory)
249 try:
250 os.mkdir(BISECT_DIR)
251 except OSError, e:
252 if e.errno != errno.EEXIST: # EEXIST indicates that it already exists.
253 os.chdir(cwd)
254 return False
255 os.chdir(BISECT_DIR)
256 return True
259 def _SubprocessCall(cmd, cwd=None):
260 """Runs a command in a subprocess.
262 Args:
263 cmd: The command to run.
264 cwd: Working directory to run from.
266 Returns:
267 The return code of the call.
269 if os.name == 'nt':
270 # "HOME" isn't normally defined on windows, but is needed
271 # for git to find the user's .netrc file.
272 if not os.getenv('HOME'):
273 os.environ['HOME'] = os.environ['USERPROFILE']
274 shell = os.name == 'nt'
275 return subprocess.call(cmd, shell=shell, cwd=cwd)
278 def RunGClient(params, cwd=None):
279 """Runs gclient with the specified parameters.
281 Args:
282 params: A list of parameters to pass to gclient.
283 cwd: Working directory to run from.
285 Returns:
286 The return code of the call.
288 cmd = ['gclient'] + params
289 return _SubprocessCall(cmd, cwd=cwd)
292 def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None):
293 """Runs gclient and creates a config containing both src and src-internal.
295 Args:
296 opts: The options parsed from the command line through parse_args().
297 custom_deps: A dictionary of additional dependencies to add to .gclient.
298 cwd: Working directory to run from.
300 Returns:
301 The return code of the call.
303 spec = GCLIENT_SPEC_DATA
305 if custom_deps:
306 for k, v in custom_deps.iteritems():
307 spec[0]['custom_deps'][k] = v
309 # Cannot have newlines in string on windows
310 spec = 'solutions =' + str(spec)
311 spec = ''.join([l for l in spec.splitlines()])
313 if 'android' in opts.target_platform:
314 spec += GCLIENT_SPEC_ANDROID
316 return_code = RunGClient(
317 ['config', '--spec=%s' % spec], cwd=cwd)
318 return return_code
322 def OnAccessError(func, path, _):
323 """Error handler for shutil.rmtree.
325 Source: http://goo.gl/DEYNCT
327 If the error is due to an access error (read only file), it attempts to add
328 write permissions, then retries.
330 If the error is for another reason it re-raises the error.
332 Args:
333 func: The function that raised the error.
334 path: The path name passed to func.
335 _: Exception information from sys.exc_info(). Not used.
337 if not os.access(path, os.W_OK):
338 os.chmod(path, stat.S_IWUSR)
339 func(path)
340 else:
341 raise
344 def _CleanupPreviousGitRuns(cwd=os.getcwd()):
345 """Cleans up any leftover index.lock files after running git."""
346 # If a previous run of git crashed, or bot was reset, etc., then we might
347 # end up with leftover index.lock files.
348 for path, _, files in os.walk(cwd):
349 for cur_file in files:
350 if cur_file.endswith('index.lock'):
351 path_to_file = os.path.join(path, cur_file)
352 os.remove(path_to_file)
355 def RunGClientAndSync(revisions=None, cwd=None):
356 """Runs gclient and does a normal sync.
358 Args:
359 revisions: List of revisions that need to be synced.
360 E.g., "src@2ae43f...", "src/third_party/webkit@asr1234" etc.
361 cwd: Working directory to run from.
363 Returns:
364 The return code of the call.
366 params = ['sync', '--verbose', '--nohooks', '--force',
367 '--delete_unversioned_trees']
368 if revisions is not None:
369 for revision in revisions:
370 if revision is not None:
371 params.extend(['--revision', revision])
372 return RunGClient(params, cwd=cwd)
375 def SetupGitDepot(opts, custom_deps):
376 """Sets up the depot for the bisection.
378 The depot will be located in a subdirectory called 'bisect'.
380 Args:
381 opts: The options parsed from the command line through parse_args().
382 custom_deps: A dictionary of additional dependencies to add to .gclient.
384 Returns:
385 True if gclient successfully created the config file and did a sync, False
386 otherwise.
388 name = 'Setting up Bisection Depot'
389 try:
390 if opts.output_buildbot_annotations:
391 OutputAnnotationStepStart(name)
393 if RunGClientAndCreateConfig(opts, custom_deps):
394 return False
396 _CleanupPreviousGitRuns()
397 RunGClient(['revert'])
398 return not RunGClientAndSync()
399 finally:
400 if opts.output_buildbot_annotations:
401 OutputAnnotationStepClosed()
404 def CheckIfBisectDepotExists(opts):
405 """Checks if the bisect directory already exists.
407 Args:
408 opts: The options parsed from the command line through parse_args().
410 Returns:
411 Returns True if it exists.
413 path_to_dir = os.path.join(opts.working_directory, BISECT_DIR, 'src')
414 return os.path.exists(path_to_dir)
417 def CheckRunGit(command, cwd=None):
418 """Run a git subcommand, returning its output and return code. Asserts if
419 the return code of the call is non-zero.
421 Args:
422 command: A list containing the args to git.
424 Returns:
425 A tuple of the output and return code.
427 (output, return_code) = RunGit(command, cwd=cwd)
429 assert not return_code, 'An error occurred while running'\
430 ' "git %s"' % ' '.join(command)
431 return output
434 def RunGit(command, cwd=None):
435 """Run a git subcommand, returning its output and return code.
437 Args:
438 command: A list containing the args to git.
439 cwd: A directory to change to while running the git command (optional).
441 Returns:
442 A tuple of the output and return code.
444 command = ['git'] + command
445 return RunProcessAndRetrieveOutput(command, cwd=cwd)
448 def CreateBisectDirectoryAndSetupDepot(opts, custom_deps):
449 """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot
450 there using gclient.
452 Args:
453 opts: The options parsed from the command line through parse_args().
454 custom_deps: A dictionary of additional dependencies to add to .gclient.
456 if CheckIfBisectDepotExists(opts):
457 path_to_dir = os.path.join(os.path.abspath(opts.working_directory),
458 BISECT_DIR, 'src')
459 (output, _) = RunGit(['rev-parse', '--is-inside-work-tree'],
460 cwd=path_to_dir)
461 if output.strip() == 'true':
462 # Before checking out master, cleanup up any leftover index.lock files.
463 _CleanupPreviousGitRuns(path_to_dir)
464 # Checks out the master branch, throws an exception if git command fails.
465 CheckRunGit(['checkout', '-f', 'master'], cwd=path_to_dir)
466 if not _CreateAndChangeToSourceDirectory(opts.working_directory):
467 raise RuntimeError('Could not create bisect directory.')
469 if not SetupGitDepot(opts, custom_deps):
470 raise RuntimeError('Failed to grab source.')
473 def RunProcess(command):
474 """Runs an arbitrary command.
476 If output from the call is needed, use RunProcessAndRetrieveOutput instead.
478 Args:
479 command: A list containing the command and args to execute.
481 Returns:
482 The return code of the call.
484 # On Windows, use shell=True to get PATH interpretation.
485 shell = IsWindowsHost()
486 return subprocess.call(command, shell=shell)
489 def RunProcessAndRetrieveOutput(command, cwd=None):
490 """Runs an arbitrary command, returning its output and return code.
492 Since output is collected via communicate(), there will be no output until
493 the call terminates. If you need output while the program runs (ie. so
494 that the buildbot doesn't terminate the script), consider RunProcess().
496 Args:
497 command: A list containing the command and args to execute.
498 cwd: A directory to change to while running the command. The command can be
499 relative to this directory. If this is None, the command will be run in
500 the current directory.
502 Returns:
503 A tuple of the output and return code.
505 if cwd:
506 original_cwd = os.getcwd()
507 os.chdir(cwd)
509 # On Windows, use shell=True to get PATH interpretation.
510 shell = IsWindowsHost()
511 proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE,
512 stderr=subprocess.STDOUT)
513 (output, _) = proc.communicate()
515 if cwd:
516 os.chdir(original_cwd)
518 return (output, proc.returncode)
521 def IsStringInt(string_to_check):
522 """Checks whether or not the given string can be converted to an int."""
523 try:
524 int(string_to_check)
525 return True
526 except ValueError:
527 return False
530 def IsStringFloat(string_to_check):
531 """Checks whether or not the given string can be converted to a float."""
532 try:
533 float(string_to_check)
534 return True
535 except ValueError:
536 return False
539 def IsWindowsHost():
540 return sys.platform == 'cygwin' or sys.platform.startswith('win')
543 def Is64BitWindows():
544 """Checks whether or not Windows is a 64-bit version."""
545 platform = os.environ.get('PROCESSOR_ARCHITEW6432')
546 if not platform:
547 # Must not be running in WoW64, so PROCESSOR_ARCHITECTURE is correct.
548 platform = os.environ.get('PROCESSOR_ARCHITECTURE')
549 return platform and platform in ['AMD64', 'I64']
552 def IsLinuxHost():
553 return sys.platform.startswith('linux')
556 def IsMacHost():
557 return sys.platform.startswith('darwin')