|  | #!/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 subprocess | 
|  | import sys | 
|  | import urllib.request | 
|  | import xml.etree.ElementTree | 
|  | import zipfile | 
|  |  | 
|  | import utils | 
|  |  | 
|  | R8_DEV_BRANCH = '8.1' | 
|  | 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 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']) | 
|  | 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) | 
|  |  | 
|  | # Double check that we want to push the release. | 
|  | if not args.dry_run: | 
|  | answer = input('Publish dev release version %s [y/N]:' % version) | 
|  | if answer != 'y': | 
|  | print('Aborting dev release for %s' % version) | 
|  | sys.exit(1) | 
|  |  | 
|  | maybe_check_call(args, [ | 
|  | 'git', 'push', 'origin', 'HEAD:%s' % R8_DEV_BRANCH]) | 
|  | maybe_tag(args, version) | 
|  |  | 
|  | return "%s dev version %s from hash %s" % ( | 
|  | 'DryRun: omitted publish of' if args.dry_run else 'Published', | 
|  | 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): | 
|  | path = os.path.join(r8_checkout, 'tools', 'update_prebuilds_in_android.py') | 
|  | commit_arg = '--commit_hash=' if len(version) == 40 else '--version=' | 
|  | subprocess.check_call([path, '--targets=lib', '--maps', commit_arg + version, checkout]) | 
|  |  | 
|  |  | 
|  | def release_studio_or_aosp(r8_checkout, path, options, git_message): | 
|  | 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) | 
|  |  | 
|  | 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'], | 
|  | stdin=subprocess.PIPE) | 
|  | return process.communicate(input=b'y\n')[0] | 
|  |  | 
|  |  | 
|  | def prepare_aosp(args): | 
|  | assert args.version | 
|  | 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)) | 
|  | return release_studio_or_aosp( | 
|  | utils.REPO_ROOT, args.aosp, options, git_message) | 
|  |  | 
|  | 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) | 
|  |  | 
|  | 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): | 
|  | return subprocess.check_output( | 
|  | 'g4 change --desc "Update R8 to version %s\n"' % (version), | 
|  | 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): | 
|  | 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 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() | 
|  | 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('retrace_full.jar') | 
|  | g4_open('retrace_lib.jar') | 
|  | g4_open('retrace_lib.jar.map') | 
|  | g4_open('desugar_jdk_libs_configuration.jar') | 
|  | download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar') | 
|  | download_file(options.version, 'r8-full-exclude-deps.jar', 'retrace_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, 'r8lib-exclude-deps.jar.map', 'retrace_lib.jar.map') | 
|  | download_file(options.version, 'desugar_jdk_libs_configuration.jar', | 
|  | 'desugar_jdk_libs_configuration.jar') | 
|  | download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace_lib.jar') | 
|  | g4_open('METADATA') | 
|  | metadata_path = os.path.join(third_party_r8, 'METADATA') | 
|  | match_count = 0 | 
|  | 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 match_count != 4: | 
|  | print(("Could not find the previous -dev release string to replace in " + | 
|  | "METADATA. Expected to find is mentioned 4 times. Please update %s " + | 
|  | "manually and run again with options --google3 " + | 
|  | "--use-existing-work-branch.") % metadata_path) | 
|  | sys.exit(1) | 
|  | sed(version_match_regexp, options.version, metadata_path) | 
|  | sed(r'\{ year.*\}', | 
|  | ('{ year: %i month: %i day: %i }' | 
|  | % (today.year, today.month, today.day)), | 
|  | metadata_path) | 
|  | subprocess.check_output('chmod u+w *', shell=True) | 
|  |  | 
|  | 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) | 
|  | 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 prepare_google3_retrace(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_retrace(options): | 
|  | print("Releasing Retrace 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') | 
|  | with utils.ChangedWorkingDirectory(third_party_r8): | 
|  | # download files | 
|  | g4_open('retrace_full.jar') | 
|  | g4_open('retrace_lib.jar') | 
|  | g4_open('retrace_lib.jar.map') | 
|  | download_file(options.version, 'r8-full-exclude-deps.jar', 'retrace_full.jar') | 
|  | download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace_lib.jar') | 
|  | download_file( | 
|  | options.version, 'r8lib-exclude-deps.jar.map', 'retrace_lib.jar.map') | 
|  | g4_open('METADATA') | 
|  | metadata_path = os.path.join(third_party_r8, 'METADATA') | 
|  | match_count = 0 | 
|  | version_match_regexp = r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev/r8retrace-exclude-deps.jar' | 
|  | for line in open(metadata_path, 'r'): | 
|  | result = re.search(version_match_regexp, line) | 
|  | if result: | 
|  | match_count = match_count + 1 | 
|  | if match_count != 1: | 
|  | print(("Could not find the previous retrace release string to replace in " + | 
|  | "METADATA. Expected to find is mentioned 1 times. Please update %s " + | 
|  | "manually and run again with options --google3retrace " + | 
|  | "--use-existing-work-branch.") % metadata_path) | 
|  | sys.exit(1) | 
|  | sed(version_match_regexp, options.version + "/r8retrace-exclude-deps.jar", metadata_path) | 
|  | subprocess.check_output('chmod u+w *', shell=True) | 
|  |  | 
|  | with utils.ChangedWorkingDirectory(google3_base): | 
|  | blaze_result = blaze_run('//third_party/java/r8:retrace -- --version') | 
|  |  | 
|  | print(blaze_result) | 
|  | assert options.version in blaze_result | 
|  |  | 
|  | if not options.no_upload: | 
|  | change_result = g4_change(options.version) | 
|  | 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_retrace | 
|  |  | 
|  | 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'))): | 
|  | 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'): | 
|  | 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 to search the 'redir' | 
|  | repository: | 
|  |  | 
|  | dependencyResolutionManagement { | 
|  | repositories { | 
|  | maven { | 
|  | url 'http://localhost:1480' | 
|  | allowInsecureProtocol true | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | and add the following repository to gradle.build for for the staged version: | 
|  |  | 
|  | dependencies { | 
|  | coreLibraryDesugaring('%s') { | 
|  | changing = true | 
|  | } | 
|  | } | 
|  |  | 
|  | Use this commands to get artifact from 'redir': | 
|  |  | 
|  | rm -rf /tmp/maven_repo_local | 
|  | %s | 
|  | """ % (redir_command, 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): | 
|  | branch_version = args.new_dev_branch[0] | 
|  | commithash = args.new_dev_branch[1] | 
|  |  | 
|  | 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): | 
|  | subprocess.check_call(['git', 'branch', branch_version, commithash]) | 
|  |  | 
|  | subprocess.check_call(['git', 'checkout', branch_version]) | 
|  |  | 
|  | # Rewrite the version, commit and validate. | 
|  | old_version = 'main' | 
|  | full_version = branch_version + '.0-dev' | 
|  | version_prefix = '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) | 
|  |  | 
|  | # Double check that we want to create a new release branch. | 
|  | if not options.dry_run: | 
|  | answer = input('Create new branch for %s [y/N]:' % branch_version) | 
|  | if answer != 'y': | 
|  | print('Aborting new branch for %s' % branch_version) | 
|  | sys.exit(1) | 
|  |  | 
|  | maybe_check_call(options, [ | 
|  | 'git', 'push', 'origin', 'HEAD:%s' % branch_version]) | 
|  | maybe_tag(options, full_version) | 
|  |  | 
|  | 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) | 
|  |  | 
|  | maybe_check_call(options, ['git', 'cl', 'upload', '-f', '-m', message]) | 
|  |  | 
|  | print('') | 
|  | print('Make sure to send out the branch change CL for review.') | 
|  | 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=2, | 
|  | metavar=('<version>', '<main hash>'), | 
|  | help='Create a new branch starting a version line (e.g. 2.0)') | 
|  | 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('--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('--google3retrace', | 
|  | default=False, | 
|  | action='store_true', | 
|  | help='Release retrace 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('--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)) | 
|  |  | 
|  | if (args.google3 | 
|  | or args.maven | 
|  | or (args.studio and not args.no_sync) | 
|  | or (args.desugar_library and not args.dry_run)): | 
|  | utils.check_gcert() | 
|  |  | 
|  | if args.google3: | 
|  | targets_to_run.append(prepare_google3(args)) | 
|  | if args.google3retrace: | 
|  | targets_to_run.append(prepare_google3_retrace(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()) |