Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / auto_bisect / bisect_utils.py
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.
4
5 """Utility functions used by the bisect tool.
6
7 This includes functions related to checking out the depot and outputting
8 annotations for the Buildbot waterfall.
9 """
10
11 import errno
12 import imp
13 import os
14 import stat
15 import subprocess
16 import sys
17
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,
50 }
51
52 GCLIENT_SPEC_DATA = [
53     {
54         'name': 'src',
55         'url': 'https://chromium.googlesource.com/chromium/src.git',
56         'deps_file': '.DEPS.git',
57         'managed': True,
58         'custom_deps': {},
59         'safesync_url': '',
60     },
61 ]
62 GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']"
63 GCLIENT_CUSTOM_DEPS_V8 = {
64     'src/v8_bleeding_edge': 'https://chromium.googlesource.com/v8/v8.git'
65 }
66 FILE_DEPS_GIT = '.DEPS.git'
67 FILE_DEPS = 'DEPS'
68
69 # Bisect working directory.
70 BISECT_DIR = 'bisect'
71
72 # The percentage at which confidence is considered high.
73 HIGH_CONFIDENCE = 95
74
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.
78 #
79 # Each depot information dictionary may contain:
80 #   src: Path to the working directory.
81 #   recurse: True if this repository will get bisected.
82 #   depends: A list of other repositories that are actually part of the same
83 #       repository in svn. If the repository has any dependent repositories
84 #       (e.g. skia/src needs skia/include and skia/gyp to be updated), then
85 #       they are specified here.
86 #   svn: URL of SVN repository. Needed for git workflow to resolve hashes to
87 #       SVN revisions.
88 #   from: Parent depot that must be bisected before this is bisected.
89 #   deps_var: Key name in vars variable in DEPS file that has revision
90 #       information.
91 DEPOT_DEPS_NAME = {
92     'chromium': {
93         'src': 'src',
94         'recurse': True,
95         'depends': None,
96         'from': ['android-chrome'],
97         'viewvc':
98             'http://src.chromium.org/viewvc/chrome?view=revision&revision=',
99         'deps_var': 'chromium_rev'
100     },
101     'webkit': {
102         'src': 'src/third_party/WebKit',
103         'recurse': True,
104         'depends': None,
105         'from': ['chromium'],
106         'viewvc':
107             'http://src.chromium.org/viewvc/blink?view=revision&revision=',
108         'deps_var': 'webkit_revision'
109     },
110     'angle': {
111         'src': 'src/third_party/angle',
112         'src_old': 'src/third_party/angle_dx11',
113         'recurse': True,
114         'depends': None,
115         'from': ['chromium'],
116         'platform': 'nt',
117         'deps_var': 'angle_revision'
118     },
119     'v8': {
120         'src': 'src/v8',
121         'recurse': True,
122         'depends': None,
123         'from': ['chromium'],
124         'custom_deps': GCLIENT_CUSTOM_DEPS_V8,
125         'viewvc': 'https://code.google.com/p/v8/source/detail?r=',
126         'deps_var': 'v8_revision'
127     },
128     'v8_bleeding_edge': {
129         'src': 'src/v8_bleeding_edge',
130         'recurse': True,
131         'depends': None,
132         'svn': 'https://v8.googlecode.com/svn/branches/bleeding_edge',
133         'from': ['v8'],
134         'viewvc': 'https://code.google.com/p/v8/source/detail?r=',
135         'deps_var': 'v8_revision'
136     },
137     'skia/src': {
138         'src': 'src/third_party/skia/src',
139         'recurse': True,
140         'svn': 'http://skia.googlecode.com/svn/trunk/src',
141         'depends': ['skia/include', 'skia/gyp'],
142         'from': ['chromium'],
143         'viewvc': 'https://code.google.com/p/skia/source/detail?r=',
144         'deps_var': 'skia_revision'
145     },
146     'skia/include': {
147         'src': 'src/third_party/skia/include',
148         'recurse': False,
149         'svn': 'http://skia.googlecode.com/svn/trunk/include',
150         'depends': None,
151         'from': ['chromium'],
152         'viewvc': 'https://code.google.com/p/skia/source/detail?r=',
153         'deps_var': 'None'
154     },
155     'skia/gyp': {
156         'src': 'src/third_party/skia/gyp',
157         'recurse': False,
158         'svn': 'http://skia.googlecode.com/svn/trunk/gyp',
159         'depends': None,
160         'from': ['chromium'],
161         'viewvc': 'https://code.google.com/p/skia/source/detail?r=',
162         'deps_var': 'None'
163     }
164 }
165
166 DEPOT_NAMES = DEPOT_DEPS_NAME.keys()
167
168 # The possible values of the --bisect_mode flag, which determines what to
169 # use when classifying a revision as "good" or "bad".
170 BISECT_MODE_MEAN = 'mean'
171 BISECT_MODE_STD_DEV = 'std_dev'
172 BISECT_MODE_RETURN_CODE = 'return_code'
173
174
175 def AddAdditionalDepotInfo(depot_info):
176   """Adds additional depot info to the global depot variables."""
177   global DEPOT_DEPS_NAME
178   global DEPOT_NAMES
179   DEPOT_DEPS_NAME = dict(DEPOT_DEPS_NAME.items() + depot_info.items())
180   DEPOT_NAMES = DEPOT_DEPS_NAME.keys()
181
182
183 def OutputAnnotationStepStart(name):
184   """Outputs annotation to signal the start of a step to a try bot.
185
186   Args:
187     name: The name of the step.
188   """
189   print
190   print '@@@SEED_STEP %s@@@' % name
191   print '@@@STEP_CURSOR %s@@@' % name
192   print '@@@STEP_STARTED@@@'
193   print
194   sys.stdout.flush()
195
196
197 def OutputAnnotationStepClosed():
198   """Outputs annotation to signal the closing of a step to a try bot."""
199   print
200   print '@@@STEP_CLOSED@@@'
201   print
202   sys.stdout.flush()
203
204
205 def OutputAnnotationStepLink(label, url):
206   """Outputs appropriate annotation to print a link.
207
208   Args:
209     label: The name to print.
210     url: The URL to print.
211   """
212   print
213   print '@@@STEP_LINK@%s@%s@@@' % (label, url)
214   print
215   sys.stdout.flush()
216
217
218 def LoadExtraSrc(path_to_file):
219   """Attempts to load an extra source file, and overrides global values.
220
221   If the extra source file is loaded successfully, then it will use the new
222   module to override some global values, such as gclient spec data.
223
224   Args:
225     path_to_file: File path.
226
227   Returns:
228     The loaded module object, or None if none was imported.
229   """
230   try:
231     global GCLIENT_SPEC_DATA
232     global GCLIENT_SPEC_ANDROID
233     extra_src = imp.load_source('data', path_to_file)
234     GCLIENT_SPEC_DATA = extra_src.GetGClientSpec()
235     GCLIENT_SPEC_ANDROID = extra_src.GetGClientSpecExtraParams()
236     return extra_src
237   except ImportError:
238     return None
239
240
241 def IsTelemetryCommand(command):
242   """Attempts to discern whether or not a given command is running telemetry."""
243   return 'tools/perf/run_' in command or 'tools\\perf\\run_' in command
244
245
246 def _CreateAndChangeToSourceDirectory(working_directory):
247   """Creates a directory 'bisect' as a subdirectory of |working_directory|.
248
249   If successful, the current working directory will be changed to the new
250   'bisect' directory.
251
252   Args:
253     working_directory: The directory to create the new 'bisect' directory in.
254
255   Returns:
256     True if the directory was successfully created (or already existed).
257   """
258   cwd = os.getcwd()
259   os.chdir(working_directory)
260   try:
261     os.mkdir(BISECT_DIR)
262   except OSError, e:
263     if e.errno != errno.EEXIST:  # EEXIST indicates that it already exists.
264       os.chdir(cwd)
265       return False
266   os.chdir(BISECT_DIR)
267   return True
268
269
270 def _SubprocessCall(cmd, cwd=None):
271   """Runs a command in a subprocess.
272
273   Args:
274     cmd: The command to run.
275     cwd: Working directory to run from.
276
277   Returns:
278     The return code of the call.
279   """
280   if os.name == 'nt':
281     # "HOME" isn't normally defined on windows, but is needed
282     # for git to find the user's .netrc file.
283     if not os.getenv('HOME'):
284       os.environ['HOME'] = os.environ['USERPROFILE']
285   shell = os.name == 'nt'
286   return subprocess.call(cmd, shell=shell, cwd=cwd)
287
288
289 def RunGClient(params, cwd=None):
290   """Runs gclient with the specified parameters.
291
292   Args:
293     params: A list of parameters to pass to gclient.
294     cwd: Working directory to run from.
295
296   Returns:
297     The return code of the call.
298   """
299   cmd = ['gclient'] + params
300   return _SubprocessCall(cmd, cwd=cwd)
301
302
303 def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None):
304   """Runs gclient and creates a config containing both src and src-internal.
305
306   Args:
307     opts: The options parsed from the command line through parse_args().
308     custom_deps: A dictionary of additional dependencies to add to .gclient.
309     cwd: Working directory to run from.
310
311   Returns:
312     The return code of the call.
313   """
314   spec = GCLIENT_SPEC_DATA
315
316   if custom_deps:
317     for k, v in custom_deps.iteritems():
318       spec[0]['custom_deps'][k] = v
319
320   # Cannot have newlines in string on windows
321   spec = 'solutions =' + str(spec)
322   spec = ''.join([l for l in spec.splitlines()])
323
324   if 'android' in opts.target_platform:
325     spec += GCLIENT_SPEC_ANDROID
326
327   return_code = RunGClient(
328       ['config', '--spec=%s' % spec], cwd=cwd)
329   return return_code
330
331
332
333 def OnAccessError(func, path, _):
334   """Error handler for shutil.rmtree.
335
336   Source: http://goo.gl/DEYNCT
337
338   If the error is due to an access error (read only file), it attempts to add
339   write permissions, then retries.
340
341   If the error is for another reason it re-raises the error.
342
343   Args:
344     func: The function that raised the error.
345     path: The path name passed to func.
346     _: Exception information from sys.exc_info(). Not used.
347   """
348   if not os.access(path, os.W_OK):
349     os.chmod(path, stat.S_IWUSR)
350     func(path)
351   else:
352     raise
353
354
355 def _CleanupPreviousGitRuns(cwd=os.getcwd()):
356   """Cleans up any leftover index.lock files after running git."""
357   # If a previous run of git crashed, or bot was reset, etc., then we might
358   # end up with leftover index.lock files.
359   for path, _, files in os.walk(cwd):
360     for cur_file in files:
361       if cur_file.endswith('index.lock'):
362         path_to_file = os.path.join(path, cur_file)
363         os.remove(path_to_file)
364
365
366 def RunGClientAndSync(cwd=None):
367   """Runs gclient and does a normal sync.
368
369   Args:
370     cwd: Working directory to run from.
371
372   Returns:
373     The return code of the call.
374   """
375   params = ['sync', '--verbose', '--nohooks', '--reset', '--force',
376             '--delete_unversioned_trees']
377   return RunGClient(params, cwd=cwd)
378
379
380 def SetupGitDepot(opts, custom_deps):
381   """Sets up the depot for the bisection.
382
383   The depot will be located in a subdirectory called 'bisect'.
384
385   Args:
386     opts: The options parsed from the command line through parse_args().
387     custom_deps: A dictionary of additional dependencies to add to .gclient.
388
389   Returns:
390     True if gclient successfully created the config file and did a sync, False
391     otherwise.
392   """
393   name = 'Setting up Bisection Depot'
394   try:
395     if opts.output_buildbot_annotations:
396       OutputAnnotationStepStart(name)
397
398     if RunGClientAndCreateConfig(opts, custom_deps):
399       return False
400
401     _CleanupPreviousGitRuns()
402     RunGClient(['revert'])
403     return not RunGClientAndSync()
404   finally:
405     if opts.output_buildbot_annotations:
406       OutputAnnotationStepClosed()
407
408
409 def CheckIfBisectDepotExists(opts):
410   """Checks if the bisect directory already exists.
411
412   Args:
413     opts: The options parsed from the command line through parse_args().
414
415   Returns:
416     Returns True if it exists.
417   """
418   path_to_dir = os.path.join(opts.working_directory, BISECT_DIR, 'src')
419   return os.path.exists(path_to_dir)
420
421
422 def CheckRunGit(command, cwd=None):
423   """Run a git subcommand, returning its output and return code. Asserts if
424   the return code of the call is non-zero.
425
426   Args:
427     command: A list containing the args to git.
428
429   Returns:
430     A tuple of the output and return code.
431   """
432   (output, return_code) = RunGit(command, cwd=cwd)
433
434   assert not return_code, 'An error occurred while running'\
435                           ' "git %s"' % ' '.join(command)
436   return output
437
438
439 def RunGit(command, cwd=None):
440   """Run a git subcommand, returning its output and return code.
441
442   Args:
443     command: A list containing the args to git.
444     cwd: A directory to change to while running the git command (optional).
445
446   Returns:
447     A tuple of the output and return code.
448   """
449   command = ['git'] + command
450   return RunProcessAndRetrieveOutput(command, cwd=cwd)
451
452
453 def CreateBisectDirectoryAndSetupDepot(opts, custom_deps):
454   """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot
455   there using gclient.
456
457   Args:
458     opts: The options parsed from the command line through parse_args().
459     custom_deps: A dictionary of additional dependencies to add to .gclient.
460   """
461   if CheckIfBisectDepotExists(opts):
462     path_to_dir = os.path.join(os.path.abspath(opts.working_directory),
463                                BISECT_DIR, 'src')
464     (output, _) = RunGit(['rev-parse', '--is-inside-work-tree'],
465                          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.')
473
474   if not SetupGitDepot(opts, custom_deps):
475     raise RuntimeError('Failed to grab source.')
476
477
478 def RunProcess(command):
479   """Runs an arbitrary command.
480
481   If output from the call is needed, use RunProcessAndRetrieveOutput instead.
482
483   Args:
484     command: A list containing the command and args to execute.
485
486   Returns:
487     The return code of the call.
488   """
489   # On Windows, use shell=True to get PATH interpretation.
490   shell = IsWindowsHost()
491   return subprocess.call(command, shell=shell)
492
493
494 def RunProcessAndRetrieveOutput(command, cwd=None):
495   """Runs an arbitrary command, returning its output and return code.
496
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().
500
501   Args:
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.
506
507   Returns:
508     A tuple of the output and return code.
509   """
510   if cwd:
511     original_cwd = os.getcwd()
512     os.chdir(cwd)
513
514   # On Windows, use shell=True to get PATH interpretation.
515   shell = IsWindowsHost()
516   proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE)
517   (output, _) = proc.communicate()
518
519   if cwd:
520     os.chdir(original_cwd)
521
522   return (output, proc.returncode)
523
524
525 def IsStringInt(string_to_check):
526   """Checks whether or not the given string can be converted to an int."""
527   try:
528     int(string_to_check)
529     return True
530   except ValueError:
531     return False
532
533
534 def IsStringFloat(string_to_check):
535   """Checks whether or not the given string can be converted to a float."""
536   try:
537     float(string_to_check)
538     return True
539   except ValueError:
540     return False
541
542
543 def IsWindowsHost():
544   return sys.platform == 'cygwin' or sys.platform.startswith('win')
545
546
547 def Is64BitWindows():
548   """Checks whether or not Windows is a 64-bit version."""
549   platform = os.environ.get('PROCESSOR_ARCHITEW6432')
550   if not platform:
551     # Must not be running in WoW64, so PROCESSOR_ARCHITECTURE is correct.
552     platform = os.environ.get('PROCESSOR_ARCHITECTURE')
553   return platform and platform in ['AMD64', 'I64']
554
555
556 def IsLinuxHost():
557   return sys.platform.startswith('linux')
558
559
560 def IsMacHost():
561   return sys.platform.startswith('darwin')