blob: dd6e2f906f72047931b33b0727717781036188f0 [file] [log] [blame]
Daniel Norman2465fc82022-03-02 12:01:20 -08001#!/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
30
31logger = logging.getLogger(__name__)
32OPTIONS = common.OPTIONS
33
34
35def MergeDexopt(temp_dir, output_target_files_dir):
36 """If needed, generates dexopt files for vendor apps.
37
38 Args:
39 temp_dir: Location containing an 'output' directory where target files have
40 been extracted, e.g. <temp_dir>/output/SYSTEM, <temp_dir>/output/IMAGES,
41 etc.
42 output_target_files_dir: The name of a directory that will be used to create
43 the output target files package after all the special cases are processed.
44 """
45 # Load vendor and framework META/misc_info.txt.
46 if (OPTIONS.vendor_misc_info.get('building_with_vsdk') != 'true' or
47 OPTIONS.framework_dexpreopt_tools is None or
48 OPTIONS.framework_dexpreopt_config is None or
49 OPTIONS.vendor_dexpreopt_config is None):
50 return
51
52 logger.info('applying dexpreopt')
53
54 # The directory structure to apply dexpreopt is:
55 #
56 # <temp_dir>/
57 # framework_meta/
58 # META/
59 # vendor_meta/
60 # META/
61 # output/
62 # SYSTEM/
63 # VENDOR/
64 # IMAGES/
65 # <other items extracted from system and vendor target files>
66 # tools/
67 # <contents of dexpreopt_tools.zip>
68 # system_config/
69 # <contents of system dexpreopt_config.zip>
70 # vendor_config/
71 # <contents of vendor dexpreopt_config.zip>
72 # system -> output/SYSTEM
73 # vendor -> output/VENDOR
74 # apex -> output/SYSTEM/apex (only for flattened APEX builds)
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
96 extract_items(
97 input_zip=OPTIONS.framework_dexpreopt_tools,
98 output_dir=dexpreopt_tools_files_temp_dir,
99 extract_item_list=('*',))
100 extract_items(
101 input_zip=OPTIONS.framework_dexpreopt_config,
102 output_dir=dexpreopt_framework_config_files_temp_dir,
103 extract_item_list=('*',))
104 extract_items(
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
116 # The directory structure for flatteded APEXes is:
117 #
118 # SYSTEM
119 # apex
120 # <APEX name, e.g., com.android.wifi>
121 # apex_manifest.pb
122 # apex_pubkey
123 # etc/
124 # javalib/
125 # lib/
126 # lib64/
127 # priv-app/
128 #
129 # The directory structure for updatable APEXes is:
130 #
131 # SYSTEM
132 # apex
133 # com.android.adbd.apex
134 # com.android.appsearch.apex
135 # com.android.art.apex
136 # ...
137 apex_root = os.path.join(output_target_files_dir, 'SYSTEM', 'apex')
138
139 # Check for flattended versus updatable APEX.
140 if OPTIONS.framework_misc_info.get('target_flatten_apex') == 'false':
141 # Extract APEX.
142 logging.info('extracting APEX')
143
144 apex_extract_root_dir = os.path.join(temp_dir, 'apex')
145 os.makedirs(apex_extract_root_dir)
146
147 for apex in (glob.glob(os.path.join(apex_root, '*.apex')) +
148 glob.glob(os.path.join(apex_root, '*.capex'))):
149 logging.info(' apex: %s', apex)
150 # deapexer is in the same directory as the merge_target_files binary extracted
151 # from otatools.zip.
152 apex_json_info = subprocess.check_output(['deapexer', 'info', apex])
153 logging.info(' info: %s', apex_json_info)
154 apex_info = json.loads(apex_json_info)
155 apex_name = apex_info['name']
156 logging.info(' name: %s', apex_name)
157
158 apex_extract_dir = os.path.join(apex_extract_root_dir, apex_name)
159 os.makedirs(apex_extract_dir)
160
161 # deapexer uses debugfs_static, which is part of otatools.zip.
162 command = [
163 'deapexer',
164 '--debugfs_path',
165 'debugfs_static',
166 'extract',
167 apex,
168 apex_extract_dir,
169 ]
170 logging.info(' running %s', command)
171 subprocess.check_call(command)
172 else:
173 # Flattened APEXes don't need to be extracted since they have the necessary
174 # directory structure.
175 os.symlink(os.path.join(apex_root), os.path.join(temp_dir, 'apex'))
176
177 # Modify system config to point to the tools that have been extracted.
178 # Absolute or .. paths are not allowed by the dexpreopt_gen tool in
179 # dexpreopt_soong.config.
180 dexpreopt_framework_soon_config = os.path.join(
181 dexpreopt_framework_config_files_temp_dir, 'dexpreopt_soong.config')
182 with open(dexpreopt_framework_soon_config, 'w') as f:
183 dexpreopt_soong_config = {
184 'Profman': 'tools/profman',
185 'Dex2oat': 'tools/dex2oatd',
186 'Aapt': 'tools/aapt2',
187 'SoongZip': 'tools/soong_zip',
188 'Zip2zip': 'tools/zip2zip',
189 'ManifestCheck': 'tools/manifest_check',
190 'ConstructContext': 'tools/construct_context',
191 }
192 json.dump(dexpreopt_soong_config, f)
193
194 # TODO(b/188179859): Make *dex location configurable to vendor or system_other.
195 use_system_other_odex = False
196
197 if use_system_other_odex:
198 dex_img = 'SYSTEM_OTHER'
199 else:
200 dex_img = 'VENDOR'
201 # Open vendor_filesystem_config to append the items generated by dexopt.
202 vendor_file_system_config = open(
203 os.path.join(temp_dir, 'output', 'META',
204 'vendor_filesystem_config.txt'), 'a')
205
206 # Dexpreopt vendor apps.
207 dexpreopt_config_suffix = '_dexpreopt.config'
208 for config in glob.glob(
209 os.path.join(dexpreopt_vendor_config_files_temp_dir,
210 '*' + dexpreopt_config_suffix)):
211 app = os.path.basename(config)[:-len(dexpreopt_config_suffix)]
212 logging.info('dexpreopt config: %s %s', config, app)
213
214 apk_dir = 'app'
215 apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
216 if not os.path.exists(apk_path):
217 apk_dir = 'priv-app'
218 apk_path = os.path.join(temp_dir, 'vendor', apk_dir, app, app + '.apk')
219 if not os.path.exists(apk_path):
220 logging.warning(
221 'skipping dexpreopt for %s, no apk found in vendor/app '
222 'or vendor/priv-app', app)
223 continue
224
225 # Generate dexpreopting script. Note 'out_dir' is not the output directory
226 # where the script is generated, but the OUT_DIR at build time referenced
227 # in the dexpreot config files, e.g., "out/.../core-oj.jar", so the tool knows
228 # how to adjust the path.
229 command = [
230 os.path.join(dexpreopt_tools_files_temp_dir, 'dexpreopt_gen'),
231 '-global',
232 os.path.join(dexpreopt_framework_config_files_temp_dir,
233 'dexpreopt.config'),
234 '-global_soong',
235 os.path.join(dexpreopt_framework_config_files_temp_dir,
236 'dexpreopt_soong.config'),
237 '-module',
238 config,
239 '-dexpreopt_script',
240 'dexpreopt_app.sh',
241 '-out_dir',
242 'out',
243 '-base_path',
244 '.',
245 '--uses_target_files',
246 ]
247
248 # Run the command from temp_dir so all tool paths are its descendants.
249 logging.info('running %s', command)
250 subprocess.check_call(command, cwd=temp_dir)
251
252 # Call the generated script.
253 command = ['sh', 'dexpreopt_app.sh', apk_path]
254 logging.info('running %s', command)
255 subprocess.check_call(command, cwd=temp_dir)
256
257 # Output files are in:
258 #
259 # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.vdex
260 # <temp_dir>/out/dex2oat_result/vendor/priv-app/<app>/oat/arm64/package.odex
261 # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.vdex
262 # <temp_dir>/out/dex2oat_result/vendor/app/<app>/oat/arm64/package.odex
263 #
264 # Copy the files to their destination. The structure of system_other is:
265 #
266 # system_other/
267 # system-other-odex-marker
268 # system/
269 # app/
270 # <app>/oat/arm64/
271 # <app>.odex
272 # <app>.vdex
273 # ...
274 # priv-app/
275 # <app>/oat/arm64/
276 # <app>.odex
277 # <app>.vdex
278 # ...
279
280 # TODO(b/188179859): Support for other architectures.
281 arch = 'arm64'
282
283 dex_destination = os.path.join(temp_dir, 'output', dex_img, apk_dir, app,
284 'oat', arch)
285 os.makedirs(dex_destination)
286 dex2oat_path = os.path.join(temp_dir, 'out', 'dex2oat_result', 'vendor',
287 apk_dir, app, 'oat', arch)
288 shutil.copy(
289 os.path.join(dex2oat_path, 'package.vdex'),
290 os.path.join(dex_destination, app + '.vdex'))
291 shutil.copy(
292 os.path.join(dex2oat_path, 'package.odex'),
293 os.path.join(dex_destination, app + '.odex'))
294
295 # Append entries to vendor_file_system_config.txt, such as:
296 #
297 # vendor/app/<app>/oat 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
298 # vendor/app/<app>/oat/arm64 0 2000 755 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
299 # vendor/app/<app>/oat/arm64/<app>.odex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
300 # vendor/app/<app>/oat/arm64/<app>.vdex 0 0 644 selabel=u:object_r:vendor_app_file:s0 capabilities=0x0
301 if not use_system_other_odex:
302 vendor_app_prefix = 'vendor/' + apk_dir + '/' + app + '/oat'
303 selabel = 'selabel=u:object_r:vendor_app_file:s0 capabilities=0x0'
304 vendor_file_system_config.writelines([
305 vendor_app_prefix + ' 0 2000 755 ' + selabel + '\n',
306 vendor_app_prefix + '/' + arch + ' 0 2000 755 ' + selabel + '\n',
307 vendor_app_prefix + '/' + arch + '/' + app + '.odex 0 0 644 ' +
308 selabel + '\n',
309 vendor_app_prefix + '/' + arch + '/' + app + '.vdex 0 0 644 ' +
310 selabel + '\n',
311 ])
312
313 if not use_system_other_odex:
314 vendor_file_system_config.close()
315 # Delete vendor.img so that it will be regenerated.
316 # TODO(b/188179859): Rebuilding a vendor image in GRF mode (e.g., T(framework)
317 # and S(vendor) may require logic similar to that in
318 # rebuild_image_with_sepolicy.
319 vendor_img = os.path.join(output_target_files_dir, 'IMAGES', 'vendor.img')
320 if os.path.exists(vendor_img):
321 logging.info('Deleting %s', vendor_img)
322 os.remove(vendor_img)