blob: b32b85c103fc4b4f0dad0a8e11514bca96d2ab27 [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
Jooyung Haneb118212022-10-17 18:07:28 +090027import os
28import shutil
Yifan Honge3ba82c2019-08-21 13:29:30 -070029import subprocess
30import sys
Yifan Honge3ba82c2019-08-21 13:29:30 -070031import zipfile
32
33import common
Jooyung Haneb118212022-10-17 18:07:28 +090034from apex_manifest import ParseApexManifest
Yifan Honge3ba82c2019-08-21 13:29:30 -070035
36logger = logging.getLogger(__name__)
37
38OPTIONS = common.OPTIONS
39
40# Keys are paths that VINTF searches. Must keep in sync with libvintf's search
41# paths (VintfObject.cpp).
42# These paths are stored in different directories in target files package, so
43# we have to search for the correct path and tell checkvintf to remap them.
Yifan Hong2870d1e2019-12-19 13:58:00 -080044# Look for TARGET_COPY_OUT_* variables in board_config.mk for possible paths for
45# each partition.
Yifan Honge3ba82c2019-08-21 13:29:30 -070046DIR_SEARCH_PATHS = {
47 '/system': ('SYSTEM',),
48 '/vendor': ('VENDOR', 'SYSTEM/vendor'),
49 '/product': ('PRODUCT', 'SYSTEM/product'),
Yifan Hong2870d1e2019-12-19 13:58:00 -080050 '/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'),
Yifan Hong9cbb6242019-12-19 13:56:59 -080051 '/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'),
Ramji Jiyani13a41372022-01-27 07:05:08 +000052 # vendor_dlkm, odm_dlkm, and system_dlkm does not have VINTF files.
Yifan Honge3ba82c2019-08-21 13:29:30 -070053}
54
55UNZIP_PATTERN = ['META/*', '*/build.prop']
56
57
58def GetDirmap(input_tmp):
59 dirmap = {}
60 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
61 for target_files_rel_path in target_files_rel_paths:
62 target_files_path = os.path.join(input_tmp, target_files_rel_path)
63 if os.path.isdir(target_files_path):
64 dirmap[device_path] = target_files_path
65 break
66 if device_path not in dirmap:
67 raise ValueError("Can't determine path for device path " + device_path +
68 ". Searched the following:" +
69 ("\n".join(target_files_rel_paths)))
70 return dirmap
71
72
73def GetArgsForSkus(info_dict):
Yifan Hong28ffd732020-03-13 13:11:10 -070074 odm_skus = info_dict.get('vintf_odm_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070075 if info_dict.get('vintf_include_empty_odm_sku', '') == "true" or not odm_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070076 odm_skus += ['']
Yifan Honge3ba82c2019-08-21 13:29:30 -070077
Yifan Hong28ffd732020-03-13 13:11:10 -070078 vendor_skus = info_dict.get('vintf_vendor_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070079 if info_dict.get('vintf_include_empty_vendor_sku', '') == "true" or \
80 not vendor_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070081 vendor_skus += ['']
82
83 return [['--property', 'ro.boot.product.hardware.sku=' + odm_sku,
84 '--property', 'ro.boot.product.vendor.sku=' + vendor_sku]
85 for odm_sku in odm_skus for vendor_sku in vendor_skus]
Yifan Honge3ba82c2019-08-21 13:29:30 -070086
Tianjie Xu0fde41e2020-05-09 05:24:18 +000087
Yifan Honge3ba82c2019-08-21 13:29:30 -070088def GetArgsForShippingApiLevel(info_dict):
Tianjie Xu0fde41e2020-05-09 05:24:18 +000089 shipping_api_level = info_dict['vendor.build.prop'].GetProp(
Yifan Honge3ba82c2019-08-21 13:29:30 -070090 'ro.product.first_api_level')
91 if not shipping_api_level:
92 logger.warning('Cannot determine ro.product.first_api_level')
93 return []
94 return ['--property', 'ro.product.first_api_level=' + shipping_api_level]
95
96
97def GetArgsForKernel(input_tmp):
98 version_path = os.path.join(input_tmp, 'META/kernel_version.txt')
99 config_path = os.path.join(input_tmp, 'META/kernel_configs.txt')
100
101 if not os.path.isfile(version_path) or not os.path.isfile(config_path):
Yifan Hong28ffd732020-03-13 13:11:10 -0700102 logger.info('Skipping kernel config checks because '
Yifan Honge3ba82c2019-08-21 13:29:30 -0700103 'PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is not set')
104 return []
105
Yifan Hong72e78f22020-11-13 18:30:06 -0800106 return ['--kernel', '{}:{}'.format(version_path, config_path)]
Yifan Honge3ba82c2019-08-21 13:29:30 -0700107
108
109def CheckVintfFromExtractedTargetFiles(input_tmp, info_dict=None):
110 """
111 Checks VINTF metadata of an extracted target files directory.
112
113 Args:
114 inp: path to the directory that contains the extracted target files archive.
115 info_dict: The build-time info dict. If None, it will be loaded from inp.
116
117 Returns:
118 True if VINTF check is skipped or compatible, False if incompatible. Raise
119 a RuntimeError if any error occurs.
120 """
121
122 if info_dict is None:
123 info_dict = common.LoadInfoDict(input_tmp)
124
125 if info_dict.get('vintf_enforce') != 'true':
126 logger.warning('PRODUCT_ENFORCE_VINTF_MANIFEST is not set, skipping checks')
127 return True
128
Rob Seymour3f1c9572022-07-28 21:56:28 +0000129
Yifan Honge3ba82c2019-08-21 13:29:30 -0700130 dirmap = GetDirmap(input_tmp)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000131
132 apex_root, apex_info_file = PrepareApexDirectory(input_tmp)
133 dirmap['/apex'] = apex_root
134
Yifan Honge3ba82c2019-08-21 13:29:30 -0700135 args_for_skus = GetArgsForSkus(info_dict)
136 shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
137 kernel_args = GetArgsForKernel(input_tmp)
138
139 common_command = [
140 'checkvintf',
141 '--check-compat',
142 ]
Rob Seymour3f1c9572022-07-28 21:56:28 +0000143 common_command += ['--apex-info-file', apex_info_file]
144
Cole Faustb820bcd2021-10-28 13:59:48 -0700145 for device_path, real_path in sorted(dirmap.items()):
Yifan Honge3ba82c2019-08-21 13:29:30 -0700146 common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
147 common_command += kernel_args
148 common_command += shipping_api_level_args
149
150 success = True
151 for sku_args in args_for_skus:
152 command = common_command + sku_args
153 proc = common.Run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
154 out, err = proc.communicate()
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000155 last_out_line = out.split()[-1] if out != "" else out
Yifan Honge3ba82c2019-08-21 13:29:30 -0700156 if proc.returncode == 0:
157 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000158 elif last_out_line.strip() == "INCOMPATIBLE":
Yifan Honge3ba82c2019-08-21 13:29:30 -0700159 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
160 success = False
161 else:
162 raise common.ExternalError(
163 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
164 .format(' '.join(command), proc.returncode, out, err))
165 logger.info("stdout: %s", out)
166 logger.info("stderr: %s", err)
167
168 return success
169
170
171def GetVintfFileList():
172 """
173 Returns a list of VINTF metadata files that should be read from a target files
174 package before executing checkvintf.
175 """
176 def PathToPatterns(path):
177 if path[-1] == '/':
Christian Oder83bfe5a2021-11-11 21:02:34 +0100178 path += '**'
Cole Faustb820bcd2021-10-28 13:59:48 -0700179
180 # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key
181 # is a prefix of path. In order to get find the correct prefix, sort the
182 # entries by decreasing length of their keys, so that we check if longer
183 # strings are prefixes before shorter strings. This is so that keys that
184 # are substrings of other keys (like /system vs /system_ext) are checked
185 # later, and we don't mistakenly mark a path that starts with /system_ext
186 # as starting with only /system.
187 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 -0700188 if path.startswith(device_path):
189 suffix = path[len(device_path):]
190 return [rel_path + suffix for rel_path in target_files_rel_paths]
191 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
192 path)
193
194 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
195 paths = out.strip().split('\n')
196 paths = sum((PathToPatterns(path) for path in paths if path), [])
197 return paths
198
Rob Seymour3f1c9572022-07-28 21:56:28 +0000199def GetVintfApexUnzipPatterns():
200 """ Build unzip pattern for APEXes. """
201 patterns = []
202 for target_files_rel_paths in DIR_SEARCH_PATHS.values():
203 for target_files_rel_path in target_files_rel_paths:
204 patterns.append(os.path.join(target_files_rel_path,"apex/*"))
205
206 return patterns
207
208def PrepareApexDirectory(inp):
209 """ Prepare the APEX data.
210
211 Apex binaries do not support dirmaps, in order to use these binaries we
212 need to move the APEXes from the extracted target file archives to the
213 expected device locations.
214
215 The APEXes will also be extracted under the APEX/ directory
216 matching what would be on the target.
217
218 Create the following structure under the input inp directory:
219 APEX/apex # Extracted APEXes
220 APEX/system/apex/ # System APEXes
221 APEX/vendor/apex/ # Vendor APEXes
222 ...
223
224 Args:
225 inp: path to the directory that contains the extracted target files archive.
226
227 Returns:
228 extracted apex directory
229 apex-info-list.xml file
230 """
231
Jooyung Haneb118212022-10-17 18:07:28 +0900232 deapexer = 'deapexer'
Dennis Shen44b714b2022-11-02 14:42:08 +0000233 debugfs_path = 'debugfs'
234 blkid_path = 'blkid'
235 fsckerofs_path = 'fsck.erofs'
Jooyung Haneb118212022-10-17 18:07:28 +0900236 if OPTIONS.search_path:
237 debugfs_path = os.path.join(OPTIONS.search_path, 'bin', 'debugfs_static')
238 deapexer_path = os.path.join(OPTIONS.search_path, 'bin', 'deapexer')
Dennis Shen44b714b2022-11-02 14:42:08 +0000239 blkid_path = os.path.join(OPTIONS.search_path, 'bin', 'blkid')
240 fsckerofs_path = os.path.join(OPTIONS.search_path, 'bin', 'fsck.erofs')
Jooyung Haneb118212022-10-17 18:07:28 +0900241 if os.path.isfile(deapexer_path):
242 deapexer = deapexer_path
243
Rob Seymour3f1c9572022-07-28 21:56:28 +0000244 def ExtractApexes(path, outp):
245 # Extract all APEXes found in input path.
Rob Seymour3f1c9572022-07-28 21:56:28 +0000246 logger.info('Extracting APEXs in %s', path)
247 for f in os.listdir(path):
248 logger.info(' adding APEX %s', os.path.basename(f))
249 apex = os.path.join(path, f)
Jooyung Haneb118212022-10-17 18:07:28 +0900250 if os.path.isdir(apex) and os.path.isfile(os.path.join(apex, 'apex_manifest.pb')):
251 info = ParseApexManifest(os.path.join(apex, 'apex_manifest.pb'))
252 # Flattened APEXes may have symlinks for libs (linked to /system/lib)
253 # We need to blindly copy them all.
254 shutil.copytree(apex, os.path.join(outp, info.name), symlinks=True)
255 elif os.path.isfile(apex) and apex.endswith(('.apex', '.capex')):
Jooyung Han62283b92022-10-17 10:24:09 +0900256 cmd = [deapexer,
257 '--debugfs_path', debugfs_path,
258 'info',
259 apex]
260 info = json.loads(common.RunAndCheckOutput(cmd))
Rob Seymour3f1c9572022-07-28 21:56:28 +0000261
Jooyung Han62283b92022-10-17 10:24:09 +0900262 cmd = [deapexer,
263 '--debugfs_path', debugfs_path,
Dennis Shen44b714b2022-11-02 14:42:08 +0000264 '--fsckerofs_path', fsckerofs_path,
265 '--blkid_path', blkid_path,
Jooyung Han62283b92022-10-17 10:24:09 +0900266 'extract',
267 apex,
268 os.path.join(outp, info['name'])]
269 common.RunAndCheckOutput(cmd)
Jooyung Haneb118212022-10-17 18:07:28 +0900270 else:
271 logger.info(' .. skipping %s (is it APEX?)', path)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000272
273 root_dir_name = 'APEX'
274 root_dir = os.path.join(inp, root_dir_name)
275 extracted_root = os.path.join(root_dir, 'apex')
276 apex_info_file = os.path.join(extracted_root, 'apex-info-list.xml')
277
278 # Always create APEX directory for dirmap
279 os.makedirs(extracted_root)
280
281 create_info_file = False
282
283 # Loop through search path looking for and processing apex/ directories.
284 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
285 for target_files_rel_path in target_files_rel_paths:
286 inp_partition = os.path.join(inp, target_files_rel_path,"apex")
287 if os.path.exists(inp_partition):
288 apex_dir = root_dir + os.path.join(device_path + "/apex");
Po Hu4d2f64b2022-10-26 02:33:03 +0000289 os.makedirs(root_dir + device_path)
290 shutil.copytree(inp_partition, apex_dir, symlinks=True)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000291 ExtractApexes(apex_dir, extracted_root)
292 create_info_file = True
293
294 if create_info_file:
295 ### Create apex-info-list.xml
296 dump_cmd = ['dump_apex_info',
297 '--root_dir', root_dir,
298 '--out_file', apex_info_file]
299 common.RunAndCheckOutput(dump_cmd)
300 if not os.path.exists(apex_info_file):
301 raise RuntimeError('Failed to create apex info file %s', apex_info_file)
302 logger.info('Created %s', apex_info_file)
303
304 return extracted_root, apex_info_file
Yifan Honge3ba82c2019-08-21 13:29:30 -0700305
306def CheckVintfFromTargetFiles(inp, info_dict=None):
307 """
308 Checks VINTF metadata of a target files zip.
309
310 Args:
311 inp: path to the 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 """
Rob Seymour3f1c9572022-07-28 21:56:28 +0000318 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
Yifan Honge3ba82c2019-08-21 13:29:30 -0700319 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
320
321
322def CheckVintf(inp, info_dict=None):
323 """
324 Checks VINTF metadata of a target files zip or extracted target files
325 directory.
326
327 Args:
328 inp: path to the (possibly extracted) target files archive.
329 info_dict: The build-time info dict. If None, it will be loaded from inp.
330
331 Returns:
332 True if VINTF check is skipped or compatible, False if incompatible. Raise
333 a RuntimeError if any error occurs.
334 """
335 if os.path.isdir(inp):
336 logger.info('Checking VINTF compatibility extracted target files...')
337 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
338
339 if zipfile.is_zipfile(inp):
340 logger.info('Checking VINTF compatibility target files...')
341 return CheckVintfFromTargetFiles(inp, info_dict)
342
343 raise ValueError('{} is not a valid directory or zip file'.format(inp))
344
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400345def CheckVintfIfTrebleEnabled(target_files, target_info):
346 """Checks compatibility info of the input target files.
347
348 Metadata used for compatibility verification is retrieved from target_zip.
349
350 Compatibility should only be checked for devices that have enabled
351 Treble support.
352
353 Args:
354 target_files: Path to zip file containing the source files to be included
355 for OTA. Can also be the path to extracted directory.
356 target_info: The BuildInfo instance that holds the target build info.
357 """
358
359 # Will only proceed if the target has enabled the Treble support (as well as
360 # having a /vendor partition).
361 if not HasTrebleEnabled(target_files, target_info):
362 return
363
364 # Skip adding the compatibility package as a workaround for b/114240221. The
365 # compatibility will always fail on devices without qualified kernels.
366 if OPTIONS.skip_compatibility_check:
367 return
368
369 if not CheckVintf(target_files, target_info):
370 raise RuntimeError("VINTF compatibility check failed")
371
372def HasTrebleEnabled(target_files, target_info):
373 def HasVendorPartition(target_files):
374 if os.path.isdir(target_files):
375 return os.path.isdir(os.path.join(target_files, "VENDOR"))
376 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400377 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400378 raise ValueError("Unknown target_files argument")
379
380 return (HasVendorPartition(target_files) and
381 target_info.GetBuildProp("ro.treble.enabled") == "true")
382
383
384def HasPartition(target_files_zip, partition):
385 try:
386 target_files_zip.getinfo(partition.upper() + "/")
387 return True
388 except KeyError:
389 return False
390
Yifan Honge3ba82c2019-08-21 13:29:30 -0700391
392def main(argv):
393 args = common.ParseOptions(argv, __doc__)
394 if len(args) != 1:
395 common.Usage(__doc__)
396 sys.exit(1)
397 common.InitLogging()
398 if not CheckVintf(args[0]):
399 sys.exit(1)
400
401
402if __name__ == '__main__':
403 try:
404 common.CloseInheritedPipes()
405 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700406 finally:
407 common.Cleanup()