Files
build/tools/releasetools/merge/merge_dexopt.py
Dennis Shen a8d1143beb Update deapexer call to explictly use blkid
To support erofs apex extract via deapexer, we need blkid to tell the filesystem type of underlying payload image. If it is ext4, debugfs_static will be used, if it is erofs, then we should use fsck.erofs. Thus we now need explicit blkid input.

Also, remove dependency on fsck.erofs for other deapexer calls. Only extract deapexer call needs blkid and fsck.erofs.

BUG: b/255963179, b/240288941
Change-Id: I8cea0f2def664f9cabf8b14c9a7ecc47bbddfbdd
2022-11-09 18:22:14 +00:00

328 lines
12 KiB
Python

#!/usr/bin/env python
#
# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
"""Generates dexopt files for vendor apps, from a merged target_files.
Expects items in OPTIONS prepared by merge_target_files.py.
"""
import glob
import json
import logging
import os
import shutil
import subprocess
import common
import merge_utils
logger = logging.getLogger(__name__)
OPTIONS = common.OPTIONS
def MergeDexopt(temp_dir, output_target_files_dir):
"""If needed, generates dexopt files for vendor apps.
Args:
temp_dir: Location containing an 'output' directory where target files have
been extracted, e.g. <temp_dir>/output/SYSTEM, <temp_dir>/output/IMAGES,
etc.
output_target_files_dir: The name of a directory that will be used to create
the output target files package after all the special cases are processed.
"""
# Load vendor and framework META/misc_info.txt.
if (OPTIONS.vendor_misc_info.get('building_with_vsdk') != 'true' or
OPTIONS.framework_dexpreopt_tools is None or
OPTIONS.framework_dexpreopt_config is None or
OPTIONS.vendor_dexpreopt_config is None):
return
logger.info('applying dexpreopt')
# The directory structure to apply dexpreopt is:
#
# <temp_dir>/
# framework_meta/
# META/
# vendor_meta/
# META/
# output/
# SYSTEM/
# VENDOR/
# IMAGES/
# <other items extracted from system and vendor target files>
# tools/
# <contents of dexpreopt_tools.zip>
# system_config/
# <contents of system dexpreopt_config.zip>
# vendor_config/
# <contents of vendor dexpreopt_config.zip>
# system -> output/SYSTEM
# vendor -> output/VENDOR
# apex -> output/SYSTEM/apex (only for flattened APEX builds)
# apex/ (extracted updatable APEX)
# <apex 1>/
# ...
# <apex 2>/
# ...
# ...
# out/dex2oat_result/vendor/
# <app>
# oat/arm64/
# package.vdex
# package.odex
# <priv-app>
# oat/arm64/
# package.vdex
# package.odex
dexpreopt_tools_files_temp_dir = os.path.join(temp_dir, 'tools')
dexpreopt_framework_config_files_temp_dir = os.path.join(
temp_dir, 'system_config')
dexpreopt_vendor_config_files_temp_dir = os.path.join(temp_dir,
'vendor_config')
merge_utils.ExtractItems(
input_zip=OPTIONS.framework_dexpreopt_tools,
output_dir=dexpreopt_tools_files_temp_dir,
extract_item_list=('*',))
merge_utils.ExtractItems(
input_zip=OPTIONS.framework_dexpreopt_config,
output_dir=dexpreopt_framework_config_files_temp_dir,
extract_item_list=('*',))
merge_utils.ExtractItems(
input_zip=OPTIONS.vendor_dexpreopt_config,
output_dir=dexpreopt_vendor_config_files_temp_dir,
extract_item_list=('*',))
os.symlink(
os.path.join(output_target_files_dir, 'SYSTEM'),
os.path.join(temp_dir, 'system'))
os.symlink(
os.path.join(output_target_files_dir, 'VENDOR'),
os.path.join(temp_dir, 'vendor'))
# The directory structure for flatteded APEXes is:
#
# SYSTEM
# apex
# <APEX name, e.g., com.android.wifi>
# apex_manifest.pb
# apex_pubkey
# etc/
# javalib/
# lib/
# lib64/
# priv-app/
#
# The directory structure for updatable APEXes is:
#
# SYSTEM
# apex
# com.android.adbd.apex
# com.android.appsearch.apex
# com.android.art.apex
# ...
apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex')
# Check for flattended versus updatable APEX.
if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false':
# Extract APEX.
logging.info('extracting APEX')
apex_extract_root_dir = os.path.join(temp_dir, 'apex')
os.makedirs(apex_extract_root_dir)
for apex in (glob.glob(os.path.join(apex_root, '*.apex')) +
glob.glob(os.path.join(apex_root, '*.capex'))):
logging.info(' apex: %s', apex)
# deapexer is in the same directory as the merge_target_files binary extracted
# from otatools.zip.
apex_json_info = subprocess.check_output(['deapexer', 'info', apex])
logging.info(' info: %s', apex_json_info)
apex_info = json.loads(apex_json_info)
apex_name = apex_info['name']
logging.info(' name: %s', apex_name)
apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name)
os.makedirs(apex_extract_dir)
# deapexer uses debugfs_static, which is part of otatools.zip.
command = [
'deapexer',
'--debugfs_path',
'debugfs_static',
'--blkid_path',
'blkid',
'--fsckerofs_path',
'fsck.erofs',
'extract',
apex,
apex_extract_dir,
]
logging.info(' running %s', command)
subprocess.check_call(command)
else:
# Flattened APEXes don't need to be extracted since they have the necessary
# directory structure.
os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex'))
# Modify system config to point to the tools that have been extracted.
# Absolute or .. paths are not allowed by the dexpreopt_gen tool in
# dexpreopt_soong.config.
dexpreopt_framework_soon_config = os.path.join(
dexpreopt_framework_config_files_temp_dir, 'dexpreopt_soong.config')
with open(dexpreopt_framework_soon_config, 'w') as f:
dexpreopt_soong_config = {
'Profman': 'tools/profman',
'Dex2oat': 'tools/dex2oatd',
'Aapt': 'tools/aapt2',
'SoongZip': 'tools/soong_zip',
'Zip2zip': 'tools/zip2zip',
'ManifestCheck': 'tools/manifest_check',
'ConstructContext': 'tools/construct_context',
}
json.dump(dexpreopt_soong_config, f)
# TODO(b/188179859): Make *dex location configurable to vendor or system_other.
use_system_other_odex = False
if use_system_other_odex:
dex_img = 'SYSTEM_OTHER'
else:
dex_img = 'VENDOR'
# Open vendor_filesystem_config to append the items generated by dexopt.
vendor_file_system_config = open(
os.path.join(temp_dir, 'output', 'META',
'vendor_filesystem_config.txt'), 'a')
# Dexpreopt vendor apps.
dexpreopt_config_suffix = '_dexpreopt.config'
for config in glob.glob(
os.path.join(dexpreopt_vendor_config_files_temp_dir,
'*' + dexpreopt_config_suffix)):
app = os.path.basename(config)[:-len(dexpreopt_config_suffix)]
logging.info('dexpreopt config: %s %s', config, app)
apk_dir = 'app'
apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
if not os.path.exists(apk_path):
apk_dir = 'priv-app'
apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
if not os.path.exists(apk_path):
logging.warning(
'skipping dexpreopt for %s, no apk found in vendor/app '
'or vendor/priv-app', app)
continue
# Generate dexpreopting script. Note 'out_dir' is not the output directory
# where the script is generated, but the OUT_DIR at build time referenced
# in the dexpreot config files, e.g., "out/.../core-oj.jar", so the tool knows
# how to adjust the path.
command = [
os.path.join(dexpreopt_tools_files_temp_dir, 'dexpreopt_gen'),
'-global',
os.path.join(dexpreopt_framework_config_files_temp_dir,
'dexpreopt.config'),
'-global_soong',
os.path.join(dexpreopt_framework_config_files_temp_dir,
'dexpreopt_soong.config'),
'-module',
config,
'-dexpreopt_script',
'dexpreopt_app.sh',
'-out_dir',
'out',
'-base_path',
'.',
'--uses_target_files',
]
# Run the command from temp_dir so all tool paths are its descendants.
logging.info('running %s', command)
subprocess.check_call(command, cwd=temp_dir)
# Call the generated script.
command = ['sh', 'dexpreopt_app.sh', apk_path]
logging.info('running %s', command)
subprocess.check_call(command, cwd=temp_dir)
# Output files are in:
#
# <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.vdex
# <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.odex
# <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.vdex
# <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.odex
#
# Copy the files to their destination. The structure of system_other is:
#
# system_other/
# system-other-odex-marker
# system/
# app/
# <app>/oat/arm64/
# <app>.odex
# <app>.vdex
# ...
# priv-app/
# <app>/oat/arm64/
# <app>.odex
# <app>.vdex
# ...
# TODO(b/188179859): Support for other architectures.
arch = 'arm64'
dex_destination = os.path.join(temp_dir, 'output', dex_img, apk_dir, app,
'oat', arch)
os.makedirs(dex_destination)
dex2oat_path = os.path.join(temp_dir, 'out', 'dex2oat_result', 'vendor',
apk_dir, app, 'oat', arch)
shutil.copy(
os.path.join(dex2oat_path, 'package.vdex'),
os.path.join(dex_destination, app + '.vdex'))
shutil.copy(
os.path.join(dex2oat_path, 'package.odex'),
os.path.join(dex_destination, app + '.odex'))
# Append entries to vendor_file_system_config.txt, such as:
#
# vendor/app/<app>/oat 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
# vendor/app/<app>/oat/arm64 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
# vendor/app/<app>/oat/arm64/<app>.odex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
# vendor/app/<app>/oat/arm64/<app>.vdex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
if not use_system_other_odex:
vendor_app_prefix = 'vendor/' + apk_dir + '/' + app + '/oat'
selabel = 'selabel=u:object_r:vendor_app_file:s0 capabilities=0x0'
vendor_file_system_config.writelines([
vendor_app_prefix + ' 0 2000 755 ' + selabel + '\n',
vendor_app_prefix + '/' + arch + ' 0 2000 755 ' + selabel + '\n',
vendor_app_prefix + '/' + arch + '/' + app + '.odex 0 0 644 ' +
selabel + '\n',
vendor_app_prefix + '/' + arch + '/' + app + '.vdex 0 0 644 ' +
selabel + '\n',
])
if not use_system_other_odex:
vendor_file_system_config.close()
# Delete vendor.img so that it will be regenerated.
# TODO(b/188179859): Rebuilding a vendor image in GRF mode (e.g., T(framework)
# and S(vendor) may require logic similar to that in
# rebuild_image_with_sepolicy.
vendor_img = os.path.join(output_target_files_dir, 'IMAGES', 'vendor.img')
if os.path.exists(vendor_img):
logging.info('Deleting %s', vendor_img)
os.remove(vendor_img)