1#!/usr/bin/env python3.4
2#
3# Copyright (C) 2017 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
18import os
19import re
20import shutil
21import subprocess
22import sys
23import tempfile
24
25from utils.const import Constant
26
27ANDROID_BUILD_TOP = os.environ.get('ANDROID_BUILD_TOP')
28if not ANDROID_BUILD_TOP:
29    print 'Run "lunch" command first.'
30    sys.exit(1)
31
32# TODO(trong): use proper packaging without referencing modules from source.
33TEST_VTS_DIR = os.path.join(ANDROID_BUILD_TOP, 'test', 'vts')
34sys.path.append(TEST_VTS_DIR)
35from proto import ComponentSpecificationMessage_pb2 as CompSpecMsg
36from google.protobuf import text_format
37import build_rule_gen_utils as utils
38
39
40class VtsSpecParser(object):
41    """Provides an API to generate a parse .vts spec files."""
42
43    def __init__(self,
44                 package_root=Constant.HAL_PACKAGE_PREFIX,
45                 path_root=Constant.HAL_INTERFACE_PATH):
46        """VtsSpecParser constructor.
47
48        For every unique pair of (hal name, hal version) available under
49        path_root, generates .vts files using hidl-gen.
50
51        Args:
52            tmp_dir: string, temporary directory to which to write .vts files.
53        """
54        self._cache = set()
55        self._tmp_dir = tempfile.mkdtemp()
56        self._package_root = package_root
57        self._path_root = path_root
58        hal_list = self.HalNamesAndVersions()
59
60    def __del__(self):
61        """VtsSpecParser destructor.
62
63        Removes all temporary files that were generated.
64        """
65        print "Removing temp files."
66        if os.path.exists(self._tmp_dir):
67            shutil.rmtree(self._tmp_dir)
68
69    def ImportedPackagesList(self, hal_name, hal_version):
70        """Returns a list of imported packages.
71
72        Args:
73          hal_name: string, name of the hal, e.g. 'vibrator'.
74          hal_version: string, version of the hal, e.g '7.4'
75
76        Returns:
77          list of strings. For example,
78              ['android.hardware.vibrator@1.3', 'android.hidl.base@1.7']
79        """
80        self.GenerateVtsSpecs(hal_name, hal_version)
81        vts_spec_protos = self.VtsSpecProtos(hal_name, hal_version)
82
83        imported_packages = set()
84        for vts_spec in vts_spec_protos:
85            for package in getattr(vts_spec, 'import', []):
86                package = package.split('::')[0]
87                imported_packages.add(package)
88
89        # Exclude the current package and packages with no corresponding libs.
90        exclude_packages = [
91            "android.hidl.base@1.0", "android.hidl.manager@1.0",
92        ]
93
94        return sorted(list(set(imported_packages) - set(exclude_packages)))
95
96    def GenerateVtsSpecs(self, hal_name, hal_version):
97        """Generates VTS specs.
98
99        Uses hidl-gen to generate .vts files under a tmp directory.
100
101        Args:
102          hal_name: string, name of the hal, e.g. 'vibrator'.
103          hal_version: string, version of the hal, e.g '7.4'
104          tmp_dir: string, location to which to write tmp files.
105        """
106        if (hal_name, hal_version) in self._cache:
107            return
108        hidl_gen_cmd = (
109            'hidl-gen -o {TEMP_DIR} -L vts -r {PACKAGE_ROOT}:{PATH_ROOT} '
110            '{PACKAGE_ROOT}.{HAL_NAME}@{HAL_VERSION}').format(
111                TEMP_DIR=self._tmp_dir,
112                PACKAGE_ROOT=self._package_root,
113                PATH_ROOT=self._path_root,
114                HAL_NAME=hal_name,
115                HAL_VERSION=hal_version)
116        subprocess.call(hidl_gen_cmd, shell=True)
117        self._cache.add((hal_name, hal_version))
118
119    def HalNamesAndVersions(self):
120        """Returns a list of hals and versions under hal interface directory.
121
122        Returns:
123            List of tuples of strings containing hal names and hal versions.
124            For example, [('vibrator', '1.3'), ('sensors', '1.7')]
125        """
126        full_path_root = os.path.join(ANDROID_BUILD_TOP, self._path_root)
127        result = set()
128        # Walk through ANDROID_BUILD_TOP/self._path_root and heuristically
129        # figure out all the HAL names and versions in the source tree.
130        for base, dirs, files in os.walk(full_path_root):
131            has_hals = any(f.endswith('.hal') for f in files)
132            if not has_hals:
133                continue
134
135            hal_dir = os.path.relpath(base, full_path_root)
136            # Find the first occurance of version in directory path.
137            match = re.search("(\d+)\.(\d+)", hal_dir)
138            if match and 'example' not in hal_dir:
139                hal_version = match.group(0)
140                # Name of the hal preceds hal version in the directory path.
141                hal_dir = hal_dir[:match.end()]
142                hal_name = os.path.dirname(hal_dir).replace('/', '.')
143                result.add((hal_name, hal_version))
144        return sorted(result)
145
146    def VtsSpecNames(self, hal_name, hal_version):
147        """Returns list of .vts file names for given hal name and version.
148
149        hal_name: string, name of the hal, e.g. 'vibrator'.
150        hal_version: string, version of the hal, e.g '7.4'
151
152        Returns:
153          list of string, .vts files for given hal name and version,
154              e.g. ['Vibrator.vts', 'types.vts']
155        """
156        self.GenerateVtsSpecs(hal_name, hal_version)
157        vts_spec_dir = os.path.join(self._tmp_dir,
158                                    self._package_root.replace('.', '/'),
159                                    utils.HalNameDir(hal_name), hal_version)
160        vts_spec_names = filter(lambda x: x.endswith('.vts'),
161                                os.listdir(vts_spec_dir))
162        return sorted(vts_spec_names)
163
164    def VtsSpecProtos(self, hal_name, hal_version):
165        """Returns list of .vts protos for given hal name and version.
166
167        hal_name: string, name of the hal, e.g. 'vibrator'.
168        hal_version: string, version of the hal, e.g '7.4'
169
170        Returns:
171          list of ComponentSpecificationMessages
172        """
173        self.GenerateVtsSpecs(hal_name, hal_version)
174        vts_spec_dir = os.path.join(self._tmp_dir,
175                                    self._package_root.replace('.', '/'),
176                                    utils.HalNameDir(hal_name), hal_version)
177        vts_spec_protos = []
178        for vts_spec in self.VtsSpecNames(hal_name, hal_version):
179            spec_proto = CompSpecMsg.ComponentSpecificationMessage()
180            vts_spec_path = os.path.join(vts_spec_dir, vts_spec)
181            with open(vts_spec_path, 'r') as spec_file:
182                spec_string = spec_file.read()
183                text_format.Merge(spec_string, spec_proto)
184
185            vts_spec_protos.append(spec_proto)
186        return vts_spec_protos
187