| OLD | NEW |
| (Empty) |
| 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 imp | |
| 11 import os | |
| 12 import shutil | |
| 13 import stat | |
| 14 import subprocess | |
| 15 import sys | |
| 16 | |
| 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,} | |
| 49 | |
| 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": "", | |
| 57 }, | |
| 58 ] | |
| 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' | |
| 63 | |
| 64 REPO_PARAMS = [ | |
| 65 'https://chrome-internal.googlesource.com/chromeos/manifest-internal/', | |
| 66 '--repo-url', | |
| 67 'https://git.chromium.org/external/repo.git' | |
| 68 ] | |
| 69 | |
| 70 REPO_SYNC_COMMAND = 'git checkout -f $(git rev-list --max-count=1 '\ | |
| 71 '--before=%d remotes/m/master)' | |
| 72 | |
| 73 ORIGINAL_ENV = {} | |
| 74 | |
| 75 def OutputAnnotationStepStart(name): | |
| 76 """Outputs appropriate annotation to signal the start of a step to | |
| 77 a trybot. | |
| 78 | |
| 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() | |
| 88 | |
| 89 | |
| 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() | |
| 97 | |
| 98 | |
| 99 def OutputAnnotationStepLink(label, url): | |
| 100 """Outputs appropriate annotation to print a link. | |
| 101 | |
| 102 Args: | |
| 103 label: The name to print. | |
| 104 url: The url to print. | |
| 105 """ | |
| 106 print | |
| 107 print '@@@STEP_LINK@%s@%s@@@' % (label, url) | |
| 108 print | |
| 109 sys.stdout.flush() | |
| 110 | |
| 111 | |
| 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. | |
| 115 | |
| 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 | |
| 127 | |
| 128 | |
| 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) | |
| 132 | |
| 133 | |
| 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. | |
| 138 | |
| 139 Returns: | |
| 140 True if the directory was successfully created (or already existed). | |
| 141 """ | |
| 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 | |
| 151 | |
| 152 | |
| 153 def SubprocessCall(cmd, cwd=None): | |
| 154 """Runs a subprocess with specified parameters. | |
| 155 | |
| 156 Args: | |
| 157 params: A list of parameters to pass to gclient. | |
| 158 cwd: Working directory to run from. | |
| 159 | |
| 160 Returns: | |
| 161 The return code of the call. | |
| 162 """ | |
| 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) | |
| 170 | |
| 171 | |
| 172 def RunGClient(params, cwd=None): | |
| 173 """Runs gclient with the specified parameters. | |
| 174 | |
| 175 Args: | |
| 176 params: A list of parameters to pass to gclient. | |
| 177 cwd: Working directory to run from. | |
| 178 | |
| 179 Returns: | |
| 180 The return code of the call. | |
| 181 """ | |
| 182 cmd = ['gclient'] + params | |
| 183 | |
| 184 return SubprocessCall(cmd, cwd=cwd) | |
| 185 | |
| 186 | |
| 187 def RunRepo(params): | |
| 188 """Runs cros repo command with specified parameters. | |
| 189 | |
| 190 Args: | |
| 191 params: A list of parameters to pass to gclient. | |
| 192 | |
| 193 Returns: | |
| 194 The return code of the call. | |
| 195 """ | |
| 196 cmd = ['repo'] + params | |
| 197 | |
| 198 return SubprocessCall(cmd) | |
| 199 | |
| 200 | |
| 201 def RunRepoSyncAtTimestamp(timestamp): | |
| 202 """Syncs all git depots to the timestamp specified using repo forall. | |
| 203 | |
| 204 Args: | |
| 205 params: Unix timestamp to sync to. | |
| 206 | |
| 207 Returns: | |
| 208 The return code of the call. | |
| 209 """ | |
| 210 repo_sync = REPO_SYNC_COMMAND % timestamp | |
| 211 cmd = ['forall', '-c', REPO_SYNC_COMMAND % timestamp] | |
| 212 return RunRepo(cmd) | |
| 213 | |
| 214 | |
| 215 def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None): | |
| 216 """Runs gclient and creates a config containing both src and src-internal. | |
| 217 | |
| 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. | |
| 222 | |
| 223 Returns: | |
| 224 The return code of the call. | |
| 225 """ | |
| 226 spec = GCLIENT_SPEC_DATA | |
| 227 | |
| 228 if custom_deps: | |
| 229 for k, v in custom_deps.iteritems(): | |
| 230 spec[0]['custom_deps'][k] = v | |
| 231 | |
| 232 # Cannot have newlines in string on windows | |
| 233 spec = 'solutions =' + str(spec) | |
| 234 spec = ''.join([l for l in spec.splitlines()]) | |
| 235 | |
| 236 if 'android' in opts.target_platform: | |
| 237 spec += GCLIENT_SPEC_ANDROID | |
| 238 | |
| 239 return_code = RunGClient( | |
| 240 ['config', '--spec=%s' % spec, '--git-deps'], cwd=cwd) | |
| 241 return return_code | |
| 242 | |
| 243 | |
| 244 def IsDepsFileBlink(): | |
| 245 """Reads .DEPS.git and returns whether or not we're using blink. | |
| 246 | |
| 247 Returns: | |
| 248 True if blink, false if webkit. | |
| 249 """ | |
| 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'] | |
| 254 | |
| 255 | |
| 256 def OnAccessError(func, path, exc_info): | |
| 257 """ | |
| 258 Source: http://stackoverflow.com/questions/2656322/python-shutil-rmtree-fails-
on-windows-with-access-is-denied | |
| 259 | |
| 260 Error handler for ``shutil.rmtree``. | |
| 261 | |
| 262 If the error is due to an access error (read only file) | |
| 263 it attempts to add write permission and then retries. | |
| 264 | |
| 265 If the error is for another reason it re-raises the error. | |
| 266 | |
| 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(). | |
| 271 """ | |
| 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 | |
| 278 | |
| 279 | |
| 280 def RemoveThirdPartyDirectory(dir_name): | |
| 281 """Removes third_party directory from the source. | |
| 282 | |
| 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. | |
| 286 | |
| 287 Returns: | |
| 288 True on success, otherwise False. | |
| 289 """ | |
| 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 | |
| 300 | |
| 301 | |
| 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) | |
| 311 | |
| 312 | |
| 313 def RunGClientAndSync(cwd=None): | |
| 314 """Runs gclient and does a normal sync. | |
| 315 | |
| 316 Args: | |
| 317 cwd: Working directory to run from. | |
| 318 | |
| 319 Returns: | |
| 320 The return code of the call. | |
| 321 """ | |
| 322 params = ['sync', '--verbose', '--nohooks', '--reset', '--force'] | |
| 323 return RunGClient(params, cwd=cwd) | |
| 324 | |
| 325 | |
| 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'. | |
| 329 | |
| 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. | |
| 333 | |
| 334 Returns: | |
| 335 True if gclient successfully created the config file and did a sync, False | |
| 336 otherwise. | |
| 337 """ | |
| 338 name = 'Setting up Bisection Depot' | |
| 339 | |
| 340 if opts.output_buildbot_annotations: | |
| 341 OutputAnnotationStepStart(name) | |
| 342 | |
| 343 passed = False | |
| 344 | |
| 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) | |
| 359 | |
| 360 if passed_deps_check: | |
| 361 _CleanupPreviousGitRuns() | |
| 362 | |
| 363 RunGClient(['revert']) | |
| 364 if not RunGClientAndSync(): | |
| 365 passed = True | |
| 366 | |
| 367 if opts.output_buildbot_annotations: | |
| 368 print | |
| 369 OutputAnnotationStepClosed() | |
| 370 | |
| 371 return passed | |
| 372 | |
| 373 | |
| 374 def SetupCrosRepo(): | |
| 375 """Sets up cros repo for bisecting chromeos. | |
| 376 | |
| 377 Returns: | |
| 378 Returns 0 on success. | |
| 379 """ | |
| 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') | |
| 387 | |
| 388 cmd = ['init', '-u'] + REPO_PARAMS | |
| 389 | |
| 390 passed = False | |
| 391 | |
| 392 if not RunRepo(cmd): | |
| 393 if not RunRepo(['sync']): | |
| 394 passed = True | |
| 395 os.chdir(cwd) | |
| 396 | |
| 397 return passed | |
| 398 | |
| 399 | |
| 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] | |
| 417 | |
| 418 global ORIGINAL_ENV | |
| 419 ORIGINAL_ENV = os.environ.copy() | |
| 420 | |
| 421 | |
| 422 def SetupAndroidBuildEnvironment(opts, path_to_src=None): | |
| 423 """Sets up the android build environment. | |
| 424 | |
| 425 Args: | |
| 426 opts: The options parsed from the command line through parse_args(). | |
| 427 path_to_src: Path to the src checkout. | |
| 428 | |
| 429 Returns: | |
| 430 True if successful. | |
| 431 """ | |
| 432 | |
| 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 | |
| 440 | |
| 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() | |
| 447 | |
| 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' | |
| 460 | |
| 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 | |
| 465 | |
| 466 | |
| 467 def SetupPlatformBuildEnvironment(opts): | |
| 468 """Performs any platform specific setup. | |
| 469 | |
| 470 Args: | |
| 471 opts: The options parsed from the command line through parse_args(). | |
| 472 | |
| 473 Returns: | |
| 474 True if successful. | |
| 475 """ | |
| 476 if 'android' in opts.target_platform: | |
| 477 CopyAndSaveOriginalEnvironmentVars() | |
| 478 return SetupAndroidBuildEnvironment(opts) | |
| 479 elif opts.target_platform == 'cros': | |
| 480 return SetupCrosRepo() | |
| 481 | |
| 482 return True | |
| 483 | |
| 484 | |
| 485 def CheckIfBisectDepotExists(opts): | |
| 486 """Checks if the bisect directory already exists. | |
| 487 | |
| 488 Args: | |
| 489 opts: The options parsed from the command line through parse_args(). | |
| 490 | |
| 491 Returns: | |
| 492 Returns True if it exists. | |
| 493 """ | |
| 494 path_to_dir = os.path.join(opts.working_directory, 'bisect', 'src') | |
| 495 return os.path.exists(path_to_dir) | |
| 496 | |
| 497 | |
| 498 def CreateBisectDirectoryAndSetupDepot(opts, custom_deps): | |
| 499 """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot | |
| 500 there using gclient. | |
| 501 | |
| 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. | |
| 505 """ | |
| 506 if not CreateAndChangeToSourceDirectory(opts.working_directory): | |
| 507 raise RuntimeError('Could not create bisect directory.') | |
| 508 | |
| 509 if not SetupGitDepot(opts, custom_deps): | |
| 510 raise RuntimeError('Failed to grab source.') | |
| OLD | NEW |