| #!/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', |
| '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) |