blob: 419b64216c465b2767e18dfb70c2a4c777b54ee6 [file] [log] [blame]
# Copyright 2020 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.
from recipe_engine import post_process
from PB.recipes.dart.roller.roll_to_dev import RollToDev
DEPS = [
'depot_tools/bot_update',
'depot_tools/gclient',
'depot_tools/git',
'depot_tools/gitiles',
'recipe_engine/context',
'recipe_engine/file',
'recipe_engine/json',
'recipe_engine/path',
'recipe_engine/properties',
'recipe_engine/raw_io',
'recipe_engine/runtime',
'recipe_engine/step',
]
DEV_BRANCH = 'dev'
DEV_REF = 'refs/heads/dev'
MESSAGE_TEMPLATE = '''Version %s
Merge commit '%s' into '%s'
'''
PROPERTIES = RollToDev
REPO_URL = 'https://dart.googlesource.com/sdk'
SUPPORTED_FIELDS = {
'CHANNEL', 'MAJOR', 'MINOR', 'PATCH', 'PRERELEASE', 'PRERELEASE_PATCH'
}
VERSION_PATH = 'tools/VERSION'
def RunSteps(api, properties):
from_ref = properties.from_ref
assert from_ref, 'the recipe requires a from_ref'
to_ref = DEV_REF
commit = api.gitiles.commit_log(
commit=from_ref,
step_name='get commit to merge',
url=REPO_URL,
)
commit_hash = commit['commit']
assert commit_hash, 'no commit hash to merge found'
api.git.checkout(
REPO_URL,
ref=to_ref,
submodules=False,
submodule_update_recursive=False,
)
with api.context(cwd=api.path['checkout']):
api.git('config', 'user.name', 'Dart CI', name='configure user.name')
api.git(
'config',
'user.email',
'dart-luci-ci-builder@dart-ci.iam.gserviceaccount.com',
name='configure user.email')
api.git('checkout', DEV_BRANCH, name='checkout dev')
api.git('fetch', 'origin', commit_hash, name='fetch %s' % commit_hash)
result = api.git(
'merge-base',
'--is-ancestor',
commit_hash,
'HEAD',
name='check if commit has been merged before',
ok_ret=[0, 1], # merge-base returns 1 if the commit is not an ancestor.
)
if result.exc_result.retcode == 0:
# The commit has already been pushed before.
return
api.git(
'merge',
'--no-commit',
'--no-ff',
commit_hash,
name='merge %s to dev' % commit_hash,
ok_ret='any')
api.git(
'checkout', '--ours', VERSION_PATH, name='restore version file on dev')
version, channel = _update_version_file(api, from_ref)
message = MESSAGE_TEMPLATE % (version, commit_hash, channel)
api.git('commit', '--all', '--message=%s' % message)
api.git('tag', '--annotate', '--message=%s' % version, version)
push_args = ['push']
if api.runtime.is_experimental:
push_args.append('--dry-run')
push_args.append('https://dart.googlesource.com/sdk.git')
api.git(*push_args + [to_ref], name='push to %s' % to_ref)
api.git(*push_args + [version], name='tag %s' % version)
def _update_version_file(api, from_ref):
from_version_file_text = api.gitiles.download_file(
REPO_URL,
VERSION_PATH,
branch=from_ref,
step_name='download from_ref version file',
)
from_version = _parse_version_file(from_version_file_text)
from_triplet = _version_triplet(from_version)
to_version_file_text = api.file.read_text(
'read to_ref version file', api.path['checkout'].join(VERSION_PATH))
to_version = _parse_version_file(to_version_file_text)
to_triplet = _version_triplet(to_version)
if not from_triplet == to_triplet:
# major, minor, or patch was bumped on from_ref
assert from_triplet > to_triplet, 'version downgrade is unsupported'
for field in ['MAJOR', 'MINOR', 'PATCH']:
to_version[field] = from_version[field]
# reset prerelease
to_version['PRERELEASE'] = '0'
else:
_bump(to_version, 'PRERELEASE')
# always '0' on dev
to_version['PRERELEASE_PATCH'] = '0'
# copy unknown fields
for field in from_version.keys():
if field not in SUPPORTED_FIELDS:
to_version[field] = from_version[field]
_write_version_file(api, from_version_file_text, to_version)
api.git('add', VERSION_PATH)
return _version_string(to_version), to_version['CHANNEL']
def _parse_version_file(version_file_text):
version = {}
for line in version_file_text.splitlines():
if not line or line.startswith('#'):
continue
field, value = line.split(' ')
version[field] = value
return version
def _version_triplet(version):
return (version['MAJOR'], version['MINOR'], version['PATCH'])
def _bump(version, field):
version[field] = str(int(version[field]) + 1)
def _write_version_file(api, version_file_text, new_version):
updated_lines = []
for line in version_file_text.splitlines():
if line.startswith('#'):
updated_lines.append(line)
else:
field, _ = line.split(' ')
updated_lines.append('%s %s' % (field, new_version[field]))
api.file.write_text('write new version file',
api.path['checkout'].join(VERSION_PATH),
'\n'.join(updated_lines))
def _version_string(version):
return '%s.%s.%s-%s.%s.%s' % (version['MAJOR'], version['MINOR'],
version['PATCH'], version['PRERELEASE'],
version['PRERELEASE_PATCH'], version['CHANNEL'])
def GenTests(api):
assertion_error = (
api.post_process(post_process.DoesNotRunRE, '(push|tag).*'),
api.expect_exception('AssertionError'),
api.post_process(post_process.StatusException),
api.post_process(post_process.DropExpectation),
)
yield api.test('no-from-ref-no-push', *assertion_error)
input_properties = api.properties(RollToDev(from_ref='refs/heads/lkgr'))
from_ref_commit = api.step_data(
'get commit to merge',
api.gitiles.make_commit_test_data(
'abcdef', 'Subject\n\nMessage\n', new_files=['foo/bar', 'baz/qux']))
from_ref_version_file = api.step_data(
'download from_ref version file',
api.gitiles.make_encoded_file('''# Updated Comment
CHANNEL abc
MAJOR 4
MINOR 1
PATCH 2
PRERELEASE 0
PRERELEASE_PATCH 0
UNKNOWN_FIELD cde
'''))
to_ref_version_file = api.step_data(
'read to_ref version file',
api.file.read_text('''# Comment
CHANNEL dev
MAJOR 4
MINOR 1
PATCH 2
PRERELEASE 34
PRERELEASE_PATCH 25
UNKNOWN_FIELD feg
'''))
yield api.test(
'no-push-if-already-merged',
input_properties,
from_ref_commit,
api.post_process(post_process.DoesNotRunRE, '(push|tag).*'),
api.post_process(post_process.StatusSuccess),
)
expected_version_file = api.post_process(
post_process.LogEquals, 'write new version file', 'VERSION',
'''# Updated Comment
CHANNEL dev
MAJOR 4
MINOR 1
PATCH 2
PRERELEASE 35
PRERELEASE_PATCH 0
UNKNOWN_FIELD cde''')
unmerged = api.step_data('check if commit has been merged before', retcode=1)
yield api.test(
'push',
input_properties,
from_ref_commit,
from_ref_version_file,
to_ref_version_file,
unmerged,
expected_version_file,
api.post_process(post_process.MustRun, 'push to refs/heads/dev'),
api.post_process(post_process.MustRun, 'tag 4.1.2-35.0.dev'),
api.post_process(post_process.StatusSuccess),
)
yield api.test(
'dry-run',
api.runtime(is_luci=True, is_experimental=True),
input_properties,
from_ref_commit,
from_ref_version_file,
to_ref_version_file,
unmerged,
expected_version_file,
api.post_process(post_process.MustRun, 'push to refs/heads/dev'),
api.post_process(post_process.MustRun, 'tag 4.1.2-35.0.dev'),
api.post_process(post_process.StepCommandContains,
'push to refs/heads/dev', ['--dry-run']),
api.post_process(post_process.StepCommandContains, 'tag 4.1.2-35.0.dev',
['--dry-run']),
api.post_process(post_process.StatusSuccess),
api.post_process(post_process.DropExpectation),
)
from_ref_version_file = api.step_data(
'download from_ref version file',
api.gitiles.make_encoded_file('''# Updated Comment
CHANNEL abc
MAJOR 5
MINOR 2
PATCH 10
PRERELEASE 0
PRERELEASE_PATCH 0
UNKNOWN_FIELD cde
'''))
expected_version_file = api.post_process(
post_process.LogEquals, 'write new version file', 'VERSION',
'''# Updated Comment
CHANNEL dev
MAJOR 5
MINOR 2
PATCH 10
PRERELEASE 0
PRERELEASE_PATCH 0
UNKNOWN_FIELD cde''')
yield api.test(
'push-version-mismatch',
input_properties,
from_ref_commit,
from_ref_version_file,
to_ref_version_file,
unmerged,
expected_version_file,
api.post_process(post_process.MustRun, 'push to refs/heads/dev'),
api.post_process(post_process.MustRun, 'tag 5.2.10-0.0.dev'),
api.post_process(post_process.StatusSuccess),
api.post_process(post_process.DropExpectation),
)
from_ref_version_file = api.step_data(
'download from_ref version file',
api.gitiles.make_encoded_file('''# Comment
CHANNEL abc
MAJOR 4
MINOR 0
PATCH 2
PRERELEASE 0
PRERELEASE_PATCH 0
UNKNOWN_FIELD cde
'''))
yield api.test('no-push-version-downgrade', input_properties, from_ref_commit,
from_ref_version_file, to_ref_version_file, unmerged,
*assertion_error)