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, legacy name ...). The legacy
50# name is for VNDK 32 and older versions. 64-bit comes before 32-bit in order
51# to sequentially search for longest prefix.
52_ABI_LIST = (
53    ("arm64", 64, "arm64", "arm64_armv8-a"),
54    ("arm64", 32, "arm_arm64", "arm_armv8-a"),
55    ("arm", 32, "arm", "arm_armv7-a-neon"),
56    ("x86_64", 64, "x86_64"),
57    ("x86_64", 32, "x86_x86_64"),
58    ("x86", 32, "x86"),
59)
60
61# The data directory.
62_GOLDEN_DIR = os.path.join("vts", "testcases", "vndk", "golden")
63
64# The data package.
65_RESOURCE_PACKAGE = "vts.testcases.vndk"
66
67# The name of the zip file containing ABI dumps.
68_ABI_DUMP_ZIP_NAME = "abi_dump.zip"
69
70# Regular expression prefix for library name patterns.
71_REGEX_PREFIX = "[regex]"
72
73
74class AbiDumpResource:
75    """The class for loading ABI dumps from the zip in resources."""
76
77    def __init__(self):
78        self._resource = None
79        self.zip_file = None
80
81    def __enter__(self):
82        self._resource = resources.files(_RESOURCE_PACKAGE).joinpath(
83            _ABI_DUMP_ZIP_NAME).open("rb")
84        self.zip_file = zipfile.ZipFile(self._resource, "r")
85        return self
86
87    def __exit__(self, exc_type, exc_val, traceback):
88        if self._resource:
89            self._resource.close()
90        if self.zip_file:
91            self.zip_file.close()
92
93
94def GetAbiDumpPathsFromResources(version, binder_bitness, abi_name, abi_bitness):
95    """Returns the VNDK dump paths in resources.
96
97    Args:
98        version: A string, the VNDK version.
99        binder_bitness: A string or an integer, 32 or 64.
100        abi_name: A string, the ABI of the library dump.
101        abi_bitness: A string or an integer, 32 or 64.
102
103    Returns:
104        A dict of {library name: dump resource path}. For example,
105        {"libbase.so": "R/64/arm64_armv8-a/source-based/libbase.so.lsdump"}.
106        If there is no dump for the version and ABI, this function returns an
107        empty dict.
108    """
109    if not resources:
110        logging.error("Could not import resources module.")
111        return dict()
112
113    abi_bitness = int(abi_bitness)
114    try:
115        arch_names = next(x[2:] for x in _ABI_LIST if
116                          abi_name.startswith(x[0]) and x[1] == abi_bitness)
117    except StopIteration:
118        logging.warning("Unknown %d-bit ABI %s.", abi_bitness, abi_name)
119        return dict()
120
121    # The separator in zipped path is always "/".
122    dump_dirs = ["/".join((version, str(binder_bitness), arch_name,
123                           "source-based")) + "/"
124                 for arch_name in arch_names]
125    ext = ".lsdump"
126
127    dump_paths = dict()
128
129    with AbiDumpResource() as dump_resource:
130        for path in dump_resource.zip_file.namelist():
131            for dump_dir in dump_dirs:
132                if path.startswith(dump_dir) and path.endswith(ext):
133                    lib_name = path[len(dump_dir):-len(ext)]
134                    dump_paths[lib_name] = path
135                    break
136
137    return dump_paths
138
139
140def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file,
141                              change_history_file=None):
142    """Load VNDK libraries from the file to the specified tuple.
143
144    Args:
145        vndk_lists: The output tuple of lists containing library names.
146        tags: Strings, the tags of the libraries to find.
147        vndk_lib_list_file: The file object containing the VNDK library list.
148        change_history_file: The file object containing the VNDK list change
149            history. It adds the vndk files that are removed.
150    """
151    def ReadTagAndFile(line):
152        # Ignore comments.
153        if line.startswith('#'):
154            return None, None
155
156        # Split columns.
157        cells = line.split(': ', 1)
158        if len(cells) < 2:
159            return None, None
160        return cells[0].strip(), cells[1].strip()
161
162    lib_sets = collections.defaultdict(set)
163
164    # Load VNDK tags from the list.
165    for line in vndk_lib_list_file:
166        tag, lib_name = ReadTagAndFile(line)
167        if not tag:
168            continue
169        lib_sets[tag].add(lib_name)
170
171    if change_history_file:
172        for line in change_history_file:
173            tag, lib_name = ReadTagAndFile(line)
174            if not tag:
175                continue
176
177            # In the history file, tag has '+' prefix if the file is added and
178            # '-' prefix if removed.
179            # To relax the test, include the removed files to the list.
180            if tag[0] != '-':
181                continue
182            tag = tag[1:]
183            lib_sets[tag].add(lib_name)
184
185    # Compute VNDK-core-private and VNDK-SP-private.
186    private = lib_sets.get('VNDK-private', set())
187
188    lib_sets[VNDK_PRIVATE].update(lib_sets[VNDK] & private)
189    lib_sets[VNDK_SP_PRIVATE].update(lib_sets[VNDK_SP] & private)
190
191    lib_sets[LL_NDK].difference_update(private)
192    lib_sets[VNDK].difference_update(private)
193    lib_sets[VNDK_SP].difference_update(private)
194
195    # Update the output entries.
196    for index, tag in enumerate(tags):
197        for lib_name in lib_sets.get(tag, tuple()):
198            if lib_name.startswith(_REGEX_PREFIX):
199                lib_name = lib_name[len(_REGEX_PREFIX):]
200            vndk_lists[index].append(lib_name)
201
202
203def LoadVndkLibraryListsFromResources(version, *tags):
204    """Find the VNDK libraries with specific tags in resources.
205
206    Args:
207        version: A string, the VNDK version.
208        *tags: Strings, the tags of the libraries to find.
209
210    Returns:
211        A tuple of lists containing library names. Each list corresponds to
212        one tag in the argument. For SP-HAL, the returned names are regular
213        expressions.
214        None if the VNDK list for the version is not found.
215    """
216    if not resources:
217        logging.error("Could not import resources module.")
218        return None
219
220    # VNDK 35 will not be frozen.
221    version_str = (version if re.match("\\d+", version) and int(version) <= 34
222                   else "current")
223    vndk_lib_list_name = version_str + ".txt"
224    vndk_lib_list = resources.files(_RESOURCE_PACKAGE).joinpath(
225        vndk_lib_list_name)
226    vndk_lib_list_history_name = version_str + "_history.txt"
227    vndk_lib_list_history = resources.files(_RESOURCE_PACKAGE).joinpath(
228        vndk_lib_list_history_name)
229    vndk_lib_extra_list_name = "vndk-lib-extra-list-" + version_str + ".txt"
230    vndk_lib_extra_list = resources.files(_RESOURCE_PACKAGE).joinpath(
231        vndk_lib_extra_list_name)
232
233    if not vndk_lib_list.is_file():
234        logging.warning("Cannot load %s.", vndk_lib_list_name)
235        return None
236
237    if not vndk_lib_extra_list.is_file():
238        logging.warning("Cannot load %s.", vndk_lib_extra_list_name)
239        return None
240
241    vndk_lists = tuple([] for x in tags)
242
243    with vndk_lib_list.open("r") as f:
244        if vndk_lib_list_history.is_file():
245            with vndk_lib_list_history.open("r") as history:
246                _LoadVndkLibraryListsFile(vndk_lists, tags, f, history)
247        else:
248            _LoadVndkLibraryListsFile(vndk_lists, tags, f)
249    with vndk_lib_extra_list.open("r") as f:
250        _LoadVndkLibraryListsFile(vndk_lists, tags, f)
251    return vndk_lists
252