blob: 543147c981cefef4f9a0821f47f8cd234fed52aa [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.
41DIR_SEARCH_PATHS = {
42 '/system': ('SYSTEM',),
43 '/vendor': ('VENDOR', 'SYSTEM/vendor'),
44 '/product': ('PRODUCT', 'SYSTEM/product'),
45 '/odm': ('ODM', 'VENDOR/odm'),
46}
47
48UNZIP_PATTERN = ['META/*', '*/build.prop']
49
50
51def GetDirmap(input_tmp):
52 dirmap = {}
53 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
54 for target_files_rel_path in target_files_rel_paths:
55 target_files_path = os.path.join(input_tmp, target_files_rel_path)
56 if os.path.isdir(target_files_path):
57 dirmap[device_path] = target_files_path
58 break
59 if device_path not in dirmap:
60 raise ValueError("Can't determine path for device path " + device_path +
61 ". Searched the following:" +
62 ("\n".join(target_files_rel_paths)))
63 return dirmap
64
65
66def GetArgsForSkus(info_dict):
67 skus = info_dict.get('vintf_odm_manifest_skus', '').strip().split()
68 if not skus:
69 logger.info("ODM_MANIFEST_SKUS is not defined. Check once without SKUs.")
70 skus = ['']
71 return [['--property', 'ro.boot.product.hardware.sku=' + sku]
72 for sku in skus]
73
74
75def GetArgsForShippingApiLevel(info_dict):
76 shipping_api_level = info_dict['vendor.build.prop'].get(
77 'ro.product.first_api_level')
78 if not shipping_api_level:
79 logger.warning('Cannot determine ro.product.first_api_level')
80 return []
81 return ['--property', 'ro.product.first_api_level=' + shipping_api_level]
82
83
84def GetArgsForKernel(input_tmp):
85 version_path = os.path.join(input_tmp, 'META/kernel_version.txt')
86 config_path = os.path.join(input_tmp, 'META/kernel_configs.txt')
87
88 if not os.path.isfile(version_path) or not os.path.isfile(config_path):
89 logger.info('Skipping kernel config checks because ' +
90 'PRODUCT_OTA_ENFORCE_VINTF_KERNEL_REQUIREMENTS is not set')
91 return []
92
93 with open(version_path) as f:
94 version = f.read().strip()
95
96 return ['--kernel', '{}:{}'.format(version, config_path)]
97
98
99def CheckVintfFromExtractedTargetFiles(input_tmp, info_dict=None):
100 """
101 Checks VINTF metadata of an extracted target files directory.
102
103 Args:
104 inp: path to the directory that contains the extracted target files archive.
105 info_dict: The build-time info dict. If None, it will be loaded from inp.
106
107 Returns:
108 True if VINTF check is skipped or compatible, False if incompatible. Raise
109 a RuntimeError if any error occurs.
110 """
111
112 if info_dict is None:
113 info_dict = common.LoadInfoDict(input_tmp)
114
115 if info_dict.get('vintf_enforce') != 'true':
116 logger.warning('PRODUCT_ENFORCE_VINTF_MANIFEST is not set, skipping checks')
117 return True
118
119 dirmap = GetDirmap(input_tmp)
120 args_for_skus = GetArgsForSkus(info_dict)
121 shipping_api_level_args = GetArgsForShippingApiLevel(info_dict)
122 kernel_args = GetArgsForKernel(input_tmp)
123
124 common_command = [
125 'checkvintf',
126 '--check-compat',
127 ]
128 for device_path, real_path in dirmap.items():
129 common_command += ['--dirmap', '{}:{}'.format(device_path, real_path)]
130 common_command += kernel_args
131 common_command += shipping_api_level_args
132
133 success = True
134 for sku_args in args_for_skus:
135 command = common_command + sku_args
136 proc = common.Run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
137 out, err = proc.communicate()
138 if proc.returncode == 0:
139 logger.info("Command `%s` returns 'compatible'", ' '.join(command))
140 elif out.strip() == "INCOMPATIBLE":
141 logger.info("Command `%s` returns 'incompatible'", ' '.join(command))
142 success = False
143 else:
144 raise common.ExternalError(
145 "Failed to run command '{}' (exit code {}):\nstdout:{}\nstderr:{}"
146 .format(' '.join(command), proc.returncode, out, err))
147 logger.info("stdout: %s", out)
148 logger.info("stderr: %s", err)
149
150 return success
151
152
153def GetVintfFileList():
154 """
155 Returns a list of VINTF metadata files that should be read from a target files
156 package before executing checkvintf.
157 """
158 def PathToPatterns(path):
159 if path[-1] == '/':
160 path += '*'
161 for device_path, target_files_rel_paths in DIR_SEARCH_PATHS.items():
162 if path.startswith(device_path):
163 suffix = path[len(device_path):]
164 return [rel_path + suffix for rel_path in target_files_rel_paths]
165 raise RuntimeError('Unrecognized path from checkvintf --dump-file-list: ' +
166 path)
167
168 out = common.RunAndCheckOutput(['checkvintf', '--dump-file-list'])
169 paths = out.strip().split('\n')
170 paths = sum((PathToPatterns(path) for path in paths if path), [])
171 return paths
172
173
174def CheckVintfFromTargetFiles(inp, info_dict=None):
175 """
176 Checks VINTF metadata of a target files zip.
177
178 Args:
179 inp: path to the target files archive.
180 info_dict: The build-time info dict. If None, it will be loaded from inp.
181
182 Returns:
183 True if VINTF check is skipped or compatible, False if incompatible. Raise
184 a RuntimeError if any error occurs.
185 """
186 input_tmp = common.UnzipTemp(inp, GetVintfFileList() + UNZIP_PATTERN)
187 return CheckVintfFromExtractedTargetFiles(input_tmp, info_dict)
188
189
190def CheckVintf(inp, info_dict=None):
191 """
192 Checks VINTF metadata of a target files zip or extracted target files
193 directory.
194
195 Args:
196 inp: path to the (possibly extracted) target files archive.
197 info_dict: The build-time info dict. If None, it will be loaded from inp.
198
199 Returns:
200 True if VINTF check is skipped or compatible, False if incompatible. Raise
201 a RuntimeError if any error occurs.
202 """
203 if os.path.isdir(inp):
204 logger.info('Checking VINTF compatibility extracted target files...')
205 return CheckVintfFromExtractedTargetFiles(inp, info_dict)
206
207 if zipfile.is_zipfile(inp):
208 logger.info('Checking VINTF compatibility target files...')
209 return CheckVintfFromTargetFiles(inp, info_dict)
210
211 raise ValueError('{} is not a valid directory or zip file'.format(inp))
212
213
214def main(argv):
215 args = common.ParseOptions(argv, __doc__)
216 if len(args) != 1:
217 common.Usage(__doc__)
218 sys.exit(1)
219 common.InitLogging()
220 if not CheckVintf(args[0]):
221 sys.exit(1)
222
223
224if __name__ == '__main__':
225 try:
226 common.CloseInheritedPipes()
227 main(sys.argv[1:])
228 except common.ExternalError:
229 logger.exception('\n ERROR:\n')
230 sys.exit(1)
231 finally:
232 common.Cleanup()