# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Common functions used for syncing Chrome.""" from __future__ import print_function import os import pprint from chromite.cbuildbot import config_lib from chromite.cbuildbot import constants from chromite.lib import cros_build_lib from chromite.lib import git from chromite.lib import osutils site_config = config_lib.GetConfig() CHROME_COMMITTER_URL = 'https://chromium.googlesource.com/chromium/src' STATUS_URL = 'https://chromium-status.appspot.com/current?format=json' # Last release for each milestone where a '.DEPS.git' was emitted. After this, # a Git-only DEPS is emitted as 'DEPS' and '.DEPS.git' is no longer created. _DEPS_GIT_TRANSITION_MAP = { 45: (45, 0, 2430, 3), 44: (44, 0, 2403, 48), 43: (43, 0, 2357, 125), } def FindGclientFile(path): """Returns the nearest higher-level gclient file from the specified path. Args: path: The path to use. Defaults to cwd. """ return osutils.FindInPathParents( '.gclient', path, test_func=os.path.isfile) def FindGclientCheckoutRoot(path): """Get the root of your gclient managed checkout.""" gclient_path = FindGclientFile(path) if gclient_path: return os.path.dirname(gclient_path) return None def _LoadGclientFile(path): """Load a gclient file and return the solutions defined by the gclient file. Args: path: The gclient file to load. Returns: A list of solutions defined by the gclient file or an empty list if no solutions exists. """ global_scope = {} # Similar to depot_tools, we use execfile() to evaluate the gclient file, # which is essentially a Python script, and then extract the solutions # defined by the gclient file from the 'solutions' variable in the global # scope. execfile(path, global_scope) return global_scope.get('solutions', []) def _FindOrAddSolution(solutions, name): """Find a solution of the specified name from the given list of solutions. If no solution with the specified name is found, a solution with the specified name is appended to the given list of solutions. This function thus always returns a solution. Args: solutions: The list of solutions to search from. name: The solution name to search for. Returns: The solution with the specified name. """ for solution in solutions: if solution['name'] == name: return solution solution = {'name': name} solutions.append(solution) return solution def BuildspecUsesDepsGit(rev): """Tests if a given buildspec revision uses .DEPS.git or DEPS. Previous, Chromium emitted two dependency files: DEPS and .DEPS.git, the latter being a Git-only construction of DEPS. Recently a switch was thrown, causing .DEPS.git to be emitted exclusively as DEPS. To support past buildspec checkouts, this logic tests a given Chromium buildspec revision against the transition thresholds, using .DEPS.git prior to transition and DEPS after. """ rev = tuple(int(d) for d in rev.split('.')) milestone = rev[0] threshold = _DEPS_GIT_TRANSITION_MAP.get(milestone) if threshold: return rev <= threshold return all(milestone < k for k in _DEPS_GIT_TRANSITION_MAP.iterkeys()) def _GetGclientURLs(internal, rev): """Get the URLs and deps_file values to use in gclient file. See WriteConfigFile below. """ results = [] if rev is None or git.IsSHA1(rev): # Regular chromium checkout; src may float to origin/master or be pinned. url = constants.CHROMIUM_GOB_URL if rev: url += ('@' + rev) results.append(('src', url, '.DEPS.git')) if internal: results.append( ('src-internal', constants.CHROME_INTERNAL_GOB_URL, '.DEPS.git')) elif internal: # Internal buildspec: check out the buildspec repo and set deps_file to # the path to the desired release spec. url = site_config.params.INTERNAL_GOB_URL + '/chrome/tools/buildspec.git' # Chromium switched to DEPS at version 45.0.2432.3. deps_file = '.DEPS.git' if BuildspecUsesDepsGit(rev) else 'DEPS' results.append(('CHROME_DEPS', url, 'releases/%s/%s' % (rev, deps_file))) else: # External buildspec: use the main chromium src repository, pinned to the # release tag, with deps_file set to .DEPS.git (which is created by # publish_deps.py). url = constants.CHROMIUM_GOB_URL + '@refs/tags/' + rev results.append(('src', url, '.DEPS.git')) return results def _GetGclientSolutions(internal, rev, template): """Get the solutions array to write to the gclient file. See WriteConfigFile below. """ urls = _GetGclientURLs(internal, rev) solutions = _LoadGclientFile(template) if template is not None else [] for (name, url, deps_file) in urls: solution = _FindOrAddSolution(solutions, name) # Always override 'url' and 'deps_file' of a solution as we need to specify # the revision information. solution['url'] = url if deps_file: solution['deps_file'] = deps_file # Use 'custom_deps' and 'custom_vars' of a solution when specified by the # template gclient file. solution.setdefault('custom_deps', {}) solution.setdefault('custom_vars', {}) return solutions def _GetGclientSpec(internal, rev, template, use_cache): """Return a formatted gclient spec. See WriteConfigFile below. """ solutions = _GetGclientSolutions(internal=internal, rev=rev, template=template) result = 'solutions = %s\n' % pprint.pformat(solutions) # Horrible hack, I will go to hell for this. The bots need to have a git # cache set up; but how can we tell whether this code is running on a bot # or a developer's machine? if cros_build_lib.HostIsCIBuilder() and use_cache: result += "cache_dir = '/b/git-cache'\n" return result def WriteConfigFile(gclient, cwd, internal, rev, template=None, use_cache=True): """Initialize the specified directory as a gclient checkout. For gclient documentation, see: http://src.chromium.org/svn/trunk/tools/depot_tools/README.gclient Args: gclient: Path to gclient. cwd: Directory to sync. internal: Whether you want an internal checkout. rev: Revision or tag to use. - If None, use the latest from trunk. - If this is a sha1, use the specified revision. - Otherwise, treat this as a chrome version string. template: An optional file to provide a template of gclient solutions. _GetGclientSolutions iterates through the solutions specified by the template and performs appropriate modifications such as filling information like url and revision and adding extra solutions. use_cache: An optional Boolean flag to indicate if the git cache should be used when available (on a continuous-integration builder). """ spec = _GetGclientSpec(internal=internal, rev=rev, template=template, use_cache=use_cache) cmd = [gclient, 'config', '--spec', spec] cros_build_lib.RunCommand(cmd, cwd=cwd) def Revert(gclient, cwd): """Revert all local changes. Args: gclient: Path to gclient. cwd: Directory to revert. """ cros_build_lib.RunCommand([gclient, 'revert', '--nohooks'], cwd=cwd) def Sync(gclient, cwd, reset=False): """Sync the specified directory using gclient. Args: gclient: Path to gclient. cwd: Directory to sync. reset: Reset to pristine version of the source code. """ cmd = [gclient, 'sync', '--verbose', '--nohooks', '--transitive', '--with_branch_heads', '--with_tags'] if reset: cmd += ['--reset', '--force', '--delete_unversioned_trees'] cros_build_lib.RunCommand(cmd, cwd=cwd)