blob: 95d09cc177cd160cfcae50d56ab748e4d5e6872b [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
25import logging
26import subprocess
27import sys
28import os
29import zipfile
30
31import common
32
33logger = logging.getLogger(__name__)
34
35OPTIONS = common.OPTIONS
36
37# Keys are paths that VINTF searches. Must keep in sync with libvintf's search
38# paths (VintfObject.cpp).
39# These paths are stored in different directories in target files package, so
40# we have to search for the correct path and tell checkvintf to remap them.
Yifan Hong2870d1e2019-12-19 13:58:00 -080041# Look for TARGET_COPY_OUT_* variables in board_config.mk for possible paths for
42# each partition.
Yifan Honge3ba82c2019-08-21 13:29:30 -070043DIR_SEARCH_PATHS = {
44 '/system': ('SYSTEM',),
45 '/vendor': ('VENDOR', 'SYSTEM/vendor'),
46 '/product': ('PRODUCT', 'SYSTEM/product'),
Yifan Hong2870d1e2019-12-19 13:58:00 -080047 '/odm': ('ODM', 'VENDOR/odm', 'SYSTEM/vendor/odm'),
Yifan Hong9cbb6242019-12-19 13:56:59 -080048 '/system_ext': ('SYSTEM_EXT', 'SYSTEM/system_ext'),
Yifan Honge3ba82c2019-08-21 13:29:30 -070049}
50
51UNZIP_PATTERN = ['META/*', '*/build.prop']
52
53
54def GetDirmap(input_tmp):
55 dirmap = {}
56 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
57 for target_files_rel_path in target_files_rel_paths:
58 target_files_path = os.path.join(input_tmp, target_files_rel_path)
59 if os.path.isdir(target_files_path):
60 dirmap[device_path] = target_files_path
61 break
62 if device_path not in dirmap:
63 raise ValueError("Can't determine path for device path " + device_path +
64 ". Searched the following:" +
65 ("\n".join(target_files_rel_paths)))
66 return dirmap
67
68
69def GetArgsForSkus(info_dict):
Yifan Hong28ffd732020-03-13 13:11:10 -070070 odm_skus = info_dict.get('vintf_odm_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070071 if info_dict.get('vintf_include_empty_odm_sku', '') == "true" or not odm_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070072 odm_skus += ['']
Yifan Honge3ba82c2019-08-21 13:29:30 -070073
Yifan Hong28ffd732020-03-13 13:11:10 -070074 vendor_skus = info_dict.get('vintf_vendor_manifest_skus', '').strip().split()
Yifan Hong69430e62020-03-17 15:18:34 -070075 if info_dict.get('vintf_include_empty_vendor_sku', '') == "true" or \
76 not vendor_skus:
Yifan Hong28ffd732020-03-13 13:11:10 -070077 vendor_skus += ['']
78
79 return [['--property', 'ro.boot.product.hardware.sku=' + odm_sku,
80 '--property', 'ro.boot.product.vendor.sku=' + vendor_sku]
81 for odm_sku in odm_skus for vendor_sku in vendor_skus]
Yifan Honge3ba82c2019-08-21 13:29:30 -070082
Tianjiefd3883f2020-04-25 19:55:54 -070083
Yifan Honge3ba82c2019-08-21 13:29:30 -070084def GetArgsForShippingApiLevel(info_dict):
Tianjiefd3883f2020-04-25 19:55:54 -070085 shipping_api_level = info_dict['vendor.build.prop'].GetProp(
Yifan Honge3ba82c2019-08-21 13:29:30 -070086 'ro.product.first_api_level')
87 if not shipping_api_level:
88 logger.warning('Cannot determine ro.product.first_api_level')
89 return []
90 return ['--property', 'ro.product.first_api_level=' + shipping_api_level]
91
92
93def GetArgsForKernel(input_tmp):
94 version_path = os.path.join(input_tmp, 'META/kernel_version.txt')
95 config_path = os.path.join(input_tmp, 'META/kernel_configs.txt')
96
97 if not os.path.isfile(version_path) or not os.path.isfile(config_path):
Yifan Hong28ffd732020-03-13 13:11:10 -070098 logger.info('Skipping kernel config checks because '
Yifan Honge3ba82c2019-08-21 13:29:30 -070099 'PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is not set')
100 return []
101
102 with open(version_path) as f:
103 version = f.read().strip()
104
105 return ['--kernel', '{}:{}'.format(version, config_path)]
106
107
108def CheckVintfFromExtractedTargetFiles(input_tmp, info_dict=None):
109 """
110 Checks VINTF metadata of an extracted target files directory.
111
112 Args:
113 inp: path to the directory that contains the extracted target files archive.
114 info_dict: The build-time info dict. If None, it will be loaded from inp.
115
116 Returns:
117 True if VINTF check is skipped or compatible, False if incompatible. Raise
118 a RuntimeError if any error occurs.
119 """
120
121 if info_dict is None:
122 info_dict = common.LoadInfoDict(input_tmp)
123
124 if info_dict.get('vintf_enforce') != 'true':
125 logger.warning('PRODUCT_ENFORCE_VINTF_MANIFEST is not set, skipping checks')
126 return True
127
128 dirmap = GetDirmap(input_tmp)
129 args_for_skus = GetArgsForSkus(info_dict)
130 shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
131 kernel_args = GetArgsForKernel(input_tmp)
132
133 common_command = [
134 'checkvintf',
135 '--check-compat',
136 ]
137 for device_path, real_path in dirmap.items():
138 common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
139 common_command += kernel_args
140 common_command += shipping_api_level_args
141
142 success = True
143 for sku_args in args_for_skus:
144 command = common_command + sku_args
145 proc = common.Run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
146 out, err = proc.communicate()
147 if proc.returncode == 0:
148 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
149 elif out.strip() == "INCOMPATIBLE":
150 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
151 success = False
152 else:
153 raise common.ExternalError(
154 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
155 .format(' '.join(command), proc.returncode, out, err))
156 logger.info("stdout: %s", out)
157 logger.info("stderr: %s", err)
158
159 return success
160
161
162def GetVintfFileList():
163 """
164 Returns a list of VINTF metadata files that should be read from a target files
165 package before executing checkvintf.
166 """
167 def PathToPatterns(path):
168 if path[-1] == '/':
169 path += '*'
170 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
171 if path.startswith(device_path):
172 suffix = path[len(device_path):]
173 return [rel_path + suffix for rel_path in target_files_rel_paths]
174 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
175 path)
176
177 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
178 paths = out.strip().split('\n')
179 paths = sum((PathToPatterns(path) for path in paths if path), [])
180 return paths
181
182
183def CheckVintfFromTargetFiles(inp, info_dict=None):
184 """
185 Checks VINTF metadata of a target files zip.
186
187 Args:
188 inp: path to the target files archive.
189 info_dict: The build-time info dict. If None, it will be loaded from inp.
190
191 Returns:
192 True if VINTF check is skipped or compatible, False if incompatible. Raise
193 a RuntimeError if any error occurs.
194 """
195 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + UNZIP_PATTERN)
196 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
197
198
199def CheckVintf(inp, info_dict=None):
200 """
201 Checks VINTF metadata of a target files zip or extracted target files
202 directory.
203
204 Args:
205 inp: path to the (possibly extracted) target files archive.
206 info_dict: The build-time info dict. If None, it will be loaded from inp.
207
208 Returns:
209 True if VINTF check is skipped or compatible, False if incompatible. Raise
210 a RuntimeError if any error occurs.
211 """
212 if os.path.isdir(inp):
213 logger.info('Checking VINTF compatibility extracted target files...')
214 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
215
216 if zipfile.is_zipfile(inp):
217 logger.info('Checking VINTF compatibility target files...')
218 return CheckVintfFromTargetFiles(inp, info_dict)
219
220 raise ValueError('{} is not a valid directory or zip file'.format(inp))
221
222
223def main(argv):
224 args = common.ParseOptions(argv, __doc__)
225 if len(args) != 1:
226 common.Usage(__doc__)
227 sys.exit(1)
228 common.InitLogging()
229 if not CheckVintf(args[0]):
230 sys.exit(1)
231
232
233if __name__ == '__main__':
234 try:
235 common.CloseInheritedPipes()
236 main(sys.argv[1:])
237 except common.ExternalError:
238 logger.exception('\n ERROR:\n')
239 sys.exit(1)
240 finally:
241 common.Cleanup()