1#
2# Copyright (C) 2017 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import collections
18import json
19import logging
20import os
21import re
22import zipfile
23
24try:
25    from importlib import resources
26except ImportError:
27    resources = None
28
29# The tags in VNDK list:
30# Low-level NDK libraries that can be used by framework and vendor modules.
31LL_NDK = "LLNDK"
32
33# Same-process HAL implementation in vendor partition.
34SP_HAL = "SP-HAL"
35
36# Framework libraries that can be used by vendor modules except same-process HAL
37# and its dependencies in vendor partition.
38VNDK = "VNDK-core"
39
40# VNDK dependencies that vendor modules cannot directly access.
41VNDK_PRIVATE = "VNDK-core-private"
42
43# Same-process HAL dependencies in framework.
44VNDK_SP = "VNDK-SP"
45
46# VNDK-SP dependencies that vendor modules cannot directly access.
47VNDK_SP_PRIVATE = "VNDK-SP-private"
48
49# The tuples of (ABI name, bitness, arch name). 64-bit comes before 32-bit in
50# order to sequentially search for longest prefix.
51_ABI_LIST = (
52    ("arm64", 64, "arm64_armv8-a"),
53    ("arm64", 32, "arm_armv8-a"),
54    ("arm", 32, "arm_armv7-a-neon"),
55    ("x86_64", 64, "x86_x86_64"),
56    ("x86_64", 32, "x86_64"),
57    ("x86", 32, "x86"),
58)
59
60# The data directory.
61_GOLDEN_DIR = os.path.join("vts", "testcases", "vndk", "golden")
62
63# The data package.
64_RESOURCE_PACKAGE = "vts.testcases.vndk";
65
66# The name of the zip file containing ABI dumps.
67_ABI_DUMP_ZIP_NAME = "abi_dump.zip"
68
69# Regular expression prefix for library name patterns.
70_REGEX_PREFIX = "[regex]"
71
72def LoadDefaultVndkVersion(data_file_path):
73    """Loads the name of the data directory for devices with no VNDK version.
74
75    Args:
76        data_file_path: The path to VTS data directory.
77
78    Returns:
79        A string, the directory name.
80        None if fails to load the name.
81    """
82    try:
83        with open(os.path.join(data_file_path, _GOLDEN_DIR,
84                               "platform_vndk_version.txt"), "r") as f:
85            return f.read().strip()
86    except IOError:
87        logging.error("Cannot load default VNDK version.")
88        return None
89
90
91def GetAbiDumpDirectory(data_file_path, version, binder_bitness, abi_name,
92                        abi_bitness):
93    """Returns the VNDK dump directory on host.
94
95    Args:
96        data_file_path: The path to VTS data directory.
97        version: A string, the VNDK version.
98        binder_bitness: A string or an integer, 32 or 64.
99        abi_name: A string, the ABI of the library dump.
100        abi_bitness: A string or an integer, 32 or 64.
101
102    Returns:
103        A string, the path to the dump directory.
104        None if there is no directory for the version and ABI.
105    """
106    try:
107        abi_dir = next(x[0] for x in _ABI_LIST if abi_name.startswith(x[0]))
108    except StopIteration:
109        logging.warning("Unknown ABI %s.", abi_name)
110        return None
111
112    version_dir = (version if version else
113                   LoadDefaultVndkVersion(data_file_path))
114    if not version_dir:
115        return None
116
117    dump_dir = os.path.join(
118        data_file_path, _GOLDEN_DIR, version_dir,
119        "binder64" if str(binder_bitness) == "64" else "binder32",
120        abi_dir, "lib64" if str(abi_bitness) == "64" else "lib")
121
122    if not os.path.isdir(dump_dir):
123        logging.warning("%s is not a directory.", dump_dir)
124        return None
125
126    return dump_dir
127
128
129class AbiDumpResource:
130    """The class for loading ABI dumps from the zip in resources."""
131
132    def __init__(self):
133        self._resource = None
134        self.zip_file = None
135
136    def __enter__(self):
137        self._resource = resources.open_binary(_RESOURCE_PACKAGE,
138                                               _ABI_DUMP_ZIP_NAME)
139        self.zip_file = zipfile.ZipFile(self._resource, "r")
140        return self
141
142    def __exit__(self, exc_type, exc_val, traceback):
143        if self._resource:
144            self._resource.close()
145        if self.zip_file:
146            self.zip_file.close()
147
148
149def GetAbiDumpPathsFromResources(version, binder_bitness, abi_name, abi_bitness):
150    """Returns the VNDK dump paths in resources.
151
152    Args:
153        version: A string, the VNDK version.
154        binder_bitness: A string or an integer, 32 or 64.
155        abi_name: A string, the ABI of the library dump.
156        abi_bitness: A string or an integer, 32 or 64.
157
158    Returns:
159        A dict of {library name: dump resource path}. For example,
160        {"libbase.so": "R/64/arm64_armv8-a/source-based/libbase.so.lsdump"}.
161        If there is no dump for the version and ABI, this function returns an
162        empty dict.
163    """
164    if not resources:
165        logging.error("Could not import resources module.")
166        return dict()
167
168    abi_bitness = int(abi_bitness)
169    try:
170        arch_name = next(x[2] for x in _ABI_LIST if
171                         abi_name.startswith(x[0]) and x[1] == abi_bitness)
172    except StopIteration:
173        logging.warning("Unknown %d-bit ABI %s.", abi_bitness, abi_name)
174        return dict()
175
176    # The separator in zipped path is always "/".
177    dump_dir = "/".join((version, str(binder_bitness), arch_name,
178                         "source-based")) + "/"
179
180    dump_paths = dict()
181
182    with AbiDumpResource() as dump_resource:
183        for path in dump_resource.zip_file.namelist():
184            if path.startswith(dump_dir) and path.endswith(".lsdump"):
185                lib_name = path[len(dump_dir):-len(".lsdump")]
186                dump_paths[lib_name] = path
187
188    return dump_paths
189
190
191def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file):
192    """Load VNDK libraries from the file to the specified tuple.
193
194    Args:
195        vndk_lists: The output tuple of lists containing library names.
196        tags: Strings, the tags of the libraries to find.
197        vndk_lib_list_file: The file object containing the VNDK library list.
198    """
199
200    lib_sets = collections.defaultdict(set)
201
202    # Load VNDK tags from the list.
203    for line in vndk_lib_list_file:
204        # Ignore comments.
205        if line.startswith('#'):
206            continue
207
208        # Split columns.
209        cells = line.split(': ', 1)
210        if len(cells) < 2:
211            continue
212        tag = cells[0]
213        lib_name = cells[1].strip()
214
215        lib_sets[tag].add(lib_name)
216
217    # Compute VNDK-core-private and VNDK-SP-private.
218    private = lib_sets.get('VNDK-private', set())
219
220    lib_sets[VNDK_PRIVATE].update(lib_sets[VNDK] & private)
221    lib_sets[VNDK_SP_PRIVATE].update(lib_sets[VNDK_SP] & private)
222
223    lib_sets[LL_NDK].difference_update(private)
224    lib_sets[VNDK].difference_update(private)
225    lib_sets[VNDK_SP].difference_update(private)
226
227    # Update the output entries.
228    for index, tag in enumerate(tags):
229        for lib_name in lib_sets.get(tag, tuple()):
230            if lib_name.startswith(_REGEX_PREFIX):
231                lib_name = lib_name[len(_REGEX_PREFIX):]
232            vndk_lists[index].append(lib_name)
233
234
235def LoadVndkLibraryLists(data_file_path, version, *tags):
236    """Find the VNDK libraries with specific tags.
237
238    Args:
239        data_file_path: The path to VTS data directory.
240        version: A string, the VNDK version.
241        *tags: Strings, the tags of the libraries to find.
242
243    Returns:
244        A tuple of lists containing library names. Each list corresponds to
245        one tag in the argument. For SP-HAL, the returned names are regular
246        expressions.
247        None if the spreadsheet for the version is not found.
248    """
249    version_dir = (version if version else
250                   LoadDefaultVndkVersion(data_file_path))
251    if not version_dir:
252        return None
253
254    vndk_lib_list_path = os.path.join(
255        data_file_path, _GOLDEN_DIR, version_dir, "vndk-lib-list.txt")
256    if not os.path.isfile(vndk_lib_list_path):
257        logging.warning("Cannot load %s.", vndk_lib_list_path)
258        return None
259
260    vndk_lib_extra_list_path = os.path.join(
261        data_file_path, _GOLDEN_DIR, version_dir, "vndk-lib-extra-list.txt")
262    if not os.path.isfile(vndk_lib_extra_list_path):
263        logging.warning("Cannot load %s.", vndk_lib_extra_list_path)
264        return None
265
266    vndk_lists = tuple([] for x in tags)
267
268    with open(vndk_lib_list_path, "r") as f:
269        _LoadVndkLibraryListsFile(vndk_lists, tags, f)
270    with open(vndk_lib_extra_list_path, "r") as f:
271        _LoadVndkLibraryListsFile(vndk_lists, tags, f)
272    return vndk_lists
273
274
275def LoadVndkLibraryListsFromResources(version, *tags):
276    """Find the VNDK libraries with specific tags in resources.
277
278    Args:
279        version: A string, the VNDK version.
280        *tags: Strings, the tags of the libraries to find.
281
282    Returns:
283        A tuple of lists containing library names. Each list corresponds to
284        one tag in the argument. For SP-HAL, the returned names are regular
285        expressions.
286        None if the VNDK list for the version is not found.
287    """
288    if not resources:
289        logging.error("Could not import resources module.")
290        return None
291
292    version_str = (version if version and re.match("\\d+", version) else
293                   "current")
294    vndk_lib_list_name = version_str + ".txt"
295    vndk_lib_extra_list_name = "vndk-lib-extra-list-" + version_str + ".txt"
296
297    if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_list_name):
298        logging.warning("Cannot load %s.", vndk_lib_list_name)
299        return None
300
301    if not resources.is_resource(_RESOURCE_PACKAGE, vndk_lib_extra_list_name):
302        logging.warning("Cannot load %s.", vndk_lib_extra_list_name)
303        return None
304
305    vndk_lists = tuple([] for x in tags)
306
307    with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_list_name) as f:
308        _LoadVndkLibraryListsFile(vndk_lists, tags, f)
309    with resources.open_text(_RESOURCE_PACKAGE, vndk_lib_extra_list_name) as f:
310        _LoadVndkLibraryListsFile(vndk_lists, tags, f)
311    return vndk_lists
312