1#!/usr/bin/env python 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16# 17"""Generates dexopt files for vendor apps, from a merged target_files. 18 19Expects items in OPTIONS prepared by merge_target_files.py. 20""" 21 22import glob 23import json 24import logging 25import os 26import shutil 27import subprocess 28 29import common 30import merge_utils 31 32logger = logging.getLogger(__name__) 33OPTIONS = common.OPTIONS 34 35 36def MergeDexopt(temp_dir, output_target_files_dir): 37 """If needed, generates dexopt files for vendor apps. 38 39 Args: 40 temp_dir: Location containing an 'output' directory where target files have 41 been extracted, e.g. <temp_dir>/output/SYSTEM, <temp_dir>/output/IMAGES, 42 etc. 43 output_target_files_dir: The name of a directory that will be used to create 44 the output target files package after all the special cases are processed. 45 """ 46 # Load vendor and framework META/misc_info.txt. 47 if (OPTIONS.vendor_misc_info.get('building_with_vsdk') != 'true' or 48 OPTIONS.framework_dexpreopt_tools is None or 49 OPTIONS.framework_dexpreopt_config is None or 50 OPTIONS.vendor_dexpreopt_config is None): 51 return 52 53 logger.info('applying dexpreopt') 54 55 # The directory structure to apply dexpreopt is: 56 # 57 # <temp_dir>/ 58 # framework_meta/ 59 # META/ 60 # vendor_meta/ 61 # META/ 62 # output/ 63 # SYSTEM/ 64 # VENDOR/ 65 # IMAGES/ 66 # <other items extracted from system and vendor target files> 67 # tools/ 68 # <contents of dexpreopt_tools.zip> 69 # system_config/ 70 # <contents of system dexpreopt_config.zip> 71 # vendor_config/ 72 # <contents of vendor dexpreopt_config.zip> 73 # system -> output/SYSTEM 74 # vendor -> output/VENDOR 75 # apex -> output/SYSTEM/apex (only for flattened APEX builds) 76 # apex/ (extracted updatable APEX) 77 # <apex 1>/ 78 # ... 79 # <apex 2>/ 80 # ... 81 # ... 82 # out/dex2oat_result/vendor/ 83 # <app> 84 # oat/arm64/ 85 # package.vdex 86 # package.odex 87 # <priv-app> 88 # oat/arm64/ 89 # package.vdex 90 # package.odex 91 dexpreopt_tools_files_temp_dir = os.path.join(temp_dir, 'tools') 92 dexpreopt_framework_config_files_temp_dir = os.path.join( 93 temp_dir, 'system_config') 94 dexpreopt_vendor_config_files_temp_dir = os.path.join(temp_dir, 95 'vendor_config') 96 97 merge_utils.ExtractItems( 98 input_zip=OPTIONS.framework_dexpreopt_tools, 99 output_dir=dexpreopt_tools_files_temp_dir, 100 extract_item_list=('*',)) 101 merge_utils.ExtractItems( 102 input_zip=OPTIONS.framework_dexpreopt_config, 103 output_dir=dexpreopt_framework_config_files_temp_dir, 104 extract_item_list=('*',)) 105 merge_utils.ExtractItems( 106 input_zip=OPTIONS.vendor_dexpreopt_config, 107 output_dir=dexpreopt_vendor_config_files_temp_dir, 108 extract_item_list=('*',)) 109 110 os.symlink( 111 os.path.join(output_target_files_dir, 'SYSTEM'), 112 os.path.join(temp_dir, 'system')) 113 os.symlink( 114 os.path.join(output_target_files_dir, 'VENDOR'), 115 os.path.join(temp_dir, 'vendor')) 116 117 # The directory structure for flatteded APEXes is: 118 # 119 # SYSTEM 120 # apex 121 # <APEX name, e.g., com.android.wifi> 122 # apex_manifest.pb 123 # apex_pubkey 124 # etc/ 125 # javalib/ 126 # lib/ 127 # lib64/ 128 # priv-app/ 129 # 130 # The directory structure for updatable APEXes is: 131 # 132 # SYSTEM 133 # apex 134 # com.android.adbd.apex 135 # com.android.appsearch.apex 136 # com.android.art.apex 137 # ... 138 apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex') 139 140 # Check for flattended versus updatable APEX. 141 if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false': 142 # Extract APEX. 143 logging.info('extracting APEX') 144 145 apex_extract_root_dir = os.path.join(temp_dir, 'apex') 146 os.makedirs(apex_extract_root_dir) 147 148 for apex in (glob.glob(os.path.join(apex_root, '*.apex')) + 149 glob.glob(os.path.join(apex_root, '*.capex'))): 150 logging.info(' apex: %s', apex) 151 # deapexer is in the same directory as the merge_target_files binary extracted 152 # from otatools.zip. 153 apex_json_info = subprocess.check_output(['deapexer', 'info', apex]) 154 logging.info(' info: %s', apex_json_info) 155 apex_info = json.loads(apex_json_info) 156 apex_name = apex_info['name'] 157 logging.info(' name: %s', apex_name) 158 159 apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name) 160 os.makedirs(apex_extract_dir) 161 162 # deapexer uses debugfs_static, which is part of otatools.zip. 163 command = [ 164 'deapexer', 165 '--debugfs_path', 166 'debugfs_static', 167 '--blkid_path', 168 'blkid', 169 '--fsckerofs_path', 170 'fsck.erofs', 171 'extract', 172 apex, 173 apex_extract_dir, 174 ] 175 logging.info(' running %s', command) 176 subprocess.check_call(command) 177 else: 178 # Flattened APEXes don't need to be extracted since they have the necessary 179 # directory structure. 180 os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex')) 181 182 # Modify system config to point to the tools that have been extracted. 183 # Absolute or .. paths are not allowed by the dexpreopt_gen tool in 184 # dexpreopt_soong.config. 185 dexpreopt_framework_soon_config = os.path.join( 186 dexpreopt_framework_config_files_temp_dir, 'dexpreopt_soong.config') 187 with open(dexpreopt_framework_soon_config, 'w') as f: 188 dexpreopt_soong_config = { 189 'Profman': 'tools/profman', 190 'Dex2oat': 'tools/dex2oatd', 191 'Aapt': 'tools/aapt2', 192 'SoongZip': 'tools/soong_zip', 193 'Zip2zip': 'tools/zip2zip', 194 'ManifestCheck': 'tools/manifest_check', 195 'ConstructContext': 'tools/construct_context', 196 } 197 json.dump(dexpreopt_soong_config, f) 198 199 # TODO(b/188179859): Make *dex location configurable to vendor or system_other. 200 use_system_other_odex = False 201 202 if use_system_other_odex: 203 dex_img = 'SYSTEM_OTHER' 204 else: 205 dex_img = 'VENDOR' 206 # Open vendor_filesystem_config to append the items generated by dexopt. 207 vendor_file_system_config = open( 208 os.path.join(temp_dir, 'output', 'META', 209 'vendor_filesystem_config.txt'), 'a') 210 211 # Dexpreopt vendor apps. 212 dexpreopt_config_suffix = '_dexpreopt.config' 213 for config in glob.glob( 214 os.path.join(dexpreopt_vendor_config_files_temp_dir, 215 '*' + dexpreopt_config_suffix)): 216 app = os.path.basename(config)[:-len(dexpreopt_config_suffix)] 217 logging.info('dexpreopt config: %s %s', config, app) 218 219 apk_dir = 'app' 220 apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk') 221 if not os.path.exists(apk_path): 222 apk_dir = 'priv-app' 223 apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk') 224 if not os.path.exists(apk_path): 225 logging.warning( 226 'skipping dexpreopt for %s, no apk found in vendor/app ' 227 'or vendor/priv-app', app) 228 continue 229 230 # Generate dexpreopting script. Note 'out_dir' is not the output directory 231 # where the script is generated, but the OUT_DIR at build time referenced 232 # in the dexpreot config files, e.g., "out/.../core-oj.jar", so the tool knows 233 # how to adjust the path. 234 command = [ 235 os.path.join(dexpreopt_tools_files_temp_dir, 'dexpreopt_gen'), 236 '-global', 237 os.path.join(dexpreopt_framework_config_files_temp_dir, 238 'dexpreopt.config'), 239 '-global_soong', 240 os.path.join(dexpreopt_framework_config_files_temp_dir, 241 'dexpreopt_soong.config'), 242 '-module', 243 config, 244 '-dexpreopt_script', 245 'dexpreopt_app.sh', 246 '-out_dir', 247 'out', 248 '-base_path', 249 '.', 250 '--uses_target_files', 251 ] 252 253 # Run the command from temp_dir so all tool paths are its descendants. 254 logging.info('running %s', command) 255 subprocess.check_call(command, cwd=temp_dir) 256 257 # Call the generated script. 258 command = ['sh', 'dexpreopt_app.sh', apk_path] 259 logging.info('running %s', command) 260 subprocess.check_call(command, cwd=temp_dir) 261 262 # Output files are in: 263 # 264 # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.vdex 265 # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.odex 266 # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.vdex 267 # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.odex 268 # 269 # Copy the files to their destination. The structure of system_other is: 270 # 271 # system_other/ 272 # system-other-odex-marker 273 # system/ 274 # app/ 275 # <app>/oat/arm64/ 276 # <app>.odex 277 # <app>.vdex 278 # ... 279 # priv-app/ 280 # <app>/oat/arm64/ 281 # <app>.odex 282 # <app>.vdex 283 # ... 284 285 # TODO(b/188179859): Support for other architectures. 286 arch = 'arm64' 287 288 dex_destination = os.path.join(temp_dir, 'output', dex_img, apk_dir, app, 289 'oat', arch) 290 os.makedirs(dex_destination) 291 dex2oat_path = os.path.join(temp_dir, 'out', 'dex2oat_result', 'vendor', 292 apk_dir, app, 'oat', arch) 293 shutil.copy( 294 os.path.join(dex2oat_path, 'package.vdex'), 295 os.path.join(dex_destination, app + '.vdex')) 296 shutil.copy( 297 os.path.join(dex2oat_path, 'package.odex'), 298 os.path.join(dex_destination, app + '.odex')) 299 300 # Append entries to vendor_file_system_config.txt, such as: 301 # 302 # vendor/app/<app>/oat 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 303 # vendor/app/<app>/oat/arm64 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 304 # vendor/app/<app>/oat/arm64/<app>.odex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 305 # vendor/app/<app>/oat/arm64/<app>.vdex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 306 if not use_system_other_odex: 307 vendor_app_prefix = 'vendor/' + apk_dir + '/' + app + '/oat' 308 selabel = 'selabel=u:object_r:vendor_app_file:s0 capabilities=0x0' 309 vendor_file_system_config.writelines([ 310 vendor_app_prefix + ' 0 2000 755 ' + selabel + '\n', 311 vendor_app_prefix + '/' + arch + ' 0 2000 755 ' + selabel + '\n', 312 vendor_app_prefix + '/' + arch + '/' + app + '.odex 0 0 644 ' + 313 selabel + '\n', 314 vendor_app_prefix + '/' + arch + '/' + app + '.vdex 0 0 644 ' + 315 selabel + '\n', 316 ]) 317 318 if not use_system_other_odex: 319 vendor_file_system_config.close() 320 # Delete vendor.img so that it will be regenerated. 321 # TODO(b/188179859): Rebuilding a vendor image in GRF mode (e.g., T(framework) 322 # and S(vendor) may require logic similar to that in 323 # rebuild_image_with_sepolicy. 324 vendor_img = os.path.join(output_target_files_dir, 'IMAGES', 'vendor.img') 325 if os.path.exists(vendor_img): 326 logging.info('Deleting %s', vendor_img) 327 os.remove(vendor_img) 328