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.
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,
55 'url': 'https://chromium.googlesource.com/chromium/src.git',
56 'deps_file': '.DEPS.git',
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'
69 # Bisect working directory.
72 # The percentage at which confidence is considered high.
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
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
91 'from': ['android-chrome'],
92 'viewvc': 'https://chromium.googlesource.com/chromium/src/+/',
93 'deps_var': 'chromium_rev'
96 'src': 'src/third_party/WebKit',
99 'viewvc': 'https://chromium.googlesource.com/chromium/blink/+/',
100 'deps_var': 'webkit_revision'
103 'src': 'src/third_party/angle',
104 'src_old': 'src/third_party/angle_dx11',
106 'from': ['chromium'],
108 'viewvc': 'https://chromium.googlesource.com/angle/angle/+/',
109 'deps_var': 'angle_revision'
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',
122 'svn': 'https://v8.googlecode.com/svn/branches/bleeding_edge',
124 'viewvc': 'https://chromium.googlesource.com/v8/v8.git/+/',
125 'deps_var': 'v8_revision'
128 'src': 'src/third_party/skia/src',
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
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.
157 name: The name of the step.
160 print '@@@SEED_STEP %s@@@' % name
161 print '@@@STEP_CURSOR %s@@@' % name
162 print '@@@STEP_STARTED@@@'
167 def OutputAnnotationStepClosed():
168 """Outputs annotation to signal the closing of a step to a try bot."""
170 print '@@@STEP_CLOSED@@@'
175 def OutputAnnotationStepText(text
):
176 """Outputs appropriate annotation to print text.
179 name: The text to print.
182 print '@@@STEP_TEXT@%s@@@' % text
187 def OutputAnnotationStepWarning():
188 """Outputs appropriate annotation to signal a warning."""
190 print '@@@STEP_WARNINGS@@@'
194 def OutputAnnotationStepFailure():
195 """Outputs appropriate annotation to signal a warning."""
197 print '@@@STEP_FAILURE@@@'
201 def OutputAnnotationStepLink(label
, url
):
202 """Outputs appropriate annotation to print a link.
205 label: The name to print.
206 url: The URL to print.
209 print '@@@STEP_LINK@%s@%s@@@' % (label
, url
)
214 def LoadExtraSrc(path_to_file
):
215 """Attempts to load an extra source file, and overrides global values.
217 If the extra source file is loaded successfully, then it will use the new
218 module to override some global values, such as gclient spec data.
221 path_to_file: File path.
224 The loaded module object, or None if none was imported.
227 global GCLIENT_SPEC_DATA
228 global GCLIENT_SPEC_ANDROID
229 extra_src
= imp
.load_source('data', path_to_file
)
230 GCLIENT_SPEC_DATA
= extra_src
.GetGClientSpec()
231 GCLIENT_SPEC_ANDROID
= extra_src
.GetGClientSpecExtraParams()
237 def IsTelemetryCommand(command
):
238 """Attempts to discern whether or not a given command is running telemetry."""
239 return 'tools/perf/run_' in command
or 'tools\\perf\\run_' in command
242 def _CreateAndChangeToSourceDirectory(working_directory
):
243 """Creates a directory 'bisect' as a subdirectory of |working_directory|.
245 If successful, the current working directory will be changed to the new
249 working_directory: The directory to create the new 'bisect' directory in.
252 True if the directory was successfully created (or already existed).
255 os
.chdir(working_directory
)
259 if e
.errno
!= errno
.EEXIST
: # EEXIST indicates that it already exists.
266 def _SubprocessCall(cmd
, cwd
=None):
267 """Runs a command in a subprocess.
270 cmd: The command to run.
271 cwd: Working directory to run from.
274 The return code of the call.
277 # "HOME" isn't normally defined on windows, but is needed
278 # for git to find the user's .netrc file.
279 if not os
.getenv('HOME'):
280 os
.environ
['HOME'] = os
.environ
['USERPROFILE']
281 shell
= os
.name
== 'nt'
282 return subprocess
.call(cmd
, shell
=shell
, cwd
=cwd
)
285 def RunGClient(params
, cwd
=None):
286 """Runs gclient with the specified parameters.
289 params: A list of parameters to pass to gclient.
290 cwd: Working directory to run from.
293 The return code of the call.
295 cmd
= ['gclient'] + params
296 return _SubprocessCall(cmd
, cwd
=cwd
)
299 def RunGClientAndCreateConfig(opts
, custom_deps
=None, cwd
=None):
300 """Runs gclient and creates a config containing both src and src-internal.
303 opts: The options parsed from the command line through parse_args().
304 custom_deps: A dictionary of additional dependencies to add to .gclient.
305 cwd: Working directory to run from.
308 The return code of the call.
310 spec
= GCLIENT_SPEC_DATA
313 for k
, v
in custom_deps
.iteritems():
314 spec
[0]['custom_deps'][k
] = v
316 # Cannot have newlines in string on windows
317 spec
= 'solutions =' + str(spec
)
318 spec
= ''.join([l
for l
in spec
.splitlines()])
320 if 'android' in opts
.target_platform
:
321 spec
+= GCLIENT_SPEC_ANDROID
323 return_code
= RunGClient(
324 ['config', '--spec=%s' % spec
], cwd
=cwd
)
328 def OnAccessError(func
, path
, _
):
329 """Error handler for shutil.rmtree.
331 Source: http://goo.gl/DEYNCT
333 If the error is due to an access error (read only file), it attempts to add
334 write permissions, then retries.
336 If the error is for another reason it re-raises the error.
339 func: The function that raised the error.
340 path: The path name passed to func.
341 _: Exception information from sys.exc_info(). Not used.
343 if not os
.access(path
, os
.W_OK
):
344 os
.chmod(path
, stat
.S_IWUSR
)
350 def _CleanupPreviousGitRuns(cwd
=os
.getcwd()):
351 """Cleans up any leftover index.lock files after running git."""
352 # If a previous run of git crashed, or bot was reset, etc., then we might
353 # end up with leftover index.lock files.
354 for path
, _
, files
in os
.walk(cwd
):
355 for cur_file
in files
:
356 if cur_file
.endswith('index.lock'):
357 path_to_file
= os
.path
.join(path
, cur_file
)
358 os
.remove(path_to_file
)
361 def RunGClientAndSync(revisions
=None, cwd
=None):
362 """Runs gclient and does a normal sync.
365 revisions: List of revisions that need to be synced.
366 E.g., "src@2ae43f...", "src/third_party/webkit@asr1234" etc.
367 cwd: Working directory to run from.
370 The return code of the call.
372 params
= ['sync', '--verbose', '--nohooks', '--force',
373 '--delete_unversioned_trees']
374 if revisions
is not None:
375 for revision
in revisions
:
376 if revision
is not None:
377 params
.extend(['--revision', revision
])
378 return RunGClient(params
, cwd
=cwd
)
381 def SetupGitDepot(opts
, custom_deps
):
382 """Sets up the depot for the bisection.
384 The depot will be located in a subdirectory called 'bisect'.
387 opts: The options parsed from the command line through parse_args().
388 custom_deps: A dictionary of additional dependencies to add to .gclient.
391 True if gclient successfully created the config file and did a sync, False
394 name
= 'Setting up Bisection Depot'
396 if opts
.output_buildbot_annotations
:
397 OutputAnnotationStepStart(name
)
399 if RunGClientAndCreateConfig(opts
, custom_deps
):
402 _CleanupPreviousGitRuns()
403 RunGClient(['revert'])
404 return not RunGClientAndSync()
406 if opts
.output_buildbot_annotations
:
407 OutputAnnotationStepClosed()
410 def CheckIfBisectDepotExists(opts
):
411 """Checks if the bisect directory already exists.
414 opts: The options parsed from the command line through parse_args().
417 Returns True if it exists.
419 path_to_dir
= os
.path
.join(opts
.working_directory
, BISECT_DIR
, 'src')
420 return os
.path
.exists(path_to_dir
)
423 def CheckRunGit(command
, cwd
=None):
424 """Run a git subcommand, returning its output and return code. Asserts if
425 the return code of the call is non-zero.
428 command: A list containing the args to git.
431 A tuple of the output and return code.
433 output
, return_code
= RunGit(command
, cwd
=cwd
)
435 assert not return_code
, 'An error occurred while running'\
436 ' "git %s"' % ' '.join(command
)
440 def RunGit(command
, cwd
=None):
441 """Run a git subcommand, returning its output and return code.
444 command: A list containing the args to git.
445 cwd: A directory to change to while running the git command (optional).
448 A tuple of the output and return code.
450 command
= ['git'] + command
451 return RunProcessAndRetrieveOutput(command
, cwd
=cwd
)
454 def CreateBisectDirectoryAndSetupDepot(opts
, custom_deps
):
455 """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot
459 opts: The options parsed from the command line through parse_args().
460 custom_deps: A dictionary of additional dependencies to add to .gclient.
462 if CheckIfBisectDepotExists(opts
):
463 path_to_dir
= os
.path
.join(os
.path
.abspath(opts
.working_directory
),
465 output
, _
= RunGit(['rev-parse', '--is-inside-work-tree'], cwd
=path_to_dir
)
466 if output
.strip() == 'true':
467 # Before checking out master, cleanup up any leftover index.lock files.
468 _CleanupPreviousGitRuns(path_to_dir
)
469 # Checks out the master branch, throws an exception if git command fails.
470 CheckRunGit(['checkout', '-f', 'master'], cwd
=path_to_dir
)
471 if not _CreateAndChangeToSourceDirectory(opts
.working_directory
):
472 raise RuntimeError('Could not create bisect directory.')
474 if not SetupGitDepot(opts
, custom_deps
):
475 raise RuntimeError('Failed to grab source.')
478 def RunProcess(command
, cwd
=None, shell
=False):
479 """Runs an arbitrary command.
481 If output from the call is needed, use RunProcessAndRetrieveOutput instead.
484 command: A list containing the command and args to execute.
487 The return code of the call.
489 # On Windows, use shell=True to get PATH interpretation.
490 shell
= shell
or IsWindowsHost()
491 return subprocess
.call(command
, cwd
=cwd
, shell
=shell
)
494 def RunProcessAndRetrieveOutput(command
, cwd
=None):
495 """Runs an arbitrary command, returning its output and return code.
497 Since output is collected via communicate(), there will be no output until
498 the call terminates. If you need output while the program runs (ie. so
499 that the buildbot doesn't terminate the script), consider RunProcess().
502 command: A list containing the command and args to execute.
503 cwd: A directory to change to while running the command. The command can be
504 relative to this directory. If this is None, the command will be run in
505 the current directory.
508 A tuple of the output and return code.
511 original_cwd
= os
.getcwd()
514 # On Windows, use shell=True to get PATH interpretation.
515 shell
= IsWindowsHost()
516 proc
= subprocess
.Popen(
517 command
, shell
=shell
, stdout
=subprocess
.PIPE
,
518 stderr
=subprocess
.STDOUT
)
519 output
, _
= proc
.communicate()
522 os
.chdir(original_cwd
)
524 return (output
, proc
.returncode
)
527 def IsStringInt(string_to_check
):
528 """Checks whether or not the given string can be converted to an int."""
536 def IsStringFloat(string_to_check
):
537 """Checks whether or not the given string can be converted to a float."""
539 float(string_to_check
)
546 return sys
.platform
== 'cygwin' or sys
.platform
.startswith('win')
549 def Is64BitWindows():
550 """Checks whether or not Windows is a 64-bit version."""
551 platform
= os
.environ
.get('PROCESSOR_ARCHITEW6432')
553 # Must not be running in WoW64, so PROCESSOR_ARCHITECTURE is correct.
554 platform
= os
.environ
.get('PROCESSOR_ARCHITECTURE')
555 return platform
and platform
in ['AMD64', 'I64']
559 return sys
.platform
.startswith('linux')
563 return sys
.platform
.startswith('darwin')