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 OutputAnnotationStepLink(label
, url
):
176 """Outputs appropriate annotation to print a link.
179 label: The name to print.
180 url: The URL to print.
183 print '@@@STEP_LINK@%s@%s@@@' % (label
, url
)
188 def LoadExtraSrc(path_to_file
):
189 """Attempts to load an extra source file, and overrides global values.
191 If the extra source file is loaded successfully, then it will use the new
192 module to override some global values, such as gclient spec data.
195 path_to_file: File path.
198 The loaded module object, or None if none was imported.
201 global GCLIENT_SPEC_DATA
202 global GCLIENT_SPEC_ANDROID
203 extra_src
= imp
.load_source('data', path_to_file
)
204 GCLIENT_SPEC_DATA
= extra_src
.GetGClientSpec()
205 GCLIENT_SPEC_ANDROID
= extra_src
.GetGClientSpecExtraParams()
211 def IsTelemetryCommand(command
):
212 """Attempts to discern whether or not a given command is running telemetry."""
213 return 'tools/perf/run_' in command
or 'tools\\perf\\run_' in command
216 def _CreateAndChangeToSourceDirectory(working_directory
):
217 """Creates a directory 'bisect' as a subdirectory of |working_directory|.
219 If successful, the current working directory will be changed to the new
223 working_directory: The directory to create the new 'bisect' directory in.
226 True if the directory was successfully created (or already existed).
229 os
.chdir(working_directory
)
233 if e
.errno
!= errno
.EEXIST
: # EEXIST indicates that it already exists.
240 def _SubprocessCall(cmd
, cwd
=None):
241 """Runs a command in a subprocess.
244 cmd: The command to run.
245 cwd: Working directory to run from.
248 The return code of the call.
251 # "HOME" isn't normally defined on windows, but is needed
252 # for git to find the user's .netrc file.
253 if not os
.getenv('HOME'):
254 os
.environ
['HOME'] = os
.environ
['USERPROFILE']
255 shell
= os
.name
== 'nt'
256 return subprocess
.call(cmd
, shell
=shell
, cwd
=cwd
)
259 def RunGClient(params
, cwd
=None):
260 """Runs gclient with the specified parameters.
263 params: A list of parameters to pass to gclient.
264 cwd: Working directory to run from.
267 The return code of the call.
269 cmd
= ['gclient'] + params
270 return _SubprocessCall(cmd
, cwd
=cwd
)
273 def RunGClientAndCreateConfig(opts
, custom_deps
=None, cwd
=None):
274 """Runs gclient and creates a config containing both src and src-internal.
277 opts: The options parsed from the command line through parse_args().
278 custom_deps: A dictionary of additional dependencies to add to .gclient.
279 cwd: Working directory to run from.
282 The return code of the call.
284 spec
= GCLIENT_SPEC_DATA
287 for k
, v
in custom_deps
.iteritems():
288 spec
[0]['custom_deps'][k
] = v
290 # Cannot have newlines in string on windows
291 spec
= 'solutions =' + str(spec
)
292 spec
= ''.join([l
for l
in spec
.splitlines()])
294 if 'android' in opts
.target_platform
:
295 spec
+= GCLIENT_SPEC_ANDROID
297 return_code
= RunGClient(
298 ['config', '--spec=%s' % spec
], cwd
=cwd
)
303 def OnAccessError(func
, path
, _
):
304 """Error handler for shutil.rmtree.
306 Source: http://goo.gl/DEYNCT
308 If the error is due to an access error (read only file), it attempts to add
309 write permissions, then retries.
311 If the error is for another reason it re-raises the error.
314 func: The function that raised the error.
315 path: The path name passed to func.
316 _: Exception information from sys.exc_info(). Not used.
318 if not os
.access(path
, os
.W_OK
):
319 os
.chmod(path
, stat
.S_IWUSR
)
325 def _CleanupPreviousGitRuns(cwd
=os
.getcwd()):
326 """Cleans up any leftover index.lock files after running git."""
327 # If a previous run of git crashed, or bot was reset, etc., then we might
328 # end up with leftover index.lock files.
329 for path
, _
, files
in os
.walk(cwd
):
330 for cur_file
in files
:
331 if cur_file
.endswith('index.lock'):
332 path_to_file
= os
.path
.join(path
, cur_file
)
333 os
.remove(path_to_file
)
336 def RunGClientAndSync(revision
=None, cwd
=None):
337 """Runs gclient and does a normal sync.
340 revision: Revision that need to be synced.
341 cwd: Working directory to run from.
344 The return code of the call.
346 params
= ['sync', '--verbose', '--nohooks', '--force',
347 '--delete_unversioned_trees']
349 params
.extend(['--revision', revision
])
350 return RunGClient(params
, cwd
=cwd
)
353 def SetupGitDepot(opts
, custom_deps
):
354 """Sets up the depot for the bisection.
356 The depot will be located in a subdirectory called 'bisect'.
359 opts: The options parsed from the command line through parse_args().
360 custom_deps: A dictionary of additional dependencies to add to .gclient.
363 True if gclient successfully created the config file and did a sync, False
366 name
= 'Setting up Bisection Depot'
368 if opts
.output_buildbot_annotations
:
369 OutputAnnotationStepStart(name
)
371 if RunGClientAndCreateConfig(opts
, custom_deps
):
374 _CleanupPreviousGitRuns()
375 RunGClient(['revert'])
376 return not RunGClientAndSync()
378 if opts
.output_buildbot_annotations
:
379 OutputAnnotationStepClosed()
382 def CheckIfBisectDepotExists(opts
):
383 """Checks if the bisect directory already exists.
386 opts: The options parsed from the command line through parse_args().
389 Returns True if it exists.
391 path_to_dir
= os
.path
.join(opts
.working_directory
, BISECT_DIR
, 'src')
392 return os
.path
.exists(path_to_dir
)
395 def CheckRunGit(command
, cwd
=None):
396 """Run a git subcommand, returning its output and return code. Asserts if
397 the return code of the call is non-zero.
400 command: A list containing the args to git.
403 A tuple of the output and return code.
405 (output
, return_code
) = RunGit(command
, cwd
=cwd
)
407 assert not return_code
, 'An error occurred while running'\
408 ' "git %s"' % ' '.join(command
)
412 def RunGit(command
, cwd
=None):
413 """Run a git subcommand, returning its output and return code.
416 command: A list containing the args to git.
417 cwd: A directory to change to while running the git command (optional).
420 A tuple of the output and return code.
422 command
= ['git'] + command
423 return RunProcessAndRetrieveOutput(command
, cwd
=cwd
)
426 def CreateBisectDirectoryAndSetupDepot(opts
, custom_deps
):
427 """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot
431 opts: The options parsed from the command line through parse_args().
432 custom_deps: A dictionary of additional dependencies to add to .gclient.
434 if CheckIfBisectDepotExists(opts
):
435 path_to_dir
= os
.path
.join(os
.path
.abspath(opts
.working_directory
),
437 (output
, _
) = RunGit(['rev-parse', '--is-inside-work-tree'],
439 if output
.strip() == 'true':
440 # Before checking out master, cleanup up any leftover index.lock files.
441 _CleanupPreviousGitRuns(path_to_dir
)
442 # Checks out the master branch, throws an exception if git command fails.
443 CheckRunGit(['checkout', '-f', 'master'], cwd
=path_to_dir
)
444 if not _CreateAndChangeToSourceDirectory(opts
.working_directory
):
445 raise RuntimeError('Could not create bisect directory.')
447 if not SetupGitDepot(opts
, custom_deps
):
448 raise RuntimeError('Failed to grab source.')
451 def RunProcess(command
):
452 """Runs an arbitrary command.
454 If output from the call is needed, use RunProcessAndRetrieveOutput instead.
457 command: A list containing the command and args to execute.
460 The return code of the call.
462 # On Windows, use shell=True to get PATH interpretation.
463 shell
= IsWindowsHost()
464 return subprocess
.call(command
, shell
=shell
)
467 def RunProcessAndRetrieveOutput(command
, cwd
=None):
468 """Runs an arbitrary command, returning its output and return code.
470 Since output is collected via communicate(), there will be no output until
471 the call terminates. If you need output while the program runs (ie. so
472 that the buildbot doesn't terminate the script), consider RunProcess().
475 command: A list containing the command and args to execute.
476 cwd: A directory to change to while running the command. The command can be
477 relative to this directory. If this is None, the command will be run in
478 the current directory.
481 A tuple of the output and return code.
484 original_cwd
= os
.getcwd()
487 # On Windows, use shell=True to get PATH interpretation.
488 shell
= IsWindowsHost()
489 proc
= subprocess
.Popen(command
, shell
=shell
, stdout
=subprocess
.PIPE
,
490 stderr
=subprocess
.STDOUT
)
491 (output
, _
) = proc
.communicate()
494 os
.chdir(original_cwd
)
496 return (output
, proc
.returncode
)
499 def IsStringInt(string_to_check
):
500 """Checks whether or not the given string can be converted to an int."""
508 def IsStringFloat(string_to_check
):
509 """Checks whether or not the given string can be converted to a float."""
511 float(string_to_check
)
518 return sys
.platform
== 'cygwin' or sys
.platform
.startswith('win')
521 def Is64BitWindows():
522 """Checks whether or not Windows is a 64-bit version."""
523 platform
= os
.environ
.get('PROCESSOR_ARCHITEW6432')
525 # Must not be running in WoW64, so PROCESSOR_ARCHITECTURE is correct.
526 platform
= os
.environ
.get('PROCESSOR_ARCHITECTURE')
527 return platform
and platform
in ['AMD64', 'I64']
531 return sys
.platform
.startswith('linux')
535 return sys
.platform
.startswith('darwin')