blob: 6a95dcc95cc757a7fd3e328d91bcc783392974bf [file] [log] [blame]
#!/usr/bin/python3
"""Updates prebuilt libraries used by Android builds.
For details on how to use this script, visit go/update-prebuilts.
"""
import os
import sys
import zipfile
import re
import argparse
import subprocess
import six
import shlex
from urllib import request
from shutil import which
from distutils.version import LooseVersion
from pathlib import Path
from maven import MavenLibraryInfo, GMavenArtifact, maven_path_for_artifact
from buildserver import fetch_and_extract, fetch_artifacts, fetch_artifact, extract_artifact, \
parse_build_id
from utils import print_e, append, cp, mv, rm
current_path = 'current'
framework_sdk_target = 'sdk'
androidx_dir = os.path.join(current_path, 'androidx')
androidx_owners = os.path.join(androidx_dir, 'OWNERS')
java_plugins_bp_path = os.path.join(androidx_dir, 'JavaPlugins.bp')
test_mapping_file = os.path.join(androidx_dir, 'TEST_MAPPING')
compose_test_mapping_file = os.path.join(androidx_dir, 'm2repository/androidx/compose/TEST_MAPPING')
gmaven_dir = os.path.join(current_path, 'gmaven')
extras_dir = os.path.join(current_path, 'extras')
buildtools_dir = 'tools'
jetifier_dir = os.path.join(buildtools_dir, 'jetifier', 'jetifier-standalone')
repo_root_dir = Path(sys.argv[0]).resolve().parents[3]
extension_sdk_finalization_cmd = '%s -r "{readme}" -b {bug} -f {extension_version} {build_id}' % (
"packages/modules/common/tools/finalize_sdk.py"
)
temp_dir = os.path.join(os.getcwd(), 'support_tmp')
os.chdir(os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
git_dir = os.getcwd()
# Leave map blank to automatically populate name and path:
# - Name format is MAVEN.replaceAll(':','_')
# - Path format is MAVEN.replaceAll(':','/').replaceAll('.','/')
maven_to_make = {
# AndroidX
'androidx.benchmark:benchmark-macro': {},
'androidx.benchmark:benchmark-macro-junit4': {},
'androidx.benchmark:benchmark-common': {},
'androidx.benchmark:benchmark-junit4': {},
'androidx.tracing:tracing': {},
'androidx.tracing:tracing-perfetto': {},
'androidx.tracing:tracing-perfetto-binary': {},
'androidx.tracing:tracing-perfetto-common': {},
'androidx.tracing:tracing-ktx': {},
'androidx.slice:slice-builders': {},
'androidx.slice:slice-core': {},
'androidx.slice:slice-view': {},
'androidx.remotecallback:remotecallback': {},
'androidx.remotecallback:remotecallback-processor': {
'host': True
},
'androidx.versionedparcelable:versionedparcelable': {},
'androidx.vectordrawable:vectordrawable-animated': {},
'androidx.activity:activity': {},
'androidx.activity:activity-ktx': {},
'androidx.annotation:annotation': {
'host_and_device': True,
'extra-static-libs': {
'androidx.annotation_annotation-jvm'
}
},
'androidx.annotation:annotation-jvm': {
'host_and_device': True
},
'androidx.annotation:annotation-experimental': {},
'androidx.asynclayoutinflater:asynclayoutinflater': {},
'androidx.collection:collection': {
'extra-static-libs': {
'androidx.collection_collection-jvm'
}
},
'androidx.collection:collection-ktx': {},
'androidx.collection:collection-jvm': {},
'androidx.concurrent:concurrent-futures': {},
'androidx.concurrent:concurrent-listenablefuture-callback': {},
'androidx.concurrent:concurrent-listenablefuture': {},
'androidx.core:core': {},
'androidx.core:core-animation': {},
'androidx.core:core-ktx': {},
'androidx.core.uwb:uwb': {},
'androidx.core.uwb:uwb-rxjava3': {},
'androidx.contentpaging:contentpaging': {},
'androidx.coordinatorlayout:coordinatorlayout': {},
'androidx.legacy:legacy-support-core-ui': {},
'androidx.legacy:legacy-support-core-utils': {},
'androidx.cursoradapter:cursoradapter': {},
'androidx.browser:browser': {},
'androidx.customview:customview': {},
'androidx.customview:customview-poolingcontainer': {},
'androidx.documentfile:documentfile': {},
'androidx.drawerlayout:drawerlayout': {},
'androidx.dynamicanimation:dynamicanimation': {},
'androidx.emoji:emoji': {},
'androidx.emoji:emoji-appcompat': {},
'androidx.emoji:emoji-bundled': {},
'androidx.emoji2:emoji2': {},
'androidx.emoji2:emoji2-views-helper': {},
'androidx.exifinterface:exifinterface': {},
'androidx.fragment:fragment': {},
'androidx.fragment:fragment-ktx': {},
'androidx.heifwriter:heifwriter': {},
'androidx.interpolator:interpolator': {},
'androidx.loader:loader': {},
'androidx.media:media': {},
'androidx.media2:media2-player': {},
'androidx.media2:media2-session': {},
'androidx.media2:media2-common': {},
'androidx.media2:media2-exoplayer': {},
'androidx.media2:media2-widget': {},
'androidx.navigation:navigation-common': {},
'androidx.navigation:navigation-common-ktx': {},
'androidx.navigation:navigation-fragment': {},
'androidx.navigation:navigation-fragment-ktx': {},
'androidx.navigation:navigation-runtime': {},
'androidx.navigation:navigation-runtime-ktx': {},
'androidx.navigation:navigation-ui': {},
'androidx.navigation:navigation-ui-ktx': {},
'androidx.percentlayout:percentlayout': {},
'androidx.print:print': {},
'androidx.recommendation:recommendation': {},
'androidx.recyclerview:recyclerview-selection': {},
'androidx.savedstate:savedstate': {},
'androidx.savedstate:savedstate-ktx': {},
'androidx.slidingpanelayout:slidingpanelayout': {},
'androidx.swiperefreshlayout:swiperefreshlayout': {},
'androidx.textclassifier:textclassifier': {},
'androidx.transition:transition': {},
'androidx.tvprovider:tvprovider': {},
'androidx.legacy:legacy-support-v13': {},
'androidx.legacy:legacy-preference-v14': {},
'androidx.leanback:leanback': {},
'androidx.leanback:leanback-grid': {},
'androidx.leanback:leanback-preference': {},
'androidx.legacy:legacy-support-v4': {},
'androidx.appcompat:appcompat': {},
'androidx.appcompat:appcompat-resources': {},
'androidx.cardview:cardview': {},
'androidx.gridlayout:gridlayout': {},
'androidx.mediarouter:mediarouter': {},
'androidx.palette:palette': {},
'androidx.preference:preference': {},
'androidx.recyclerview:recyclerview': {},
'androidx.vectordrawable:vectordrawable': {},
'androidx.viewpager:viewpager': {},
'androidx.viewpager2:viewpager2': {},
'androidx.wear:wear': {},
'androidx.wear:wear-ongoing': {},
'androidx.javascriptengine:javascriptengine': {},
'androidx.webkit:webkit': {},
'androidx.biometric:biometric': {},
'androidx.autofill:autofill': {},
'androidx.appsearch:appsearch': {},
'androidx.appsearch:appsearch-builtin-types': {},
'androidx.appsearch:appsearch-compiler': {
'name': 'androidx.appsearch_appsearch-compiler',
'host': True
},
'androidx.appsearch:appsearch-local-storage': {
'name': 'androidx.appsearch_appsearch_local_storage'
},
'androidx.appsearch:appsearch-platform-storage': {},
'androidx.car.app:app': {},
'androidx.car.app:app-automotive': {},
'androidx.car.app:app-testing': {},
'androidx.startup:startup-runtime': {},
'androidx.window:window': {
'optional-uses-libs': {
'androidx.window.extensions',
'androidx.window.sidecar'
}
},
'androidx.resourceinspection:resourceinspection-annotation': {},
'androidx.profileinstaller:profileinstaller': {},
'androidx.test.uiautomator:uiautomator': {},
# AndroidX for Compose
'androidx.compose.compiler:compiler-hosted': {
'host': True
},
'androidx.compose.runtime:runtime': {},
'androidx.compose.runtime:runtime-saveable': {},
'androidx.compose.runtime:runtime-livedata': {},
'androidx.compose.foundation:foundation': {},
'androidx.compose.foundation:foundation-layout': {},
'androidx.compose.foundation:foundation-text': {},
'androidx.compose.ui:ui': {},
'androidx.compose.ui:ui-geometry': {},
'androidx.compose.ui:ui-graphics': {},
'androidx.compose.ui:ui-text': {},
'androidx.compose.ui:ui-tooling': {},
'androidx.compose.ui:ui-tooling-preview': {},
'androidx.compose.ui:ui-tooling-data': {},
'androidx.compose.ui:ui-unit': {},
'androidx.compose.ui:ui-util': {},
'androidx.compose.ui:ui-test': { },
'androidx.compose.ui:ui-test-junit4': { },
'androidx.compose.ui:ui-test-manifest': { },
'androidx.compose.animation:animation-core': {},
'androidx.compose.animation:animation': {},
'androidx.compose.material:material-icons-core': {},
'androidx.compose.material:material-icons-extended': { },
'androidx.compose.material:material-ripple': {},
'androidx.compose.material:material': {},
'androidx.compose.material3:material3': {},
'androidx.activity:activity-compose': {},
'androidx.navigation:navigation-compose': { },
'androidx.lifecycle:lifecycle-viewmodel-compose': { },
# AndroidX for Multidex
'androidx.multidex:multidex': {},
'androidx.multidex:multidex-instrumentation': {},
# AndroidX for Constraint Layout
'androidx.constraintlayout:constraintlayout': {
'name': 'androidx-constraintlayout_constraintlayout'
},
'androidx.constraintlayout:constraintlayout-solver': {
'name': 'androidx-constraintlayout_constraintlayout-solver'
},
# AndroidX for Architecture Components
'androidx.arch.core:core-common': {},
'androidx.arch.core:core-runtime': {},
'androidx.lifecycle:lifecycle-common': {},
'androidx.lifecycle:lifecycle-common-java8': {},
'androidx.lifecycle:lifecycle-extensions': {},
'androidx.lifecycle:lifecycle-livedata': {},
'androidx.lifecycle:lifecycle-livedata-ktx': {},
'androidx.lifecycle:lifecycle-livedata-core': {},
'androidx.lifecycle:lifecycle-livedata-core-ktx': {},
'androidx.lifecycle:lifecycle-process': {},
'androidx.lifecycle:lifecycle-runtime': {},
'androidx.lifecycle:lifecycle-runtime-ktx': {},
'androidx.lifecycle:lifecycle-service': {},
'androidx.lifecycle:lifecycle-viewmodel': {},
'androidx.lifecycle:lifecycle-viewmodel-ktx': {},
'androidx.lifecycle:lifecycle-viewmodel-savedstate': {},
'androidx.paging:paging-common': {},
'androidx.paging:paging-common-ktx': {},
'androidx.paging:paging-runtime': {},
'androidx.sqlite:sqlite': {},
'androidx.sqlite:sqlite-framework': {},
'androidx.room:room-common': {
'host_and_device': True
},
'androidx.room:room-compiler': {
'host': True,
'extra-static-libs': {
'guava'
}
},
'androidx.room:room-migration': {
'host_and_device': True
},
'androidx.room:room-runtime': {},
'androidx.room:room-testing': {},
'androidx.room:room-compiler-processing': {
'host': True
},
'androidx.work:work-runtime': {},
'androidx.work:work-runtime-ktx': {},
'androidx.work:work-testing': {},
# Third-party dependencies
'com.google.android:flexbox': {
'name': 'flexbox',
'path': 'flexbox'
},
# Androidx Material Design Components
'com.google.android.material:material': {},
}
# Mapping of POM dependencies to Soong build targets
deps_rewrite = {
'auto-common': 'auto_common',
'auto-value-annotations': 'auto_value_annotations',
'com.google.auto.value:auto-value': 'libauto_value_plugin',
'monitor': 'androidx.test.monitor',
'rules': 'androidx.test.rules',
'runner': 'androidx.test.runner',
'androidx.test:core': 'androidx.test.core',
'com.squareup:javapoet': 'javapoet',
'com.google.guava:listenablefuture': 'guava-listenablefuture-prebuilt-jar',
'sqlite-jdbc': 'xerial-sqlite-jdbc',
'com.intellij:annotations': 'jetbrains-annotations',
'javax.annotation:javax.annotation-api': 'javax-annotation-api-prebuilt-host-jar',
'org.robolectric:robolectric': 'Robolectric_all-target',
'org.jetbrains.kotlin:kotlin-stdlib-common': 'kotlin-stdlib',
'org.jetbrains.kotlinx:kotlinx-coroutines-core': 'kotlinx_coroutines',
'org.jetbrains.kotlinx:kotlinx-coroutines-android': 'kotlinx_coroutines_android',
'org.jetbrains.kotlinx:kotlinx-coroutines-test':'kotlinx_coroutines_test',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm': 'kotlinx_metadata_jvm',
'androidx.test.espresso:espresso-core':'androidx.test.espresso.core',
'androidx.test.espresso:espresso-idling-resource':'androidx.test.espresso.idling-resource',
}
# List of artifacts that will be updated from GMaven
# Use pattern: `group:library:version:extension`
# e.g.:
# androidx.appcompat:appcompat:1.2.0:aar
# Use `latest` to always fetch the latest version.
# e.g.:
# androidx.appcompat:appcompat:latest:aar
# Also make sure you add `group:library`:{} to maven_to_make as well.
gmaven_artifacts = {}
# Always remove these files.
denylist_files = [
'annotations.zip',
'public.txt',
'R.txt',
'AndroidManifest.xml',
os.path.join('libs', 'noto-emoji-compat-java.jar')
]
artifact_pattern = re.compile(r'^(.+?)-(\d+\.\d+\.\d+(?:-\w+\d+)?(?:-[\d.]+)*)\.(jar|aar)$')
def name_for_artifact(group_artifact):
"""Returns the build system target name for a given library's Maven coordinate.
Args:
group_artifact: an unversioned Maven artifact coordinate, ex. androidx.core:core
Returns:
The build system target name for the artifact, ex. androidx.core_core.
"""
return group_artifact.replace(':', '_')
def path_for_artifact(group_artifact):
"""Returns the file system path for a given library's Maven coordinate.
Args:
group_artifact: an unversioned Maven artifact coordinate, ex. androidx.core:core
Returns:
The file system path for the artifact, ex. androidx/core/core.
"""
return group_artifact.replace('.', '/').replace(':', '/')
def populate_maven_to_make(mapping):
"""Modifies the input mapping by expanding Maven coordinate keys into build target names and
paths.
Args:
mapping: a map where the keys are Maven coordinates
"""
for key in mapping:
if 'name' not in mapping[key]:
mapping[key]['name'] = name_for_artifact(key)
if 'path' not in maven_to_make[key]:
mapping[key]['path'] = path_for_artifact(key)
def detect_artifacts(maven_repo_dirs):
"""Parses Maven libraries from the specified directories.
Args:
maven_repo_dirs: a list of maven repository roots
Returns:
A map of Maven coordinate keys to MavenLibraryInfo objects parsed from POM files.
"""
maven_lib_info = {}
# Find the latest revision for each artifact, remove others
for repo_dir in maven_repo_dirs:
for root, dirs, files in os.walk(repo_dir):
for file in files:
if file[-4:] == '.pom':
# Read the POM (hack hack hack).
group_id = ''
artifact_id = ''
version = ''
file = os.path.join(root, file)
with open(file) as pom_file:
for line in pom_file:
if line[:11] == ' <groupId>':
group_id = line[11:-11]
elif line[:14] == ' <artifactId>':
artifact_id = line[14:-14]
elif line[:11] == ' <version>':
version = line[11:-11]
if group_id == '' or artifact_id == '' or version == '':
print_e('Failed to find Maven artifact data in ' + file)
continue
# Locate the artifact.
artifact_file = file[:-4]
if os.path.exists(artifact_file + '.jar'):
artifact_file = artifact_file + '.jar'
elif os.path.exists(artifact_file + '.aar'):
artifact_file = artifact_file + '.aar'
else:
# This error only occurs for a handful of gradle.plugin artifacts that only
# ship POM files, so we probably don't need to log unless we're debugging.
# print_e('Failed to find artifact for ' + artifact_file)
continue
# Make relative to root.
artifact_file = artifact_file[len(root) + 1:]
# Find the mapping.
group_artifact = group_id + ':' + artifact_id
if group_artifact in maven_to_make:
key = group_artifact
elif artifact_id in maven_to_make:
key = artifact_id
else:
# No mapping entry, skip this library.
continue
# Store the latest version.
version = LooseVersion(version)
if key not in maven_lib_info \
or version > maven_lib_info[key].version:
maven_lib_info[key] = MavenLibraryInfo(key, group_id, artifact_id, version,
root, repo_dir, artifact_file)
return maven_lib_info
def transform_maven_repos(maven_repo_dirs, transformed_dir, extract_res=True,
include_static_deps=True, include=None, exclude=None, prepend=None):
"""Transforms a standard Maven repository to be compatible with the Android build system.
When using the include argument by itself, all other libraries will be excluded. When using the
exclude argument by itself, all other libraries will be included. When using both arguments, the
inclusion list will be applied followed by the exclusion list.
Args:
maven_repo_dirs: path to local Maven repository
transformed_dir: relative path for output, ex. androidx
extract_res: whether to extract Android resources like AndroidManifest.xml from AARs
include_static_deps: whether to pass --static-deps to pom2bp
include: list of Maven groupIds or unversioned artifact coordinates to include for
updates, ex. androidx.core or androidx.core:core
exclude: list of Maven groupIds or unversioned artifact coordinates to exclude from
updates, ex. androidx.core or androidx.core:core
prepend: Path to a file containing text to be inserted at the beginning of the generated
build file
Returns:
True if successful, false otherwise.
"""
if exclude is None:
exclude = []
if include is None:
include = []
cwd = os.getcwd()
local_repo = os.path.join(cwd, transformed_dir)
working_dir = temp_dir
# Handle inclusions by stashing the remote artifacts for the inclusions, replacing the entire
# remote repo with the local repo, then restoring the stashed artifacts.
for remote_repo in maven_repo_dirs:
remote_repo = os.path.join(cwd, remote_repo)
paths_to_copy = []
for group_artifact in include:
artifact_path = os.path.join('m2repository', path_for_artifact(group_artifact))
remote_path = os.path.join(remote_repo, artifact_path)
working_path = os.path.join(working_dir, artifact_path)
if os.path.exists(remote_path):
print(f'Included {group_artifact} in update')
paths_to_copy.append([remote_path, working_path])
# Move included artifacts from repo to temp.
for [remote_path, working_path] in paths_to_copy:
mv(remote_path, working_path)
# Replace all remaining artifacts in remote repo with local repo.
cp(local_repo, remote_repo)
# Restore included artifacts to remote repo.
for [remote_path, working_path] in paths_to_copy:
mv(working_path, remote_path)
# Handle exclusions by replacing the remote artifacts for the exclusions with local artifacts.
# This must happen before we parse the artifacts.
for remote_repo in maven_repo_dirs:
for group_artifact in exclude:
artifact_path = os.path.join('m2repository', path_for_artifact(group_artifact))
remote_path = os.path.join(remote_repo, artifact_path)
if os.path.exists(remote_path):
rm(remote_path)
local_path = os.path.join(local_repo, artifact_path)
if os.path.exists(local_path):
print(f'Excluded {group_artifact} from update, used local artifact')
mv(local_path, remote_path)
else:
print(f'Excluded {group_artifact} from update, no local artifact present')
# Parse artifacts.
maven_lib_info = detect_artifacts(maven_repo_dirs)
if not maven_lib_info:
print_e('Failed to detect artifacts')
return False
# Move libraries into the working directory, performing any necessary transformations.
for info in maven_lib_info.values():
transform_maven_lib(working_dir, info, extract_res)
# Generate a single Android.bp that specifies to use all of the above artifacts.
makefile = os.path.join(working_dir, 'Android.bp')
with open(makefile, 'w') as f:
args = ['pom2bp']
args.extend(['-sdk-version', '31'])
args.extend(['-default-min-sdk-version', '24'])
if include_static_deps:
args.append('-static-deps')
if prepend:
args.append(f'-prepend={prepend}')
rewrite_names = sorted(maven_to_make.keys())
args.extend([f'-rewrite=^{name}$={maven_to_make[name]["name"]}' for name in rewrite_names])
args.extend([f'-rewrite=^{key}$={value}' for key, value in deps_rewrite.items()])
args.extend(["-extra-static-libs=" + maven_to_make[name]['name'] + "=" + ",".join(
sorted(maven_to_make[name]['extra-static-libs'])) for name in maven_to_make if
'extra-static-libs' in maven_to_make[name]])
args.extend(["-optional-uses-libs=" + maven_to_make[name]['name'] + "=" + ",".join(
sorted(maven_to_make[name]['optional-uses-libs'])) for name in maven_to_make if
'optional-uses-libs' in maven_to_make[name]])
args.extend([f'-host={name}' for name in maven_to_make
if maven_to_make[name].get('host')])
args.extend([f'-host-and-device={name}' for name in maven_to_make
if maven_to_make[name].get('host_and_device')])
args.extend(['.'])
subprocess.check_call(args, stdout=f, cwd=working_dir)
# Replace the old directory.
local_repo = os.path.join(cwd, transformed_dir)
mv(working_dir, local_repo)
return True
def transform_maven_lib(working_dir, artifact_info, extract_res):
"""Transforms the specified artifact for use in the Android build system.
Moves relevant files for the artifact represented by artifact_info of type MavenLibraryInfo into
the appropriate path inside working_dir, unpacking files needed by the build system from AARs.
Args:
working_dir: The directory into which the artifact should be moved
artifact_info: A MavenLibraryInfo representing the library artifact
extract_res: True to extract resources from AARs, false otherwise.
"""
# Move library into working dir
new_dir = os.path.normpath(
os.path.join(working_dir, os.path.relpath(artifact_info.dir, artifact_info.repo_dir)))
mv(artifact_info.dir, new_dir)
maven_lib_type = os.path.splitext(artifact_info.file)[1][1:]
group_artifact = artifact_info.key
make_lib_name = maven_to_make[group_artifact]['name']
make_dir_name = maven_to_make[group_artifact]['path']
artifact_file = os.path.join(new_dir, artifact_info.file)
if maven_lib_type == 'aar':
if extract_res:
target_dir = os.path.join(working_dir, make_dir_name)
if not os.path.exists(target_dir):
os.makedirs(target_dir)
process_aar(artifact_file, target_dir)
with zipfile.ZipFile(artifact_file) as zip_file:
manifests_dir = os.path.join(working_dir, 'manifests')
zip_file.extract('AndroidManifest.xml', os.path.join(manifests_dir, make_lib_name))
def process_aar(artifact_file, target_dir):
"""Extracts and cleans up the contents of an AAR file to the specified directory.
Removes classes.jar, empty directories, and denylisted files.
Args:
artifact_file: path to the AAR to extract
target_dir: directory into which the contents should be extracted
"""
# Extract AAR file to target_dir.
with zipfile.ZipFile(artifact_file) as zip_file:
zip_file.extractall(target_dir)
# Remove classes.jar
classes_jar = os.path.join(target_dir, 'classes.jar')
if os.path.exists(classes_jar):
os.remove(classes_jar)
# Remove empty dirs.
for root, dirs, files in os.walk(target_dir, topdown=False):
for dir_name in dirs:
dir_path = os.path.join(root, dir_name)
if not os.listdir(dir_path):
os.rmdir(dir_path)
# Remove top-level cruft.
for file in denylist_files:
file_path = os.path.join(target_dir, file)
if os.path.exists(file_path):
os.remove(file_path)
def fetch_gmaven_artifact(artifact):
"""Fetch a GMaven artifact.
Downloads a GMaven artifact
(https://developer.android.com/studio/build/dependencies#gmaven-access)
Args:
artifact: an instance of GMavenArtifact.
"""
pom_path = maven_path_for_artifact(
'gmaven', artifact.group, artifact.library, artifact.version, 'pom')
artifact_path = maven_path_for_artifact(
'gmaven', artifact.group, artifact.library, artifact.version, artifact.ext)
download_file_to_disk(artifact.get_pom_file_url(), pom_path)
download_file_to_disk(artifact.get_artifact_url(), artifact_path)
return os.path.dirname(artifact_path)
def download_file_to_disk(url, filepath):
"""Download the file at URL to the location dictated by the path.
Args:
url: Remote URL to download file from.
filepath: Filesystem path to write the file to.
"""
print(f'Downloading URL: {url}')
file_data = request.urlopen(url)
try:
os.makedirs(os.path.dirname(filepath))
except os.error:
# This is a common situation - os.makedirs fails if dir already exists.
pass
try:
with open(filepath, 'wb') as f:
f.write(six.ensure_binary(file_data.read()))
except Exception as e:
print_e(e.__class__, 'occurred while reading', filepath)
os.remove(os.path.dirname(filepath))
raise
def update_gmaven(gmaven_artifacts_list):
artifacts = [GMavenArtifact(artifact) for artifact in gmaven_artifacts_list]
for artifact in artifacts:
if artifact.version == 'latest':
artifact.version = artifact.get_latest_version()
if not transform_maven_repos(['gmaven'], gmaven_dir, extract_res=False):
return []
return [artifact.key for artifact in artifacts]
def update_androidx(target, build_id, local_file, include, exclude, beyond_corp):
"""Fetches and extracts Jetpack library prebuilts.
Args:
target: Android build server target name, must be specified if local_file is empty
build_id: Optional Android build server ID, must be specified if local_file is empty
local_file: Optional local top-of-tree ZIP, must be specified if build_id is empty
include: List of Maven groupIds or unversioned artifact coordinates to include for
updates, ex. android.core or androidx.core:core
exclude: List of Maven groupIds or unversioned artifact coordinates to exclude from
updates, ex. android.core or androidx.core:core
beyond_corp: Whether to use BeyondCorp-compatible artifact fetcher
Returns:
True if successful, false otherwise.
"""
if build_id:
repo_file = 'top-of-tree-m2repository-all-%s.zip' % build_id.fs_id
repo_dir = fetch_and_extract(target, build_id.url_id, repo_file, beyond_corp, None)
else:
repo_dir = fetch_and_extract(target, None, None, beyond_corp, local_file)
if not repo_dir:
print_e('Failed to extract AndroidX repository')
return False
prepend_path = os.path.relpath('update_prebuilts/prepend_androidx_license', start=temp_dir)
# Transform the repo archive into a Makefile-compatible format.
if not transform_maven_repos([repo_dir], androidx_dir, extract_res=False, include=include,
exclude=exclude, prepend=prepend_path):
return False
# Import JavaPlugins.bp in Android.bp.
makefile = os.path.join(androidx_dir, 'Android.bp')
with open(makefile, 'a+') as f:
f.write('\nbuild = ["JavaPlugins.bp"]\n')
# Keep OWNERs file, JavaPlugins.bp file, and TEST_MAPPING files untouched.
files_to_restore = [androidx_owners, java_plugins_bp_path, test_mapping_file,
compose_test_mapping_file]
for file_to_restore in files_to_restore:
# Ignore any output or error - these files are not gauranteed to exist, but
# if they do, we want to restore them.
subprocess.call(['git', 'restore', file_to_restore],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
def update_jetifier(target, build_id, beyond_corp):
"""
Fetches and extracts Jetifier tool prebuilts.
Args:
target: Android build server target name
build_id: Android build server ID
beyond_corp: Whether to use BeyondCorp-compatible artifact fetcher
Return:
Whether the prebuilt was successfully updated.
"""
repo_file = 'jetifier-standalone.zip'
repo_dir = fetch_and_extract(target, build_id.url_id, repo_file, beyond_corp)
if not repo_dir:
print_e('Failed to extract Jetifier')
return False
rm(jetifier_dir)
mv(os.path.join(repo_dir, 'jetifier-standalone'), jetifier_dir)
os.chmod(os.path.join(jetifier_dir, 'bin', 'jetifier-standalone'), 0o755)
return True
def update_constraint(local_file):
"""
Extracts ConstraintLayout library prebuilts.
Args:
local_file: local Maven repository ZIP containing library artifacts
Return:
Whether the prebuilts were successfully updated.
"""
repo_dir = extract_artifact(local_file)
if not repo_dir:
print_e('Failed to extract Constraint Layout')
return False
return transform_maven_repos([repo_dir], os.path.join(extras_dir, 'constraint-layout-x'),
extract_res=False)
def update_material(local_file):
"""
Extracts Material Design Components library prebuilts.
Args:
local_file: local Maven repository ZIP containing library artifacts
Return:
Whether the prebuilts were successfully updated.
"""
design_dir = extract_artifact(local_file)
if not design_dir:
print_e('Failed to extract Material Design Components')
return False
return transform_maven_repos([design_dir], os.path.join(extras_dir, 'material-design-x'),
extract_res=False)
def update_framework(target, build_id, sdk_dir, beyond_corp):
api_scope_list = ['public', 'system', 'test', 'module-lib', 'system-server']
if sdk_dir == 'current':
api_scope_list.append('core')
for api_scope in api_scope_list:
target_dir = os.path.join(sdk_dir, api_scope)
if api_scope == 'core':
artifact_to_path = {'core.current.stubs.jar': os.path.join(target_dir, 'android.jar')}
else:
artifact_to_path = {
'apistubs/android/' + api_scope + '/*.jar': os.path.join(target_dir, '*'),
}
if api_scope == 'public' or api_scope == 'module-lib':
# Distinct core-for-system-modules.jar files are only provided
# for the public and module-lib API surfaces.
artifact_to_path[
'system-modules/' + api_scope + '/core-for-system-modules.jar'] = os.path.join(
target_dir, '*')
if not fetch_artifacts(target, build_id, artifact_to_path, beyond_corp):
return False
if api_scope == 'public':
# Fetch a few artifacts from the public sdk.
artifact = 'sdk-repo-linux-platforms-%s.zip' % build_id.fs_id
artifact_path = fetch_artifact(target, build_id.url_id, artifact, beyond_corp)
if not artifact_path:
return False
with zipfile.ZipFile(artifact_path) as zipFile:
extra_files = [
'android.jar',
'framework.aidl',
'uiautomator.jar',
'data/annotations.zip',
'data/api-versions.xml']
for filename in extra_files:
matches = list(filter(lambda path: filename in path, zipFile.namelist()))
if len(matches) != 1:
print_e('Expected 1 file named \'%s\' in zip %s, found %d' %
(filename, zipFile.filename, len(matches)))
return False
zip_path = matches[0]
src_path = zipFile.extract(zip_path)
dst_path = os.path.join(target_dir, filename)
mv(src_path, dst_path)
# Filtered API DB is currently only available for "public"
fetch_artifacts(target, build_id, {'api-versions-public-filtered.xml': os.path.join(
target_dir, 'data/api-versions-filtered.xml')}, beyond_corp)
return True
def update_makefile(build_id):
template = '"%s",\n\
"current"'
makefile = os.path.join(git_dir, 'Android.bp')
with open(makefile, 'r+') as f:
contents = f.read().replace('"current"', template % build_id)
f.seek(0)
f.write(contents)
return True
def finalize_sdk(target, build_id, sdk_version, beyond_corp):
target_finalize_dir = '%d' % sdk_version
for api_scope in ['public', 'system', 'test', 'module-lib', 'system-server']:
artifact_to_path = {f'apistubs/android/{api_scope}/api/*.txt': os.path.join(
target_finalize_dir, api_scope, 'api', '*')}
if not fetch_artifacts(target, build_id, artifact_to_path, beyond_corp):
return False
return update_framework(target, build_id, target_finalize_dir, beyond_corp) and update_makefile(
target_finalize_dir)
def update_framework_current(target, build_id, beyond_corp):
return update_framework(target, build_id, current_path, beyond_corp)
def update_buildtools(target, arch, build_id, beyond_corp):
artifact_path = fetch_and_extract(target, build_id.url_id,
f'sdk-repo-{arch}-build-tools-{build_id.fs_id}.zip',
beyond_corp)
if not artifact_path:
return False
top_level_dir = os.listdir(artifact_path)[0]
src_path = os.path.join(artifact_path, top_level_dir)
dst_path = os.path.join(buildtools_dir, arch)
# There are a few libraries that have been manually added to the
# build tools, copy them from the destination back to the source
# before the destination is overwritten.
files_to_save = (
'lib64/libconscrypt_openjdk_jni.dylib',
'lib64/libconscrypt_openjdk_jni.so',
'bin/lib64/libwinpthread-1.dll',
)
for file in files_to_save:
src_file = os.path.join(dst_path, file)
dst_file = os.path.join(src_path, file)
if os.path.exists(dst_path):
mv(src_file, dst_file)
mv(src_path, dst_path)
# Move all top-level files to /bin and make them executable
bin_path = os.path.join(dst_path, 'bin')
top_level_files = filter(lambda e: os.path.isfile(os.path.join(dst_path, e)), os.listdir(dst_path))
for file in top_level_files:
src_file = os.path.join(dst_path, file)
dst_file = os.path.join(bin_path, file)
mv(src_file, dst_file)
os.chmod(dst_file, 0o755)
# Make the files under lld-bin executable
lld_bin_files = os.listdir(os.path.join(dst_path, 'lld-bin'))
for file in lld_bin_files:
os.chmod(os.path.join(dst_path, 'lld-bin', file), 0o755)
# Remove renderscript
rm(os.path.join(dst_path, 'renderscript'))
return True
def has_uncommitted_changes():
try:
# Make sure we don't overwrite any pending changes.
diff_command = f'cd {git_dir} && git diff --quiet'
subprocess.check_call(diff_command, shell=True)
subprocess.check_call(f'{diff_command} --cached', shell=True)
return False
except subprocess.CalledProcessError:
return True
def main():
parser = argparse.ArgumentParser(
description='Update current prebuilts')
parser.add_argument(
'source', nargs='?',
help='Build server build ID or local Maven ZIP file')
parser.add_argument(
'-m', '--material', action='store_true',
help='If specified, updates only Material Design Components')
parser.add_argument(
'-c', '--constraint', action='store_true',
help='If specified, updates only Constraint Layout')
parser.add_argument(
'-j', '--jetifier', action='store_true',
help='If specified, updates only Jetifier')
parser.add_argument(
'-p', '--platform', action='store_true',
help='If specified, updates only the Android Platform')
parser.add_argument(
'-f', '--finalize_sdk', type=int,
help='Finalize the build as the specified SDK version. Must be used together with -e')
parser.add_argument(
'-e', '--finalize_extension', type=int,
help='Finalize the build as the specified extension SDK version. Must be used together with -f')
parser.add_argument('--bug', type=int, help='The bug number to add to the commit message.')
parser.add_argument(
'--sdk_target',
default=framework_sdk_target,
help='If specified, the name of the build target from which to retrieve the SDK when -p or -f '
'is specified.')
parser.add_argument(
'-b', '--buildtools', action='store_true',
help='If specified, updates only the Build Tools')
parser.add_argument(
'-x', '--androidx', action='store_true',
help='If specified, updates only the Jetpack (androidx) libraries excluding those covered by '
'other arguments')
parser.add_argument(
'--include', action='append', default=[],
help='If specified with -x, includes the specified Jetpack library Maven group or artifact for '
'updates. Applied before exclude.')
parser.add_argument(
'--exclude', action='append', default=[],
help='If specified with -x, excludes the specified Jetpack library Maven group or artifact '
'from updates')
parser.add_argument(
'-g', '--gmaven', action='store_true',
help='If specified, updates only the artifact from GMaven libraries excluding those covered by '
'other arguments')
parser.add_argument(
'--commit-first', action='store_true',
help='If specified, then if uncommited changes exist, commit before continuing')
parser.add_argument(
'--beyond-corp', action='store_true',
help='If specified, then fetch artifacts with tooling that works on BeyondCorp devices')
rm(temp_dir)
args = parser.parse_args()
# Validate combinations of arguments.
if not args.source and (args.platform or args.buildtools or args.jetifier
or args.androidx or args.material or args.finalize_sdk
or args.constraint):
parser.error('You must specify a build ID or local Maven ZIP file')
sys.exit(1)
if not (args.gmaven or args.platform or args.buildtools or args.jetifier
or args.androidx or args.material or args.finalize_sdk
or args.finalize_extension or args.constraint):
parser.error('You must specify at least one target to update')
sys.exit(1)
if (args.finalize_sdk is None) != (args.finalize_extension is None):
parser.error('Either both or neither of -e and -f must be specified.')
sys.exit(1)
if args.finalize_sdk and not args.bug:
parser.error('Specifying a bug ID with --bug is required when finalizing an SDK.')
sys.exit(1)
# Validate the build environment for POM-dependent targets.
if (args.constraint or args.material or args.androidx or args.gmaven) \
and which('pom2bp') is None:
parser.error('Cannot find pom2bp in path; please run lunch to set up build environment. '
'You may also need to run \'m pom2bp\' if it hasn\'t been built already.')
sys.exit(1)
# Validate the git status.
if has_uncommitted_changes():
if args.commit_first:
subprocess.check_call(f'cd {git_dir} && git add -u', shell=True)
subprocess.check_call(f'cd {git_dir} && git commit -m \'save working state\'',
shell=True)
if has_uncommitted_changes():
self_file = os.path.basename(__file__)
print_e(f'FAIL: There are uncommitted changes here. Please commit or stash before '
f'continuing, because {self_file} will run "git reset --hard" if execution fails')
sys.exit(1)
if args.bug:
commit_msg_suffix = '\n\nBug: {args.bug}'
else:
commit_msg_suffix = ''
# Are we fetching a build ID or using a local file?
build_id = None
file = None
if args.source:
build_id = parse_build_id(args.source)
if build_id is None:
file = args.source
try:
components = []
if args.constraint:
if update_constraint(file):
components.append('Constraint Layout')
else:
print_e('Failed to update Constraint Layout, aborting...')
sys.exit(1)
if args.material:
if update_material(file):
components.append('Material Design Components')
else:
print_e('Failed to update Material Design Components, aborting...')
sys.exit(1)
if args.gmaven:
updated_artifacts = update_gmaven(gmaven_artifacts)
if updated_artifacts:
components.append('\n'.join(updated_artifacts))
else:
print_e('Failed to update GMaven, aborting...')
sys.exit(1)
if args.androidx:
if update_androidx('androidx', build_id, file, args.include, args.exclude,
args.beyond_corp):
components.append('AndroidX')
else:
print_e('Failed to update AndroidX, aborting...')
sys.exit(1)
if args.jetifier:
if update_jetifier('androidx', build_id, args.beyond_corp):
components.append('Jetifier')
else:
print_e('Failed to update Jetifier, aborting...')
sys.exit(1)
if args.platform or args.finalize_sdk:
if update_framework_current(args.sdk_target, build_id, args.beyond_corp):
components.append('platform SDK')
else:
print_e('Failed to update platform SDK, aborting...')
sys.exit(1)
if args.finalize_sdk:
n = args.finalize_sdk
if not finalize_sdk(args.sdk_target, build_id, n, args.beyond_corp):
print_e('Failed to finalize SDK %d, aborting...' % n)
sys.exit(1)
# We commit the finalized dir separately from the current sdk update.
msg = f'Import final sdk version {n} from build {build_id.url_id}{commit_msg_suffix}'
subprocess.check_call(['git', 'add', '%d' % n])
subprocess.check_call(['git', 'add', 'Android.bp'])
subprocess.check_call(['git', 'commit', '-m', msg])
# Finalize extension sdk level
readme = (f'- {args.finalize_extension}: Finalized together with '
'Android {args.finalize_sdk} (all modules)')
cmd = extension_sdk_finalization_cmd.format(
readme=readme,
bug=args.bug,
extension_version=args.finalize_extension,
build_id=build_id.url_id)
subprocess.check_call(shlex.split(cmd), cwd=repo_root_dir.resolve())
if args.buildtools:
if update_buildtools('sdk_mac', 'darwin', build_id, args.beyond_corp) \
and update_buildtools('sdk', 'linux', build_id, args.beyond_corp) \
and update_buildtools('sdk', 'windows', build_id, args.beyond_corp):
components.append('build tools')
else:
print_e('Failed to update build tools, aborting...')
sys.exit(1)
# Build the git commit.
subprocess.check_call(['git', 'add', current_path, buildtools_dir])
# Build the commit message.
components_msg = ', '.join(components)
argv_msg = ' '.join(sys.argv)
if not args.source and args.gmaven:
src_msg = 'GMaven'
elif not args.source.isnumeric():
src_msg = 'local Maven ZIP'
else:
src_msg = f'build {build_id.url_id}'
msg = f'Import {components_msg} from {src_msg}\n\n{argv_msg}{commit_msg_suffix}'
# Create the git commit.
subprocess.check_call(['git', 'commit', '-q', '-m', msg])
if args.finalize_sdk:
print('NOTE: Created three commits:')
subprocess.check_call(['git', 'log', '-3', '--oneline'])
else:
print('Created commit:')
subprocess.check_call(['git', 'log', '-1', '--oneline'])
print('Remember to test this change before uploading it to Gerrit!')
finally:
# Revert all stray files, including the downloaded zip.
try:
with open(os.devnull, 'w') as bitbucket:
subprocess.check_call(['git', 'add', '-Af', '.'], stdout=bitbucket)
subprocess.check_call(
['git', 'commit', '-m', 'COMMIT TO REVERT - RESET ME!!!', '--allow-empty'],
stdout=bitbucket)
subprocess.check_call(['git', 'reset', '--hard', 'HEAD~1'], stdout=bitbucket)
except subprocess.CalledProcessError:
print_e('ERROR: Failed cleaning up, manual cleanup required!!!')
# Add automatic entries to maven_to_make.
populate_maven_to_make(maven_to_make)
if __name__ == '__main__':
main()