blob: 38149157d265cc77b2a2a3f92b7e24320ab50cfc [file] [log] [blame]
# Copyright 2019 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Recipe for automatically updating Crasphad and its sub-dependencies - mini_chromium & lss."""
from collections import OrderedDict
import re
from recipe_engine.config import List
from recipe_engine.recipe_api import Property
DEPS = [
"fuchsia/auto_roller",
"fuchsia/buildbucket_util",
"fuchsia/gerrit",
"fuchsia/git",
"fuchsia/gitiles",
"fuchsia/jiri",
"fuchsia/status_check",
"recipe_engine/buildbucket",
"recipe_engine/context",
"recipe_engine/file",
"recipe_engine/json",
"recipe_engine/path",
"recipe_engine/properties",
"recipe_engine/raw_io",
"recipe_engine/step",
]
PROPERTIES = {
"project": Property(kind=str, help="Jiri remote manifest project", default=None),
"manifest": Property(kind=str, help="Jiri manifest to use"),
"remote": Property(kind=str, help="Remote manifest repository"),
"import_in": Property(
kind=str,
help="Path to the manifest to edit relative to $project",
default="fuchsia/third_party/flower",
),
"revision": Property(kind=str, help="Crashpad revision to roll to", default=""),
"lockfiles": Property(
kind=List(str),
default=[],
help='The list of lockfiles to update in "${manifest}=${lockfile}" format',
),
}
CRASHPAD_PROJECT_NAME = "third_party/crashpad"
MINI_CHROMIUM_PROJECT_NAME = "third_party/mini_chromium"
LSS_PROJECT_NAME = "third_party/lss"
CRASHPAD_REMOTE = "https://chromium.googlesource.com/crashpad/crashpad"
COMMIT_MESSAGE = """\
[roll] Update {deps}
{logs}
"""
PROJECT_LOG_FORMAT = """\
{project} {old}..{new} ({count} commits)
{commits}
"""
TEST_DEPS_DATA_TEMPLATE = """\
'%s':
Var('chromium_git') + '/linux-syscall-support.git@' +
'8048ece6c16c91acfe0d36d1d3cc0890ab6e945c',
"""
def RunSteps(api, manifest, remote, project, revision, import_in, lockfiles):
with api.context(infra_steps=True):
api.jiri.init(use_lock_file=True)
api.jiri.import_manifest(manifest, remote, project)
api.jiri.update(run_hooks=False)
manifest_repo = api.path["start_dir"].join(*project.split("/"))
manifest_path = manifest_repo.join(*import_in.split("/"))
if not revision:
revision = api.git.get_remote_branch_head(CRASHPAD_REMOTE, "refs/heads/master")
updated_deps = update_crashpad_deps(api, revision, manifest_path)
if not updated_deps:
api.step("manifest up-to-date; nothing to roll", None)
return api.auto_roller.nothing_to_roll()
# Update lockfiles
with api.context(cwd=manifest_repo):
for lock_entry in lockfiles:
fields = lock_entry.split("=")
manifest = fields[0]
lock = fields[1]
api.jiri.resolve(local_manifest=True, output=lock, manifests=[manifest])
# Land the changes
commit_message = COMMIT_MESSAGE.format(
deps=", ".join(updated_deps.keys()),
logs="\n".join(updated_deps.itervalues()),
builder=api.buildbucket.builder_name,
build_id=api.buildbucket_util.id,
)
change = api.auto_roller.attempt_roll(
api.gerrit.host_from_remote_url(remote),
gerrit_project=project,
repo_dir=manifest_repo,
commit_message=commit_message,
)
return api.auto_roller.raw_result(change)
def extract_revision_from_deps(api, deps_path, dep_name):
"""Extracts the dependency version from Crashpad's DEPS file.
Args:
api (RecipeApi): Recipe API object.
deps_path (Path): A path to a DEPS file.
dep_name (str): The name of a dependency to extract from the DEPS file.
Returns:
A String containing the revision.
"""
dep_id = "crashpad/third_party/%s/%s" % (dep_name, dep_name)
contents = api.file.read_text(
name="read DEPS file for %s" % dep_name,
source=deps_path,
test_data=TEST_DEPS_DATA_TEMPLATE % dep_id,
)
m = re.search(r"\'%s\':[\s\S]*?\'(?P<revision>[0-9a-f]{40})\'," % dep_id, contents)
if m:
return m.group("revision")
raise api.step.InfraFailure("failed to find %s in DEPS" % dep_id)
def update_crashpad_deps(api, crashpad_revision, manifest):
"""Update manifest for all Crashpad dependencies.
Args:
api (RecipeApi): Recipe API object.
crashpad_revision (str): SHA-1 hash representing the Crashpad revision
to roll dependencies to.
manifest (str): Path to the manifest to edit relative to project.
Returns:
A dictionary mapping of updated dependencies to their corresponding
update logs.
"""
updated_deps = OrderedDict()
with api.step.nest("crashpad"):
update_manifest_project(
api,
manifest=manifest,
project_name=CRASHPAD_PROJECT_NAME,
revision=crashpad_revision,
updated_deps=updated_deps,
)
# Only check for sub-dependency changes if Crashpad's revision has been updated
if updated_deps:
tmp_dir = api.path.mkdtemp("git_checkout")
crashpad_dir = tmp_dir.join("crashpad")
crashpad_deps_path = crashpad_dir.join("DEPS")
# Checkout the Crashpad revision that we are rolling to in order to view
# its sub-dependencies which are defined in the Crashpad repo's DEPS file
api.git.checkout(
url=CRASHPAD_REMOTE,
path=crashpad_dir,
ref=crashpad_revision,
)
mini_chromium_revision = extract_revision_from_deps(
api, crashpad_deps_path, "mini_chromium"
)
lss_revision = extract_revision_from_deps(api, crashpad_deps_path, "lss")
with api.step.nest("mini_chromium"):
update_manifest_project(
api,
manifest=manifest,
project_name=MINI_CHROMIUM_PROJECT_NAME,
revision=mini_chromium_revision,
updated_deps=updated_deps,
)
with api.step.nest("lss"):
update_manifest_project(
api,
manifest=manifest,
project_name=LSS_PROJECT_NAME,
revision=lss_revision,
updated_deps=updated_deps,
)
return updated_deps
def update_manifest_project(api, manifest, project_name, revision, updated_deps):
"""Updates the revision for a project in a manifest.
Adds an entry to "updated_deps" if manifest is successfully updated;
otherwise, no new entries are added.
Args:
manifest (Path): Path to the Jiri manifest to update.
project_name (str): Name of the project in the Jiri manifest to update.
revision (str): SHA-1 hash representing the updated revision for
project_name in the manifest.
updated_deps (OrderedDict): A dictionary mapping project name to a formatted
log of the corresponding changes.
Returns:
The project's remote property.
"""
remote = api.jiri.read_manifest_element(
manifest=manifest,
element_type="project",
element_name=project_name,
).get("remote")
changes = api.jiri.edit_manifest(
manifest=manifest,
projects=[(project_name, revision)],
test_data={"projects": [{"old_revision": "abc123", "new_revision": "def456"}]},
name="jiri edit %s" % project_name,
)
if not changes["projects"]:
api.step.active_result.presentation.step_text = (
"manifest up-to-date, nothing to roll"
)
return remote
old_rev = changes["projects"][0]["old_revision"]
new_rev = changes["projects"][0]["new_revision"]
log = api.gitiles.log(
remote, "%s..%s" % (old_rev, new_rev), step_name="log %s" % project_name
)
formatted_log = PROJECT_LOG_FORMAT.format(
project=project_name,
old=old_rev[:7],
new=new_rev[:7],
count=len(log),
commits="\n".join(
[
"{commit} {subject}".format(
commit=commit["id"][:7],
subject=commit["message"].splitlines()[0],
)
for commit in log
]
),
)
# Add dependency update entry
updated_deps[project_name] = formatted_log
return remote
def GenTests(api):
project_names = {
"crashpad": CRASHPAD_PROJECT_NAME,
"mini_chromium": MINI_CHROMIUM_PROJECT_NAME,
"lss": LSS_PROJECT_NAME,
}
noop_edit = lambda name: api.step_data(
"%s.jiri edit %s" % (name, project_names[name]),
api.json.output({"projects": []}),
)
log_data = lambda name: api.gitiles.log(
"%s.log %s" % (name, project_names[name]), "A"
)
crashpad_check_data = api.jiri.read_manifest_element(
manifest="fuchsia/third_party/flower",
element_name=project_names["crashpad"],
element_type="project",
test_output={"remote": "https://chromium.googlesource.com/crashpad/crashpad"},
nesting="crashpad",
)
crashpad_log_data = log_data("crashpad")
mini_chromium_check_data = api.jiri.read_manifest_element(
manifest="fuchsia/third_party/flower",
element_name=project_names["mini_chromium"],
element_type="project",
test_output={"remote": "https://chromium.googlesource.com/crashpad/crashpad"},
nesting="mini_chromium",
)
mini_chromium_log_data = log_data("mini_chromium")
lss_check_data = api.jiri.read_manifest_element(
manifest="fuchsia/third_party/flower",
element_name=project_names["lss"],
element_type="project",
test_output={"remote": "https://chromium.googlesource.com/crashpad/crashpad"},
nesting="lss",
)
lss_log_data = log_data("lss")
yield (
api.status_check.test("noop_roll")
+ api.properties(
revision="abc123",
project="integration",
manifest="fuchsia/minimal",
remote="sso://integration",
)
+ crashpad_check_data
+ noop_edit("crashpad")
)
yield (
api.status_check.test("DEPS_parsing_error", status="infra_failure")
+ api.properties(
revision="abc123",
project="integration",
manifest="fuchsia/minimal",
remote="sso://integration",
)
+ crashpad_check_data
+ crashpad_log_data
+ api.step_data(
"read DEPS file for mini_chromium",
api.raw_io.output_text("invalid content"),
)
)
yield (
api.status_check.test("missing_revision")
+ api.properties(
project="integration",
manifest="fuchsia/minimal",
remote="sso://integration",
)
+ api.git.get_remote_branch_head(
"git ls-remote", "fc4dc762688d2263b254208f444f5c0a4b91bc07"
)
+ crashpad_check_data
+ crashpad_log_data
+ mini_chromium_check_data
+ noop_edit("mini_chromium")
+ lss_check_data
+ noop_edit("lss")
+ api.auto_roller.success()
)
yield (
api.status_check.test("crashpad_roll_with_lockfile")
+ api.properties(
revision="abc123",
project="integration",
manifest="fuchsia/minimal",
remote="sso://integration",
lockfiles=["manifest/topaz=jiri.lock"],
)
+ crashpad_check_data
+ crashpad_log_data
+ mini_chromium_check_data
+ noop_edit("mini_chromium")
+ lss_check_data
+ noop_edit("lss")
+ api.auto_roller.success()
)
yield (
api.status_check.test("crashpad")
+ api.properties(
revision="abc123",
project="integration",
manifest="fuchsia/minimal",
remote="sso://integration",
)
+ crashpad_check_data
+ crashpad_log_data
+ mini_chromium_check_data
+ noop_edit("mini_chromium")
+ lss_check_data
+ noop_edit("lss")
+ api.auto_roller.success()
)
yield (
api.status_check.test("crashpad_and_mini_chromium")
+ api.properties(
revision="abc123",
project="integration",
manifest="fuchsia/minimal",
remote="sso://integration",
)
+ crashpad_check_data
+ crashpad_log_data
+ mini_chromium_check_data
+ mini_chromium_log_data
+ lss_check_data
+ noop_edit("lss")
+ api.auto_roller.success()
)
yield (
api.status_check.test("crashpad_mini_chromium_and_lss")
+ api.properties(
revision="abc123",
project="integration",
manifest="fuchsia/minimal",
remote="sso://integration",
)
+ crashpad_check_data
+ crashpad_log_data
+ mini_chromium_check_data
+ mini_chromium_log_data
+ lss_check_data
+ lss_log_data
+ api.auto_roller.success()
)