blob: e808436b4edd5bfa99af1d0aa1a58e66de87c1a4 [file] [edit]
#!/usr/bin/env python3
# Copyright (c) 2026, 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.
# Fetches ART binaries and ART bootclasspath jars from AOSP head or Google Maven.
# Saves and unzips the result, and creates a boot image.
import argparse
import os
import re
import shutil
import subprocess
import sys
import urllib.request
import zipfile
import utils
URL = 'https://ci.android.com/builds/latest/branches/aosp-android-latest-release/targets/aosp_cf_x86_64_only_phone-userdebug/view/BUILD_INFO'
PATTERN = r'(.*\/submitted\/([0-9]+)\/.*)/view/BUILD_INFO$'
MAVEN_URL_PATTERN = 'https://dl.google.com/android/maven2/com/android/art/art/{version}/art-{version}.zip'
MAVEN_INDEX_URL = 'https://dl.google.com/android/maven2/com/android/art/group-index.xml'
def parse_options():
parser = argparse.ArgumentParser(description='Fetch ART binaries.')
parser.add_argument('--maven',
help='Download from Google Maven.',
action='store_true')
group = parser.add_mutually_exclusive_group()
group.add_argument('--head',
help='Download from AOSP head.',
action='store_true')
group.add_argument(
'--version',
help='Version to download from Google Maven (e.g., 36.0, 33.10).')
parser.add_argument('--list-versions',
'--list_versions',
help='List versions available on Google Maven.',
action='store_true')
parser.add_argument(
'--output',
help='Output directory to unzip into (default: %(default)s).',
default='third_party/dex2oat')
parser.add_argument(
'--old',
help='Exclude riscv64 and CMC GC (for versions 33.10 and before).',
action='store_true')
return parser.parse_args()
def list_maven_versions():
response = urllib.request.urlopen(MAVEN_INDEX_URL)
content = response.read().decode('utf-8')
response.close()
# Basic XML parsing to find <art versions="..."/>
match = re.search(r'<art versions="([^"]+)"', content)
if match:
versions = match.group(1).split(',')
print('Available versions on Google Maven:')
for version in versions:
print(' %s' % version)
else:
print('Failed to parse versions from %s' % MAVEN_INDEX_URL)
return 1
return 0
def download_from_head(temp_dir):
response = urllib.request.urlopen(URL)
redirected_url = response.geturl()
response.close()
match = re.match(PATTERN, redirected_url)
if not match:
print('Got unexpected URL %s' % redirected_url)
return None
download_url = '%s/raw/art_release.zip' % match.group(1)
dest = os.path.join(temp_dir, 'art_release.zip')
print('Downloading: %s to: %s' % (download_url, dest))
urllib.request.urlretrieve(download_url, dest)
return dest
def download_from_maven(version, temp_dir):
download_url = MAVEN_URL_PATTERN.format(version=version)
dest = os.path.join(temp_dir, 'art_release.zip')
print('Downloading: %s to: %s' % (download_url, dest))
try:
urllib.request.urlretrieve(download_url, dest)
except Exception as e:
print('Failed to download from Maven: %s' % e)
return None
return dest
def unzip(zip_file, out_dir):
print('Unzipping %s to %s' % (zip_file, out_dir))
if os.path.exists(out_dir):
shutil.rmtree(out_dir)
os.makedirs(out_dir)
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
zip_ref.extractall(out_dir)
# Ensure binaries are executable
bin_dir = os.path.join(out_dir, 'x86_64', 'bin')
if os.path.exists(bin_dir):
for f in os.listdir(bin_dir):
os.chmod(os.path.join(bin_dir, f), 0o755)
def create_boot_image(out_dir, old=False):
# Constants from create_boot_image.py
DIR_FOR_BOOT_IMAGE = 'app/system/framework'
instruction_sets = ['arm', 'arm64', 'x86', 'x86_64', 'riscv64']
if old:
instruction_sets.remove('riscv64')
BOOTJARS = [
'core-oj.jar', 'core-libart.jar', 'okhttp.jar', 'bouncycastle.jar',
'apache-xml.jar'
]
BOOT_CLASS_PATH = ':'.join(['bootjars/%s' % jar for jar in BOOTJARS])
BOOT_CLASS_PATH_LOCATIONS = ':'.join(
['/apex/com.android.art/javalib/%s' % jar for jar in BOOTJARS])
with utils.ChangedWorkingDirectory(out_dir):
for instruction_set in instruction_sets:
output_dir = os.path.join(DIR_FOR_BOOT_IMAGE, instruction_set)
os.makedirs(output_dir, exist_ok=True)
cmd = [
'x86_64/bin/dex2oat64',
'--runtime-arg',
'-Xms64m',
'--runtime-arg',
'-Xmx512m',
]
if not old:
cmd.extend(['--runtime-arg', '-Xgc:CMC'])
cmd.extend([
'--runtime-arg',
'-Xbootclasspath:%s' % BOOT_CLASS_PATH,
'--runtime-arg',
'-Xbootclasspath-locations:%s' % BOOT_CLASS_PATH_LOCATIONS,
'--instruction-set=%s' % instruction_set,
'--compiler-filter=speed',
])
for jar in BOOTJARS:
cmd.append('--dex-file=bootjars/%s' % jar)
for jar in BOOTJARS:
cmd.append('--dex-location=/apex/com.android.art/javalib/%s' %
jar)
cmd.extend([
'--image=%s' % os.path.join(output_dir, 'boot.art'),
'--oat-file=%s' % os.path.join(output_dir, 'boot.oat'),
])
if not old:
cmd.append('--output-vdex=%s' %
os.path.join(output_dir, 'boot.vdex'))
cmd.extend([
'--android-root=.',
'--abort-on-hard-verifier-error',
'--no-abort-on-soft-verifier-error',
'--compilation-reason=cloud',
'--image-format=lz4',
'--force-determinism',
'--resolve-startup-const-strings=true',
'--avoid-storing-invocation',
'--generate-mini-debug-info',
])
if not old:
cmd.append('--force-allow-oj-inlines')
cmd.append('--no-watch-dog')
if not old:
cmd.append('--single-image')
cmd.append('--base=0x70000000')
print('Creating dex2oat boot image for %s' % instruction_set)
subprocess.check_call(cmd)
def main():
args = parse_options()
if args.list_versions:
if not args.maven:
print('--list-versions is only supported when using --maven')
return 1
return list_maven_versions()
if not args.head and not args.version:
print('Either --head or --version must be specified')
return 1
version_name = 'head' if args.head else args.version
with utils.TempDir() as temp_dir:
if args.maven:
if args.head:
print('--head is not supported when using --maven')
return 1
zip_file = download_from_maven(args.version, temp_dir)
else:
if args.version:
print('--version is only supported when using --maven')
return 1
zip_file = download_from_head(temp_dir)
if not zip_file:
return 1
out_dir = os.path.join(args.output, version_name)
unzip(zip_file, out_dir)
old = args.old or version_name == '33.10'
create_boot_image(out_dir, old=old)
print('Now run')
print('(cd %s; upload_to_google_storage.py -a --bucket r8-deps %s)' %
(args.output, version_name))
return 0
if __name__ == '__main__':
sys.exit(main())