blob: c369a5961cc070cc72bf9faedbad1a7f134b7430 [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 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
Rob Seymour3f1c9572022-07-28 21:56:28 +0000240 def ExtractApexes(path, outp):
241 # Extract all APEXes found in input path.
Rob Seymour3f1c9572022-07-28 21:56:28 +0000242 logger.info('Extracting APEXs in %s', path)
243 for f in os.listdir(path):
244 logger.info(' adding APEX %s', os.path.basename(f))
245 apex = os.path.join(path, f)
Jooyung Haneb118212022-10-17 18:07:28 +0900246 if os.path.isdir(apex) and os.path.isfile(os.path.join(apex, 'apex_manifest.pb')):
247 info = ParseApexManifest(os.path.join(apex, 'apex_manifest.pb'))
248 # Flattened APEXes may have symlinks for libs (linked to /system/lib)
249 # We need to blindly copy them all.
250 shutil.copytree(apex, os.path.join(outp, info.name), symlinks=True)
251 elif os.path.isfile(apex) and apex.endswith(('.apex', '.capex')):
Jooyung Han62283b92022-10-17 10:24:09 +0900252 cmd = [deapexer,
253 '--debugfs_path', debugfs_path,
254 'info',
255 apex]
256 info = json.loads(common.RunAndCheckOutput(cmd))
Rob Seymour3f1c9572022-07-28 21:56:28 +0000257
Jooyung Han62283b92022-10-17 10:24:09 +0900258 cmd = [deapexer,
259 '--debugfs_path', debugfs_path,
260 'extract',
261 apex,
262 os.path.join(outp, info['name'])]
263 common.RunAndCheckOutput(cmd)
Jooyung Haneb118212022-10-17 18:07:28 +0900264 else:
265 logger.info(' .. skipping %s (is it APEX?)', path)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000266
267 root_dir_name = 'APEX'
268 root_dir = os.path.join(inp, root_dir_name)
269 extracted_root = os.path.join(root_dir, 'apex')
270 apex_info_file = os.path.join(extracted_root, 'apex-info-list.xml')
271
272 # Always create APEX directory for dirmap
273 os.makedirs(extracted_root)
274
275 create_info_file = False
276
277 # Loop through search path looking for and processing apex/ directories.
278 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
279 for target_files_rel_path in target_files_rel_paths:
280 inp_partition = os.path.join(inp, target_files_rel_path,"apex")
281 if os.path.exists(inp_partition):
282 apex_dir = root_dir + os.path.join(device_path + "/apex");
283 os.makedirs(apex_dir)
284 os.rename(inp_partition, apex_dir)
285 ExtractApexes(apex_dir, extracted_root)
286 create_info_file = True
287
288 if create_info_file:
289 ### Create apex-info-list.xml
290 dump_cmd = ['dump_apex_info',
291 '--root_dir', root_dir,
292 '--out_file', apex_info_file]
293 common.RunAndCheckOutput(dump_cmd)
294 if not os.path.exists(apex_info_file):
295 raise RuntimeError('Failed to create apex info file %s', apex_info_file)
296 logger.info('Created %s', apex_info_file)
297
298 return extracted_root, apex_info_file
Yifan Honge3ba82c2019-08-21 13:29:30 -0700299
300def CheckVintfFromTargetFiles(inp, info_dict=None):
301 """
302 Checks VINTF metadata of a target files zip.
303
304 Args:
305 inp: path to the target files archive.
306 info_dict: The build-time info dict. If None, it will be loaded from inp.
307
308 Returns:
309 True if VINTF check is skipped or compatible, False if incompatible. Raise
310 a RuntimeError if any error occurs.
311 """
Rob Seymour3f1c9572022-07-28 21:56:28 +0000312 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
Yifan Honge3ba82c2019-08-21 13:29:30 -0700313 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
314
315
316def CheckVintf(inp, info_dict=None):
317 """
318 Checks VINTF metadata of a target files zip or extracted target files
319 directory.
320
321 Args:
322 inp: path to the (possibly extracted) target files archive.
323 info_dict: The build-time info dict. If None, it will be loaded from inp.
324
325 Returns:
326 True if VINTF check is skipped or compatible, False if incompatible. Raise
327 a RuntimeError if any error occurs.
328 """
329 if os.path.isdir(inp):
330 logger.info('Checking VINTF compatibility extracted target files...')
331 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
332
333 if zipfile.is_zipfile(inp):
334 logger.info('Checking VINTF compatibility target files...')
335 return CheckVintfFromTargetFiles(inp, info_dict)
336
337 raise ValueError('{} is not a valid directory or zip file'.format(inp))
338
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400339def CheckVintfIfTrebleEnabled(target_files, target_info):
340 """Checks compatibility info of the input target files.
341
342 Metadata used for compatibility verification is retrieved from target_zip.
343
344 Compatibility should only be checked for devices that have enabled
345 Treble support.
346
347 Args:
348 target_files: Path to zip file containing the source files to be included
349 for OTA. Can also be the path to extracted directory.
350 target_info: The BuildInfo instance that holds the target build info.
351 """
352
353 # Will only proceed if the target has enabled the Treble support (as well as
354 # having a /vendor partition).
355 if not HasTrebleEnabled(target_files, target_info):
356 return
357
358 # Skip adding the compatibility package as a workaround for b/114240221. The
359 # compatibility will always fail on devices without qualified kernels.
360 if OPTIONS.skip_compatibility_check:
361 return
362
363 if not CheckVintf(target_files, target_info):
364 raise RuntimeError("VINTF compatibility check failed")
365
366def HasTrebleEnabled(target_files, target_info):
367 def HasVendorPartition(target_files):
368 if os.path.isdir(target_files):
369 return os.path.isdir(os.path.join(target_files, "VENDOR"))
370 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400371 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400372 raise ValueError("Unknown target_files argument")
373
374 return (HasVendorPartition(target_files) and
375 target_info.GetBuildProp("ro.treble.enabled") == "true")
376
377
378def HasPartition(target_files_zip, partition):
379 try:
380 target_files_zip.getinfo(partition.upper() + "/")
381 return True
382 except KeyError:
383 return False
384
Yifan Honge3ba82c2019-08-21 13:29:30 -0700385
386def main(argv):
387 args = common.ParseOptions(argv, __doc__)
388 if len(args) != 1:
389 common.Usage(__doc__)
390 sys.exit(1)
391 common.InitLogging()
392 if not CheckVintf(args[0]):
393 sys.exit(1)
394
395
396if __name__ == '__main__':
397 try:
398 common.CloseInheritedPipes()
399 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700400 finally:
401 common.Cleanup()