• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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