blob: fa2eaebac123c9b77d81e38f6f67620373efdcdb [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)
Jooyung Han62283b92022-10-17 10:24:09 +0900244 if os.path.isdir(apex):
245 # TODO(b/242314000) Handle "flattened" apex
246 pass
247 else:
248 cmd = [deapexer,
249 '--debugfs_path', debugfs_path,
250 'info',
251 apex]
252 info = json.loads(common.RunAndCheckOutput(cmd))
Rob Seymour3f1c9572022-07-28 21:56:28 +0000253
Jooyung Han62283b92022-10-17 10:24:09 +0900254 cmd = [deapexer,
255 '--debugfs_path', debugfs_path,
256 'extract',
257 apex,
258 os.path.join(outp, info['name'])]
259 common.RunAndCheckOutput(cmd)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000260
261 root_dir_name = 'APEX'
262 root_dir = os.path.join(inp, root_dir_name)
263 extracted_root = os.path.join(root_dir, 'apex')
264 apex_info_file = os.path.join(extracted_root, 'apex-info-list.xml')
265
266 # Always create APEX directory for dirmap
267 os.makedirs(extracted_root)
268
269 create_info_file = False
270
271 # Loop through search path looking for and processing apex/ directories.
272 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
273 for target_files_rel_path in target_files_rel_paths:
274 inp_partition = os.path.join(inp, target_files_rel_path,"apex")
275 if os.path.exists(inp_partition):
276 apex_dir = root_dir + os.path.join(device_path + "/apex");
277 os.makedirs(apex_dir)
278 os.rename(inp_partition, apex_dir)
279 ExtractApexes(apex_dir, extracted_root)
280 create_info_file = True
281
282 if create_info_file:
283 ### Create apex-info-list.xml
284 dump_cmd = ['dump_apex_info',
285 '--root_dir', root_dir,
286 '--out_file', apex_info_file]
287 common.RunAndCheckOutput(dump_cmd)
288 if not os.path.exists(apex_info_file):
289 raise RuntimeError('Failed to create apex info file %s', apex_info_file)
290 logger.info('Created %s', apex_info_file)
291
292 return extracted_root, apex_info_file
Yifan Honge3ba82c2019-08-21 13:29:30 -0700293
294def CheckVintfFromTargetFiles(inp, info_dict=None):
295 """
296 Checks VINTF metadata of a target files zip.
297
298 Args:
299 inp: path to the target files archive.
300 info_dict: The build-time info dict. If None, it will be loaded from inp.
301
302 Returns:
303 True if VINTF check is skipped or compatible, False if incompatible. Raise
304 a RuntimeError if any error occurs.
305 """
Rob Seymour3f1c9572022-07-28 21:56:28 +0000306 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
Yifan Honge3ba82c2019-08-21 13:29:30 -0700307 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
308
309
310def CheckVintf(inp, info_dict=None):
311 """
312 Checks VINTF metadata of a target files zip or extracted target files
313 directory.
314
315 Args:
316 inp: path to the (possibly extracted) target files archive.
317 info_dict: The build-time info dict. If None, it will be loaded from inp.
318
319 Returns:
320 True if VINTF check is skipped or compatible, False if incompatible. Raise
321 a RuntimeError if any error occurs.
322 """
323 if os.path.isdir(inp):
324 logger.info('Checking VINTF compatibility extracted target files...')
325 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
326
327 if zipfile.is_zipfile(inp):
328 logger.info('Checking VINTF compatibility target files...')
329 return CheckVintfFromTargetFiles(inp, info_dict)
330
331 raise ValueError('{} is not a valid directory or zip file'.format(inp))
332
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400333def CheckVintfIfTrebleEnabled(target_files, target_info):
334 """Checks compatibility info of the input target files.
335
336 Metadata used for compatibility verification is retrieved from target_zip.
337
338 Compatibility should only be checked for devices that have enabled
339 Treble support.
340
341 Args:
342 target_files: Path to zip file containing the source files to be included
343 for OTA. Can also be the path to extracted directory.
344 target_info: The BuildInfo instance that holds the target build info.
345 """
346
347 # Will only proceed if the target has enabled the Treble support (as well as
348 # having a /vendor partition).
349 if not HasTrebleEnabled(target_files, target_info):
350 return
351
352 # Skip adding the compatibility package as a workaround for b/114240221. The
353 # compatibility will always fail on devices without qualified kernels.
354 if OPTIONS.skip_compatibility_check:
355 return
356
357 if not CheckVintf(target_files, target_info):
358 raise RuntimeError("VINTF compatibility check failed")
359
360def HasTrebleEnabled(target_files, target_info):
361 def HasVendorPartition(target_files):
362 if os.path.isdir(target_files):
363 return os.path.isdir(os.path.join(target_files, "VENDOR"))
364 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400365 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400366 raise ValueError("Unknown target_files argument")
367
368 return (HasVendorPartition(target_files) and
369 target_info.GetBuildProp("ro.treble.enabled") == "true")
370
371
372def HasPartition(target_files_zip, partition):
373 try:
374 target_files_zip.getinfo(partition.upper() + "/")
375 return True
376 except KeyError:
377 return False
378
Yifan Honge3ba82c2019-08-21 13:29:30 -0700379
380def main(argv):
381 args = common.ParseOptions(argv, __doc__)
382 if len(args) != 1:
383 common.Usage(__doc__)
384 sys.exit(1)
385 common.InitLogging()
386 if not CheckVintf(args[0]):
387 sys.exit(1)
388
389
390if __name__ == '__main__':
391 try:
392 common.CloseInheritedPipes()
393 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700394 finally:
395 common.Cleanup()