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 OutputAnnotationStepLink(label
, url
):
195 """Outputs appropriate annotation to print a link.
198 label: The name to print.
199 url: The URL to print.
202 print '@@@STEP_LINK@%s@%s@@@' % (label
, url
)
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.
214 path_to_file: File path.
217 The loaded module object, or None if none was imported.
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()
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
242 working_directory: The directory to create the new 'bisect' directory in.
245 True if the directory was successfully created (or already existed).
248 os
.chdir(working_directory
)
252 if e
.errno
!= errno
.EEXIST
: # EEXIST indicates that it already exists.
259 def _SubprocessCall(cmd
, cwd
=None):
260 """Runs a command in a subprocess.
263 cmd: The command to run.
264 cwd: Working directory to run from.
267 The return code of the call.
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.
282 params: A list of parameters to pass to gclient.
283 cwd: Working directory to run from.
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.
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.
301 The return code of the call.
303 spec
= GCLIENT_SPEC_DATA
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
)
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.
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
)
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.
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.
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'.
381 opts: The options parsed from the command line through parse_args().
382 custom_deps: A dictionary of additional dependencies to add to .gclient.
385 True if gclient successfully created the config file and did a sync, False
388 name
= 'Setting up Bisection Depot'
390 if opts
.output_buildbot_annotations
:
391 OutputAnnotationStepStart(name
)
393 if RunGClientAndCreateConfig(opts
, custom_deps
):
396 _CleanupPreviousGitRuns()
397 RunGClient(['revert'])
398 return not RunGClientAndSync()
400 if opts
.output_buildbot_annotations
:
401 OutputAnnotationStepClosed()
404 def CheckIfBisectDepotExists(opts
):
405 """Checks if the bisect directory already exists.
408 opts: The options parsed from the command line through parse_args().
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.
422 command: A list containing the args to git.
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
)
434 def RunGit(command
, cwd
=None):
435 """Run a git subcommand, returning its output and return code.
438 command: A list containing the args to git.
439 cwd: A directory to change to while running the git command (optional).
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
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
),
459 (output
, _
) = RunGit(['rev-parse', '--is-inside-work-tree'],
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.
479 command: A list containing the command and args to execute.
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().
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.
503 A tuple of the output and return code.
506 original_cwd
= os
.getcwd()
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()
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."""
530 def IsStringFloat(string_to_check
):
531 """Checks whether or not the given string can be converted to a float."""
533 float(string_to_check
)
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')
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']
553 return sys
.platform
.startswith('linux')
557 return sys
.platform
.startswith('darwin')