Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 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 | |
| 19 | Expects items in OPTIONS prepared by merge_target_files.py. |
| 20 | """ |
| 21 | |
| 22 | import glob |
| 23 | import json |
| 24 | import logging |
| 25 | import os |
| 26 | import shutil |
| 27 | import subprocess |
| 28 | |
| 29 | import common |
Rob Seymour | c4e2754 | 2022-03-15 17:43:51 +0000 | [diff] [blame] | 30 | import merge_utils |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 31 | |
| 32 | logger = logging.getLogger(__name__) |
| 33 | OPTIONS = common.OPTIONS |
| 34 | |
| 35 | |
| 36 | def 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 |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 75 | # apex/ (extracted updatable APEX) |
| 76 | # <apex 1>/ |
| 77 | # ... |
| 78 | # <apex 2>/ |
| 79 | # ... |
| 80 | # ... |
| 81 | # out/dex2oat_result/vendor/ |
| 82 | # <app> |
| 83 | # oat/arm64/ |
| 84 | # package.vdex |
| 85 | # package.odex |
| 86 | # <priv-app> |
| 87 | # oat/arm64/ |
| 88 | # package.vdex |
| 89 | # package.odex |
| 90 | dexpreopt_tools_files_temp_dir = os.path.join(temp_dir, 'tools') |
| 91 | dexpreopt_framework_config_files_temp_dir = os.path.join( |
| 92 | temp_dir, 'system_config') |
| 93 | dexpreopt_vendor_config_files_temp_dir = os.path.join(temp_dir, |
| 94 | 'vendor_config') |
| 95 | |
Rob Seymour | c4e2754 | 2022-03-15 17:43:51 +0000 | [diff] [blame] | 96 | merge_utils.ExtractItems( |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 97 | input_zip=OPTIONS.framework_dexpreopt_tools, |
| 98 | output_dir=dexpreopt_tools_files_temp_dir, |
| 99 | extract_item_list=('*',)) |
Rob Seymour | c4e2754 | 2022-03-15 17:43:51 +0000 | [diff] [blame] | 100 | merge_utils.ExtractItems( |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 101 | input_zip=OPTIONS.framework_dexpreopt_config, |
| 102 | output_dir=dexpreopt_framework_config_files_temp_dir, |
| 103 | extract_item_list=('*',)) |
Rob Seymour | c4e2754 | 2022-03-15 17:43:51 +0000 | [diff] [blame] | 104 | merge_utils.ExtractItems( |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 105 | input_zip=OPTIONS.vendor_dexpreopt_config, |
| 106 | output_dir=dexpreopt_vendor_config_files_temp_dir, |
| 107 | extract_item_list=('*',)) |
| 108 | |
| 109 | os.symlink( |
| 110 | os.path.join(output_target_files_dir, 'SYSTEM'), |
| 111 | os.path.join(temp_dir, 'system')) |
| 112 | os.symlink( |
| 113 | os.path.join(output_target_files_dir, 'VENDOR'), |
| 114 | os.path.join(temp_dir, 'vendor')) |
| 115 | |
Jooyung Han | 2ac1f2f | 2023-06-27 13:01:56 +0900 | [diff] [blame^] | 116 | # Extract APEX. |
| 117 | logging.info('extracting APEX') |
| 118 | apex_extract_root_dir = os.path.join(temp_dir, 'apex') |
| 119 | os.makedirs(apex_extract_root_dir) |
| 120 | |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 121 | # The directory structure for updatable APEXes is: |
| 122 | # |
| 123 | # SYSTEM |
| 124 | # apex |
| 125 | # com.android.adbd.apex |
| 126 | # com.android.appsearch.apex |
| 127 | # com.android.art.apex |
| 128 | # ... |
| 129 | apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex') |
Jooyung Han | 2ac1f2f | 2023-06-27 13:01:56 +0900 | [diff] [blame^] | 130 | for apex in (glob.glob(os.path.join(apex_root, '*.apex')) + |
| 131 | glob.glob(os.path.join(apex_root, '*.capex'))): |
| 132 | logging.info(' apex: %s', apex) |
| 133 | # deapexer is in the same directory as the merge_target_files binary extracted |
| 134 | # from otatools.zip. |
| 135 | apex_json_info = subprocess.check_output(['deapexer', 'info', apex]) |
| 136 | logging.info(' info: %s', apex_json_info) |
| 137 | apex_info = json.loads(apex_json_info) |
| 138 | apex_name = apex_info['name'] |
| 139 | logging.info(' name: %s', apex_name) |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 140 | |
Jooyung Han | 2ac1f2f | 2023-06-27 13:01:56 +0900 | [diff] [blame^] | 141 | apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name) |
| 142 | os.makedirs(apex_extract_dir) |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 143 | |
Jooyung Han | 2ac1f2f | 2023-06-27 13:01:56 +0900 | [diff] [blame^] | 144 | # deapexer uses debugfs_static/fsck.erofs from otatools.zip. |
| 145 | command = [ |
| 146 | 'deapexer', |
| 147 | '--debugfs_path', |
| 148 | 'debugfs_static', |
| 149 | '--fsckerofs_path', |
| 150 | 'fsck.erofs', |
| 151 | 'extract', |
| 152 | apex, |
| 153 | apex_extract_dir, |
| 154 | ] |
| 155 | logging.info(' running %s', command) |
| 156 | subprocess.check_call(command) |
Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame] | 157 | |
| 158 | # Modify system config to point to the tools that have been extracted. |
| 159 | # Absolute or .. paths are not allowed by the dexpreopt_gen tool in |
| 160 | # dexpreopt_soong.config. |
| 161 | dexpreopt_framework_soon_config = os.path.join( |
| 162 | dexpreopt_framework_config_files_temp_dir, 'dexpreopt_soong.config') |
| 163 | with open(dexpreopt_framework_soon_config, 'w') as f: |
| 164 | dexpreopt_soong_config = { |
| 165 | 'Profman': 'tools/profman', |
| 166 | 'Dex2oat': 'tools/dex2oatd', |
| 167 | 'Aapt': 'tools/aapt2', |
| 168 | 'SoongZip': 'tools/soong_zip', |
| 169 | 'Zip2zip': 'tools/zip2zip', |
| 170 | 'ManifestCheck': 'tools/manifest_check', |
| 171 | 'ConstructContext': 'tools/construct_context', |
| 172 | } |
| 173 | json.dump(dexpreopt_soong_config, f) |
| 174 | |
| 175 | # TODO(b/188179859): Make *dex location configurable to vendor or system_other. |
| 176 | use_system_other_odex = False |
| 177 | |
| 178 | if use_system_other_odex: |
| 179 | dex_img = 'SYSTEM_OTHER' |
| 180 | else: |
| 181 | dex_img = 'VENDOR' |
| 182 | # Open vendor_filesystem_config to append the items generated by dexopt. |
| 183 | vendor_file_system_config = open( |
| 184 | os.path.join(temp_dir, 'output', 'META', |
| 185 | 'vendor_filesystem_config.txt'), 'a') |
| 186 | |
| 187 | # Dexpreopt vendor apps. |
| 188 | dexpreopt_config_suffix = '_dexpreopt.config' |
| 189 | for config in glob.glob( |
| 190 | os.path.join(dexpreopt_vendor_config_files_temp_dir, |
| 191 | '*' + dexpreopt_config_suffix)): |
| 192 | app = os.path.basename(config)[:-len(dexpreopt_config_suffix)] |
| 193 | logging.info('dexpreopt config: %s %s', config, app) |
| 194 | |
| 195 | apk_dir = 'app' |
| 196 | apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk') |
| 197 | if not os.path.exists(apk_path): |
| 198 | apk_dir = 'priv-app' |
| 199 | apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk') |
| 200 | if not os.path.exists(apk_path): |
| 201 | logging.warning( |
| 202 | 'skipping dexpreopt for %s, no apk found in vendor/app ' |
| 203 | 'or vendor/priv-app', app) |
| 204 | continue |
| 205 | |
| 206 | # Generate dexpreopting script. Note 'out_dir' is not the output directory |
| 207 | # where the script is generated, but the OUT_DIR at build time referenced |
| 208 | # in the dexpreot config files, e.g., "out/.../core-oj.jar", so the tool knows |
| 209 | # how to adjust the path. |
| 210 | command = [ |
| 211 | os.path.join(dexpreopt_tools_files_temp_dir, 'dexpreopt_gen'), |
| 212 | '-global', |
| 213 | os.path.join(dexpreopt_framework_config_files_temp_dir, |
| 214 | 'dexpreopt.config'), |
| 215 | '-global_soong', |
| 216 | os.path.join(dexpreopt_framework_config_files_temp_dir, |
| 217 | 'dexpreopt_soong.config'), |
| 218 | '-module', |
| 219 | config, |
| 220 | '-dexpreopt_script', |
| 221 | 'dexpreopt_app.sh', |
| 222 | '-out_dir', |
| 223 | 'out', |
| 224 | '-base_path', |
| 225 | '.', |
| 226 | '--uses_target_files', |
| 227 | ] |
| 228 | |
| 229 | # Run the command from temp_dir so all tool paths are its descendants. |
| 230 | logging.info('running %s', command) |
| 231 | subprocess.check_call(command, cwd=temp_dir) |
| 232 | |
| 233 | # Call the generated script. |
| 234 | command = ['sh', 'dexpreopt_app.sh', apk_path] |
| 235 | logging.info('running %s', command) |
| 236 | subprocess.check_call(command, cwd=temp_dir) |
| 237 | |
| 238 | # Output files are in: |
| 239 | # |
| 240 | # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.vdex |
| 241 | # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.odex |
| 242 | # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.vdex |
| 243 | # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.odex |
| 244 | # |
| 245 | # Copy the files to their destination. The structure of system_other is: |
| 246 | # |
| 247 | # system_other/ |
| 248 | # system-other-odex-marker |
| 249 | # system/ |
| 250 | # app/ |
| 251 | # <app>/oat/arm64/ |
| 252 | # <app>.odex |
| 253 | # <app>.vdex |
| 254 | # ... |
| 255 | # priv-app/ |
| 256 | # <app>/oat/arm64/ |
| 257 | # <app>.odex |
| 258 | # <app>.vdex |
| 259 | # ... |
| 260 | |
| 261 | # TODO(b/188179859): Support for other architectures. |
| 262 | arch = 'arm64' |
| 263 | |
| 264 | dex_destination = os.path.join(temp_dir, 'output', dex_img, apk_dir, app, |
| 265 | 'oat', arch) |
| 266 | os.makedirs(dex_destination) |
| 267 | dex2oat_path = os.path.join(temp_dir, 'out', 'dex2oat_result', 'vendor', |
| 268 | apk_dir, app, 'oat', arch) |
| 269 | shutil.copy( |
| 270 | os.path.join(dex2oat_path, 'package.vdex'), |
| 271 | os.path.join(dex_destination, app + '.vdex')) |
| 272 | shutil.copy( |
| 273 | os.path.join(dex2oat_path, 'package.odex'), |
| 274 | os.path.join(dex_destination, app + '.odex')) |
| 275 | |
| 276 | # Append entries to vendor_file_system_config.txt, such as: |
| 277 | # |
| 278 | # vendor/app/<app>/oat 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 |
| 279 | # vendor/app/<app>/oat/arm64 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 |
| 280 | # vendor/app/<app>/oat/arm64/<app>.odex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 |
| 281 | # vendor/app/<app>/oat/arm64/<app>.vdex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0 |
| 282 | if not use_system_other_odex: |
| 283 | vendor_app_prefix = 'vendor/' + apk_dir + '/' + app + '/oat' |
| 284 | selabel = 'selabel=u:object_r:vendor_app_file:s0 capabilities=0x0' |
| 285 | vendor_file_system_config.writelines([ |
| 286 | vendor_app_prefix + ' 0 2000 755 ' + selabel + '\n', |
| 287 | vendor_app_prefix + '/' + arch + ' 0 2000 755 ' + selabel + '\n', |
| 288 | vendor_app_prefix + '/' + arch + '/' + app + '.odex 0 0 644 ' + |
| 289 | selabel + '\n', |
| 290 | vendor_app_prefix + '/' + arch + '/' + app + '.vdex 0 0 644 ' + |
| 291 | selabel + '\n', |
| 292 | ]) |
| 293 | |
| 294 | if not use_system_other_odex: |
| 295 | vendor_file_system_config.close() |
| 296 | # Delete vendor.img so that it will be regenerated. |
| 297 | # TODO(b/188179859): Rebuilding a vendor image in GRF mode (e.g., T(framework) |
| 298 | # and S(vendor) may require logic similar to that in |
| 299 | # rebuild_image_with_sepolicy. |
| 300 | vendor_img = os.path.join(output_target_files_dir, 'IMAGES', 'vendor.img') |
| 301 | if os.path.exists(vendor_img): |
| 302 | logging.info('Deleting %s', vendor_img) |
| 303 | os.remove(vendor_img) |