blob: 6abbf630642cc301c5b8d828367f8c74d7bd3cde [file] [log] [blame]
#!/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())