blob: 906ad1f35b6506162d4d84ea0695d4e5a71048e3 [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
Jooyung Han8af44a92022-11-24 18:36:47 +0900132 # Simulate apexd from target-files.
133 dirmap['/apex'] = PrepareApexDirectory(input_tmp)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000134
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
Cole Faustb820bcd2021-10-28 13:59:48 -0700144 for device_path, real_path in sorted(dirmap.items()):
Yifan Honge3ba82c2019-08-21 13:29:30 -0700145 common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
146 common_command += kernel_args
147 common_command += shipping_api_level_args
148
149 success = True
150 for sku_args in args_for_skus:
151 command = common_command + sku_args
152 proc = common.Run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153 out, err = proc.communicate()
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000154 last_out_line = out.split()[-1] if out != "" else out
Yifan Honge3ba82c2019-08-21 13:29:30 -0700155 if proc.returncode == 0:
156 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
Rob Seymourdc4e0f22022-10-05 19:52:54 +0000157 elif last_out_line.strip() == "INCOMPATIBLE":
Yifan Honge3ba82c2019-08-21 13:29:30 -0700158 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
159 success = False
160 else:
161 raise common.ExternalError(
162 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
163 .format(' '.join(command), proc.returncode, out, err))
164 logger.info("stdout: %s", out)
165 logger.info("stderr: %s", err)
166
167 return success
168
169
170def GetVintfFileList():
171 """
172 Returns a list of VINTF metadata files that should be read from a target files
173 package before executing checkvintf.
174 """
175 def PathToPatterns(path):
176 if path[-1] == '/':
Christian Oder83bfe5a2021-11-11 21:02:34 +0100177 path += '**'
Cole Faustb820bcd2021-10-28 13:59:48 -0700178
179 # Loop over all the entries in DIR_SEARCH_PATHS and find one where the key
180 # is a prefix of path. In order to get find the correct prefix, sort the
181 # entries by decreasing length of their keys, so that we check if longer
182 # strings are prefixes before shorter strings. This is so that keys that
183 # are substrings of other keys (like /system vs /system_ext) are checked
184 # later, and we don't mistakenly mark a path that starts with /system_ext
185 # as starting with only /system.
186 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 -0700187 if path.startswith(device_path):
188 suffix = path[len(device_path):]
189 return [rel_path + suffix for rel_path in target_files_rel_paths]
190 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
191 path)
192
193 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
194 paths = out.strip().split('\n')
195 paths = sum((PathToPatterns(path) for path in paths if path), [])
196 return paths
197
Rob Seymour3f1c9572022-07-28 21:56:28 +0000198def GetVintfApexUnzipPatterns():
199 """ Build unzip pattern for APEXes. """
200 patterns = []
201 for target_files_rel_paths in DIR_SEARCH_PATHS.values():
202 for target_files_rel_path in target_files_rel_paths:
203 patterns.append(os.path.join(target_files_rel_path,"apex/*"))
204
205 return patterns
206
207def PrepareApexDirectory(inp):
Jooyung Han8af44a92022-11-24 18:36:47 +0900208 """ Prepare /apex directory before running checkvintf
Rob Seymour3f1c9572022-07-28 21:56:28 +0000209
210 Apex binaries do not support dirmaps, in order to use these binaries we
211 need to move the APEXes from the extracted target file archives to the
212 expected device locations.
213
Jooyung Han8af44a92022-11-24 18:36:47 +0900214 This simulates how apexd activates APEXes.
215 1. create {inp}/APEX which is treated as a "/" on device.
216 2. copy apexes from target-files to {root}/{partition}/apex.
217 3. mount apexes under {root}/{partition}/apex at {root}/apex.
218 4. generate info files with dump_apex_info.
Rob Seymour3f1c9572022-07-28 21:56:28 +0000219
Jooyung Han8af44a92022-11-24 18:36:47 +0900220 We'll get the following layout
221 {inp}/APEX/apex # Activated APEXes + some info files
222 {inp}/APEX/system/apex # System APEXes
223 {inp}/APEX/vendor/apex # Vendor APEXes
Rob Seymour3f1c9572022-07-28 21:56:28 +0000224 ...
225
226 Args:
227 inp: path to the directory that contains the extracted target files archive.
228
229 Returns:
Jooyung Han8af44a92022-11-24 18:36:47 +0900230 directory representing /apex on device
Rob Seymour3f1c9572022-07-28 21:56:28 +0000231 """
232
Jooyung Haneb118212022-10-17 18:07:28 +0900233 deapexer = 'deapexer'
Dennis Shen44b714b2022-11-02 14:42:08 +0000234 debugfs_path = 'debugfs'
Dennis Shen44b714b2022-11-02 14:42:08 +0000235 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 fsckerofs_path = os.path.join(OPTIONS.search_path, 'bin', 'fsck.erofs')
Jooyung Haneb118212022-10-17 18:07:28 +0900240 if os.path.isfile(deapexer_path):
241 deapexer = deapexer_path
242
Rob Seymour3f1c9572022-07-28 21:56:28 +0000243 def ExtractApexes(path, outp):
244 # Extract all APEXes found in input path.
Rob Seymour3f1c9572022-07-28 21:56:28 +0000245 logger.info('Extracting APEXs in %s', path)
246 for f in os.listdir(path):
247 logger.info(' adding APEX %s', os.path.basename(f))
248 apex = os.path.join(path, f)
Jooyung Haneb118212022-10-17 18:07:28 +0900249 if os.path.isdir(apex) and os.path.isfile(os.path.join(apex, 'apex_manifest.pb')):
250 info = ParseApexManifest(os.path.join(apex, 'apex_manifest.pb'))
251 # Flattened APEXes may have symlinks for libs (linked to /system/lib)
252 # We need to blindly copy them all.
253 shutil.copytree(apex, os.path.join(outp, info.name), symlinks=True)
254 elif os.path.isfile(apex) and apex.endswith(('.apex', '.capex')):
Jooyung Han62283b92022-10-17 10:24:09 +0900255 cmd = [deapexer,
256 '--debugfs_path', debugfs_path,
257 'info',
258 apex]
259 info = json.loads(common.RunAndCheckOutput(cmd))
Rob Seymour3f1c9572022-07-28 21:56:28 +0000260
Jooyung Han62283b92022-10-17 10:24:09 +0900261 cmd = [deapexer,
262 '--debugfs_path', debugfs_path,
Dennis Shen44b714b2022-11-02 14:42:08 +0000263 '--fsckerofs_path', fsckerofs_path,
Jooyung Han62283b92022-10-17 10:24:09 +0900264 'extract',
265 apex,
266 os.path.join(outp, info['name'])]
267 common.RunAndCheckOutput(cmd)
Jooyung Haneb118212022-10-17 18:07:28 +0900268 else:
269 logger.info(' .. skipping %s (is it APEX?)', path)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000270
271 root_dir_name = 'APEX'
272 root_dir = os.path.join(inp, root_dir_name)
273 extracted_root = os.path.join(root_dir, 'apex')
Rob Seymour3f1c9572022-07-28 21:56:28 +0000274
Jooyung Han8af44a92022-11-24 18:36:47 +0900275 # Always create /apex directory for dirmap
Rob Seymour3f1c9572022-07-28 21:56:28 +0000276 os.makedirs(extracted_root)
277
278 create_info_file = False
279
280 # Loop through search path looking for and processing apex/ directories.
281 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
Jooyung Han8af44a92022-11-24 18:36:47 +0900282 # checkvintf only needs vendor apexes. skip other partitions for efficiency
283 if device_path not in ['/vendor', '/odm']:
284 continue
285 # First, copy VENDOR/apex/foo.apex to APEX/vendor/apex/foo.apex
286 # Then, extract the contents to APEX/apex/foo/
Rob Seymour3f1c9572022-07-28 21:56:28 +0000287 for target_files_rel_path in target_files_rel_paths:
288 inp_partition = os.path.join(inp, target_files_rel_path,"apex")
289 if os.path.exists(inp_partition):
290 apex_dir = root_dir + os.path.join(device_path + "/apex");
Po Hu4d2f64b2022-10-26 02:33:03 +0000291 os.makedirs(root_dir + device_path)
292 shutil.copytree(inp_partition, apex_dir, symlinks=True)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000293 ExtractApexes(apex_dir, extracted_root)
294 create_info_file = True
295
296 if create_info_file:
Jooyung Han8af44a92022-11-24 18:36:47 +0900297 ### Dump apex info files
298 dump_cmd = ['dump_apex_info', '--root_dir', root_dir]
Rob Seymour3f1c9572022-07-28 21:56:28 +0000299 common.RunAndCheckOutput(dump_cmd)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000300
Jooyung Han8af44a92022-11-24 18:36:47 +0900301 return extracted_root
Yifan Honge3ba82c2019-08-21 13:29:30 -0700302
303def CheckVintfFromTargetFiles(inp, info_dict=None):
304 """
305 Checks VINTF metadata of a target files zip.
306
307 Args:
308 inp: path to the target files archive.
309 info_dict: The build-time info dict. If None, it will be loaded from inp.
310
311 Returns:
312 True if VINTF check is skipped or compatible, False if incompatible. Raise
313 a RuntimeError if any error occurs.
314 """
Rob Seymour3f1c9572022-07-28 21:56:28 +0000315 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + GetVintfApexUnzipPatterns() + UNZIP_PATTERN)
Yifan Honge3ba82c2019-08-21 13:29:30 -0700316 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
317
318
319def CheckVintf(inp, info_dict=None):
320 """
321 Checks VINTF metadata of a target files zip or extracted target files
322 directory.
323
324 Args:
325 inp: path to the (possibly extracted) target files archive.
326 info_dict: The build-time info dict. If None, it will be loaded from inp.
327
328 Returns:
329 True if VINTF check is skipped or compatible, False if incompatible. Raise
330 a RuntimeError if any error occurs.
331 """
332 if os.path.isdir(inp):
333 logger.info('Checking VINTF compatibility extracted target files...')
334 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
335
336 if zipfile.is_zipfile(inp):
337 logger.info('Checking VINTF compatibility target files...')
338 return CheckVintfFromTargetFiles(inp, info_dict)
339
340 raise ValueError('{} is not a valid directory or zip file'.format(inp))
341
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400342def CheckVintfIfTrebleEnabled(target_files, target_info):
343 """Checks compatibility info of the input target files.
344
345 Metadata used for compatibility verification is retrieved from target_zip.
346
347 Compatibility should only be checked for devices that have enabled
348 Treble support.
349
350 Args:
351 target_files: Path to zip file containing the source files to be included
352 for OTA. Can also be the path to extracted directory.
353 target_info: The BuildInfo instance that holds the target build info.
354 """
355
356 # Will only proceed if the target has enabled the Treble support (as well as
357 # having a /vendor partition).
358 if not HasTrebleEnabled(target_files, target_info):
359 return
360
361 # Skip adding the compatibility package as a workaround for b/114240221. The
362 # compatibility will always fail on devices without qualified kernels.
363 if OPTIONS.skip_compatibility_check:
364 return
365
366 if not CheckVintf(target_files, target_info):
367 raise RuntimeError("VINTF compatibility check failed")
368
369def HasTrebleEnabled(target_files, target_info):
370 def HasVendorPartition(target_files):
371 if os.path.isdir(target_files):
372 return os.path.isdir(os.path.join(target_files, "VENDOR"))
373 if zipfile.is_zipfile(target_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400374 return HasPartition(zipfile.ZipFile(target_files, allowZip64=True), "vendor")
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400375 raise ValueError("Unknown target_files argument")
376
377 return (HasVendorPartition(target_files) and
378 target_info.GetBuildProp("ro.treble.enabled") == "true")
379
380
381def HasPartition(target_files_zip, partition):
382 try:
383 target_files_zip.getinfo(partition.upper() + "/")
384 return True
385 except KeyError:
386 return False
387
Yifan Honge3ba82c2019-08-21 13:29:30 -0700388
389def main(argv):
390 args = common.ParseOptions(argv, __doc__)
391 if len(args) != 1:
392 common.Usage(__doc__)
393 sys.exit(1)
394 common.InitLogging()
395 if not CheckVintf(args[0]):
396 sys.exit(1)
397
398
399if __name__ == '__main__':
400 try:
401 common.CloseInheritedPipes()
402 main(sys.argv[1:])
Yifan Honge3ba82c2019-08-21 13:29:30 -0700403 finally:
404 common.Cleanup()