Home | History | Annotate | Download | only in tools
      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.
      4 
      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."""
      8 
      9 import errno
     10 import os
     11 import shutil
     12 import subprocess
     13 import sys
     14 
     15 GCLIENT_SPEC_DATA = [
     16   { "name"        : "src",
     17     "url"         : "https://chromium.googlesource.com/chromium/src.git",
     18     "deps_file"   : ".DEPS.git",
     19     "managed"     : True,
     20     "custom_deps" : {
     21       "src/data/page_cycler": "https://chrome-internal.googlesource.com/"
     22                               "chrome/data/page_cycler/.git",
     23       "src/data/dom_perf": "https://chrome-internal.googlesource.com/"
     24                            "chrome/data/dom_perf/.git",
     25       "src/data/mach_ports": "https://chrome-internal.googlesource.com/"
     26                            "chrome/data/mach_ports/.git",
     27       "src/tools/perf/data": "https://chrome-internal.googlesource.com/"
     28                              "chrome/tools/perf/data/.git",
     29       "src/third_party/adobe/flash/binaries/ppapi/linux":
     30           "https://chrome-internal.googlesource.com/"
     31           "chrome/deps/adobe/flash/binaries/ppapi/linux/.git",
     32       "src/third_party/adobe/flash/binaries/ppapi/linux_x64":
     33           "https://chrome-internal.googlesource.com/"
     34           "chrome/deps/adobe/flash/binaries/ppapi/linux_x64/.git",
     35       "src/third_party/adobe/flash/binaries/ppapi/mac":
     36           "https://chrome-internal.googlesource.com/"
     37           "chrome/deps/adobe/flash/binaries/ppapi/mac/.git",
     38       "src/third_party/adobe/flash/binaries/ppapi/mac_64":
     39           "https://chrome-internal.googlesource.com/"
     40           "chrome/deps/adobe/flash/binaries/ppapi/mac_64/.git",
     41       "src/third_party/adobe/flash/binaries/ppapi/win":
     42           "https://chrome-internal.googlesource.com/"
     43           "chrome/deps/adobe/flash/binaries/ppapi/win/.git",
     44       "src/third_party/adobe/flash/binaries/ppapi/win_x64":
     45           "https://chrome-internal.googlesource.com/"
     46           "chrome/deps/adobe/flash/binaries/ppapi/win_x64/.git",
     47     },
     48     "safesync_url": "",
     49   },
     50 ]
     51 GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']"
     52 GCLIENT_CUSTOM_DEPS_V8 = {"src/v8_bleeding_edge": "git://github.com/v8/v8.git"}
     53 FILE_DEPS_GIT = '.DEPS.git'
     54 
     55 REPO_PARAMS = [
     56   'https://chrome-internal.googlesource.com/chromeos/manifest-internal/',
     57   '--repo-url',
     58   'https://git.chromium.org/external/repo.git'
     59 ]
     60 
     61 REPO_SYNC_COMMAND = 'git checkout -f $(git rev-list --max-count=1 '\
     62                     '--before=%d remotes/m/master)'
     63 
     64 ORIGINAL_ENV = {}
     65 
     66 def OutputAnnotationStepStart(name):
     67   """Outputs appropriate annotation to signal the start of a step to
     68   a trybot.
     69 
     70   Args:
     71     name: The name of the step.
     72   """
     73   print
     74   print '@@@SEED_STEP %s@@@' % name
     75   print '@@@STEP_CURSOR %s@@@' % name
     76   print '@@@STEP_STARTED@@@'
     77   print
     78   sys.stdout.flush()
     79 
     80 
     81 def OutputAnnotationStepClosed():
     82   """Outputs appropriate annotation to signal the closing of a step to
     83   a trybot."""
     84   print
     85   print '@@@STEP_CLOSED@@@'
     86   print
     87   sys.stdout.flush()
     88 
     89 
     90 def CreateAndChangeToSourceDirectory(working_directory):
     91   """Creates a directory 'bisect' as a subdirectory of 'working_directory'.  If
     92   the function is successful, the current working directory will change to that
     93   of the new 'bisect' directory.
     94 
     95   Returns:
     96     True if the directory was successfully created (or already existed).
     97   """
     98   cwd = os.getcwd()
     99   os.chdir(working_directory)
    100   try:
    101     os.mkdir('bisect')
    102   except OSError, e:
    103     if e.errno != errno.EEXIST:
    104       return False
    105   os.chdir('bisect')
    106   return True
    107 
    108 
    109 def SubprocessCall(cmd, cwd=None):
    110   """Runs a subprocess with specified parameters.
    111 
    112   Args:
    113     params: A list of parameters to pass to gclient.
    114     cwd: Working directory to run from.
    115 
    116   Returns:
    117     The return code of the call.
    118   """
    119   if os.name == 'nt':
    120     # "HOME" isn't normally defined on windows, but is needed
    121     # for git to find the user's .netrc file.
    122     if not os.getenv('HOME'):
    123       os.environ['HOME'] = os.environ['USERPROFILE']
    124   shell = os.name == 'nt'
    125   return subprocess.call(cmd, shell=shell, cwd=cwd)
    126 
    127 
    128 def RunGClient(params, cwd=None):
    129   """Runs gclient with the specified parameters.
    130 
    131   Args:
    132     params: A list of parameters to pass to gclient.
    133     cwd: Working directory to run from.
    134 
    135   Returns:
    136     The return code of the call.
    137   """
    138   cmd = ['gclient'] + params
    139 
    140   return SubprocessCall(cmd, cwd=cwd)
    141 
    142 
    143 def RunRepo(params):
    144   """Runs cros repo command with specified parameters.
    145 
    146   Args:
    147     params: A list of parameters to pass to gclient.
    148 
    149   Returns:
    150     The return code of the call.
    151   """
    152   cmd = ['repo'] + params
    153 
    154   return SubprocessCall(cmd)
    155 
    156 
    157 def RunRepoSyncAtTimestamp(timestamp):
    158   """Syncs all git depots to the timestamp specified using repo forall.
    159 
    160   Args:
    161     params: Unix timestamp to sync to.
    162 
    163   Returns:
    164     The return code of the call.
    165   """
    166   repo_sync = REPO_SYNC_COMMAND % timestamp
    167   cmd = ['forall', '-c', REPO_SYNC_COMMAND % timestamp]
    168   return RunRepo(cmd)
    169 
    170 
    171 def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None):
    172   """Runs gclient and creates a config containing both src and src-internal.
    173 
    174   Args:
    175     opts: The options parsed from the command line through parse_args().
    176     custom_deps: A dictionary of additional dependencies to add to .gclient.
    177     cwd: Working directory to run from.
    178 
    179   Returns:
    180     The return code of the call.
    181   """
    182   spec = GCLIENT_SPEC_DATA
    183 
    184   if custom_deps:
    185     for k, v in custom_deps.iteritems():
    186       spec[0]['custom_deps'][k] = v
    187 
    188   # Cannot have newlines in string on windows
    189   spec = 'solutions =' + str(spec)
    190   spec = ''.join([l for l in spec.splitlines()])
    191 
    192   if opts.target_platform == 'android':
    193     spec += GCLIENT_SPEC_ANDROID
    194 
    195   return_code = RunGClient(
    196       ['config', '--spec=%s' % spec, '--git-deps'], cwd=cwd)
    197   return return_code
    198 
    199 
    200 def IsDepsFileBlink():
    201   """Reads .DEPS.git and returns whether or not we're using blink.
    202 
    203   Returns:
    204     True if blink, false if webkit.
    205   """
    206   locals = {'Var': lambda _: locals["vars"][_],
    207             'From': lambda *args: None}
    208   execfile(FILE_DEPS_GIT, {}, locals)
    209   return 'blink.git' in locals['vars']['webkit_url']
    210 
    211 
    212 def RemoveThirdPartyWebkitDirectory():
    213   """Removes third_party/WebKit.
    214 
    215   Returns:
    216     True on success.
    217   """
    218   try:
    219     path_to_dir = os.path.join(os.getcwd(), 'third_party', 'WebKit')
    220     if os.path.exists(path_to_dir):
    221       shutil.rmtree(path_to_dir)
    222   except OSError, e:
    223     if e.errno != errno.ENOENT:
    224       return False
    225   return True
    226 
    227 
    228 def RunGClientAndSync(cwd=None):
    229   """Runs gclient and does a normal sync.
    230 
    231   Args:
    232     cwd: Working directory to run from.
    233 
    234   Returns:
    235     The return code of the call.
    236   """
    237   params = ['sync', '--verbose', '--nohooks', '--reset', '--force']
    238   return RunGClient(params, cwd=cwd)
    239 
    240 
    241 def SetupGitDepot(opts):
    242   """Sets up the depot for the bisection. The depot will be located in a
    243   subdirectory called 'bisect'.
    244 
    245   Args:
    246     opts: The options parsed from the command line through parse_args().
    247 
    248   Returns:
    249     True if gclient successfully created the config file and did a sync, False
    250     otherwise.
    251   """
    252   name = 'Setting up Bisection Depot'
    253 
    254   if opts.output_buildbot_annotations:
    255     OutputAnnotationStepStart(name)
    256 
    257   passed = False
    258 
    259   if not RunGClientAndCreateConfig(opts):
    260     passed_deps_check = True
    261     if os.path.isfile(os.path.join('src', FILE_DEPS_GIT)):
    262       cwd = os.getcwd()
    263       os.chdir('src')
    264       if not IsDepsFileBlink():
    265         passed_deps_check = RemoveThirdPartyWebkitDirectory()
    266       else:
    267         passed_deps_check = True
    268       os.chdir(cwd)
    269 
    270     if passed_deps_check:
    271       RunGClient(['revert'])
    272       if not RunGClientAndSync():
    273         passed = True
    274 
    275   if opts.output_buildbot_annotations:
    276     print
    277     OutputAnnotationStepClosed()
    278 
    279   return passed
    280 
    281 
    282 def SetupCrosRepo():
    283   """Sets up cros repo for bisecting chromeos.
    284 
    285   Returns:
    286     Returns 0 on success.
    287   """
    288   cwd = os.getcwd()
    289   try:
    290     os.mkdir('cros')
    291   except OSError, e:
    292     if e.errno != errno.EEXIST:
    293       return False
    294   os.chdir('cros')
    295 
    296   cmd = ['init', '-u'] + REPO_PARAMS
    297 
    298   passed = False
    299 
    300   if not RunRepo(cmd):
    301     if not RunRepo(['sync']):
    302       passed = True
    303   os.chdir(cwd)
    304 
    305   return passed
    306 
    307 
    308 def CopyAndSaveOriginalEnvironmentVars():
    309   """Makes a copy of the current environment variables."""
    310   # TODO: Waiting on crbug.com/255689, will remove this after.
    311   vars_to_remove = []
    312   for k, v in os.environ.iteritems():
    313     if 'ANDROID' in k:
    314       vars_to_remove.append(k)
    315   vars_to_remove.append('CHROME_SRC')
    316   vars_to_remove.append('CHROMIUM_GYP_FILE')
    317   vars_to_remove.append('GYP_CROSSCOMPILE')
    318   vars_to_remove.append('GYP_DEFINES')
    319   vars_to_remove.append('GYP_GENERATORS')
    320   vars_to_remove.append('GYP_GENERATOR_FLAGS')
    321   vars_to_remove.append('OBJCOPY')
    322   for k in vars_to_remove:
    323     if os.environ.has_key(k):
    324       del os.environ[k]
    325 
    326   global ORIGINAL_ENV
    327   ORIGINAL_ENV = os.environ.copy()
    328 
    329 
    330 def SetupAndroidBuildEnvironment(opts):
    331   """Sets up the android build environment.
    332 
    333   Args:
    334     opts: The options parsed from the command line through parse_args().
    335     path_to_file: Path to the bisect script's directory.
    336 
    337   Returns:
    338     True if successful.
    339   """
    340 
    341   # Revert the environment variables back to default before setting them up
    342   # with envsetup.sh.
    343   env_vars = os.environ.copy()
    344   for k, _ in env_vars.iteritems():
    345     del os.environ[k]
    346   for k, v in ORIGINAL_ENV.iteritems():
    347     os.environ[k] = v
    348 
    349   path_to_file = os.path.join('build', 'android', 'envsetup.sh')
    350   proc = subprocess.Popen(['bash', '-c', 'source %s && env' % path_to_file],
    351                            stdout=subprocess.PIPE,
    352                            stderr=subprocess.PIPE,
    353                            cwd='src')
    354   (out, _) = proc.communicate()
    355 
    356   for line in out.splitlines():
    357     (k, _, v) = line.partition('=')
    358     os.environ[k] = v
    359   return not proc.returncode
    360 
    361 
    362 def SetupPlatformBuildEnvironment(opts):
    363   """Performs any platform specific setup.
    364 
    365   Args:
    366     opts: The options parsed from the command line through parse_args().
    367     path_to_file: Path to the bisect script's directory.
    368 
    369   Returns:
    370     True if successful.
    371   """
    372   if opts.target_platform == 'android':
    373     CopyAndSaveOriginalEnvironmentVars()
    374     return SetupAndroidBuildEnvironment(opts)
    375   elif opts.target_platform == 'cros':
    376     return SetupCrosRepo()
    377 
    378   return True
    379 
    380 
    381 def CreateBisectDirectoryAndSetupDepot(opts):
    382   """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot
    383   there using gclient.
    384 
    385   Args:
    386     opts: The options parsed from the command line through parse_args().
    387     reset: Whether to reset any changes to the depot.
    388 
    389   Returns:
    390     Returns 0 on success, otherwise 1.
    391   """
    392   if not CreateAndChangeToSourceDirectory(opts.working_directory):
    393     print 'Error: Could not create bisect directory.'
    394     print
    395     return 1
    396 
    397   if not SetupGitDepot(opts):
    398     print 'Error: Failed to grab source.'
    399     print
    400     return 1
    401 
    402   return 0
    403