| # 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/path', |
| 'recipe_engine/properties', |
| 'recipe_engine/runtime', |
| 'recipe_engine/step', |
| 'recipe_engine/time', |
| ] |
| 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', |
| DEV_BRANCH, |
| '--', |
| 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', '--atomic'] |
| if api.runtime.is_experimental: |
| push_args.append('--dry-run') |
| push_args.append('https://dart.googlesource.com/sdk.git') |
| tag = 'refs/tags/%s' % version |
| try: |
| api.git(*push_args + [to_ref, tag], name='push to %s' % to_ref) |
| except api.step.InfraFailure: |
| # If the step fails, it's likely because of GoB replication delay. Wait |
| # 30s and then try again. |
| api.time.sleep(30) |
| api.git(*push_args + [to_ref, tag], name='retry push to %s' % to_ref) |
| |
| |
| 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.StepCommandContains, |
| 'push to refs/heads/dev', [ |
| '--atomic', |
| 'https://dart.googlesource.com/sdk.git', |
| 'refs/heads/dev', |
| 'refs/tags/4.1.2-35.0.dev', |
| ]), |
| api.post_process(post_process.StatusSuccess), |
| ) |
| yield api.test( |
| 'push-retry', |
| 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.step_data('push to refs/heads/dev', retcode=1), |
| api.post_process(post_process.MustRun, 'retry push to refs/heads/dev'), |
| api.post_process(post_process.StepCommandContains, |
| 'retry push to refs/heads/dev', [ |
| '--atomic', |
| 'https://dart.googlesource.com/sdk.git', |
| 'refs/heads/dev', |
| 'refs/tags/4.1.2-35.0.dev', |
| ]), |
| api.post_process(post_process.StatusSuccess), |
| api.post_process(post_process.Filter().include_re('(retry )?push')), |
| ) |
| yield api.test( |
| 'dry-run', |
| api.runtime(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.StepCommandContains, |
| 'push to refs/heads/dev', ['--atomic', '--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.StepCommandContains, |
| 'push to refs/heads/dev', [ |
| '--atomic', |
| 'https://dart.googlesource.com/sdk.git', |
| 'refs/heads/dev', |
| 'refs/tags/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) |