blob: a254cabb7bbc42e4d5e554e90434d8b64c689d71 [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'
235 blkid_path = 'blkid'
236 fsckerofs_path = 'fsck.erofs'
Jooyung Haneb118212022-10-17 18:07:28 +0900237 if OPTIONS.search_path:
238 debugfs_path = os.path.join(OPTIONS.search_path, 'bin', 'debugfs_static')
239 deapexer_path = os.path.join(OPTIONS.search_path, 'bin', 'deapexer')
Dennis Shen44b714b2022-11-02 14:42:08 +0000240 blkid_path = os.path.join(OPTIONS.search_path, 'bin', 'blkid')
241 fsckerofs_path = os.path.join(OPTIONS.search_path, 'bin', 'fsck.erofs')
Jooyung Haneb118212022-10-17 18:07:28 +0900242 if os.path.isfile(deapexer_path):
243 deapexer = deapexer_path
244
Rob Seymour3f1c9572022-07-28 21:56:28 +0000245 def ExtractApexes(path, outp):
246 # Extract all APEXes found in input path.
Rob Seymour3f1c9572022-07-28 21:56:28 +0000247 logger.info('Extracting APEXs in %s', path)
248 for f in os.listdir(path):
249 logger.info(' adding APEX %s', os.path.basename(f))
250 apex = os.path.join(path, f)
Jooyung Haneb118212022-10-17 18:07:28 +0900251 if os.path.isdir(apex) and os.path.isfile(os.path.join(apex, 'apex_manifest.pb')):
252 info = ParseApexManifest(os.path.join(apex, 'apex_manifest.pb'))
253 # Flattened APEXes may have symlinks for libs (linked to /system/lib)
254 # We need to blindly copy them all.
255 shutil.copytree(apex, os.path.join(outp, info.name), symlinks=True)
256 elif os.path.isfile(apex) and apex.endswith(('.apex', '.capex')):
Jooyung Han62283b92022-10-17 10:24:09 +0900257 cmd = [deapexer,
258 '--debugfs_path', debugfs_path,
259 'info',
260 apex]
261 info = json.loads(common.RunAndCheckOutput(cmd))
Rob Seymour3f1c9572022-07-28 21:56:28 +0000262
Jooyung Han62283b92022-10-17 10:24:09 +0900263 cmd = [deapexer,
264 '--debugfs_path', debugfs_path,
Dennis Shen44b714b2022-11-02 14:42:08 +0000265 '--fsckerofs_path', fsckerofs_path,
266 '--blkid_path', blkid_path,
Jooyung Han62283b92022-10-17 10:24:09 +0900267 'extract',
268 apex,
269 os.path.join(outp, info['name'])]
270 common.RunAndCheckOutput(cmd)
Jooyung Haneb118212022-10-17 18:07:28 +0900271 else:
272 logger.info(' .. skipping %s (is it APEX?)', path)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000273
274 root_dir_name = 'APEX'
275 root_dir = os.path.join(inp, root_dir_name)
276 extracted_root = os.path.join(root_dir, 'apex')
Rob Seymour3f1c9572022-07-28 21:56:28 +0000277
Jooyung Han8af44a92022-11-24 18:36:47 +0900278 # Always create /apex directory for dirmap
Rob Seymour3f1c9572022-07-28 21:56:28 +0000279 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():
Jooyung Han8af44a92022-11-24 18:36:47 +0900285 # checkvintf only needs vendor apexes. skip other partitions for efficiency
286 if device_path not in ['/vendor', '/odm']:
287 continue
288 # First, copy VENDOR/apex/foo.apex to APEX/vendor/apex/foo.apex
289 # Then, extract the contents to APEX/apex/foo/
Rob Seymour3f1c9572022-07-28 21:56:28 +0000290 for target_files_rel_path in target_files_rel_paths:
291 inp_partition = os.path.join(inp, target_files_rel_path,"apex")
292 if os.path.exists(inp_partition):
293 apex_dir = root_dir + os.path.join(device_path + "/apex");
Po Hu4d2f64b2022-10-26 02:33:03 +0000294 os.makedirs(root_dir + device_path)
295 shutil.copytree(inp_partition, apex_dir, symlinks=True)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000296 ExtractApexes(apex_dir, extracted_root)
297 create_info_file = True
298
299 if create_info_file:
Jooyung Han8af44a92022-11-24 18:36:47 +0900300 ### Dump apex info files
301 dump_cmd = ['dump_apex_info', '--root_dir', root_dir]
Rob Seymour3f1c9572022-07-28 21:56:28 +0000302 common.RunAndCheckOutput(dump_cmd)
Rob Seymour3f1c9572022-07-28 21:56:28 +0000303
Jooyung Han8af44a92022-11-24 18:36:47 +0900304 return extracted_root
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()