roll libyuv to r1025 for mips n32 support, arm nacl port, psnr tool jpeg support.
[chromium-blink-merge.git] / tools / bisect_utils.py
blobac5de35719fb6462d91d3bfa161b3c8a22744533
1 # Copyright (c) 2013 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 """Set of operations/utilities related to checking out the depot, and
6 outputting annotations on the buildbot waterfall. These are intended to be
7 used by the bisection scripts."""
9 import errno
10 import imp
11 import os
12 import shutil
13 import stat
14 import subprocess
15 import sys
17 DEFAULT_GCLIENT_CUSTOM_DEPS = {
18 "src/data/page_cycler": "https://chrome-internal.googlesource.com/"
19 "chrome/data/page_cycler/.git",
20 "src/data/dom_perf": "https://chrome-internal.googlesource.com/"
21 "chrome/data/dom_perf/.git",
22 "src/data/mach_ports": "https://chrome-internal.googlesource.com/"
23 "chrome/data/mach_ports/.git",
24 "src/tools/perf/data": "https://chrome-internal.googlesource.com/"
25 "chrome/tools/perf/data/.git",
26 "src/third_party/adobe/flash/binaries/ppapi/linux":
27 "https://chrome-internal.googlesource.com/"
28 "chrome/deps/adobe/flash/binaries/ppapi/linux/.git",
29 "src/third_party/adobe/flash/binaries/ppapi/linux_x64":
30 "https://chrome-internal.googlesource.com/"
31 "chrome/deps/adobe/flash/binaries/ppapi/linux_x64/.git",
32 "src/third_party/adobe/flash/binaries/ppapi/mac":
33 "https://chrome-internal.googlesource.com/"
34 "chrome/deps/adobe/flash/binaries/ppapi/mac/.git",
35 "src/third_party/adobe/flash/binaries/ppapi/mac_64":
36 "https://chrome-internal.googlesource.com/"
37 "chrome/deps/adobe/flash/binaries/ppapi/mac_64/.git",
38 "src/third_party/adobe/flash/binaries/ppapi/win":
39 "https://chrome-internal.googlesource.com/"
40 "chrome/deps/adobe/flash/binaries/ppapi/win/.git",
41 "src/third_party/adobe/flash/binaries/ppapi/win_x64":
42 "https://chrome-internal.googlesource.com/"
43 "chrome/deps/adobe/flash/binaries/ppapi/win_x64/.git",
44 "src/chrome/tools/test/reference_build/chrome_win": None,
45 "src/chrome/tools/test/reference_build/chrome_mac": None,
46 "src/chrome/tools/test/reference_build/chrome_linux": None,
47 "src/third_party/WebKit/LayoutTests": None,
48 "src/tools/valgrind": None,}
50 GCLIENT_SPEC_DATA = [
51 { "name" : "src",
52 "url" : "https://chromium.googlesource.com/chromium/src.git",
53 "deps_file" : ".DEPS.git",
54 "managed" : True,
55 "custom_deps" : {},
56 "safesync_url": "",
59 GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']"
60 GCLIENT_CUSTOM_DEPS_V8 = {"src/v8_bleeding_edge": "git://github.com/v8/v8.git"}
61 FILE_DEPS_GIT = '.DEPS.git'
62 FILE_DEPS = 'DEPS'
64 REPO_PARAMS = [
65 'https://chrome-internal.googlesource.com/chromeos/manifest-internal/',
66 '--repo-url',
67 'https://git.chromium.org/external/repo.git'
70 REPO_SYNC_COMMAND = 'git checkout -f $(git rev-list --max-count=1 '\
71 '--before=%d remotes/m/master)'
73 ORIGINAL_ENV = {}
75 def OutputAnnotationStepStart(name):
76 """Outputs appropriate annotation to signal the start of a step to
77 a trybot.
79 Args:
80 name: The name of the step.
81 """
82 print
83 print '@@@SEED_STEP %s@@@' % name
84 print '@@@STEP_CURSOR %s@@@' % name
85 print '@@@STEP_STARTED@@@'
86 print
87 sys.stdout.flush()
90 def OutputAnnotationStepClosed():
91 """Outputs appropriate annotation to signal the closing of a step to
92 a trybot."""
93 print
94 print '@@@STEP_CLOSED@@@'
95 print
96 sys.stdout.flush()
99 def OutputAnnotationStepLink(label, url):
100 """Outputs appropriate annotation to print a link.
102 Args:
103 label: The name to print.
104 url: The url to print.
106 print
107 print '@@@STEP_LINK@%s@%s@@@' % (label, url)
108 print
109 sys.stdout.flush()
112 def LoadExtraSrc(path_to_file):
113 """Attempts to load an extra source file. If this is successful, uses the
114 new module to override some global values, such as gclient spec data.
116 Returns:
117 The loaded src module, or None."""
118 try:
119 global GCLIENT_SPEC_DATA
120 global GCLIENT_SPEC_ANDROID
121 extra_src = imp.load_source('data', path_to_file)
122 GCLIENT_SPEC_DATA = extra_src.GetGClientSpec()
123 GCLIENT_SPEC_ANDROID = extra_src.GetGClientSpecExtraParams()
124 return extra_src
125 except ImportError, e:
126 return None
129 def IsTelemetryCommand(command):
130 """Attempts to discern whether or not a given command is running telemetry."""
131 return ('tools/perf/run_' in command or 'tools\\perf\\run_' in command)
134 def CreateAndChangeToSourceDirectory(working_directory):
135 """Creates a directory 'bisect' as a subdirectory of 'working_directory'. If
136 the function is successful, the current working directory will change to that
137 of the new 'bisect' directory.
139 Returns:
140 True if the directory was successfully created (or already existed).
142 cwd = os.getcwd()
143 os.chdir(working_directory)
144 try:
145 os.mkdir('bisect')
146 except OSError, e:
147 if e.errno != errno.EEXIST:
148 return False
149 os.chdir('bisect')
150 return True
153 def SubprocessCall(cmd, cwd=None):
154 """Runs a subprocess with specified parameters.
156 Args:
157 params: A list of parameters to pass to gclient.
158 cwd: Working directory to run from.
160 Returns:
161 The return code of the call.
163 if os.name == 'nt':
164 # "HOME" isn't normally defined on windows, but is needed
165 # for git to find the user's .netrc file.
166 if not os.getenv('HOME'):
167 os.environ['HOME'] = os.environ['USERPROFILE']
168 shell = os.name == 'nt'
169 return subprocess.call(cmd, shell=shell, cwd=cwd)
172 def RunGClient(params, cwd=None):
173 """Runs gclient with the specified parameters.
175 Args:
176 params: A list of parameters to pass to gclient.
177 cwd: Working directory to run from.
179 Returns:
180 The return code of the call.
182 cmd = ['gclient'] + params
184 return SubprocessCall(cmd, cwd=cwd)
187 def RunRepo(params):
188 """Runs cros repo command with specified parameters.
190 Args:
191 params: A list of parameters to pass to gclient.
193 Returns:
194 The return code of the call.
196 cmd = ['repo'] + params
198 return SubprocessCall(cmd)
201 def RunRepoSyncAtTimestamp(timestamp):
202 """Syncs all git depots to the timestamp specified using repo forall.
204 Args:
205 params: Unix timestamp to sync to.
207 Returns:
208 The return code of the call.
210 repo_sync = REPO_SYNC_COMMAND % timestamp
211 cmd = ['forall', '-c', REPO_SYNC_COMMAND % timestamp]
212 return RunRepo(cmd)
215 def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None):
216 """Runs gclient and creates a config containing both src and src-internal.
218 Args:
219 opts: The options parsed from the command line through parse_args().
220 custom_deps: A dictionary of additional dependencies to add to .gclient.
221 cwd: Working directory to run from.
223 Returns:
224 The return code of the call.
226 spec = GCLIENT_SPEC_DATA
228 if custom_deps:
229 for k, v in custom_deps.iteritems():
230 spec[0]['custom_deps'][k] = v
232 # Cannot have newlines in string on windows
233 spec = 'solutions =' + str(spec)
234 spec = ''.join([l for l in spec.splitlines()])
236 if 'android' in opts.target_platform:
237 spec += GCLIENT_SPEC_ANDROID
239 return_code = RunGClient(
240 ['config', '--spec=%s' % spec, '--git-deps'], cwd=cwd)
241 return return_code
244 def IsDepsFileBlink():
245 """Reads .DEPS.git and returns whether or not we're using blink.
247 Returns:
248 True if blink, false if webkit.
250 locals = {'Var': lambda _: locals["vars"][_],
251 'From': lambda *args: None}
252 execfile(FILE_DEPS_GIT, {}, locals)
253 return 'blink.git' in locals['vars']['webkit_url']
256 def OnAccessError(func, path, exc_info):
258 Source: http://stackoverflow.com/questions/2656322/python-shutil-rmtree-fails-on-windows-with-access-is-denied
260 Error handler for ``shutil.rmtree``.
262 If the error is due to an access error (read only file)
263 it attempts to add write permission and then retries.
265 If the error is for another reason it re-raises the error.
267 Args:
268 func: The function that raised the error.
269 path: The path name passed to func.
270 exc_info: Exception information returned by sys.exc_info().
272 if not os.access(path, os.W_OK):
273 # Is the error an access error ?
274 os.chmod(path, stat.S_IWUSR)
275 func(path)
276 else:
277 raise
280 def RemoveThirdPartyDirectory(dir_name):
281 """Removes third_party directory from the source.
283 At some point, some of the third_parties were causing issues to changes in
284 the way they are synced. We remove such folder in order to avoid sync errors
285 while bisecting.
287 Returns:
288 True on success, otherwise False.
290 path_to_dir = os.path.join(os.getcwd(), 'third_party', dir_name)
291 try:
292 if os.path.exists(path_to_dir):
293 shutil.rmtree(path_to_dir, onerror=OnAccessError)
294 except OSError, e:
295 print 'Error #%d while running shutil.rmtree(%s): %s' % (
296 e.errno, path_to_dir, str(e))
297 if e.errno != errno.ENOENT:
298 return False
299 return True
302 def _CleanupPreviousGitRuns():
303 """Performs necessary cleanup between runs."""
304 # If a previous run of git crashed, bot was reset, etc... we
305 # might end up with leftover index.lock files.
306 for (path, dir, files) in os.walk(os.getcwd()):
307 for cur_file in files:
308 if cur_file.endswith('index.lock'):
309 path_to_file = os.path.join(path, cur_file)
310 os.remove(path_to_file)
313 def RunGClientAndSync(cwd=None):
314 """Runs gclient and does a normal sync.
316 Args:
317 cwd: Working directory to run from.
319 Returns:
320 The return code of the call.
322 params = ['sync', '--verbose', '--nohooks', '--reset', '--force']
323 return RunGClient(params, cwd=cwd)
326 def SetupGitDepot(opts, custom_deps):
327 """Sets up the depot for the bisection. The depot will be located in a
328 subdirectory called 'bisect'.
330 Args:
331 opts: The options parsed from the command line through parse_args().
332 custom_deps: A dictionary of additional dependencies to add to .gclient.
334 Returns:
335 True if gclient successfully created the config file and did a sync, False
336 otherwise.
338 name = 'Setting up Bisection Depot'
340 if opts.output_buildbot_annotations:
341 OutputAnnotationStepStart(name)
343 passed = False
345 if not RunGClientAndCreateConfig(opts, custom_deps):
346 passed_deps_check = True
347 if os.path.isfile(os.path.join('src', FILE_DEPS_GIT)):
348 cwd = os.getcwd()
349 os.chdir('src')
350 if not IsDepsFileBlink():
351 passed_deps_check = RemoveThirdPartyDirectory('Webkit')
352 else:
353 passed_deps_check = True
354 if passed_deps_check:
355 passed_deps_check = RemoveThirdPartyDirectory('libjingle')
356 if passed_deps_check:
357 passed_deps_check = RemoveThirdPartyDirectory('skia')
358 os.chdir(cwd)
360 if passed_deps_check:
361 _CleanupPreviousGitRuns()
363 RunGClient(['revert'])
364 if not RunGClientAndSync():
365 passed = True
367 if opts.output_buildbot_annotations:
368 print
369 OutputAnnotationStepClosed()
371 return passed
374 def SetupCrosRepo():
375 """Sets up cros repo for bisecting chromeos.
377 Returns:
378 Returns 0 on success.
380 cwd = os.getcwd()
381 try:
382 os.mkdir('cros')
383 except OSError, e:
384 if e.errno != errno.EEXIST:
385 return False
386 os.chdir('cros')
388 cmd = ['init', '-u'] + REPO_PARAMS
390 passed = False
392 if not RunRepo(cmd):
393 if not RunRepo(['sync']):
394 passed = True
395 os.chdir(cwd)
397 return passed
400 def CopyAndSaveOriginalEnvironmentVars():
401 """Makes a copy of the current environment variables."""
402 # TODO: Waiting on crbug.com/255689, will remove this after.
403 vars_to_remove = []
404 for k, v in os.environ.iteritems():
405 if 'ANDROID' in k:
406 vars_to_remove.append(k)
407 vars_to_remove.append('CHROME_SRC')
408 vars_to_remove.append('CHROMIUM_GYP_FILE')
409 vars_to_remove.append('GYP_CROSSCOMPILE')
410 vars_to_remove.append('GYP_DEFINES')
411 vars_to_remove.append('GYP_GENERATORS')
412 vars_to_remove.append('GYP_GENERATOR_FLAGS')
413 vars_to_remove.append('OBJCOPY')
414 for k in vars_to_remove:
415 if os.environ.has_key(k):
416 del os.environ[k]
418 global ORIGINAL_ENV
419 ORIGINAL_ENV = os.environ.copy()
422 def SetupAndroidBuildEnvironment(opts, path_to_src=None):
423 """Sets up the android build environment.
425 Args:
426 opts: The options parsed from the command line through parse_args().
427 path_to_src: Path to the src checkout.
429 Returns:
430 True if successful.
433 # Revert the environment variables back to default before setting them up
434 # with envsetup.sh.
435 env_vars = os.environ.copy()
436 for k, _ in env_vars.iteritems():
437 del os.environ[k]
438 for k, v in ORIGINAL_ENV.iteritems():
439 os.environ[k] = v
441 path_to_file = os.path.join('build', 'android', 'envsetup.sh')
442 proc = subprocess.Popen(['bash', '-c', 'source %s && env' % path_to_file],
443 stdout=subprocess.PIPE,
444 stderr=subprocess.PIPE,
445 cwd=path_to_src)
446 (out, _) = proc.communicate()
448 for line in out.splitlines():
449 (k, _, v) = line.partition('=')
450 os.environ[k] = v
451 # envsetup.sh no longer sets OS=android to GYP_DEFINES env variable
452 # (CL/170273005). Set this variable explicitly inorder to build chrome on
453 # android.
454 try:
455 if 'OS=android' not in os.environ['GYP_DEFINES']:
456 os.environ['GYP_DEFINES'] = '%s %s' % (os.environ['GYP_DEFINES'],
457 'OS=android')
458 except KeyError:
459 os.environ['GYP_DEFINES'] = 'OS=android'
461 if opts.use_goma:
462 os.environ['GYP_DEFINES'] = '%s %s' % (os.environ['GYP_DEFINES'],
463 'use_goma=1')
464 return not proc.returncode
467 def SetupPlatformBuildEnvironment(opts):
468 """Performs any platform specific setup.
470 Args:
471 opts: The options parsed from the command line through parse_args().
473 Returns:
474 True if successful.
476 if 'android' in opts.target_platform:
477 CopyAndSaveOriginalEnvironmentVars()
478 return SetupAndroidBuildEnvironment(opts)
479 elif opts.target_platform == 'cros':
480 return SetupCrosRepo()
482 return True
485 def CheckIfBisectDepotExists(opts):
486 """Checks if the bisect directory already exists.
488 Args:
489 opts: The options parsed from the command line through parse_args().
491 Returns:
492 Returns True if it exists.
494 path_to_dir = os.path.join(opts.working_directory, 'bisect', 'src')
495 return os.path.exists(path_to_dir)
498 def CreateBisectDirectoryAndSetupDepot(opts, custom_deps):
499 """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot
500 there using gclient.
502 Args:
503 opts: The options parsed from the command line through parse_args().
504 custom_deps: A dictionary of additional dependencies to add to .gclient.
506 if not CreateAndChangeToSourceDirectory(opts.working_directory):
507 raise RuntimeError('Could not create bisect directory.')
509 if not SetupGitDepot(opts, custom_deps):
510 raise RuntimeError('Failed to grab source.')