blob: e10291d1ee97f9ee8126326a0f25b58e9e72dcc3 [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()
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000153 last_out_line = out.split()[-1] if out != "" else out
Yifan Honge3ba82c2019-08-21 13:29:30 -0700154 if proc.returncode == 0:
155 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000156 elif last_out_line.strip() == "INCOMPATIBLE":
Yifan Honge3ba82c2019-08-21 13:29:30 -0700157 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
158 success = False
159 else:
160 raise common.ExternalError(
161 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
162 .format(' '.join(command), proc.returncode, out, err))
163 logger.info("stdout: %s", out)
164 logger.info("stderr: %s", err)
165
166 return success
167
168
169def GetVintfFileList():
170 """
171 Returns a list of VINTF metadata files that should be read from a target files
172 package before executing checkvintf.
173 """
174 def PathToPatterns(path):
175 if path[-1] == '/':
Christian Oder83bfe5a2021-11-11 21:02:34 +0100176 path += '**'
Cole Faustb820bcd2021-10-28 13:59:48 -0700177
178 # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key
179 # is a prefix of path. In order to get find the correct prefix, sort the
180 # entries by decreasing length of their keys, so that we check if longer
181 # strings are prefixes before shorter strings. This is so that keys that
182 # are substrings of other keys (like /system vs /system_ext) are checked
183 # later, and we don't mistakenly mark a path that starts with /system_ext
184 # as starting with only /system.
185 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 -0700186 if path.startswith(device_path):
187 suffix = path[len(device_path):]
188 return [rel_path + suffix for rel_path in target_files_rel_paths]
189 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
190 path)
191
192 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
193 paths = out.strip().split('\n')
194 paths = sum((PathToPatterns(path) for path in paths if path), [])
195 return paths
196
Rob Seymour3f1c9572022-07-28 21:56:28 +0000197def GetVintfApexUnzipPatterns():
198 """ Build unzip pattern for APEXes. """
199 patterns = []
200 for target_files_rel_paths in DIR_SEARCH_PATHS.values():
201 for target_files_rel_path in target_files_rel_paths:
202 patterns.append(os.path.join(target_files_rel_path,"apex/*"))
203
204 return patterns
205
206def PrepareApexDirectory(inp):
207 """ Prepare the APEX data.
208
209 Apex binaries do not support dirmaps, in order to use these binaries we
210 need to move the APEXes from the extracted target file archives to the
211 expected device locations.
212
213 The APEXes will also be extracted under the APEX/ directory
214 matching what would be on the target.
215
216 Create the following structure under the input inp directory:
217 APEX/apex # Extracted APEXes
218 APEX/system/apex/ # System APEXes
219 APEX/vendor/apex/ # Vendor APEXes
220 ...
221
222 Args:
223 inp: path to the directory that contains the extracted target files archive.
224
225 Returns:
226 extracted apex directory
227 apex-info-list.xml file
228 """
229
230 def ExtractApexes(path, outp):
231 # Extract all APEXes found in input path.
232 debugfs_path = 'debugfs'
233 deapexer = 'deapexer'
234 if OPTIONS.search_path:
235 debugfs_path = os.path.join(OPTIONS.search_path, 'bin', 'debugfs_static')
236 deapexer_path = os.path.join(OPTIONS.search_path, 'bin', 'deapexer')
237 if os.path.isfile(deapexer_path):
238 deapexer = deapexer_path
239
240 logger.info('Extracting APEXs in %s', path)
241 for f in os.listdir(path):
242 logger.info(' adding APEX %s', os.path.basename(f))
243 apex = os.path.join(path, f)
244 cmd = [deapexer,
245 '--debugfs_path', debugfs_path,
246 'info',
247 apex]
248 info = json.loads(common.RunAndCheckOutput(cmd))
249
250 cmd = [deapexer,
251 '--debugfs_path', debugfs_path,
252 'extract',
253 apex,
254 os.path.join(outp, info['name'])]
255 common.RunAndCheckOutput(cmd)
256
257 root_dir_name = 'APEX'
258 root_dir = os.path.join(inp, root_dir_name)
259 extracted_root = os.path.join(root_dir, 'apex')
260 apex_info_file = os.path.join(extracted_root, 'apex-info-list.xml')
261
262 # Always create APEX directory for dirmap
263 os.makedirs(extracted_root)
264
265 create_info_file = False
266
267 # Loop through search path looking for and processing apex/ directories.
268 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
269 for target_files_rel_path in target_files_rel_paths:
270 inp_partition = os.path.join(inp, target_files_rel_path,"apex")
271 if os.path.exists(inp_partition):
272 apex_dir = root_dir + os.path.join(device_path + "/apex");
273 os.makedirs(apex_dir)
274 os.rename(inp_partition, apex_dir)
275 ExtractApexes(apex_dir, extracted_root)
276 create_info_file = True
277
278 if create_info_file:
279 ### Create apex-info-list.xml
280 dump_cmd = ['dump_apex_info',
281 '--root_dir', root_dir,
282 '--out_file', apex_info_file]
283 common.RunAndCheckOutput(dump_cmd)
284 if not os.path.exists(apex_info_file):
285 raise RuntimeError('Failed to create apex info file %s', apex_info_file)
286 logger.info('Created %s', apex_info_file)
287
288 return extracted_root, apex_info_file
Yifan Honge3ba82c2019-08-21 13:29:30 -0700289
290def CheckVintfFromTargetFiles(inp, info_dict=None):
291 """
292 Checks VINTF metadata of a target files zip.
293
294 Args:
295 inp: path to the target files archive.
296 info_dict: The build-time info dict. If None, it will be loaded from inp.
297
298 Returns:
299 True if VINTF check is skipped or compatible, False if incompatible. Raise
300 a RuntimeError if any error occurs.
301 """
Rob Seymour3f1c9572022-07-28 21:56:28 +0000302 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
Yifan Honge3ba82c2019-08-21 13:29:30 -0700303 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
304
305
306def CheckVintf(inp, info_dict=None):
307 """
308 Checks VINTF metadata of a target files zip or extracted target files
309 directory.
310
311 Args:
312 inp: path to the (possibly extracted) target files archive.
313 info_dict: The build-time info dict. If None, it will be loaded from inp.
314
315 Returns:
316 True if VINTF check is skipped or compatible, False if incompatible. Raise
317 a RuntimeError if any error occurs.
318 """
319 if os.path.isdir(inp):
320 logger.info('Checking VINTF compatibility extracted target files...')
321 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
322
323 if zipfile.is_zipfile(inp):
324 logger.info('Checking VINTF compatibility target files...')
325 return CheckVintfFromTargetFiles(inp, info_dict)
326
327 raise ValueError('{} is not a valid directory or zip file'.format(inp))
328
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400329def CheckVintfIfTrebleEnabled(target_files, target_info):
330 """Checks compatibility info of the input target files.
331
332 Metadata used for compatibility verification is retrieved from target_zip.
333
334 Compatibility should only be checked for devices that have enabled
335 Treble support.
336
337 Args:
338 target_files: Path to zip file containing the source files to be included
339 for OTA. Can also be the path to extracted directory.
340 target_info: The BuildInfo instance that holds the target build info.
341 """
342
343 # Will only proceed if the target has enabled the Treble support (as well as
344 # having a /vendor partition).
345 if not HasTrebleEnabled(target_files, target_info):
346 return
347
348 # Skip adding the compatibility package as a workaround for b/114240221. The
349 # compatibility will always fail on devices without qualified kernels.
350 if OPTIONS.skip_compatibility_check:
351 return
352
353 if not CheckVintf(target_files, target_info):
354 raise RuntimeError("VINTF compatibility check failed")
355
356def HasTrebleEnabled(target_files, target_info):
357 def HasVendorPartition(target_files):
358 if os.path.isdir(target_files):
359 return os.path.isdir(os.path.join(target_files, "VENDOR"))
360 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400361 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400362 raise ValueError("Unknown target_files argument")
363
364 return (HasVendorPartition(target_files) and
365 target_info.GetBuildProp("ro.treble.enabled") == "true")
366
367
368def HasPartition(target_files_zip, partition):
369 try:
370 target_files_zip.getinfo(partition.upper() + "/")
371 return True
372 except KeyError:
373 return False
374
Yifan Honge3ba82c2019-08-21 13:29:30 -0700375
376def main(argv):
377 args = common.ParseOptions(argv, __doc__)
378 if len(args) != 1:
379 common.Usage(__doc__)
380 sys.exit(1)
381 common.InitLogging()
382 if not CheckVintf(args[0]):
383 sys.exit(1)
384
385
386if __name__ == '__main__':
387 try:
388 common.CloseInheritedPipes()
389 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700390 finally:
391 common.Cleanup()