Previously, the SDK finalization script consumed the SDK snapshots from the `for-latest-build` directory. However, in trunk stable that is no longer viable as that directory will include flagged APIs but the finalized SDK cannot use flagged APIs. It is not possible to remove flagged APIs from `for-latest-build` as the snapshots in there are dropped into Android itself and so must include flagged APIs. This change switches the script to use `for-next-build` which is currently identical to `for-latest-build` but will, eventually contain no flagged APIs. Bug: 313065235 Test: TBD Change-Id: I2e7a63a02aa218e259d6b970d64016d15f42fb0f
186 lines
8.0 KiB
Python
Executable File
186 lines
8.0 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import argparse
|
|
import glob
|
|
import os
|
|
import re
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import zipfile
|
|
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
# See go/fetch_artifact for details on this script.
|
|
FETCH_ARTIFACT = '/google/data/ro/projects/android/fetch_artifact'
|
|
COMPAT_REPO = Path('prebuilts/sdk')
|
|
COMPAT_README = Path('extensions/README.md')
|
|
# This build target is used when fetching from a train build (TXXXXXXXX)
|
|
BUILD_TARGET_TRAIN = 'train_build'
|
|
# This build target is used when fetching from a non-train build (XXXXXXXX)
|
|
BUILD_TARGET_CONTINUOUS = 'mainline_modules_sdks-userdebug'
|
|
# The glob of sdk artifacts to fetch from remote build
|
|
ARTIFACT_PATTERN = 'mainline-sdks/for-next-build/current/{module_name}/sdk/*.zip'
|
|
# The glob of sdk artifacts to fetch from local build
|
|
ARTIFACT_LOCAL_PATTERN = 'out/dist/mainline-sdks/for-next-build/current/{module_name}/sdk/*.zip'
|
|
COMMIT_TEMPLATE = """Finalize artifacts for extension SDK %d
|
|
|
|
Import from build id %s.
|
|
|
|
Generated with:
|
|
$ %s
|
|
|
|
Bug: %d
|
|
Test: presubmit"""
|
|
|
|
def fail(*args, **kwargs):
|
|
print(*args, file=sys.stderr, **kwargs)
|
|
sys.exit(1)
|
|
|
|
def fetch_artifacts(target, build_id, module_name):
|
|
tmpdir = Path(tempfile.TemporaryDirectory().name)
|
|
tmpdir.mkdir()
|
|
if args.local_mode:
|
|
artifact_path = ARTIFACT_LOCAL_PATTERN.format(module_name='*')
|
|
print('Copying %s to %s ...' % (artifact_path, tmpdir))
|
|
for file in glob.glob(artifact_path):
|
|
shutil.copy(file, tmpdir)
|
|
else:
|
|
artifact_path = ARTIFACT_PATTERN.format(module_name=module_name)
|
|
print('Fetching %s from %s ...' % (artifact_path, target))
|
|
fetch_cmd = [FETCH_ARTIFACT]
|
|
fetch_cmd.extend(['--bid', str(build_id)])
|
|
fetch_cmd.extend(['--target', target])
|
|
fetch_cmd.append(artifact_path)
|
|
fetch_cmd.append(str(tmpdir))
|
|
print("Running: " + ' '.join(fetch_cmd))
|
|
try:
|
|
subprocess.check_output(fetch_cmd, stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError:
|
|
fail('FAIL: Unable to retrieve %s artifact for build ID %s' % (artifact_path, build_id))
|
|
return tmpdir
|
|
|
|
def repo_for_sdk(filename):
|
|
module = filename.split('-')[0]
|
|
target_dir = ''
|
|
if module == 'btservices': return Path('prebuilts/module_sdk/Bluetooth')
|
|
if module == 'media': return Path('prebuilts/module_sdk/Media')
|
|
if module == 'rkpd': return Path('prebuilts/module_sdk/RemoteKeyProvisioning')
|
|
if module == 'tethering': return Path('prebuilts/module_sdk/Connectivity')
|
|
for dir in os.listdir('prebuilts/module_sdk/'):
|
|
if module.lower() in dir.lower():
|
|
if target_dir:
|
|
fail('Multiple target dirs matched "%s": %s' % (module, (target_dir, dir)))
|
|
target_dir = dir
|
|
if not target_dir:
|
|
fail('Could not find a target dir for %s' % filename)
|
|
|
|
return Path('prebuilts/module_sdk/%s' % target_dir)
|
|
|
|
def dir_for_sdk(filename, version):
|
|
base = str(version)
|
|
if 'test-exports' in filename:
|
|
return os.path.join(base, 'test-exports')
|
|
if 'host-exports' in filename:
|
|
return os.path.join(base, 'host-exports')
|
|
return base
|
|
|
|
def is_ignored(file):
|
|
# Conscrypt has some legacy API tracking files that we don't consider for extensions.
|
|
bad_stem_prefixes = ['conscrypt.module.intra.core.api', 'conscrypt.module.platform.api']
|
|
return any([file.stem.startswith(p) for p in bad_stem_prefixes])
|
|
|
|
|
|
def maybe_tweak_compat_stem(file):
|
|
# For legacy reasons, art and conscrypt txt file names in the SDKs (*.module.public.api)
|
|
# do not match their expected filename in prebuilts/sdk (art, conscrypt). So rename them
|
|
# to match.
|
|
new_stem = file.stem
|
|
new_stem = new_stem.replace('art.module.public.api', 'art')
|
|
new_stem = new_stem.replace('conscrypt.module.public.api', 'conscrypt')
|
|
|
|
# The stub jar artifacts from official builds are named '*-stubs.jar', but
|
|
# the convention for the copies in prebuilts/sdk is just '*.jar'. Fix that.
|
|
new_stem = new_stem.replace('-stubs', '')
|
|
|
|
return file.with_stem(new_stem)
|
|
|
|
if not os.path.isdir('build/soong'):
|
|
fail("This script must be run from the top of an Android source tree.")
|
|
|
|
parser = argparse.ArgumentParser(description=('Finalize an extension SDK with prebuilts'))
|
|
parser.add_argument('-f', '--finalize_sdk', type=int, required=True, help='The numbered SDK to finalize.')
|
|
parser.add_argument('-b', '--bug', type=int, required=True, help='The bug number to add to the commit message.')
|
|
parser.add_argument('-r', '--readme', required=True, help='Version history entry to add to %s' % (COMPAT_REPO / COMPAT_README))
|
|
parser.add_argument('-a', '--amend_last_commit', action="store_true", help='Amend current HEAD commits instead of making new commits.')
|
|
parser.add_argument('-m', '--modules', action='append', help='Modules to include. Can be provided multiple times, or not at all for all modules.')
|
|
parser.add_argument('-l', '--local_mode', action="store_true", help='Local mode: use locally built artifacts and don\'t upload the result to Gerrit.')
|
|
parser.add_argument('bid', help='Build server build ID')
|
|
args = parser.parse_args()
|
|
|
|
build_target = BUILD_TARGET_TRAIN if args.bid[0] == 'T' else BUILD_TARGET_CONTINUOUS
|
|
branch_name = 'finalize-%d' % args.finalize_sdk
|
|
cmdline = shlex.join([x for x in sys.argv if x not in ['-a', '--amend_last_commit', '-l', '--local_mode']])
|
|
commit_message = COMMIT_TEMPLATE % (args.finalize_sdk, args.bid, cmdline, args.bug)
|
|
module_names = args.modules or ['*']
|
|
|
|
compat_dir = COMPAT_REPO.joinpath('extensions/%d' % args.finalize_sdk)
|
|
if compat_dir.is_dir():
|
|
print('Removing existing dir %s' % compat_dir)
|
|
shutil.rmtree(compat_dir)
|
|
|
|
created_dirs = defaultdict(set)
|
|
for m in module_names:
|
|
tmpdir = fetch_artifacts(build_target, args.bid, m)
|
|
for f in tmpdir.iterdir():
|
|
repo = repo_for_sdk(f.name)
|
|
dir = dir_for_sdk(f.name, args.finalize_sdk)
|
|
target_dir = repo.joinpath(dir)
|
|
if target_dir.is_dir():
|
|
print('Removing existing dir %s' % target_dir)
|
|
shutil.rmtree(target_dir)
|
|
with zipfile.ZipFile(tmpdir.joinpath(f)) as zipFile:
|
|
zipFile.extractall(target_dir)
|
|
|
|
# Disable the Android.bp, but keep it for reference / potential future use.
|
|
shutil.move(target_dir.joinpath('Android.bp'), target_dir.joinpath('Android.bp.auto'))
|
|
|
|
print('Created %s' % target_dir)
|
|
created_dirs[repo].add(dir)
|
|
|
|
# Copy api txt files to compat tracking dir
|
|
src_files = [Path(p) for p in glob.glob(os.path.join(target_dir, 'sdk_library/*/*.txt')) + glob.glob(os.path.join(target_dir, 'sdk_library/*/*.jar'))]
|
|
for src_file in src_files:
|
|
if is_ignored(src_file):
|
|
continue
|
|
api_type = src_file.parts[-2]
|
|
dest_dir = compat_dir.joinpath(api_type, 'api') if src_file.suffix == '.txt' else compat_dir.joinpath(api_type)
|
|
dest_file = maybe_tweak_compat_stem(dest_dir.joinpath(src_file.name))
|
|
os.makedirs(dest_dir, exist_ok = True)
|
|
shutil.copy(src_file, dest_file)
|
|
created_dirs[COMPAT_REPO].add(dest_dir.relative_to(COMPAT_REPO))
|
|
|
|
if args.local_mode:
|
|
print('Updated prebuilts using locally built artifacts. Don\'t submit or use for anything besides local testing.')
|
|
sys.exit(0)
|
|
|
|
subprocess.check_output(['repo', 'start', branch_name] + list(created_dirs.keys()))
|
|
print('Running git commit')
|
|
for repo in created_dirs:
|
|
git = ['git', '-C', str(repo)]
|
|
subprocess.check_output(git + ['add'] + list(created_dirs[repo]))
|
|
|
|
if repo == COMPAT_REPO:
|
|
with open(COMPAT_REPO / COMPAT_README, "a") as readme:
|
|
readme.write(f"- {args.finalize_sdk}: {args.readme}\n")
|
|
subprocess.check_output(git + ['add', COMPAT_README])
|
|
|
|
if args.amend_last_commit:
|
|
change_id = '\n' + re.search(r'Change-Id: [^\\n]+', str(subprocess.check_output(git + ['log', '-1']))).group(0)
|
|
subprocess.check_output(git + ['commit', '--amend', '-m', commit_message + change_id])
|
|
else:
|
|
subprocess.check_output(git + ['commit', '-m', commit_message])
|