| #!/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.2' | 
 | 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, 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 | 
 |   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) | 
 |  | 
 |   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 = '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) | 
 |  | 
 |         # 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()) |