1#!/usr/bin/env python
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
20
21from utils.const import Constant
22from vts_spec_parser import VtsSpecParser
23import build_rule_gen_utils as utils
24
25
26class BuildRuleGen(object):
27    """Build rule generator for test/vts-testcase/hal."""
28    _ANDROID_BUILD_TOP = os.environ.get('ANDROID_BUILD_TOP')
29    if not _ANDROID_BUILD_TOP:
30        print 'Run "lunch" command first.'
31        sys.exit(1)
32    _PROJECT_PATH = os.path.join(_ANDROID_BUILD_TOP, 'test', 'vts-testcase',
33                                 'hal')
34    _VTS_BUILD_TEMPLATE = os.path.join(_PROJECT_PATH, 'script', 'build',
35                                       'template', 'vts_build_template.bp')
36
37    def __init__(self, warning_header, package_root, path_root):
38        """BuildRuleGen constructor.
39
40        Args:
41            warning_header: string, warning header for every generated file.
42            package_root: string, prefix of the hal package.
43            path_root: string, root path that stores the hal definition.
44        """
45        self._warning_header = warning_header
46        self._vts_spec_parser = VtsSpecParser(package_root, path_root)
47        self._package_root = package_root
48        self._path_root = path_root
49
50    def UpdateBuildRule(self, test_config_dir):
51        """Updates build rules under the configuration directory.
52
53        Args:
54            test_config_dir: string, directory storing the configurations.
55
56        Returns:
57            a list of strings where each string contains the path of a checked
58            or updated build file.
59            True if any file is updated, False otherwise
60        """
61        hal_list = self._vts_spec_parser.HalNamesAndVersions()
62        gen_file_paths, updated_file_paths, updated = self.UpdateHalDirBuildRule(
63            hal_list, test_config_dir)
64        updated |= utils.RemoveFilesInDirIf(
65            os.path.join(self._ANDROID_BUILD_TOP, test_config_dir),
66            lambda x: self._IsAutoGenerated(x) and (x not in gen_file_paths))
67        return updated_file_paths, updated
68
69    def UpdateHalDirBuildRule(self, hal_list, test_config_dir):
70        """Updates build rules for vts drivers/profilers.
71
72        Updates vts drivers/profilers for each pair of (hal_name, hal_version)
73        in hal_list.
74
75        Args:
76            hal_list: list of tuple of strings. For example,
77                [('vibrator', '1.3'), ('sensors', '1.7')]
78            test_config_dir: string, directory storing the configurations.
79
80        Returns:
81            a list of strings where each string contains the path of a checked
82            build file,
83            a list of strings where each string contains the path of an updated
84            build file,
85            boolean, True if any file is updated, False otherwise
86        """
87        checked_file_paths = []
88        updated_file_paths = []
89        ever_updated = False
90        for target in hal_list:
91            hal_name = target[0]
92            hal_version = target[1]
93
94            hal_dir = os.path.join(
95                self._ANDROID_BUILD_TOP, test_config_dir,
96                utils.HalNameDir(hal_name), utils.HalVerDir(hal_version))
97
98            file_path = os.path.join(hal_dir, 'build', 'Android.bp')
99            updated = utils.WriteBuildRule(file_path, self._VtsBuildRuleFromTemplate(
100                self._VTS_BUILD_TEMPLATE, hal_name, hal_version))
101            checked_file_paths.append(file_path)
102            if updated:
103                updated_file_paths.append(file_path)
104                ever_updated = True
105        return checked_file_paths, updated_file_paths, ever_updated
106
107    def _VtsBuildRuleFromTemplate(self, template_path, hal_name, hal_version):
108        """Returns build rules in string form by filling out a template.
109
110        Reads template from given path and fills it out.
111
112        Args:
113          template_path: string, path to build rule template file.
114          hal_name: string, name of the hal, e.g. 'vibrator'.
115          hal_version: string, version of the hal, e.g '7.4'
116
117        Returns:
118          string, complete build rules in string form
119        """
120        with open(template_path) as template_file:
121            build_template = str(template_file.read())
122        return self._FillOutBuildRuleTemplate(hal_name, hal_version,
123                                              build_template)
124
125    def _FillOutBuildRuleTemplate(self, hal_name, hal_version, template):
126        """Returns build rules in string form by filling out given template.
127
128        Args:
129          hal_name: string, name of the hal, e.g. 'vibrator'.
130          hal_version: string, version of the hal, e.g '7.4'
131          template: string, build rule template to fill out.
132
133        Returns:
134          string, complete build rule in string form.
135        """
136        package_root_dir = self._package_root.replace(".", "/")
137
138        def GeneratedOutput(hal_name, hal_version, extension):
139            """Formats list of vts spec names into a string.
140
141            Formats list of vts spec name for given hal_name, hal_version
142            into a string that can be inserted into build template.
143
144            Args:
145              hal_name: string, name of the hal, e.g. 'vibrator'.
146              hal_version: string, version of the hal, e.g '7.4'
147              extension: string, extension of files e.g. '.cpp'.
148
149            Returns:
150              string, to be inserted into build template.
151            """
152            result = []
153            vts_spec_names = self._vts_spec_parser.VtsSpecNames(hal_name,
154                                                                hal_version)
155            for vts_spec in vts_spec_names:
156                result.append('%s/%s/%s/%s%s' %
157                              (package_root_dir, utils.HalNameDir(hal_name),
158                               hal_version, vts_spec, extension))
159            return ListToBuildString(result, 2)
160
161        def ImportedPackages(vts_pkg_type, imported_packages):
162            """Formats list of imported packages into a string.
163
164            Formats list of imported packages for given hal_name, hal_version
165            into a string that can be inserted into build template.
166
167            Args:
168              vts_pkg_type: string 'driver' or 'profiler'
169              imported_packages: list of imported packages
170
171            Returns:
172              string, to be inserted into build template.
173            """
174            result = []
175            for package in imported_packages:
176                if re.match(Constant.HAL_PACKAGE_NAME_PATTERN, package):
177                    vts_pkg_name = package + '-vts.' + vts_pkg_type
178                    result.append(vts_pkg_name)
179                else:
180                    result.append(package)
181            return ListToBuildString(result, 2)
182
183        build_rule = self._warning_header + template
184        build_rule = build_rule.replace('{HAL_NAME}', hal_name)
185        build_rule = build_rule.replace('{HAL_NAME_DIR}',
186                                        utils.HalNameDir(hal_name))
187        build_rule = build_rule.replace('{HAL_VERSION}', hal_version)
188        build_rule = build_rule.replace('{PACKAGE_ROOT}', self._package_root)
189        build_rule = build_rule.replace('{PACKAGE_ROOT_DIR}', package_root_dir)
190        build_rule = build_rule.replace(
191            '{HIDL_GEN_ARGS}',
192            "-r %s:%s" % (self._package_root, self._path_root))
193        build_rule = build_rule.replace(
194            '{GENERATED_VTS_SPECS}',
195            GeneratedOutput(hal_name, hal_version, ''))
196        build_rule = build_rule.replace(
197            '{GENERATED_SOURCES}',
198            GeneratedOutput(hal_name, hal_version, '.cpp'))
199        build_rule = build_rule.replace(
200            '{GENERATED_HEADERS}', GeneratedOutput(hal_name, hal_version, '.h'))
201
202        imported_packages = self._vts_spec_parser.ImportedPackagesList(
203            hal_name, hal_version)
204        build_rule = build_rule.replace(
205            '{IMPORTED_DRIVER_PACKAGES}',
206            ImportedPackages('driver', imported_packages))
207        build_rule = build_rule.replace(
208            '{IMPORTED_PROFILER_PACKAGES}',
209            ImportedPackages('profiler', imported_packages))
210
211        this_package = '%s.%s@%s' % (self._package_root, hal_name, hal_version)
212        imported_packages.append(this_package)
213        hal_libs = sorted(imported_packages)
214
215        build_rule = build_rule.replace(
216            '{HAL_LIBS}', ListToBuildString(hal_libs, 2))
217
218        return build_rule
219
220    def _IsAutoGenerated(self, abs_file_path):
221        """Checks if file was auto-generated.
222
223        Args:
224            abs_file_path: string, absolute file path.
225
226        Returns:
227            True iff file was auto-generated by BuildRuleGen.
228        """
229        [dir_name, file_name] = os.path.split(abs_file_path)
230        if file_name != 'Android.bp':
231            return False
232        with open(abs_file_path) as myfile:
233            return self._warning_header in myfile.read()
234
235def ListToBuildString(lst, indent_lvl):
236    """Formats a list of item into a string to be inserted into build rule.
237
238    Args:
239        lst: string list, e.g. [a, b, c].
240        indent_lvl: int, indentation level of the output list.
241
242    Returns:
243        string to be inserted into build rule.
244    """
245    single_indent = '    '
246    indent = single_indent * indent_lvl
247    result = ''.join(map(lambda x: '\n%s"%s",' % (indent, x), sorted(lst)))
248    if result:
249        result += '\n' + single_indent * (indent_lvl - 1)
250    return result
251