blob: 15486da1267ca23a50c1f1bcd5f61d407314341c [file] [log] [blame]
# Copyright 2012 The ChromiumOS Authors
# 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."""
import os
import pprint
from chromite.lib import config_lib
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import git
from chromite.lib import osutils
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 exec() 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.
with open(path, "rb") as fp:
# pylint: disable=exec-used
exec(compile(fp.read(), path, "exec"), 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.keys())
def _GetGclientURLs(internal, rev):
"""Get the URLs and deps_file values to use in gclient file.
See WriteConfigFile below.
Returns:
Tuple of (name, url, deps_file).
"""
if rev is None or git.IsSHA1(rev) or rev == "HEAD":
# Regular chromium checkout; src may float to origin/main or be pinned.
url = constants.CHROMIUM_GOB_URL
if rev:
url += "@" + rev
return "src", url, None
else:
try:
major = int(rev.split(".")[0])
except ValueError:
major = None
# Starting with m90, start using the normal gclient fetch process rather
# than buildspecs. But keep using buildspecs for older versions for
# safety.
if major and major < 90 and internal:
# Internal buildspec: check out the buildspec repo and set deps_file
# to the path to the desired release spec.
site_params = config_lib.GetSiteParams()
url = site_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"
return "CHROME_DEPS", url, "releases/%s/%s" % (rev, deps_file)
else:
# Normal gclient fetch process: use the main chromium src
# repository, pinned to the release tag.
url = constants.CHROMIUM_GOB_URL + "@" + rev
return "src", url, None
def _GetGclientSolutions(internal, rev, template, managed):
"""Get the solutions array to write to the gclient file.
See WriteConfigFile below.
"""
name, url, deps_file = _GetGclientURLs(internal, rev)
solutions = LoadGclientFile(template) if template is not None else []
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
# TODO(engeg@): Remove this ugly hack once we get an acceptable build.
# See crbug.com/1044411 for more information. We landed
# this custom dep here: crrev.com/i/3304125.
# Also relanded for b:187795779.
if deps_file == "releases/51.0.2701.0/DEPS":
solution["deps_file"] = "releases/51.0.2701.0/DEPS_crbug_1044411"
# Use 'custom_deps' and 'custom_vars' of a solution when specified by the
# template gclient file.
solution.setdefault("custom_deps", {})
solution.setdefault("custom_vars", {})
if internal:
solution["custom_vars"].setdefault("checkout_src_internal", True)
solution["custom_vars"].setdefault("checkout_google_internal", True)
solution.setdefault("managed", managed)
return solutions
def _GetGclientSpec(internal, rev, template, use_cache, managed, git_cache_dir):
"""Return a formatted gclient spec.
See WriteConfigFile below.
"""
solutions = _GetGclientSolutions(internal, rev, template, managed)
result = "solutions = %s\n" % pprint.pformat(solutions)
result += "target_os = ['chromeos']\n"
if use_cache:
if not os.path.exists(git_cache_dir):
# Horrible hack, but we need a place to put the cache, and it can
# change dynamically (such as when inside the chroot).
git_cache_dir = "/tmp/git-cache"
result += "cache_dir = '%s'\n" % git_cache_dir
return result
def WriteConfigFile(
gclient,
cwd,
internal,
rev,
template=None,
use_cache=True,
managed=True,
git_cache_dir=None,
) -> None:
"""Initialize the specified directory as a gclient checkout.
For gclient documentation, see:
https://chromium.googlesource.com/chromium/tools/depot_tools/+/HEAD/README.gclient.md
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).
managed: Default value of gclient config's 'managed' field. Default True
(see crbug.com/624177).
git_cache_dir: Git Cache directory to use. If None will use
/b/git-cache.
"""
if not git_cache_dir:
git_cache_dir = "/b/git-cache"
spec = _GetGclientSpec(
internal, rev, template, use_cache, managed, git_cache_dir=git_cache_dir
)
cmd = [gclient, "config", "--spec", spec]
cros_build_lib.run(cmd, cwd=cwd)
def Revert(gclient, cwd) -> None:
"""Revert all local changes.
Args:
gclient: Path to gclient.
cwd: Directory to revert.
"""
cros_build_lib.run([gclient, "revert", "--nohooks"], cwd=cwd)
def Sync(gclient, cwd, reset=False, nohooks=True, verbose=True, run_args=None):
"""Sync the specified directory using gclient.
Args:
gclient: Path to gclient.
cwd: Directory to sync.
reset: Reset to pristine version of the source code.
nohooks: If set, add '--nohooks' argument.
verbose: If set, add '--verbose' argument.
run_args: If set (dict), pass to run as kwargs.
Returns:
A CompletedProcess object.
"""
if run_args is None:
run_args = {}
cmd = [gclient, "sync"]
if reset:
cmd += ["--reset", "--force", "--delete_unversioned_trees"]
if nohooks:
cmd.append("--nohooks")
if verbose:
cmd.append("--verbose")
return cros_build_lib.run(cmd, cwd=cwd, **run_args)