| #!/usr/bin/env python3 |
| # Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file |
| # for details. All rights reserved. Use of this source code is governed by a |
| # BSD-style license that can be found in the LICENSE file. |
| |
| import argparse |
| import datetime |
| import os.path |
| import re |
| import shutil |
| import stat |
| import subprocess |
| import sys |
| import urllib.request |
| import xml.etree.ElementTree |
| import zipfile |
| |
| import utils |
| |
| R8_DEV_BRANCH = '8.9' |
| R8_VERSION_FILE = os.path.join('src', 'main', 'java', 'com', 'android', 'tools', |
| 'r8', 'Version.java') |
| THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py') |
| GMAVEN_PUBLISHER = '/google/bin/releases/android-devtools/gmaven/publisher/gmaven-publisher' |
| |
| DESUGAR_JDK_LIBS = 'desugar_jdk_libs' |
| DESUGAR_JDK_LIBS_CONFIGURATION = DESUGAR_JDK_LIBS + '_configuration' |
| ANDROID_TOOLS_PACKAGE = 'com.android.tools' |
| |
| GITHUB_DESUGAR_JDK_LIBS = 'https://github.com/google/desugar_jdk_libs' |
| |
| def install_gerrit_change_id_hook(checkout_dir): |
| with utils.ChangedWorkingDirectory(checkout_dir): |
| # Fancy way of getting the string ".git". |
| git_dir = subprocess.check_output(['git', 'rev-parse', '--git-dir' |
| ]).decode('utf-8').strip() |
| commit_msg_hooks = '%s/hooks/commit-msg' % git_dir |
| if not os.path.exists(os.path.dirname(commit_msg_hooks)): |
| os.mkdir(os.path.dirname(commit_msg_hooks)) |
| # Install commit hook to generate Gerrit 'Change-Id:'. |
| urllib.request.urlretrieve( |
| 'https://gerrit-review.googlesource.com/tools/hooks/commit-msg', |
| commit_msg_hooks) |
| st = os.stat(commit_msg_hooks) |
| os.chmod(commit_msg_hooks, |
| st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) |
| |
| |
| def checkout_r8(temp, branch): |
| subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp]) |
| with utils.ChangedWorkingDirectory(temp): |
| subprocess.check_call([ |
| 'git', 'new-branch', '--upstream', |
| 'origin/%s' % branch, 'dev-release' |
| ]) |
| install_gerrit_change_id_hook(temp) |
| return temp |
| |
| |
| def prepare_release(args): |
| if args.version: |
| print("Cannot manually specify version when making a dev release.") |
| sys.exit(1) |
| |
| def make_release(args): |
| commithash = args.dev_release |
| |
| with utils.TempDir() as temp: |
| with utils.ChangedWorkingDirectory(checkout_r8(temp, |
| R8_DEV_BRANCH)): |
| # Compute the current and new version on the branch. |
| result = None |
| for line in open(R8_VERSION_FILE, 'r'): |
| result = re.match( |
| r'.*LABEL = "%s\.(\d+)\-dev";' % R8_DEV_BRANCH, line) |
| if result: |
| break |
| if not result or not result.group(1): |
| print('Failed to find version label matching %s(\d+)-dev'\ |
| % R8_DEV_BRANCH) |
| sys.exit(1) |
| try: |
| patch_version = int(result.group(1)) |
| except ValueError: |
| print('Failed to convert version to integer: %s' % |
| result.group(1)) |
| |
| old_version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version) |
| version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version + 1) |
| |
| # Verify that the merge point from main is not empty. |
| merge_diff_output = subprocess.check_output( |
| ['git', 'diff', 'HEAD..%s' % commithash]).decode('utf-8') |
| other_diff = version_change_diff(merge_diff_output, old_version, |
| "main") |
| if not other_diff: |
| print('Merge point from main (%s)' % commithash, \ |
| 'is the same as exiting release (%s).' % old_version) |
| sys.exit(1) |
| |
| if args.dev_pre_cherry_pick: |
| for pre_commit in args.dev_pre_cherry_pick: |
| subprocess.check_call( |
| ['git', 'cherry-pick', '--no-edit', pre_commit]) |
| |
| # Merge the desired commit from main on to the branch. |
| subprocess.check_call( |
| ['git', 'merge', '--no-ff', '--no-edit', commithash]) |
| |
| # Rewrite the version, commit and validate. |
| sed(old_version, version, R8_VERSION_FILE) |
| |
| subprocess.check_call( |
| ['git', 'commit', '-a', '-m', |
| 'Version %s' % version]) |
| |
| version_diff_output = subprocess.check_output( |
| ['git', 'diff', '%s..HEAD' % commithash]).decode('utf-8') |
| |
| validate_version_change_diff(version_diff_output, "main", |
| version) |
| |
| cmd = ['git', 'cl', 'upload', '--no-squash', '--bypass-hooks'] |
| maybe_check_call(args, cmd) |
| |
| if args.dry_run: |
| input( |
| 'DryRun: check %s for content of version %s [enter to continue]:' |
| % (temp, version)) |
| |
| return "%s dev version %s from hash %s for review" % ( |
| 'DryRun: omitted upload of' if args.dry_run else 'Uploaded', |
| version, commithash) |
| |
| return make_release |
| |
| |
| def maybe_tag(args, version): |
| maybe_check_call(args, |
| ['git', 'tag', '-a', version, '-m', |
| '"%s"' % version]) |
| maybe_check_call(args, ['git', 'push', 'origin', 'refs/tags/%s' % version]) |
| |
| |
| def version_change_diff(diff, old_version, new_version): |
| invalid_line = None |
| for line in str(diff).splitlines(): |
| if line.startswith('- ') and \ |
| line != '- public static final String LABEL = "%s";' % old_version: |
| invalid_line = line |
| elif line.startswith('+ ') and \ |
| line != '+ public static final String LABEL = "%s";' % new_version: |
| invalid_line = line |
| return invalid_line |
| |
| |
| def validate_version_change_diff(version_diff_output, old_version, new_version): |
| invalid = version_change_diff(version_diff_output, old_version, new_version) |
| if invalid: |
| print("Unexpected diff:") |
| print("=" * 80) |
| print(version_diff_output) |
| print("=" * 80) |
| accept_string = 'THE DIFF IS OK!' |
| answer = input("Accept the additonal diff as part of the release? " |
| "Type '%s' to accept: " % accept_string) |
| if answer != accept_string: |
| print("You did not type '%s'" % accept_string) |
| print('Aborting dev release for %s' % version) |
| sys.exit(1) |
| |
| |
| def maybe_check_call(args, cmd): |
| if args.dry_run: |
| print('DryRun:', ' '.join(cmd)) |
| else: |
| print(' '.join(cmd)) |
| return subprocess.check_call(cmd) |
| |
| |
| def update_prebuilds(r8_checkout, version, checkout, keepanno=False): |
| path = os.path.join(r8_checkout, 'tools', 'update_prebuilds_in_android.py') |
| commit_arg = '--commit_hash=' if len(version) == 40 else '--version=' |
| cmd = [path, '--targets=lib', '--maps', commit_arg + version, checkout] |
| if keepanno: |
| cmd.append("--keepanno") |
| subprocess.check_call(cmd) |
| |
| |
| def release_studio_or_aosp(r8_checkout, |
| path, |
| options, |
| git_message, |
| keepanno=False): |
| with utils.ChangedWorkingDirectory(path): |
| if not options.use_existing_work_branch: |
| subprocess.call(['repo', 'abandon', 'update-r8']) |
| if not options.no_sync: |
| subprocess.check_call(['repo', 'sync', '-cq', '-j', '16']) |
| |
| prebuilts_r8 = os.path.join(path, 'prebuilts', 'r8') |
| |
| if not options.use_existing_work_branch: |
| with utils.ChangedWorkingDirectory(prebuilts_r8): |
| subprocess.check_call(['repo', 'start', 'update-r8']) |
| |
| update_prebuilds(r8_checkout, options.version, path, keepanno) |
| |
| with utils.ChangedWorkingDirectory(prebuilts_r8): |
| if not options.use_existing_work_branch: |
| subprocess.check_call( |
| ['git', 'commit', '-a', '-m', git_message]) |
| else: |
| print('Not committing when --use-existing-work-branch. ' + |
| 'Commit message should be:\n\n' + git_message + '\n') |
| # Don't upload if requested not to, or if changes are not committed due |
| # to --use-existing-work-branch |
| if not options.no_upload and not options.use_existing_work_branch: |
| process = subprocess.Popen( |
| ['repo', 'upload', '.', '--verify', '--current-branch'], |
| stdin=subprocess.PIPE) |
| return process.communicate(input=b'y\n')[0] |
| |
| |
| def prepare_aosp(args): |
| assert args.version |
| |
| if (not args.legacy_release): |
| print("Please use the new release process, see go/r8-release-prebuilts. " |
| + "If for some reason the legacy release process is needed " |
| + "pass --legacy-release") |
| sys.exit(1) |
| |
| assert os.path.exists(args.aosp), "Could not find AOSP path %s" % args.aosp |
| |
| def release_aosp(options): |
| print("Releasing for AOSP") |
| if options.dry_run: |
| return 'DryRun: omitting AOSP release for %s' % options.version |
| |
| git_message = ("""Update D8 and R8 to %s |
| |
| Version: %s |
| This build IS NOT suitable for preview or public release. |
| |
| Built here: go/r8-releases/raw/%s |
| |
| Test: TARGET_PRODUCT=aosp_arm64 m -j core-oj""" % |
| (args.version, args.version, args.version)) |
| # Fixes to Android U branch is based of 8.2.2-dev where the keepanno library |
| # is not built. |
| keepanno = not args.version.startswith('8.2.2-udc') |
| return release_studio_or_aosp(utils.REPO_ROOT, |
| args.aosp, |
| options, |
| git_message, |
| keepanno=keepanno) |
| |
| return release_aosp |
| |
| |
| def prepare_maven(args): |
| assert args.version |
| |
| def release_maven(options): |
| gfile = '/bigstore/r8-releases/raw/%s/r8lib.zip' % args.version |
| release_id = gmaven_publisher_stage(options, [gfile]) |
| |
| print("Staged Release ID " + release_id + ".\n") |
| gmaven_publisher_stage_redir_test_info( |
| release_id, "com.android.tools:r8:%s" % args.version, "r8lib.jar") |
| |
| print |
| answer = input("Continue with publishing [y/N]:") |
| |
| if answer != 'y': |
| print('Aborting release to Google maven') |
| sys.exit(1) |
| |
| gmaven_publisher_publish(args, release_id) |
| |
| print("") |
| print("Published. Use the email workflow for approval.") |
| |
| return release_maven |
| |
| |
| # ------------------------------------------------------ column 70 --v |
| def git_message_dev(version, bugs): |
| return """Update D8 R8 to %s |
| |
| This is a development snapshot, it's fine to use for studio canary |
| build, but not for BETA or release, for those we would need a release |
| version of R8 binaries. This build IS suitable for preview release |
| but IS NOT suitable for public release. |
| |
| Built here: go/r8-releases/raw/%s |
| Test: ./gradlew check |
| Bug: %s""" % (version, version, '\nBug: '.join(map(bug_fmt, bugs))) |
| |
| |
| def git_message_release(version, bugs): |
| return """D8 R8 version %s |
| |
| Built here: go/r8-releases/raw/%s/ |
| Test: ./gradlew check |
| |
| Bug: %s""" % (version, version, '\nBug: '.join(map(bug_fmt, bugs))) |
| |
| |
| def bug_fmt(bug): |
| return "b/%s" % bug |
| |
| |
| def prepare_studio(args): |
| assert args.version |
| assert os.path.exists(args.studio), ("Could not find STUDIO path %s" % |
| args.studio) |
| if (not args.legacy_release): |
| print("Please use the new release process, see go/r8-release-prebuilts. " |
| + "If for some reason the legacy release process is needed " |
| + "pass --legacy-release") |
| sys.exit(1) |
| |
| def release_studio(options): |
| print("Releasing for STUDIO") |
| if options.dry_run: |
| return 'DryRun: omitting studio release for %s' % options.version |
| |
| if 'dev' in options.version: |
| git_message = git_message_dev(options.version, options.bug) |
| r8_checkout = utils.REPO_ROOT |
| return release_studio_or_aosp(r8_checkout, args.studio, options, |
| git_message) |
| else: |
| with utils.TempDir() as temp: |
| checkout_r8(temp, |
| options.version[0:options.version.rindex('.')]) |
| git_message = git_message_release(options.version, options.bug) |
| return release_studio_or_aosp(temp, args.studio, options, |
| git_message) |
| |
| return release_studio |
| |
| |
| def g4_cp(old, new, file): |
| subprocess.check_call('g4 cp {%s,%s}/%s' % (old, new, file), shell=True) |
| |
| |
| def g4_open(file): |
| if not os.access(file, os.W_OK): |
| subprocess.check_call('g4 open %s' % file, shell=True) |
| |
| |
| def g4_change(version, commit_info): |
| message = f'Update R8 to {version}' |
| if version == 'main': |
| message = f'DO NOT SUBMIT: {message}' |
| if commit_info: |
| message += f'\n\n{commit_info}' |
| message = message.replace("'", r"\'") |
| return subprocess.check_output( |
| f"g4 change --desc $'{message}\n'", |
| shell=True).decode('utf-8') |
| |
| |
| def get_cl_id(c4_change_output): |
| startIndex = c4_change_output.find('Change ') + len('Change ') |
| endIndex = c4_change_output.find(' ', startIndex) |
| cl = c4_change_output[startIndex:endIndex] |
| assert cl.isdigit() |
| return cl |
| |
| |
| def sed(pattern, replace, path): |
| with open(path, "r") as sources: |
| lines = sources.readlines() |
| with open(path, "w") as sources: |
| for line in lines: |
| sources.write(re.sub(pattern, replace, line)) |
| |
| def download_file(version, file, dst): |
| if version == 'main': |
| src = os.path.join(utils.LIBS, file) |
| if os.path.exists(src): |
| shutil.copyfile(src, dst) |
| else: |
| print(f"WARNING: no file found for {src}. Skipping.") |
| return |
| dir = 'raw' if len(version) != 40 else 'raw/main' |
| urllib.request.urlretrieve( |
| ('https://storage.googleapis.com/r8-releases/%s/%s/%s' % |
| (dir, version, file)), dst) |
| |
| |
| def download_gfile(gfile, dst): |
| if not gfile.startswith('/bigstore/r8-releases'): |
| print('Unexpected gfile prefix for %s' % gfile) |
| sys.exit(1) |
| |
| urllib.request.urlretrieve( |
| 'https://storage.googleapis.com/%s' % gfile[len('/bigstore/'):], dst) |
| |
| |
| def blaze_run(target): |
| return subprocess.check_output('blaze run %s' % target, |
| shell=True, |
| stderr=subprocess.STDOUT).decode('utf-8') |
| |
| |
| def find_r8_version_hash(branch, version): |
| if not branch.startswith('origin/'): |
| print('Expected branch to start with origin/') |
| return 1 |
| output = subprocess.check_output([ |
| 'git', |
| 'log', |
| '--pretty=format:%H\t%s', |
| '--grep', |
| '^Version [[:digit:]]\+.[[:digit:]]\+.[[:digit:]]\+\(\|-dev\)$', |
| branch]).decode('utf-8') |
| for l in output.split('\n'): |
| (hash, subject) = l.split('\t') |
| m = re.search('Version (.+)', subject) |
| if not m: |
| print('Unable to find a version for line: %s' % l) |
| continue |
| if (m.group(1) == version): |
| return hash |
| print(f'ERROR: Did not find commit for {version} on branch {branch}') |
| |
| |
| def find_2nd(string, substring): |
| return string.find(substring, string.find(substring) + 1) |
| |
| |
| def branch_from_version(version): |
| return version[0:find_2nd(version, '.')] |
| |
| |
| def prepare_google3(args): |
| assert args.version |
| # Check if an existing client exists. |
| if not args.use_existing_work_branch: |
| check_no_google3_client(args, args.p4_client) |
| |
| def release_google3(options): |
| print("Releasing for Google 3") |
| if options.dry_run: |
| return 'DryRun: omitting g3 release for %s' % options.version |
| |
| google3_base = subprocess.check_output( |
| ['p4', 'g4d', '-f', args.p4_client]).decode('utf-8').rstrip() |
| third_party_r8 = os.path.join(google3_base, 'third_party', 'java', 'r8') |
| today = datetime.date.today() |
| commit_info = 'No info on changes merged.' |
| with utils.ChangedWorkingDirectory(third_party_r8): |
| # download files |
| g4_open('full.jar') |
| g4_open('src.jar') |
| g4_open('lib.jar') |
| g4_open('lib.jar.map') |
| g4_open('desugar_jdk_libs_configuration.jar') |
| g4_open('threading-module-blocking.jar') |
| g4_open('threading-module-single-threaded.jar') |
| download_file(options.version, |
| 'r8-full-exclude-deps.jar', |
| 'full.jar') |
| download_file(options.version, |
| 'r8-src.jar', |
| 'src.jar') |
| download_file(options.version, |
| 'r8lib-exclude-deps.jar', |
| 'lib.jar') |
| download_file(options.version, |
| 'r8lib-exclude-deps.jar.map', |
| 'lib.jar.map') |
| download_file(options.version, |
| 'desugar_jdk_libs_configuration.jar', |
| 'desugar_jdk_libs_configuration.jar') |
| download_file(options.version, |
| 'threading-module-blocking.jar', |
| 'threading-module-blocking.jar') |
| download_file(options.version, |
| 'threading-module-single-threaded.jar', |
| 'threading-module-single-threaded.jar') |
| if options.version != 'main': |
| g4_open('METADATA') |
| metadata_path = os.path.join(third_party_r8, 'METADATA') |
| match_count = 0 |
| match_count_expected = 10 |
| match_value = None |
| version_match_regexp = r'([1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev)' |
| for line in open(metadata_path, 'r'): |
| result = re.search(version_match_regexp, line) |
| if result: |
| match_count = match_count + 1 |
| if not match_value: |
| match_value = result.group(1) |
| else: |
| if match_value != result.group(1): |
| print(f"""ERROR: |
| Multiple -dev release strings ({match_value} and |
| {result.group(0)}) found in METADATA. Please update |
| {metadata_path} manually and run again with options |
| --google3 --use-existing-work-branch. |
| """) |
| sys.exit(1) |
| if match_count != match_count_expected: |
| print(f"""ERROR: |
| Could not find the previous -dev release string to replace in METADATA. |
| Expected to find it mentioned {match_count_expected} times, but found |
| {match_count} occurrences. Please update {metadata_path} manually and |
| run again with options --google3 --use-existing-work-branch. |
| """) |
| sys.exit(1) |
| sed(version_match_regexp, options.version, metadata_path) |
| sed(r'\{ year.*\}', |
| f'{{ year: {today.year} month: {today.month} day: {today.day} }}', |
| metadata_path) |
| subprocess.check_output('chmod u+w *', shell=True) |
| previous_version = match_value |
| if not options.version.endswith('-dev') or not previous_version.endswith('-dev'): |
| print(f'ERROR: At least one of {options.version} (new version) ' |
| + f'and {previous_version} (previous version) is not a -dev version. ' |
| + 'Expected both to be.') |
| sys.exit(1) |
| print(f'Previous version was: {previous_version}') |
| with utils.TempDir() as temp: |
| subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp]) |
| with utils.ChangedWorkingDirectory(temp): |
| current_version_hash = find_r8_version_hash( |
| 'origin/' + branch_from_version(previous_version), previous_version) |
| new_version_hash = find_r8_version_hash( |
| 'origin/' + branch_from_version(options.version), options.version) |
| if not current_version_hash or not new_version_hash: |
| print('ERROR: Failed to generate merged commits log, missing version') |
| sys.exit(1) |
| commits_merged = subprocess.check_output([ |
| 'git', |
| 'log', |
| '--oneline', |
| f"{current_version_hash}..{new_version_hash}"]).decode('utf-8') |
| if len(commits_merged) == 0: |
| print('ERROR: Failed to generate merged commits log, commit log is empty') |
| sys.exit(1) |
| commit_info = ( |
| f'Commits merged (since {previous_version}):\n' |
| + f'{commits_merged}\n' |
| + f'See https://r8.googlesource.com/r8/+log/{new_version_hash}') |
| |
| with utils.ChangedWorkingDirectory(google3_base): |
| blaze_result = blaze_run('//third_party/java/r8:d8 -- --version') |
| |
| assert options.version in blaze_result |
| |
| if not options.no_upload: |
| change_result = g4_change(options.version, commit_info) |
| change_result += 'Run \'(g4d ' + args.p4_client \ |
| + ' && tap_presubmit -p all --train -c ' \ |
| + get_cl_id(change_result) + ')\' for running TAP global' \ |
| + ' presubmit using the train.\n' \ |
| + 'Run \'(g4d ' + args.p4_client \ |
| + ' && tap_presubmit -p all --notrain --detach --email' \ |
| + ' --skip_flaky_targets --skip_already_failing -c ' \ |
| + get_cl_id(change_result) + ')\' for running an isolated' \ |
| + ' TAP global presubmit.' |
| return change_result |
| |
| return release_google3 |
| |
| |
| def update_desugar_library_in_studio(args): |
| assert os.path.exists(args.studio), ("Could not find STUDIO path %s" % |
| args.studio) |
| |
| def make_release(args): |
| library_version = args.update_desugar_library_in_studio[0] |
| configuration_version = args.update_desugar_library_in_studio[1] |
| change_name = 'update-desugar-library-dependencies' |
| |
| with utils.ChangedWorkingDirectory(args.studio): |
| if not args.use_existing_work_branch: |
| subprocess.call(['repo', 'abandon', change_name]) |
| if not args.no_sync: |
| subprocess.check_call(['repo', 'sync', '-cq', '-j', '16']) |
| |
| cmd = [ |
| 'tools/base/bazel/bazel', 'run', |
| '//tools/base/bazel:add_dependency', '--', |
| '--repo=https://maven.google.com com.android.tools:desugar_jdk_libs:%s' |
| % library_version |
| ] |
| utils.PrintCmd(cmd) |
| subprocess.check_call(" ".join(cmd), shell=True) |
| cmd = ['tools/base/bazel/bazel', 'shutdown'] |
| utils.PrintCmd(cmd) |
| subprocess.check_call(cmd) |
| |
| prebuilts_tools = os.path.join(args.studio, 'prebuilts', 'tools') |
| with utils.ChangedWorkingDirectory(prebuilts_tools): |
| if not args.use_existing_work_branch: |
| with utils.ChangedWorkingDirectory(prebuilts_tools): |
| subprocess.check_call(['repo', 'start', change_name]) |
| m2_dir = os.path.join('common', 'm2', 'repository', 'com', |
| 'android', 'tools') |
| subprocess.check_call([ |
| 'git', 'add', |
| os.path.join(m2_dir, DESUGAR_JDK_LIBS, library_version) |
| ]) |
| subprocess.check_call([ |
| 'git', 'add', |
| os.path.join(m2_dir, DESUGAR_JDK_LIBS_CONFIGURATION, |
| configuration_version) |
| ]) |
| |
| git_message = ("""Update library desugaring dependencies |
| |
| com.android.tools:desugar_jdk_libs:%s |
| com.android.tools:desugar_jdk_libs_configuration:%s |
| |
| Bug: %s |
| Test: L8ToolTest, L8DexDesugarTest""" % |
| (library_version, configuration_version, |
| '\nBug: '.join(map(bug_fmt, args.bug)))) |
| |
| if not args.use_existing_work_branch: |
| subprocess.check_call( |
| ['git', 'commit', '-a', '-m', git_message]) |
| else: |
| print('Not committing when --use-existing-work-branch. ' + |
| 'Commit message should be:\n\n' + git_message + '\n') |
| # Don't upload if requested not to, or if changes are not committed due |
| # to --use-existing-work-branch |
| if not args.no_upload and not args.use_existing_work_branch: |
| process = subprocess.Popen(['repo', 'upload', '.', '--verify'], |
| stdin=subprocess.PIPE) |
| return process.communicate(input='y\n')[0] |
| |
| return make_release |
| |
| |
| def prepare_desugar_library(args): |
| |
| def make_release(args): |
| library_version = args.desugar_library[0] |
| configuration_version = args.desugar_library[1] |
| |
| # TODO(b/237636871): Cleanup and generalize. |
| if (not (library_version.startswith('1.1') or |
| library_version.startswith('1.2') or |
| library_version.startswith('2.0') or |
| library_version.startswith('2.1'))): |
| print( |
| "Release script does not support desugared library version %s" % |
| library_version) |
| sys.exit(1) |
| |
| postfixes = [''] |
| if library_version.startswith('1.2'): |
| postfixes = ['_legacy'] |
| if library_version.startswith('2.0') or library_version.startswith('2.1'): |
| postfixes = ['_minimal', '', '_nio'] |
| |
| with utils.TempDir() as temp: |
| with utils.ChangedWorkingDirectory(temp): |
| artifacts = [] |
| for postfix in postfixes: |
| group_postfix = ('' if postfix == '_legacy' else postfix) |
| archive_postfix = (postfix |
| if library_version.startswith('1.1') else |
| '_jdk11' + postfix) |
| library_jar = DESUGAR_JDK_LIBS + postfix + '.jar' |
| library_archive = DESUGAR_JDK_LIBS + archive_postfix + '.zip' |
| configuration_archive = DESUGAR_JDK_LIBS_CONFIGURATION + archive_postfix + '.zip' |
| library_gfile = ('/bigstore/r8-releases/raw/%s/%s/%s' % |
| (DESUGAR_JDK_LIBS + group_postfix, |
| library_version, library_archive)) |
| configuration_gfile = ( |
| '/bigstore/r8-releases/raw/main/%s/%s' % |
| (configuration_version, configuration_archive)) |
| |
| download_gfile(library_gfile, library_archive) |
| download_gfile(configuration_gfile, configuration_archive) |
| check_configuration(configuration_archive, group_postfix) |
| artifacts.append(library_gfile) |
| artifacts.append(configuration_gfile) |
| |
| release_id = gmaven_publisher_stage(args, artifacts) |
| |
| print("Staged Release ID " + release_id + ".\n") |
| library_artifact_id = \ |
| '%s:%s:%s' % (ANDROID_TOOLS_PACKAGE, DESUGAR_JDK_LIBS, library_version) |
| gmaven_publisher_stage_redir_test_info(release_id, |
| library_artifact_id, |
| library_jar) |
| |
| print("") |
| answer = input("Continue with publishing [y/N]:") |
| |
| if answer != 'y': |
| print('Aborting release to Google maven') |
| sys.exit(1) |
| |
| gmaven_publisher_publish(args, release_id) |
| |
| print("") |
| print("Published. Use the email workflow for approval.") |
| |
| return make_release |
| |
| |
| def check_configuration(configuration_archive, postfix): |
| zip = zipfile.ZipFile(configuration_archive) |
| zip.extractall() |
| dirs = os.listdir( |
| os.path.join('com', 'android', 'tools', |
| DESUGAR_JDK_LIBS_CONFIGURATION + postfix)) |
| if len(dirs) != 1: |
| print('Unexpected archive content, %s' + dirs) |
| sys.exit(1) |
| |
| version = dirs[0] |
| pom_file = os.path.join( |
| 'com', 'android', 'tools', DESUGAR_JDK_LIBS_CONFIGURATION + postfix, |
| version, |
| '%s-%s.pom' % (DESUGAR_JDK_LIBS_CONFIGURATION + postfix, version)) |
| version_from_pom = extract_version_from_pom(pom_file) |
| if version != version_from_pom: |
| print('Version mismatch, %s != %s' % (version, version_from_pom)) |
| sys.exit(1) |
| |
| |
| def check_no_google3_client(args, client_name): |
| if not args.use_existing_work_branch: |
| clients = subprocess.check_output('g4 myclients', |
| shell=True).decode('utf-8') |
| if ':%s:' % client_name in clients: |
| if args.delete_work_branch: |
| subprocess.check_call('g4 citc -d -f %s' % client_name, |
| shell=True) |
| else: |
| print(("Remove the existing '%s' client before continuing " + |
| "(force delete: 'g4 citc -d -f %s'), " + |
| "or use either --use-existing-work-branch or " + |
| "--delete-work-branch.") % (client_name, client_name)) |
| sys.exit(1) |
| |
| |
| def extract_version_from_pom(pom_file): |
| ns = "http://maven.apache.org/POM/4.0.0" |
| xml.etree.ElementTree.register_namespace('', ns) |
| tree = xml.etree.ElementTree.ElementTree() |
| tree.parse(pom_file) |
| return tree.getroot().find("{%s}version" % ns).text |
| |
| |
| GMAVEN_PUBLISH_STAGE_RELEASE_ID_PATTERN = re.compile( |
| 'Release ID = ([0-9a-f\-]+)') |
| |
| |
| def gmaven_publisher_stage(args, gfiles): |
| if args.dry_run: |
| print('Dry-run, would have staged %s' % gfiles) |
| return 'dry-run-release-id' |
| |
| print("Staging: %s" % ', '.join(gfiles)) |
| print("") |
| |
| cmd = [GMAVEN_PUBLISHER, 'stage', '--gfile', ','.join(gfiles)] |
| output = subprocess.check_output(cmd) |
| |
| # Expect output to contain: |
| # [INFO] 06/19/2020 09:35:12 CEST: >>>>>>>>>> Staged |
| # [INFO] 06/19/2020 09:35:12 CEST: Release ID = 9171d015-18f6-4a90-9984-1c362589dc1b |
| # [INFO] 06/19/2020 09:35:12 CEST: Stage Path = /bigstore/studio_staging/maven2/sgjesse/9171d015-18f6-4a90-9984-1c362589dc1b |
| |
| matches = GMAVEN_PUBLISH_STAGE_RELEASE_ID_PATTERN.findall( |
| output.decode("utf-8")) |
| if matches == None or len(matches) > 1: |
| print("Could not determine the release ID from the gmaven_publisher " + |
| "output. Expected a line with 'Release ID = <release id>'.") |
| print("Output was:") |
| print(output) |
| sys.exit(1) |
| |
| print(output) |
| |
| release_id = matches[0] |
| return release_id |
| |
| |
| def gmaven_publisher_stage_redir_test_info(release_id, artifact, dst): |
| |
| redir_command = ("/google/data/ro/teams/android-devtools-infra/tools/redir " |
| + "--alsologtostderr " + |
| "--gcs_bucket_path=/bigstore/gmaven-staging/${USER}/%s " + |
| "--port=1480") % release_id |
| |
| get_command = ( |
| "mvn org.apache.maven.plugins:maven-dependency-plugin:2.4:get " + |
| "-Dmaven.repo.local=/tmp/maven_repo_local " + |
| "-DremoteRepositories=http://localhost:1480 " + "-Dartifact=%s " + |
| "-Ddest=%s") % (artifact, dst) |
| |
| print("""To test the staged content with 'redir' run: |
| |
| %s |
| |
| Then add the following repository to settings.gradle.kts (Kotlin Script) to |
| search the 'redir' repository: |
| |
| dependencyResolutionManagement { |
| repositories { |
| maven { |
| url = uri("http://localhost:1480") |
| isAllowInsecureProtocol = true |
| } |
| } |
| } |
| |
| or the following to settings.gradle (Groovy); |
| |
| dependencyResolutionManagement { |
| repositories { |
| maven { |
| url 'http://localhost:1480' |
| allowInsecureProtocol true |
| } |
| } |
| } |
| |
| and add the following repository to gradle.build.kts (Kotlin Script) for the |
| staged version: |
| |
| coreLibraryDesugaring("%s") { |
| isChanging = true |
| } |
| |
| or the following to settings.gradle (Groovy); |
| |
| dependencies { |
| coreLibraryDesugaring('%s') { |
| changing = true |
| } |
| } |
| |
| Use this commands to get artifact from 'redir': |
| |
| rm -rf /tmp/maven_repo_local |
| %s |
| """ % (redir_command, artifact, artifact, get_command)) |
| |
| |
| def gmaven_publisher_publish(args, release_id): |
| if args.dry_run: |
| print('Dry-run, would have published %s' % release_id) |
| return |
| |
| cmd = [GMAVEN_PUBLISHER, 'publish', release_id] |
| output = subprocess.check_output(cmd) |
| |
| |
| def branch_change_diff(diff, old_version, new_version): |
| invalid_line = None |
| for line in str(diff).splitlines(): |
| if line.startswith('-R8') and \ |
| line != "-R8_DEV_BRANCH = '%s'" % old_version: |
| print(line) |
| invalid_line = line |
| elif line.startswith('+R8') and \ |
| line != "+R8_DEV_BRANCH = '%s'" % new_version: |
| print(line) |
| invalid_line = line |
| return invalid_line |
| |
| |
| def validate_branch_change_diff(version_diff_output, old_version, new_version): |
| invalid = branch_change_diff(version_diff_output, old_version, new_version) |
| if invalid: |
| print("") |
| print( |
| "The diff for the branch change in tools/release.py is not as expected:" |
| ) |
| print("") |
| print("=" * 80) |
| print(version_diff_output) |
| print("=" * 80) |
| print("") |
| print("Validate the uploaded CL before landing.") |
| print("") |
| |
| |
| def prepare_branch(args): |
| if (len(args.new_dev_branch) < 1 or len(args.new_dev_branch) > 2): |
| print("One or two arguments required for --new-dev-branch") |
| sys.exit(1) |
| branch_version = args.new_dev_branch[0] |
| |
| current_semver = utils.check_basic_semver_version( |
| R8_DEV_BRANCH, ", current release branch version should be x.y", 2) |
| semver = utils.check_basic_semver_version( |
| branch_version, ", release branch version should be x.y", 2) |
| if not semver.larger_than(current_semver): |
| print('New branch version "' + branch_version + |
| '" must be strictly larger than the current "' + R8_DEV_BRANCH + |
| '"') |
| sys.exit(1) |
| |
| def make_branch(options): |
| with utils.TempDir() as temp: |
| subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp]) |
| with utils.ChangedWorkingDirectory(temp): |
| if len(options.new_dev_branch) == 1: |
| # Calculate the usual branch hash. |
| subprocess.check_call(['git', 'fetch', 'origin', R8_DEV_BRANCH]) |
| hashes = subprocess.check_output( |
| ['git', |
| 'show', |
| '-s', |
| '--pretty=%P', |
| 'origin/%s~1' % R8_DEV_BRANCH]).decode('utf-8').strip() |
| if (len(hashes.split()) != 2): |
| print('Expected two parent hashes for commit origin/%s~1' |
| % R8_DEV_BRANCH) |
| sys.exit(0) |
| commithash = hashes.split()[1] |
| print() |
| print('Calculated branch hash: %s' % commithash) |
| print('Please double check that this is the correct branch hash. It' |
| ' is obtained as the second parent of origin/%s~1.' % R8_DEV_BRANCH) |
| print('If not rerun the script passing an explicit hash to branch from.') |
| else: |
| commithash = options.new_dev_branch[1] |
| print() |
| print('Using explicit branch hash %s' % commithash) |
| print() |
| print('Use the Gerrit admin UI at' |
| ' https://r8-review.googlesource.com/admin/repos/r8,branches' |
| ' to create the %s branch from %s.' % (branch_version, commithash)) |
| answer = input("Branch created in Gerrit UI? [y/N]:") |
| if answer != 'y': |
| print('Aborting preparing branch for %s' % branch_version) |
| sys.exit(1) |
| |
| # Fetch and checkout the new branch created through the Gerrit UI. |
| subprocess.check_call(['git', 'fetch', 'origin', branch_version]) |
| subprocess.check_call(['git', 'checkout', branch_version]) |
| |
| # Rewrite the version on the branch, commit and validate. |
| old_version = 'main' |
| full_version = branch_version + '.0-dev' |
| version_prefix = 'public static final String LABEL = "' |
| sed(version_prefix + old_version, version_prefix + full_version, |
| R8_VERSION_FILE) |
| |
| subprocess.check_call( |
| ['git', 'commit', '-a', '-m', |
| 'Version %s' % full_version]) |
| |
| version_diff_output = subprocess.check_output( |
| ['git', 'diff', '%s..HEAD' % commithash]) |
| |
| validate_version_change_diff(version_diff_output, old_version, |
| full_version) |
| |
| if options.dry_run: |
| input( |
| 'DryRun: check %s for content of version %s [enter to continue]:' |
| % (temp, branch_version)) |
| |
| # Double check that we want to create a new release branch. |
| if not options.dry_run: |
| answer = input('Continue with branch for %s [y/N]:' % |
| branch_version) |
| if answer != 'y': |
| print('Aborting preparing branch for %s' % branch_version) |
| sys.exit(1) |
| |
| maybe_check_call( |
| options, |
| ['git', 'cl', 'upload', '--bypass-hooks']) |
| |
| print( |
| 'Updating tools/r8_release.py to make new dev releases on %s' |
| % branch_version) |
| |
| subprocess.check_call( |
| ['git', 'new-branch', 'update-release-script']) |
| |
| # Check this file for the setting of the current dev branch. |
| result = None |
| for line in open(THIS_FILE_RELATIVE, 'r'): |
| result = re.match(r"^R8_DEV_BRANCH = '(\d+).(\d+)'", line) |
| if result: |
| break |
| if not result or not result.group(1): |
| print('Failed to find version label in %s' % |
| THIS_FILE_RELATIVE) |
| sys.exit(1) |
| |
| # Update this file with the new dev branch. |
| sed( |
| "R8_DEV_BRANCH = '%s.%s" % |
| (result.group(1), result.group(2)), |
| "R8_DEV_BRANCH = '%s.%s" % |
| (str(semver.major), str(semver.minor)), THIS_FILE_RELATIVE) |
| |
| message = \ |
| 'Prepare %s for branch %s' % (THIS_FILE_RELATIVE, branch_version) |
| subprocess.check_call(['git', 'commit', '-a', '-m', message]) |
| |
| branch_diff_output = subprocess.check_output( |
| ['git', 'diff', 'HEAD~']) |
| |
| validate_branch_change_diff(branch_diff_output, R8_DEV_BRANCH, |
| branch_version) |
| |
| if options.dry_run: |
| input( |
| 'DryRun: check %s for content of version main [enter to continue]:' |
| % temp) |
| |
| maybe_check_call(options, |
| ['git', 'cl', 'upload', '-f', '-m', message]) |
| |
| print('') |
| print('Make sure to send out the two branch change CL for review' |
| ' (on %s and main).' % branch_version) |
| print('') |
| |
| return make_branch |
| |
| |
| def parse_options(): |
| result = argparse.ArgumentParser(description='Release r8') |
| group = result.add_mutually_exclusive_group() |
| group.add_argument('--dev-release', |
| metavar=('<main hash>'), |
| help='The hash to use for the new dev version of R8') |
| group.add_argument( |
| '--version', |
| metavar=('<version>'), |
| help= |
| 'The new version of R8 (e.g., 1.4.51) to release to selected channels') |
| group.add_argument( |
| '--desugar-library', |
| nargs=2, |
| metavar=('<version>', '<configuration hash>'), |
| help='The new version of com.android.tools:desugar_jdk_libs') |
| group.add_argument( |
| '--update-desugar-library-in-studio', |
| nargs=2, |
| metavar=('<version>', '<configuration version>'), |
| help='Update studio mirror of com.android.tools:desugar_jdk_libs') |
| group.add_argument( |
| '--new-dev-branch', |
| nargs='+', |
| metavar=('<version>', '<branch hash>'), |
| help=('Prepare new branch for a version line (e.g. 8.0)' |
| ' Suggested branch hash is calculated ' |
| ' if not explicitly specified')) |
| result.add_argument('--dev-pre-cherry-pick', |
| metavar=('<main hash(s)>'), |
| default=[], |
| action='append', |
| help='List of commits to cherry pick before doing full ' |
| 'merge, mostly used for reverting cherry picks') |
| result.add_argument('--no-sync', |
| '--no_sync', |
| default=False, |
| action='store_true', |
| help='Do not sync repos before uploading') |
| result.add_argument('--bug', |
| metavar=('<bug(s)>'), |
| default=[], |
| action='append', |
| help='List of bugs for release version') |
| result.add_argument('--no-bugs', |
| default=False, |
| action='store_true', |
| help='Allow Studio release without specifying any bugs') |
| result.add_argument( |
| '--studio', |
| metavar=('<path>'), |
| help='Release for studio by setting the path to a studio ' |
| 'checkout') |
| result.add_argument('--legacy-release', |
| default=False, |
| action='store_true', |
| help='Allow Studio/AOSP release using the legacy process') |
| result.add_argument('--aosp', |
| metavar=('<path>'), |
| help='Release for aosp by setting the path to the ' |
| 'checkout') |
| result.add_argument('--maven', |
| default=False, |
| action='store_true', |
| help='Release to Google Maven') |
| result.add_argument('--google3', |
| default=False, |
| action='store_true', |
| help='Release for google 3') |
| result.add_argument('--p4-client', |
| default='update-r8', |
| metavar=('<client name>'), |
| help='P4 client name for google 3') |
| result.add_argument( |
| '--use-existing-work-branch', |
| '--use_existing_work_branch', |
| default=False, |
| action='store_true', |
| help='Use existing work branch/CL in aosp/studio/google3') |
| result.add_argument('--delete-work-branch', |
| '--delete_work_branch', |
| default=False, |
| action='store_true', |
| help='Delete CL in google3') |
| result.add_argument('--bypass-hooks', |
| '--bypass_hooks', |
| default=False, |
| action='store_true', |
| help="Bypass hooks when uploading") |
| result.add_argument('--no-upload', |
| '--no_upload', |
| default=False, |
| action='store_true', |
| help="Don't upload for code review") |
| result.add_argument( |
| '--dry-run', |
| default=False, |
| action='store_true', |
| help='Only perform non-commiting tasks and print others.') |
| result.add_argument('--dry-run-output', |
| '--dry_run_output', |
| default=os.getcwd(), |
| metavar=('<path>'), |
| help='Location for dry run output.') |
| args = result.parse_args() |
| if (len(args.bug) > 0 and args.no_bugs): |
| print("Use of '--bug' and '--no-bugs' are mutually exclusive") |
| sys.exit(1) |
| |
| if (args.studio and args.version and not 'dev' in args.version and |
| args.bug == [] and not args.no_bugs): |
| print("When releasing a release version to Android Studio add the " + |
| "list of bugs by using '--bug'") |
| sys.exit(1) |
| |
| if args.version and not 'dev' in args.version and args.google3: |
| print("WARNING: You should not roll a release version into google 3") |
| |
| return args |
| |
| |
| def main(): |
| args = parse_options() |
| targets_to_run = [] |
| |
| if args.new_dev_branch: |
| if args.google3 or args.studio or args.aosp: |
| print('Cannot create a branch and roll at the same time.') |
| sys.exit(1) |
| targets_to_run.append(prepare_branch(args)) |
| |
| if args.dev_release: |
| if args.google3 or args.studio or args.aosp: |
| print('Cannot create a dev release and roll at the same time.') |
| sys.exit(1) |
| targets_to_run.append(prepare_release(args)) |
| |
| utils.check_gcert() |
| |
| if args.google3: |
| targets_to_run.append(prepare_google3(args)) |
| if args.studio and not args.update_desugar_library_in_studio: |
| targets_to_run.append(prepare_studio(args)) |
| if args.aosp: |
| targets_to_run.append(prepare_aosp(args)) |
| if args.maven: |
| targets_to_run.append(prepare_maven(args)) |
| |
| if args.desugar_library: |
| targets_to_run.append(prepare_desugar_library(args)) |
| |
| if args.update_desugar_library_in_studio: |
| if not args.studio: |
| print("--studio required") |
| sys.exit(1) |
| if args.bug == []: |
| print( |
| "Update studio mirror of com.android.tools:desugar_jdk_libs " + |
| "requires at least one bug by using '--bug'") |
| sys.exit(1) |
| targets_to_run.append(update_desugar_library_in_studio(args)) |
| |
| final_results = [] |
| for target_closure in targets_to_run: |
| final_results.append(target_closure(args)) |
| |
| print('\n\n**************************************************************') |
| print('PRINTING SUMMARY') |
| print('**************************************************************\n\n') |
| |
| for result in final_results: |
| if result is not None: |
| print(result) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |