| # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Set of operations/utilities related to checking out the depot, and |
| outputting annotations on the buildbot waterfall. These are intended to be |
| used by the bisection scripts.""" |
| |
| import errno |
| import imp |
| import os |
| import shutil |
| import stat |
| import subprocess |
| import sys |
| |
| DEFAULT_GCLIENT_CUSTOM_DEPS = { |
| "src/data/page_cycler": "https://chrome-internal.googlesource.com/" |
| "chrome/data/page_cycler/.git", |
| "src/data/dom_perf": "https://chrome-internal.googlesource.com/" |
| "chrome/data/dom_perf/.git", |
| "src/data/mach_ports": "https://chrome-internal.googlesource.com/" |
| "chrome/data/mach_ports/.git", |
| "src/tools/perf/data": "https://chrome-internal.googlesource.com/" |
| "chrome/tools/perf/data/.git", |
| "src/third_party/adobe/flash/binaries/ppapi/linux": |
| "https://chrome-internal.googlesource.com/" |
| "chrome/deps/adobe/flash/binaries/ppapi/linux/.git", |
| "src/third_party/adobe/flash/binaries/ppapi/linux_x64": |
| "https://chrome-internal.googlesource.com/" |
| "chrome/deps/adobe/flash/binaries/ppapi/linux_x64/.git", |
| "src/third_party/adobe/flash/binaries/ppapi/mac": |
| "https://chrome-internal.googlesource.com/" |
| "chrome/deps/adobe/flash/binaries/ppapi/mac/.git", |
| "src/third_party/adobe/flash/binaries/ppapi/mac_64": |
| "https://chrome-internal.googlesource.com/" |
| "chrome/deps/adobe/flash/binaries/ppapi/mac_64/.git", |
| "src/third_party/adobe/flash/binaries/ppapi/win": |
| "https://chrome-internal.googlesource.com/" |
| "chrome/deps/adobe/flash/binaries/ppapi/win/.git", |
| "src/third_party/adobe/flash/binaries/ppapi/win_x64": |
| "https://chrome-internal.googlesource.com/" |
| "chrome/deps/adobe/flash/binaries/ppapi/win_x64/.git",} |
| |
| GCLIENT_SPEC_DATA = [ |
| { "name" : "src", |
| "url" : "https://chromium.googlesource.com/chromium/src.git", |
| "deps_file" : ".DEPS.git", |
| "managed" : True, |
| "custom_deps" : {}, |
| "safesync_url": "", |
| }, |
| ] |
| GCLIENT_SPEC_ANDROID = "\ntarget_os = ['android']" |
| GCLIENT_CUSTOM_DEPS_V8 = {"src/v8_bleeding_edge": "git://github.com/v8/v8.git"} |
| FILE_DEPS_GIT = '.DEPS.git' |
| |
| REPO_PARAMS = [ |
| 'https://chrome-internal.googlesource.com/chromeos/manifest-internal/', |
| '--repo-url', |
| 'https://git.chromium.org/external/repo.git' |
| ] |
| |
| REPO_SYNC_COMMAND = 'git checkout -f $(git rev-list --max-count=1 '\ |
| '--before=%d remotes/m/master)' |
| |
| ORIGINAL_ENV = {} |
| |
| def OutputAnnotationStepStart(name): |
| """Outputs appropriate annotation to signal the start of a step to |
| a trybot. |
| |
| Args: |
| name: The name of the step. |
| """ |
| print |
| print '@@@SEED_STEP %s@@@' % name |
| print '@@@STEP_CURSOR %s@@@' % name |
| print '@@@STEP_STARTED@@@' |
| print |
| sys.stdout.flush() |
| |
| |
| def OutputAnnotationStepClosed(): |
| """Outputs appropriate annotation to signal the closing of a step to |
| a trybot.""" |
| print |
| print '@@@STEP_CLOSED@@@' |
| print |
| sys.stdout.flush() |
| |
| |
| def OutputAnnotationStepLink(label, url): |
| """Outputs appropriate annotation to print a link. |
| |
| Args: |
| label: The name to print. |
| url: The url to print. |
| """ |
| print |
| print '@@@STEP_LINK@%s@%s@@@' % (label, url) |
| print |
| sys.stdout.flush() |
| |
| |
| def LoadExtraSrc(path_to_file): |
| """Attempts to load an extra source file. If this is successful, uses the |
| new module to override some global values, such as gclient spec data. |
| |
| Returns: |
| The loaded src module, or None.""" |
| try: |
| global GCLIENT_SPEC_DATA |
| global GCLIENT_SPEC_ANDROID |
| extra_src = imp.load_source('data', path_to_file) |
| GCLIENT_SPEC_DATA = extra_src.GetGClientSpec() |
| GCLIENT_SPEC_ANDROID = extra_src.GetGClientSpecExtraParams() |
| return extra_src |
| except ImportError, e: |
| return None |
| |
| |
| def IsTelemetryCommand(command): |
| """Attempts to discern whether or not a given command is running telemetry.""" |
| return ('tools/perf/run_' in command or 'tools\\perf\\run_' in command) |
| |
| |
| def CreateAndChangeToSourceDirectory(working_directory): |
| """Creates a directory 'bisect' as a subdirectory of 'working_directory'. If |
| the function is successful, the current working directory will change to that |
| of the new 'bisect' directory. |
| |
| Returns: |
| True if the directory was successfully created (or already existed). |
| """ |
| cwd = os.getcwd() |
| os.chdir(working_directory) |
| try: |
| os.mkdir('bisect') |
| except OSError, e: |
| if e.errno != errno.EEXIST: |
| return False |
| os.chdir('bisect') |
| return True |
| |
| |
| def SubprocessCall(cmd, cwd=None): |
| """Runs a subprocess with specified parameters. |
| |
| Args: |
| params: A list of parameters to pass to gclient. |
| cwd: Working directory to run from. |
| |
| Returns: |
| The return code of the call. |
| """ |
| if os.name == 'nt': |
| # "HOME" isn't normally defined on windows, but is needed |
| # for git to find the user's .netrc file. |
| if not os.getenv('HOME'): |
| os.environ['HOME'] = os.environ['USERPROFILE'] |
| shell = os.name == 'nt' |
| return subprocess.call(cmd, shell=shell, cwd=cwd) |
| |
| |
| def RunGClient(params, cwd=None): |
| """Runs gclient with the specified parameters. |
| |
| Args: |
| params: A list of parameters to pass to gclient. |
| cwd: Working directory to run from. |
| |
| Returns: |
| The return code of the call. |
| """ |
| cmd = ['gclient'] + params |
| |
| return SubprocessCall(cmd, cwd=cwd) |
| |
| |
| def RunRepo(params): |
| """Runs cros repo command with specified parameters. |
| |
| Args: |
| params: A list of parameters to pass to gclient. |
| |
| Returns: |
| The return code of the call. |
| """ |
| cmd = ['repo'] + params |
| |
| return SubprocessCall(cmd) |
| |
| |
| def RunRepoSyncAtTimestamp(timestamp): |
| """Syncs all git depots to the timestamp specified using repo forall. |
| |
| Args: |
| params: Unix timestamp to sync to. |
| |
| Returns: |
| The return code of the call. |
| """ |
| repo_sync = REPO_SYNC_COMMAND % timestamp |
| cmd = ['forall', '-c', REPO_SYNC_COMMAND % timestamp] |
| return RunRepo(cmd) |
| |
| |
| def RunGClientAndCreateConfig(opts, custom_deps=None, cwd=None): |
| """Runs gclient and creates a config containing both src and src-internal. |
| |
| Args: |
| opts: The options parsed from the command line through parse_args(). |
| custom_deps: A dictionary of additional dependencies to add to .gclient. |
| cwd: Working directory to run from. |
| |
| Returns: |
| The return code of the call. |
| """ |
| spec = GCLIENT_SPEC_DATA |
| |
| if custom_deps: |
| for k, v in custom_deps.iteritems(): |
| spec[0]['custom_deps'][k] = v |
| |
| # Cannot have newlines in string on windows |
| spec = 'solutions =' + str(spec) |
| spec = ''.join([l for l in spec.splitlines()]) |
| |
| if 'android' in opts.target_platform: |
| spec += GCLIENT_SPEC_ANDROID |
| |
| return_code = RunGClient( |
| ['config', '--spec=%s' % spec, '--git-deps'], cwd=cwd) |
| return return_code |
| |
| |
| def IsDepsFileBlink(): |
| """Reads .DEPS.git and returns whether or not we're using blink. |
| |
| Returns: |
| True if blink, false if webkit. |
| """ |
| locals = {'Var': lambda _: locals["vars"][_], |
| 'From': lambda *args: None} |
| execfile(FILE_DEPS_GIT, {}, locals) |
| return 'blink.git' in locals['vars']['webkit_url'] |
| |
| |
| def RemoveThirdPartyWebkitDirectory(): |
| """Removes third_party/WebKit. |
| |
| Returns: |
| True on success. |
| """ |
| try: |
| path_to_dir = os.path.join(os.getcwd(), 'third_party', 'WebKit') |
| if os.path.exists(path_to_dir): |
| shutil.rmtree(path_to_dir) |
| except OSError, e: |
| if e.errno != errno.ENOENT: |
| return False |
| return True |
| |
| |
| def OnAccessError(func, path, exc_info): |
| """ |
| Source: http://stackoverflow.com/questions/2656322/python-shutil-rmtree-fails-on-windows-with-access-is-denied |
| |
| Error handler for ``shutil.rmtree``. |
| |
| If the error is due to an access error (read only file) |
| it attempts to add write permission and then retries. |
| |
| If the error is for another reason it re-raises the error. |
| |
| Args: |
| func: The function that raised the error. |
| path: The path name passed to func. |
| exc_info: Exception information returned by sys.exc_info(). |
| """ |
| if not os.access(path, os.W_OK): |
| # Is the error an access error ? |
| os.chmod(path, stat.S_IWUSR) |
| func(path) |
| else: |
| raise |
| |
| |
| def RemoveThirdPartyLibjingleDirectory(): |
| """Removes third_party/libjingle. At some point, libjingle was causing issues |
| syncing when using the git workflow (crbug.com/266324). |
| |
| Returns: |
| True on success. |
| """ |
| path_to_dir = os.path.join(os.getcwd(), 'third_party', 'libjingle') |
| try: |
| if os.path.exists(path_to_dir): |
| shutil.rmtree(path_to_dir, onerror=OnAccessError) |
| except OSError, e: |
| print 'Error #%d while running shutil.rmtree(%s): %s' % ( |
| e.errno, path_to_dir, str(e)) |
| if e.errno != errno.ENOENT: |
| return False |
| return True |
| |
| |
| def _CleanupPreviousGitRuns(): |
| """Performs necessary cleanup between runs.""" |
| # If a previous run of git crashed, bot was reset, etc... we |
| # might end up with leftover index.lock files. |
| for (path, dir, files) in os.walk(os.getcwd()): |
| for cur_file in files: |
| if cur_file.endswith('index.lock'): |
| path_to_file = os.path.join(path, cur_file) |
| os.remove(path_to_file) |
| |
| |
| def RunGClientAndSync(cwd=None): |
| """Runs gclient and does a normal sync. |
| |
| Args: |
| cwd: Working directory to run from. |
| |
| Returns: |
| The return code of the call. |
| """ |
| params = ['sync', '--verbose', '--nohooks', '--reset', '--force'] |
| return RunGClient(params, cwd=cwd) |
| |
| |
| def SetupGitDepot(opts, custom_deps): |
| """Sets up the depot for the bisection. The depot will be located in a |
| subdirectory called 'bisect'. |
| |
| Args: |
| opts: The options parsed from the command line through parse_args(). |
| custom_deps: A dictionary of additional dependencies to add to .gclient. |
| |
| Returns: |
| True if gclient successfully created the config file and did a sync, False |
| otherwise. |
| """ |
| name = 'Setting up Bisection Depot' |
| |
| if opts.output_buildbot_annotations: |
| OutputAnnotationStepStart(name) |
| |
| passed = False |
| |
| if not RunGClientAndCreateConfig(opts, custom_deps): |
| passed_deps_check = True |
| if os.path.isfile(os.path.join('src', FILE_DEPS_GIT)): |
| cwd = os.getcwd() |
| os.chdir('src') |
| if not IsDepsFileBlink(): |
| passed_deps_check = RemoveThirdPartyWebkitDirectory() |
| else: |
| passed_deps_check = True |
| if passed_deps_check: |
| passed_deps_check = RemoveThirdPartyLibjingleDirectory() |
| os.chdir(cwd) |
| |
| if passed_deps_check: |
| _CleanupPreviousGitRuns() |
| |
| RunGClient(['revert']) |
| if not RunGClientAndSync(): |
| passed = True |
| |
| if opts.output_buildbot_annotations: |
| print |
| OutputAnnotationStepClosed() |
| |
| return passed |
| |
| |
| def SetupCrosRepo(): |
| """Sets up cros repo for bisecting chromeos. |
| |
| Returns: |
| Returns 0 on success. |
| """ |
| cwd = os.getcwd() |
| try: |
| os.mkdir('cros') |
| except OSError, e: |
| if e.errno != errno.EEXIST: |
| return False |
| os.chdir('cros') |
| |
| cmd = ['init', '-u'] + REPO_PARAMS |
| |
| passed = False |
| |
| if not RunRepo(cmd): |
| if not RunRepo(['sync']): |
| passed = True |
| os.chdir(cwd) |
| |
| return passed |
| |
| |
| def CopyAndSaveOriginalEnvironmentVars(): |
| """Makes a copy of the current environment variables.""" |
| # TODO: Waiting on crbug.com/255689, will remove this after. |
| vars_to_remove = [] |
| for k, v in os.environ.iteritems(): |
| if 'ANDROID' in k: |
| vars_to_remove.append(k) |
| vars_to_remove.append('CHROME_SRC') |
| vars_to_remove.append('CHROMIUM_GYP_FILE') |
| vars_to_remove.append('GYP_CROSSCOMPILE') |
| vars_to_remove.append('GYP_DEFINES') |
| vars_to_remove.append('GYP_GENERATORS') |
| vars_to_remove.append('GYP_GENERATOR_FLAGS') |
| vars_to_remove.append('OBJCOPY') |
| for k in vars_to_remove: |
| if os.environ.has_key(k): |
| del os.environ[k] |
| |
| global ORIGINAL_ENV |
| ORIGINAL_ENV = os.environ.copy() |
| |
| |
| def SetupAndroidBuildEnvironment(opts, path_to_src=None): |
| """Sets up the android build environment. |
| |
| Args: |
| opts: The options parsed from the command line through parse_args(). |
| path_to_src: Path to the src checkout. |
| |
| Returns: |
| True if successful. |
| """ |
| |
| # Revert the environment variables back to default before setting them up |
| # with envsetup.sh. |
| env_vars = os.environ.copy() |
| for k, _ in env_vars.iteritems(): |
| del os.environ[k] |
| for k, v in ORIGINAL_ENV.iteritems(): |
| os.environ[k] = v |
| |
| path_to_file = os.path.join('build', 'android', 'envsetup.sh') |
| proc = subprocess.Popen(['bash', '-c', 'source %s && env' % path_to_file], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| cwd=path_to_src) |
| (out, _) = proc.communicate() |
| |
| for line in out.splitlines(): |
| (k, _, v) = line.partition('=') |
| os.environ[k] = v |
| |
| return not proc.returncode |
| |
| |
| def SetupPlatformBuildEnvironment(opts): |
| """Performs any platform specific setup. |
| |
| Args: |
| opts: The options parsed from the command line through parse_args(). |
| |
| Returns: |
| True if successful. |
| """ |
| if 'android' in opts.target_platform: |
| CopyAndSaveOriginalEnvironmentVars() |
| return SetupAndroidBuildEnvironment(opts) |
| elif opts.target_platform == 'cros': |
| return SetupCrosRepo() |
| |
| return True |
| |
| |
| def CheckIfBisectDepotExists(opts): |
| """Checks if the bisect directory already exists. |
| |
| Args: |
| opts: The options parsed from the command line through parse_args(). |
| |
| Returns: |
| Returns True if it exists. |
| """ |
| path_to_dir = os.path.join(opts.working_directory, 'bisect', 'src') |
| return os.path.exists(path_to_dir) |
| |
| |
| def CreateBisectDirectoryAndSetupDepot(opts, custom_deps): |
| """Sets up a subdirectory 'bisect' and then retrieves a copy of the depot |
| there using gclient. |
| |
| Args: |
| opts: The options parsed from the command line through parse_args(). |
| custom_deps: A dictionary of additional dependencies to add to .gclient. |
| """ |
| if not CreateAndChangeToSourceDirectory(opts.working_directory): |
| raise RuntimeError('Could not create bisect directory.') |
| |
| if not SetupGitDepot(opts, custom_deps): |
| raise RuntimeError('Failed to grab source.') |