blob: b81e535977a3f65a655e811bcb969e6546fae930 [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 rolling flutter and dependencies.
Flutter and dependencies need to get rolled based on the version
dependencies of another fuchsia prebuilt. In this recipe we are
calling the fuchsia prebuilt dictating the dependencies "orchestrator".
The orchestrator cipd package contains a json file with the dependencies
it was built with. The following is an example of the json file:
{
"flutter_version": "flutter_version_xyz",
"engine_version": "engine_version_xyz",
"skia_version": "skia_version_xyz",
"dart_version": "dart_version_xyz"
}
This recipe checks if there is a new version of the orchestrator and
if it requires different versions of the dependencies from the ones in
the source tree. If both conditions are true then it proceeds to roll
new versions of projects and packages as described in the
rolling_packages_projects parameter and using the versions from the
orchestrator dependencies json file.
Warning: If this recipe runs in dry mode it will still roll changes into
fuchsia dart 3p repository but the fuchsia tree won't pin to the latest
version. This is safe as the fuchsia dart 3p hash will be bumped only on a
successful roll of all the other dependencies.
"""
import copy
import os
from recipe_engine.config import List
from recipe_engine.post_process import StepSuccess
from recipe_engine.recipe_api import Property
DEPS = [
'fuchsia/jiri',
'fuchsia/auto_roller',
'fuchsia/buildbucket_util',
'fuchsia/cipd_dependencies',
'fuchsia/dart_util',
'fuchsia/debug_symbols',
'fuchsia/gitiles',
'recipe_engine/archive',
'recipe_engine/buildbucket',
'recipe_engine/cipd',
'recipe_engine/context',
'recipe_engine/file',
'recipe_engine/json',
'recipe_engine/properties',
'recipe_engine/path',
'recipe_engine/step',
]
PROPERTIES = {
'project':
Property(kind=str, help='Jiri remote manifest project', default=None),
'manifests':
Property(
kind=List(dict),
help=(
'A list of dictionaries with project, manifest and remote keys '
'used to signal jiri wich manifests to synchronize. '
'E.g. [{"project": "integration", "manifest": "other/dependency", '
' "remote": "sso://fuchsia/integration", '
' "lock_file": "integration/jiri.lock"}]')),
'locks':
Property(
kind=List(dict),
help=('A list of dictionaries with manifest and files keys used to '
'signal jiri which lock files to update. '
' E.g. [{"manifest": "integration/fuchsia/flower", '
' "file": "integration/fuchsia/jiri.lock"}]')),
'orchestrator_package':
Property(
kind=str,
help=('A string with the CIPD package used to validate '
'flutter and dependencies versions. This is a special '
'cipd package that contains a json file describing '
'the dependencies it was built with.')),
'orchestrator_ref':
Property(
kind=str,
default='latest',
help=('A cipd ref used to get a given version of the orchestrator '
'package')),
'orchestrator_import_in':
Property(
kind=str,
default='',
help=('A string with the location where the orchestrator package '
'should be imported into the source tree.')),
'owners':
Property(
kind=List(str),
default=(),
help=('The owners responsible for watching this roller '
'(example: "username@google.com").')),
'rolling_packages_projects':
Property(
kind=List(dict),
default=(),
help=('A list of dictionaries with rolling package/project, type,'
' and location to import_in. This is used to list the '
'dependencies that should be updated: '
'E.g. [{"name": "dart", "type": "package", '
' "import_in": "fuchsia/prebuilts", '
' "version_tag": "dart_version"}]')),
'versions_file':
Property(
kind=str,
help=('A string with the path to the versions file inside'
'the orchestrator CIPD package.')),
'validate_against_package':
Property(
kind=dict,
help=('A dictionary with the package and tag for the version to use'
' for validation. A single dependency is used to validate if'
' a roll is needed. E.g. {"package": "flutter/fuchsia", '
' "version_tag": "flutter_version"} '
'will validate the source tree version of flutter/fuchsia '
'with the value of flutter_version from the orchestrator '
'json file.')),
'dry_run':
Property(
kind=bool,
default=False,
help=('Whether to dry-run the auto-roller (CQ+1 and abandon the '
'change)')),
'debug_symbol_packages':
Property(
kind=List(str),
default=(),
help=('A list of strings with the cipd packages containing debug '
'symbols and their associated packages.')),
'debug_symbol_attribute':
Property(
kind=str,
default='',
help=(
'String with the cipd attribute to validate if a cipd package '
'contains debug symbols.')),
'debug_symbol_import_in':
Property(
kind=str,
default='',
help=('String with the path where debug symbols will be imported '
'in the source tree.')),
'debug_symbol_gcs_buckets':
Property(
kind=List(str),
default=(),
help=('List of strings with the gcs buckets where the debug '
'symbols will be uploaded to.')),
'debug_symbol_remote':
Property(
kind=str,
default='',
help='String with the url of the debug symbol remote repository')
}
COMMIT_MESSAGE = """[roll] Roll {roller} packages and projects:
{packages}
Test: CQ
CQ-Do-Not-Cancel-Tryjobs: true"""
def RunSteps(api, project, manifests, locks, orchestrator_ref, owners,
versions_file, rolling_packages_projects, orchestrator_package,
orchestrator_import_in, validate_against_package, dry_run,
debug_symbol_packages, debug_symbol_attribute,
debug_symbol_gcs_buckets, debug_symbol_import_in,
debug_symbol_remote):
"""Run the recipe steps."""
with api.context(infra_steps=True):
if owners:
with api.step.nest('owners') as presentation:
presentation.step_summary_text = ', '.join(owners)
# Import manifests
with api.step.nest('jiri-manifests') as presentation:
api.jiri.init(use_lock_file=True)
presentation.logs['manifests'] = str(manifests)
for manifest_config in manifests:
api.jiri.import_manifest(
manifest=manifest_config['manifest'],
remote=manifest_config['remote'],
name=manifest_config['project'])
api.jiri.update(run_hooks=False)
api.jiri.run_hooks()
deps_info = api.jiri.package([orchestrator_package])
cipd_description = api.cipd.describe(orchestrator_package, orchestrator_ref)
orchestrator_current_version = deps_info.json.output[0]['version']
# Is there a new version to roll?
orchestrator_expected_version = cipd_description.tags[0].tag
if orchestrator_expected_version == orchestrator_current_version:
msg = '%s in tree version is [%s] and latest from cipd is [%s]' % (
orchestrator_package, orchestrator_current_version,
orchestrator_expected_version)
api.step('manifest up-to-date; nothing to roll',
None).presentation.step_text = msg
return
# Read main package versions file.
orchestrator_version_dict = api.cipd_dependencies.get_dependencies(
'read versions', orchestrator_package, cipd_description.pin.instance_id,
versions_file)
# Use version of the validation package to decide whether a roll is needed or
# not.
validation_info = api.jiri.package([validate_against_package['package']])
local_version = validation_info.json.output[0]['version']
expected_version = 'git_revision:%s' % orchestrator_version_dict[
validate_against_package['version_tag']]
if local_version == expected_version:
msg = 'Expected version [%s] == local fuchsia tree version [%s]' % (
expected_version, local_version)
api.step('validation_summary', cmd=None).presentation.step_text = msg
return
# Start rolling orchestrator
rolling_packages_projects.append({
'type': 'package',
'name': orchestrator_package,
'version_tag': orchestrator_package,
'import_in': orchestrator_import_in
})
orchestrator_version_dict[
orchestrator_package] = orchestrator_expected_version
# Roll all the dependencies
package_msgs = []
package_msg_template = '%s %s rolled from %s to %s'
with api.step.nest('edit jiri manifests'):
for package_project in rolling_packages_projects:
package_msg = ''
if package_project['type'] == 'package':
if package_project['name'] == orchestrator_package:
expected_version = orchestrator_version_dict[
package_project['version_tag']]
else:
expected_version = 'git_revision:%s' % orchestrator_version_dict[
package_project['version_tag']]
changes = api.jiri.edit_manifest(
package_project['import_in'],
packages=[(package_project['name'], expected_version)],
name=package_project['name'])
# Sometimes only a subset of packages are updated. In those cases
# the changes list is empty.
if changes['packages']:
old_version = changes['packages'][0]['old_version']
package_msg = package_msg_template % (
'package', package_project['name'], old_version, expected_version)
else:
expected_version = orchestrator_version_dict[
package_project['version_tag']]
# We need to save dart/sdk version to update the dart
# plugins version later in this recipe.
if package_project['name'] == 'dart/sdk':
dart_sdk_revision = expected_version
changes = api.jiri.edit_manifest(
package_project['import_in'],
name=package_project['name'],
projects=[(package_project['name'], expected_version)])
# Sometimes only a subset of projects are updated. In those cases
# the changes list is empty.
if changes['projects']:
old_version = changes['projects'][0]['old_revision']
package_msg = package_msg_template % (
'project', package_project['name'], old_version, expected_version)
if package_msg:
package_msgs.append(package_msg)
# Roll dart internal plugins
sandbox_dir = api.path.mkdtemp('sandbox-flutter-dart')
dart_path = sandbox_dir.join('dart')
with api.step.nest('dart sdk checkout'):
api.dart_util.checkout(path=dart_path, revision=dart_sdk_revision)
with api.step.nest('dart third-party packages'):
checkout_root = api.path['start_dir'].join('integration')
api.dart_util.update_pkg_manifest(
path=dart_path, checkout_root=checkout_root)
# Update fuchsia's third-party dart packages, not to be confused with
# the previous updating of dart's own third-party packages.
manifest_repo = api.path['start_dir'].join('integration')
manifest_path = manifest_repo.join('fuchsia', 'topaz', 'topaz')
with api.step.nest('third-party dart packages'):
checkout_root = api.path['start_dir']
rolled_hash = api.dart_util.update_3p_packages(checkout_root=checkout_root)
changes = api.jiri.edit_manifest(
'integration/fuchsia/topaz/dart',
name='third_party/dart-pkg',
projects=[('third_party/dart-pkg', rolled_hash)])
# Update lock files
with api.step.nest('update jiri lock files'):
for lock in locks:
api.jiri.resolve(
local_manifest=True,
output=lock['file'],
manifests=[lock['manifest']])
roller_name = api.buildbucket.builder_name
if roller_name.endswith('-roller'):
roller_name = api.buildbucket.builder_name[:7]
message = COMMIT_MESSAGE.format(
roller=roller_name,
packages='\n\n'.join(package_msgs),
builder=api.buildbucket.builder_name,
build_id=api.buildbucket_util.id,
)
# All the changes are happening inside one of the git cloned projects.
# The "project" property points to the location of the project containing
# the changes and we need to move to that directory for the roller to pick
# up the changes.
project_dir = api.path['start_dir'].join(*project.split('/'))
rolled = False
with api.context(cwd=project_dir):
rolled = api.auto_roller.attempt_roll(
gerrit_project=project,
repo_dir=project_dir,
commit_message=message,
dry_run=dry_run,
)
# Push debug symbols.
# If running on dry_run mode skip building and pushing debug symbols
# because the correct dependencies won't be available in the tree.
if rolled and debug_symbol_gcs_buckets and not dry_run:
checkout_root = api.path['start_dir']
api.debug_symbols.fetch_and_upload(
project=project,
checkout_root=checkout_root,
import_in=debug_symbol_import_in,
remote=debug_symbol_remote,
project_dir=project_dir,
packages=debug_symbol_packages,
debug_symbol_attribute=debug_symbol_attribute,
debug_symbol_gcs_buckets=debug_symbol_gcs_buckets)
def GenTests(api):
"""Tests for cipd with dependents roller."""
properties = {
'project': 'integration',
'manifests': [{
'project': 'integration',
'manifest': 'other/dependency',
'remote': 'sso://fuchsia/integration',
'lock_file': 'integration/jiri.lock'
}, {
'project': 'integration',
'manifest': 'fuchsia/flower',
'remote': 'sso://fuchsia/integration',
'lock_file': 'integration/fuchsia/jiri.lock'
}],
'locks': [{
'manifest': 'a/b',
'file': 'a/b/jiri.lock'
}],
'orchestrator_package': 'fuchsia/orchestrator',
'orchestrator_import_in': 'fuchsia/prebuilts',
'validate_against_package': {
'package': 'flutter/fuchsia',
'version_tag': 'flutter_version'
},
'rolling_packages_projects': [
{
'name': 'dart/sdk',
'type': 'project',
'import_in': 'fuchsia/prebuilts',
'version_tag': 'dart_version'
},
{
'name': 'skia',
'type': 'project',
'import_in': 'fuchsia/prebuilts',
'version_tag': 'skia_version'
},
{
'name': 'flutter',
'type': 'package',
'import_in': 'fuchsia/prebuilts',
'version_tag': 'engine_version'
},
],
'versions_file': 'flutter/versions.json',
'owners': ['abc@gmail.com'],
'dry_run': True,
'debug_symbol_packages': ['flutter/fuchsia-debug-symbols-x64',],
'debug_symbol_attribute': 'debug-symbols',
'debug_symbol_gcs_buckets': ['fuchsia-debug-symbols-shortlived'],
'debug_symbol_import_in': 'integration/fuchsia/prebuilts',
'debug_symbol_remote': 'sso://fuchsia/integration'
}
package_no_match_test_data = api.step_data(
'cipd describe fuchsia/orchestrator',
api.cipd.example_describe(
package_name='fuchsia/orchestrator',
version='version_abc',
test_data_tags=['git_revision:revision_abc']))
jiri_package_test_data = [
{
'name': 'fuchsia/orchestrator',
'path': 'fuchsia/orchestrator',
'version': 'git_revision:revision_abc',
'manifest': 'manifest1'
},
]
default_properties = api.properties(**properties)
# Version of the cipd orchestrator package is the same as the one
# in the tree.
yield (
api.test('nothing_to_roll') + default_properties +
package_no_match_test_data +
api.step_data('jiri package', api.jiri.package(jiri_package_test_data)))
# Version of the cipd orchestrator package is different from the one
# in the tree and ready to get rolled but flutter version is the same as the
# one in the tree.
package_match_test_data = api.step_data(
'cipd describe fuchsia/orchestrator',
api.cipd.example_describe(
package_name='fuchsia/orchestrator',
version='version_abc',
test_data_tags=['git_revision:revision_jkl']))
package_versions = {
'flutter_version': 'flutter_version_xyz',
'engine_version': 'engine_version_xyz',
'skia_version': 'skia_version_xyz',
'dart_version': 'dart_version_xyz'
}
jiri_flutter_package_test_data = [
{
'name': 'flutter/fuchsia',
'path': 'flutter/fuchsia',
'version': 'git_revision:flutter_version_xyz',
'manifest': 'manifest1'
},
]
yield (api.test('orchestrator_dep_version_same_as_tree') +
default_properties + package_match_test_data + api.step_data(
'jiri package', api.jiri.package(jiri_package_test_data)) +
api.step_data('read versions',
api.file.read_json(json_content=package_versions)) +
package_match_test_data +
api.step_data('jiri package (2)',
api.jiri.package(jiri_flutter_package_test_data)) +
api.post_process(StepSuccess, 'validation_summary'))
# Version of the validation cipd package is different from the one in the tree
# and ready to get rolled along its dependencies.
jiri_flutter_package_test_data_no_match = [
{
'name': 'flutter/fuchsia',
'path': 'flutter/fuchsia',
'version': 'git_revision:flutter_version_xyz1',
'manifest': 'manifest1'
},
]
yield (
api.test('orchestrator_dep_version_different_from_tree_dry_run') +
default_properties + package_match_test_data +
api.step_data('jiri package', api.jiri.package(jiri_package_test_data)) +
api.step_data('read versions',
api.file.read_json(json_content=package_versions)) +
api.step_data('jiri package (2)',
api.jiri.package(jiri_flutter_package_test_data_no_match)) +
api.auto_roller.success_step_data() + api.buildbucket.build(
api.buildbucket.ci_build_message(builder='flutter-dependents-roller'))
+ api.auto_roller.success_step_data(
name='third-party dart packages.check for completion.check if done (0)'
))
# Validate the build and upload debug symbols path.
props = copy.deepcopy(properties)
props['dry_run'] = False
no_dry_run_props = api.properties(**props)
yield (
api.test('orchestrator_dep_version_different_from_tree') +
no_dry_run_props + package_match_test_data +
api.step_data('jiri package', api.jiri.package(jiri_package_test_data)) +
api.step_data('read versions',
api.file.read_json(json_content=package_versions)) +
api.step_data('jiri package (2)',
api.jiri.package(jiri_flutter_package_test_data_no_match)) +
api.auto_roller.success_step_data() + api.buildbucket.build(
api.buildbucket.ci_build_message(builder='flutter-dependents-roller'))
+ api.auto_roller.success_step_data(
name='third-party dart packages.check for completion.check if done (0)'
) + api.jiri.read_manifest_element(
api,
'fuchsia/prebuilts',
'package',
'flutter/fuchsia-debug-symbols-x64',
test_output={
'path': 'prebuilt/build_ids/arm64/flutter',
'attributes': 'debug-symbols',
}))