blob: ac3a5ebb7654acca0e9ea569182696e149e802e3 [file] [log] [blame]
Yifan Honge3ba82c2019-08-21 13:29:30 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of 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,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Check VINTF compatibility from a target files package.
19
20Usage: check_target_files_vintf target_files
21
22target_files can be a ZIP file or an extracted target files directory.
23"""
24
Rob Seymour3f1c9572022-07-28 21:56:28 +000025import json
Yifan Honge3ba82c2019-08-21 13:29:30 -070026import logging
27import subprocess
28import sys
29import os
30import zipfile
31
32import common
33
34logger = logging.getLogger(__name__)
35
36OPTIONS = common.OPTIONS
37
38# Keys are paths that VINTF searches. Must keep in sync with libvintf's search
39# paths (VintfObject.cpp).
40# These paths are stored in different directories in target files package, so
41# we have to search for the correct path and tell checkvintf to remap them.
Yifan Hong2870d1e2019-12-19 13:58:00 -080042# Look for TARGET_COPY_OUT_* variables in board_config.mk for possible paths for
43# each partition.
Yifan Honge3ba82c2019-08-21 13:29:30 -070044DIR_SEARCH_PATHS = {
45 '/system': ('SYSTEM',),
46 '/vendor': ('VENDOR', 'SYSTEM/vendor'),
47 '/product': ('PRODUCT', 'SYSTEM/product'),
Yifan Hong2870d1e2019-12-19 13:58:00 -080048 '/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'),
Yifan Hong9cbb6242019-12-19 13:56:59 -080049 '/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'),
Ramji Jiyani13a41372022-01-27 07:05:08 +000050 # vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
Yifan Honge3ba82c2019-08-21 13:29:30 -070051}
52
53UNZIP_PATTERN = ['META/*', '*/build.prop']
54
55
56def GetDirmap(input_tmp):
57 dirmap = {}
58 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
59 for target_files_rel_path in target_files_rel_paths:
60 target_files_path = os.path.join(input_tmp, target_files_rel_path)
61 if os.path.isdir(target_files_path):
62 dirmap[device_path] = target_files_path
63 break
64 if device_path not in dirmap:
65 raise ValueError("Can't determine path for device path " + device_path +
66 ". Searched the following:" +
67 ("\n".join(target_files_rel_paths)))
68 return dirmap
69
70
71def GetArgsForSkus(info_dict):
Yifan Hong28ffd732020-03-13 13:11:10 -070072 odm_skus = info_dict.get('vintf_odm_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070073 if info_dict.get('vintf_include_empty_odm_sku', '') == "true" or not odm_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070074 odm_skus += ['']
Yifan Honge3ba82c2019-08-21 13:29:30 -070075
Yifan Hong28ffd732020-03-13 13:11:10 -070076 vendor_skus = info_dict.get('vintf_vendor_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070077 if info_dict.get('vintf_include_empty_vendor_sku', '') == "true" or \
78 not vendor_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070079 vendor_skus += ['']
80
81 return [['--property', 'ro.boot.product.hardware.sku=' + odm_sku,
82 '--property', 'ro.boot.product.vendor.sku=' + vendor_sku]
83 for odm_sku in odm_skus for vendor_sku in vendor_skus]
Yifan Honge3ba82c2019-08-21 13:29:30 -070084
Tianjie Xu0fde41e2020-05-09 05:24:18 +000085
Yifan Honge3ba82c2019-08-21 13:29:30 -070086def GetArgsForShippingApiLevel(info_dict):
Tianjie Xu0fde41e2020-05-09 05:24:18 +000087 shipping_api_level = info_dict['vendor.build.prop'].GetProp(
Yifan Honge3ba82c2019-08-21 13:29:30 -070088 'ro.product.first_api_level')
89 if not shipping_api_level:
90 logger.warning('Cannot determine ro.product.first_api_level')
91 return []
92 return ['--property', 'ro.product.first_api_level=' + shipping_api_level]
93
94
95def GetArgsForKernel(input_tmp):
96 version_path = os.path.join(input_tmp, 'META/kernel_version.txt')
97 config_path = os.path.join(input_tmp, 'META/kernel_configs.txt')
98
99 if not os.path.isfile(version_path) or not os.path.isfile(config_path):
Yifan Hong28ffd732020-03-13 13:11:10 -0700100 logger.info('Skipping kernel config checks because '
Yifan Honge3ba82c2019-08-21 13:29:30 -0700101 'PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is not set')
102 return []
103
Yifan Hong72e78f22020-11-13 18:30:06 -0800104 return ['--kernel', '{}:{}'.format(version_path, config_path)]
Yifan Honge3ba82c2019-08-21 13:29:30 -0700105
106
107def CheckVintfFromExtractedTargetFiles(input_tmp, info_dict=None):
108 """
109 Checks VINTF metadata of an extracted target files directory.
110
111 Args:
112 inp: path to the directory that contains the extracted target files archive.
113 info_dict: The build-time info dict. If None, it will be loaded from inp.
114
115 Returns:
116 True if VINTF check is skipped or compatible, False if incompatible. Raise
117 a RuntimeError if any error occurs.
118 """
119
120 if info_dict is None:
121 info_dict = common.LoadInfoDict(input_tmp)
122
123 if info_dict.get('vintf_enforce') != 'true':
124 logger.warning('PRODUCT_ENFORCE_VINTF_MANIFEST is not set, skipping checks')
125 return True
126
Rob Seymour3f1c9572022-07-28 21:56:28 +0000127
Yifan Honge3ba82c2019-08-21 13:29:30 -0700128 dirmap = GetDirmap(input_tmp)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000129
130 apex_root, apex_info_file = PrepareApexDirectory(input_tmp)
131 dirmap['/apex'] = apex_root
132
Yifan Honge3ba82c2019-08-21 13:29:30 -0700133 args_for_skus = GetArgsForSkus(info_dict)
134 shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
135 kernel_args = GetArgsForKernel(input_tmp)
136
137 common_command = [
138 'checkvintf',
139 '--check-compat',
140 ]
Rob Seymour3f1c9572022-07-28 21:56:28 +0000141 common_command += ['--apex-info-file', apex_info_file]
142
Cole Faustb820bcd2021-10-28 13:59:48 -0700143 for device_path, real_path in sorted(dirmap.items()):
Yifan Honge3ba82c2019-08-21 13:29:30 -0700144 common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
145 common_command += kernel_args
146 common_command += shipping_api_level_args
147
148 success = True
149 for sku_args in args_for_skus:
150 command = common_command + sku_args
151 proc = common.Run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
152 out, err = proc.communicate()
153 if proc.returncode == 0:
154 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
155 elif out.strip() == "INCOMPATIBLE":
156 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
157 success = False
158 else:
159 raise common.ExternalError(
160 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
161 .format(' '.join(command), proc.returncode, out, err))
162 logger.info("stdout: %s", out)
163 logger.info("stderr: %s", err)
164
165 return success
166
167
168def GetVintfFileList():
169 """
170 Returns a list of VINTF metadata files that should be read from a target files
171 package before executing checkvintf.
172 """
173 def PathToPatterns(path):
174 if path[-1] == '/':
Christian Oder83bfe5a2021-11-11 21:02:34 +0100175 path += '**'
Cole Faustb820bcd2021-10-28 13:59:48 -0700176
177 # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key
178 # is a prefix of path. In order to get find the correct prefix, sort the
179 # entries by decreasing length of their keys, so that we check if longer
180 # strings are prefixes before shorter strings. This is so that keys that
181 # are substrings of other keys (like /system vs /system_ext) are checked
182 # later, and we don't mistakenly mark a path that starts with /system_ext
183 # as starting with only /system.
184 for device_path, target_files_rel_paths in sorted(DIR_SEARCH_PATHS.items(), key=lambda i: len(i[0]), reverse=True):
Yifan Honge3ba82c2019-08-21 13:29:30 -0700185 if path.startswith(device_path):
186 suffix = path[len(device_path):]
187 return [rel_path + suffix for rel_path in target_files_rel_paths]
188 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
189 path)
190
191 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
192 paths = out.strip().split('\n')
193 paths = sum((PathToPatterns(path) for path in paths if path), [])
194 return paths
195
Rob Seymour3f1c9572022-07-28 21:56:28 +0000196def GetVintfApexUnzipPatterns():
197 """ Build unzip pattern for APEXes. """
198 patterns = []
199 for target_files_rel_paths in DIR_SEARCH_PATHS.values():
200 for target_files_rel_path in target_files_rel_paths:
201 patterns.append(os.path.join(target_files_rel_path,"apex/*"))
202
203 return patterns
204
205def PrepareApexDirectory(inp):
206 """ Prepare the APEX data.
207
208 Apex binaries do not support dirmaps, in order to use these binaries we
209 need to move the APEXes from the extracted target file archives to the
210 expected device locations.
211
212 The APEXes will also be extracted under the APEX/ directory
213 matching what would be on the target.
214
215 Create the following structure under the input inp directory:
216 APEX/apex # Extracted APEXes
217 APEX/system/apex/ # System APEXes
218 APEX/vendor/apex/ # Vendor APEXes
219 ...
220
221 Args:
222 inp: path to the directory that contains the extracted target files archive.
223
224 Returns:
225 extracted apex directory
226 apex-info-list.xml file
227 """
228
229 def ExtractApexes(path, outp):
230 # Extract all APEXes found in input path.
231 debugfs_path = 'debugfs'
232 deapexer = 'deapexer'
233 if OPTIONS.search_path:
234 debugfs_path = os.path.join(OPTIONS.search_path, 'bin', 'debugfs_static')
235 deapexer_path = os.path.join(OPTIONS.search_path, 'bin', 'deapexer')
236 if os.path.isfile(deapexer_path):
237 deapexer = deapexer_path
238
239 logger.info('Extracting APEXs in %s', path)
240 for f in os.listdir(path):
241 logger.info(' adding APEX %s', os.path.basename(f))
242 apex = os.path.join(path, f)
243 cmd = [deapexer,
244 '--debugfs_path', debugfs_path,
245 'info',
246 apex]
247 info = json.loads(common.RunAndCheckOutput(cmd))
248
249 cmd = [deapexer,
250 '--debugfs_path', debugfs_path,
251 'extract',
252 apex,
253 os.path.join(outp, info['name'])]
254 common.RunAndCheckOutput(cmd)
255
256 root_dir_name = 'APEX'
257 root_dir = os.path.join(inp, root_dir_name)
258 extracted_root = os.path.join(root_dir, 'apex')
259 apex_info_file = os.path.join(extracted_root, 'apex-info-list.xml')
260
261 # Always create APEX directory for dirmap
262 os.makedirs(extracted_root)
263
264 create_info_file = False
265
266 # Loop through search path looking for and processing apex/ directories.
267 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
268 for target_files_rel_path in target_files_rel_paths:
269 inp_partition = os.path.join(inp, target_files_rel_path,"apex")
270 if os.path.exists(inp_partition):
271 apex_dir = root_dir + os.path.join(device_path + "/apex");
272 os.makedirs(apex_dir)
273 os.rename(inp_partition, apex_dir)
274 ExtractApexes(apex_dir, extracted_root)
275 create_info_file = True
276
277 if create_info_file:
278 ### Create apex-info-list.xml
279 dump_cmd = ['dump_apex_info',
280 '--root_dir', root_dir,
281 '--out_file', apex_info_file]
282 common.RunAndCheckOutput(dump_cmd)
283 if not os.path.exists(apex_info_file):
284 raise RuntimeError('Failed to create apex info file %s', apex_info_file)
285 logger.info('Created %s', apex_info_file)
286
287 return extracted_root, apex_info_file
Yifan Honge3ba82c2019-08-21 13:29:30 -0700288
289def CheckVintfFromTargetFiles(inp, info_dict=None):
290 """
291 Checks VINTF metadata of a target files zip.
292
293 Args:
294 inp: path to the target files archive.
295 info_dict: The build-time info dict. If None, it will be loaded from inp.
296
297 Returns:
298 True if VINTF check is skipped or compatible, False if incompatible. Raise
299 a RuntimeError if any error occurs.
300 """
Rob Seymour3f1c9572022-07-28 21:56:28 +0000301 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
Yifan Honge3ba82c2019-08-21 13:29:30 -0700302 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
303
304
305def CheckVintf(inp, info_dict=None):
306 """
307 Checks VINTF metadata of a target files zip or extracted target files
308 directory.
309
310 Args:
311 inp: path to the (possibly extracted) target files archive.
312 info_dict: The build-time info dict. If None, it will be loaded from inp.
313
314 Returns:
315 True if VINTF check is skipped or compatible, False if incompatible. Raise
316 a RuntimeError if any error occurs.
317 """
318 if os.path.isdir(inp):
319 logger.info('Checking VINTF compatibility extracted target files...')
320 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
321
322 if zipfile.is_zipfile(inp):
323 logger.info('Checking VINTF compatibility target files...')
324 return CheckVintfFromTargetFiles(inp, info_dict)
325
326 raise ValueError('{} is not a valid directory or zip file'.format(inp))
327
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400328def CheckVintfIfTrebleEnabled(target_files, target_info):
329 """Checks compatibility info of the input target files.
330
331 Metadata used for compatibility verification is retrieved from target_zip.
332
333 Compatibility should only be checked for devices that have enabled
334 Treble support.
335
336 Args:
337 target_files: Path to zip file containing the source files to be included
338 for OTA. Can also be the path to extracted directory.
339 target_info: The BuildInfo instance that holds the target build info.
340 """
341
342 # Will only proceed if the target has enabled the Treble support (as well as
343 # having a /vendor partition).
344 if not HasTrebleEnabled(target_files, target_info):
345 return
346
347 # Skip adding the compatibility package as a workaround for b/114240221. The
348 # compatibility will always fail on devices without qualified kernels.
349 if OPTIONS.skip_compatibility_check:
350 return
351
352 if not CheckVintf(target_files, target_info):
353 raise RuntimeError("VINTF compatibility check failed")
354
355def HasTrebleEnabled(target_files, target_info):
356 def HasVendorPartition(target_files):
357 if os.path.isdir(target_files):
358 return os.path.isdir(os.path.join(target_files, "VENDOR"))
359 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400360 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400361 raise ValueError("Unknown target_files argument")
362
363 return (HasVendorPartition(target_files) and
364 target_info.GetBuildProp("ro.treble.enabled") == "true")
365
366
367def HasPartition(target_files_zip, partition):
368 try:
369 target_files_zip.getinfo(partition.upper() + "/")
370 return True
371 except KeyError:
372 return False
373
Yifan Honge3ba82c2019-08-21 13:29:30 -0700374
375def main(argv):
376 args = common.ParseOptions(argv, __doc__)
377 if len(args) != 1:
378 common.Usage(__doc__)
379 sys.exit(1)
380 common.InitLogging()
381 if not CheckVintf(args[0]):
382 sys.exit(1)
383
384
385if __name__ == '__main__':
386 try:
387 common.CloseInheritedPipes()
388 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700389 finally:
390 common.Cleanup()