[SyncFS] Initialize SyncWorker when sync is enabled.
[chromium-blink-merge.git] / tools / auto_bisect / bisect_utils.py
blob330c4648508f91457200dbd3acbfd29efe0e75d2
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 shutil
15 import stat
16 import subprocess
17 import sys
19 DEFAULT_GCLIENT_CUSTOM_DEPS = {
20 'src/data/page_cycler': 'https://chrome-internal.googlesource.com/'
21 'chrome/data/page_cycler/.git',
22 'src/data/dom_perf': 'https://chrome-internal.googlesource.com/'
23 'chrome/data/dom_perf/.git',
24 'src/data/mach_ports': 'https://chrome-internal.googlesource.com/'
25 'chrome/data/mach_ports/.git',
26 'src/tools/perf/data': 'https://chrome-internal.googlesource.com/'
27 'chrome/tools/perf/data/.git',
28 'src/third_party/adobe/flash/binaries/ppapi/linux':
29 'https://chrome-internal.googlesource.com/'
30 'chrome/deps/adobe/flash/binaries/ppapi/linux/.git',
31 'src/third_party/adobe/flash/binaries/ppapi/linux_x64':
32 'https://chrome-internal.googlesource.com/'
33 'chrome/deps/adobe/flash/binaries/ppapi/linux_x64/.git',
34 'src/third_party/adobe/flash/binaries/ppapi/mac':
35 'https://chrome-internal.googlesource.com/'
36 'chrome/deps/adobe/flash/binaries/ppapi/mac/.git',
37 'src/third_party/adobe/flash/binaries/ppapi/mac_64':
38 'https://chrome-internal.googlesource.com/'
39 'chrome/deps/adobe/flash/binaries/ppapi/mac_64/.git',
40 'src/third_party/adobe/flash/binaries/ppapi/win':
41 'https://chrome-internal.googlesource.com/'
42 'chrome/deps/adobe/flash/binaries/ppapi/win/.git',
43 'src/third_party/adobe/flash/binaries/ppapi/win_x64':
44 'https://chrome-internal.googlesource.com/'
45 'chrome/deps/adobe/flash/binaries/ppapi/win_x64/.git',
46 'src/chrome/tools/test/reference_build/chrome_win': None,
47 'src/chrome/tools/test/reference_build/chrome_mac': None,
48 'src/chrome/tools/test/reference_build/chrome_linux': None,
49 'src/third_party/WebKit/LayoutTests': None,
50 'src/tools/valgrind': None,
53 GCLIENT_SPEC_DATA = [
55 'name': 'src',
56 'url': 'https://chromium.googlesource.com/chromium/src.git',
57 'deps_file': '.DEPS.git',
58 'managed': True,
59 'custom_deps': {},
60 'safesync_url': '',
63 GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']"
64 GCLIENT_CUSTOM_DEPS_V8 = {'src/v8_bleeding_edge': 'git://github.com/v8/v8.git'}
65 FILE_DEPS_GIT = '.DEPS.git'
66 FILE_DEPS = 'DEPS'
68 REPO_SYNC_COMMAND = ('git checkout -f $(git rev-list --max-count=1 '
69 '--before=%d remotes/m/master)')
71 # Paths to CrOS-related files.
72 # WARNING(qyearsley, 2014-08-15): These haven't been tested recently.
73 CROS_SDK_PATH = os.path.join('..', 'cros', 'chromite', 'bin', 'cros_sdk')
74 CROS_TEST_KEY_PATH = os.path.join(
75 '..', 'cros', 'chromite', 'ssh_keys', 'testing_rsa')
76 CROS_SCRIPT_KEY_PATH = os.path.join(
77 '..', 'cros', 'src', 'scripts', 'mod_for_test_scripts', 'ssh_keys',
78 'testing_rsa')
80 REPO_PARAMS = [
81 'https://chrome-internal.googlesource.com/chromeos/manifest-internal/',
82 '--repo-url',
83 'https://git.chromium.org/external/repo.git'
87 def OutputAnnotationStepStart(name):
88 """Outputs annotation to signal the start of a step to a try bot.
90 Args:
91 name: The name of the step.
92 """
93 print
94 print '@@@SEED_STEP %s@@@' % name
95 print '@@@STEP_CURSOR %s@@@' % name
96 print '@@@STEP_STARTED@@@'
97 print
98 sys.stdout.flush()
101 def OutputAnnotationStepClosed():
102 """Outputs annotation to signal the closing of a step to a try bot."""
103 print
104 print '@@@STEP_CLOSED@@@'
105 print
106 sys.stdout.flush()
109 def OutputAnnotationStepLink(label, url):
110 """Outputs appropriate annotation to print a link.
112 Args:
113 label: The name to print.
114 url: The URL to print.
116 print
117 print '@@@STEP_LINK@%s@%s@@@' % (label, url)
118 print
119 sys.stdout.flush()
122 def LoadExtraSrc(path_to_file):
123 """Attempts to load an extra source file, and overrides global values.
125 If the extra source file is loaded successfully, then it will use the new
126 module to override some global values, such as gclient spec data.
128 Args:
129 path_to_file: File path.
131 Returns:
132 The loaded module object, or None if none was imported.
134 try:
135 global GCLIENT_SPEC_DATA
136 global GCLIENT_SPEC_ANDROID
137 extra_src = imp.load_source('data', path_to_file)
138 GCLIENT_SPEC_DATA = extra_src.GetGClientSpec()
139 GCLIENT_SPEC_ANDROID = extra_src.GetGClientSpecExtraParams()
140 return extra_src
141 except ImportError:
142 return None
145 def IsTelemetryCommand(command):
146 """Attempts to discern whether or not a given command is running telemetry."""
147 return ('tools/perf/run_' in command or 'tools\\perf\\run_' in command)
150 def _CreateAndChangeToSourceDirectory(working_directory):
151 """Creates a directory 'bisect' as a subdirectory of |working_directory|.
153 If successful, the current working directory will be changed to the new
154 'bisect' directory.
156 Args:
157 working_directory: The directory to create the new 'bisect' directory in.
159 Returns:
160 True if the directory was successfully created (or already existed).
162 cwd = os.getcwd()
163 os.chdir(working_directory)
164 try:
165 os.mkdir('bisect')
166 except OSError, e:
167 if e.errno != errno.EEXIST: # EEXIST indicates that it already exists.
168 os.chdir(cwd)
169 return False
170 os.chdir('bisect')
171 return True
174 def _SubprocessCall(cmd, cwd=None):
175 """Runs a command in a subprocess.
177 Args:
178 cmd: The command to run.
179 cwd: Working directory to run from.
181 Returns:
182 The return code of the call.
184 if os.name == 'nt':
185 # "HOME" isn't normally defined on windows, but is needed
186 # for git to find the user's .netrc file.
187 if not os.getenv('HOME'):
188 os.environ['HOME'] = os.environ['USERPROFILE']
189 shell = os.name == 'nt'
190 return subprocess.call(cmd, shell=shell, cwd=cwd)
193 def RunGClient(params, cwd=None):
194 """Runs gclient with the specified parameters.
196 Args:
197 params: A list of parameters to pass to gclient.
198 cwd: Working directory to run from.
200 Returns:
201 The return code of the call.
203 cmd = ['gclient'] + params
204 return _SubprocessCall(cmd, cwd=cwd)
207 def SetupCrosRepo():
208 """Sets up CrOS repo for bisecting ChromeOS.
210 Returns:
211 True if successful, False otherwise.
213 cwd = os.getcwd()
214 try:
215 os.mkdir('cros')
216 except OSError as e:
217 if e.errno != errno.EEXIST: # EEXIST means the directory already exists.
218 return False
219 os.chdir('cros')
221 cmd = ['init', '-u'] + REPO_PARAMS
223 passed = False
225 if not _RunRepo(cmd):
226 if not _RunRepo(['sync']):
227 passed = True
228 os.chdir(cwd)
230 return passed
233 def _RunRepo(params):
234 """Runs CrOS repo command with specified parameters.
236 Args:
237 params: A list of parameters to pass to gclient.
239 Returns:
240 The return code of the call (zero indicates success).
242 cmd = ['repo'] + params
243 return _SubprocessCall(cmd)
246 def RunRepoSyncAtTimestamp(timestamp):
247 """Syncs all git depots to the timestamp specified using repo forall.
249 Args:
250 params: Unix timestamp to sync to.
252 Returns:
253 The return code of the call.
255 cmd = ['forall', '-c', REPO_SYNC_COMMAND % timestamp]
256 return _RunRepo(cmd)
259 def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None):
260 """Runs gclient and creates a config containing both src and src-internal.
262 Args:
263 opts: The options parsed from the command line through parse_args().
264 custom_deps: A dictionary of additional dependencies to add to .gclient.
265 cwd: Working directory to run from.
267 Returns:
268 The return code of the call.
270 spec = GCLIENT_SPEC_DATA
272 if custom_deps:
273 for k, v in custom_deps.iteritems():
274 spec[0]['custom_deps'][k] = v
276 # Cannot have newlines in string on windows
277 spec = 'solutions =' + str(spec)
278 spec = ''.join([l for l in spec.splitlines()])
280 if 'android' in opts.target_platform:
281 spec += GCLIENT_SPEC_ANDROID
283 return_code = RunGClient(
284 ['config', '--spec=%s' % spec], cwd=cwd)
285 return return_code
289 def OnAccessError(func, path, _):
290 """Error handler for shutil.rmtree.
292 Source: http://goo.gl/DEYNCT
294 If the error is due to an access error (read only file), it attempts to add
295 write permissions, then retries.
297 If the error is for another reason it re-raises the error.
299 Args:
300 func: The function that raised the error.
301 path: The path name passed to func.
302 _: Exception information from sys.exc_info(). Not used.
304 if not os.access(path, os.W_OK):
305 os.chmod(path, stat.S_IWUSR)
306 func(path)
307 else:
308 raise
311 def RemoveThirdPartyDirectory(dir_name):
312 """Removes third_party directory from the source.
314 At some point, some of the third_parties were causing issues to changes in
315 the way they are synced. We remove such folder in order to avoid sync errors
316 while bisecting.
318 Returns:
319 True on success, otherwise False.
321 path_to_dir = os.path.join(os.getcwd(), 'third_party', dir_name)
322 try:
323 if os.path.exists(path_to_dir):
324 shutil.rmtree(path_to_dir, onerror=OnAccessError)
325 except OSError, e:
326 print 'Error #%d while running shutil.rmtree(%s): %s' % (
327 e.errno, path_to_dir, str(e))
328 if e.errno != errno.ENOENT:
329 return False
330 return True
333 def _CleanupPreviousGitRuns():
334 """Cleans up any leftover index.lock files after running git."""
335 # If a previous run of git crashed, or bot was reset, etc., then we might
336 # end up with leftover index.lock files.
337 for path, _, files in os.walk(os.getcwd()):
338 for cur_file in files:
339 if cur_file.endswith('index.lock'):
340 path_to_file = os.path.join(path, cur_file)
341 os.remove(path_to_file)
344 def RunGClientAndSync(cwd=None):
345 """Runs gclient and does a normal sync.
347 Args:
348 cwd: Working directory to run from.
350 Returns:
351 The return code of the call.
353 params = ['sync', '--verbose', '--nohooks', '--reset', '--force']
354 return RunGClient(params, cwd=cwd)
357 def SetupGitDepot(opts, custom_deps):
358 """Sets up the depot for the bisection.
360 The depot will be located in a subdirectory called 'bisect'.
362 Args:
363 opts: The options parsed from the command line through parse_args().
364 custom_deps: A dictionary of additional dependencies to add to .gclient.
366 Returns:
367 True if gclient successfully created the config file and did a sync, False
368 otherwise.
370 name = 'Setting up Bisection Depot'
372 if opts.output_buildbot_annotations:
373 OutputAnnotationStepStart(name)
375 passed = False
377 if not RunGClientAndCreateConfig(opts, custom_deps):
378 passed_deps_check = True
379 if os.path.isfile(os.path.join('src', FILE_DEPS_GIT)):
380 cwd = os.getcwd()
381 os.chdir('src')
382 passed_deps_check = True
383 if passed_deps_check:
384 passed_deps_check = RemoveThirdPartyDirectory('libjingle')
385 if passed_deps_check:
386 passed_deps_check = RemoveThirdPartyDirectory('skia')
387 os.chdir(cwd)
389 if passed_deps_check:
390 _CleanupPreviousGitRuns()
392 RunGClient(['revert'])
393 if not RunGClientAndSync():
394 passed = True
396 if opts.output_buildbot_annotations:
397 print
398 OutputAnnotationStepClosed()
400 return passed
403 def CheckIfBisectDepotExists(opts):
404 """Checks if the bisect directory already exists.
406 Args:
407 opts: The options parsed from the command line through parse_args().
409 Returns:
410 Returns True if it exists.
412 path_to_dir = os.path.join(opts.working_directory, 'bisect', 'src')
413 return os.path.exists(path_to_dir)
416 def CheckRunGit(command, cwd=None):
417 """Run a git subcommand, returning its output and return code. Asserts if
418 the return code of the call is non-zero.
420 Args:
421 command: A list containing the args to git.
423 Returns:
424 A tuple of the output and return code.
426 (output, return_code) = RunGit(command, cwd=cwd)
428 assert not return_code, 'An error occurred while running'\
429 ' "git %s"' % ' '.join(command)
430 return output
433 def RunGit(command, cwd=None):
434 """Run a git subcommand, returning its output and return code.
436 Args:
437 command: A list containing the args to git.
438 cwd: A directory to change to while running the git command (optional).
440 Returns:
441 A tuple of the output and return code.
443 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 not _CreateAndChangeToSourceDirectory(opts.working_directory):
457 raise RuntimeError('Could not create bisect directory.')
459 if not SetupGitDepot(opts, custom_deps):
460 raise RuntimeError('Failed to grab source.')
463 def RunProcess(command):
464 """Runs an arbitrary command.
466 If output from the call is needed, use RunProcessAndRetrieveOutput instead.
468 Args:
469 command: A list containing the command and args to execute.
471 Returns:
472 The return code of the call.
474 # On Windows, use shell=True to get PATH interpretation.
475 shell = IsWindowsHost()
476 return subprocess.call(command, shell=shell)
479 def RunProcessAndRetrieveOutput(command, cwd=None):
480 """Runs an arbitrary command, returning its output and return code.
482 Since output is collected via communicate(), there will be no output until
483 the call terminates. If you need output while the program runs (ie. so
484 that the buildbot doesn't terminate the script), consider RunProcess().
486 Args:
487 command: A list containing the command and args to execute.
488 cwd: A directory to change to while running the command. The command can be
489 relative to this directory. If this is None, the command will be run in
490 the current directory.
492 Returns:
493 A tuple of the output and return code.
495 if cwd:
496 original_cwd = os.getcwd()
497 os.chdir(cwd)
499 # On Windows, use shell=True to get PATH interpretation.
500 shell = IsWindowsHost()
501 proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE)
502 (output, _) = proc.communicate()
504 if cwd:
505 os.chdir(original_cwd)
507 return (output, proc.returncode)
510 def IsStringInt(string_to_check):
511 """Checks whether or not the given string can be converted to a integer.
513 Args:
514 string_to_check: Input string to check if it can be converted to an int.
516 Returns:
517 True if the string can be converted to an int.
519 try:
520 int(string_to_check)
521 return True
522 except ValueError:
523 return False
526 def IsStringFloat(string_to_check):
527 """Checks whether or not the given string can be converted to a floating
528 point number.
530 Args:
531 string_to_check: Input string to check if it can be converted to a float.
533 Returns:
534 True if the string can be converted to a float.
536 try:
537 float(string_to_check)
538 return True
539 except ValueError:
540 return False
543 def IsWindowsHost():
544 """Checks whether or not the script is running on Windows.
546 Returns:
547 True if running on Windows.
549 return sys.platform == 'cygwin' or sys.platform.startswith('win')
552 def Is64BitWindows():
553 """Returns whether or not Windows is a 64-bit version.
555 Returns:
556 True if Windows is 64-bit, False if 32-bit.
558 platform = os.environ['PROCESSOR_ARCHITECTURE']
559 try:
560 platform = os.environ['PROCESSOR_ARCHITEW6432']
561 except KeyError:
562 # Must not be running in WoW64, so PROCESSOR_ARCHITECTURE is correct
563 pass
565 return platform in ['AMD64', 'I64']
568 def IsLinuxHost():
569 """Checks whether or not the script is running on Linux.
571 Returns:
572 True if running on Linux.
574 return sys.platform.startswith('linux')
577 def IsMacHost():
578 """Checks whether or not the script is running on Mac.
580 Returns:
581 True if running on Mac.
583 return sys.platform.startswith('darwin')