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 glob
19import os
20import sys
21
22import utils
23
24
25class GenBuildFile(object):
26    """Generates Android.mk and Android.bp for VNDK snapshot.
27
28    VNDK snapshot directory structure under prebuilts/vndk/v{version}:
29        {SNAPSHOT_VARIANT}/
30            Android.bp
31            arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT}/
32                shared/
33                    vndk-core/
34                        (VNDK-core libraries, e.g. libbinder.so)
35                    vndk-sp/
36                        (VNDK-SP libraries, e.g. libc++.so)
37            arch-{TARGET_2ND_ARCH}-{TARGET_2ND_ARCH_VARIANT}/
38                shared/
39                    vndk-core/
40                        (VNDK-core libraries, e.g. libbinder.so)
41                    vndk-sp/
42                        (VNDK-SP libraries, e.g. libc++.so)
43            configs/
44                (various *.txt configuration files, e.g. ld.config.*.txt)
45        ... (other {SNAPSHOT_VARIANT}/ directories)
46        common/
47            Android.mk
48            NOTICE_FILES/
49                (license files, e.g. libfoo.so.txt)
50    """
51    INDENT = '    '
52    ETC_MODULES = [
53        'ld.config.txt', 'llndk.libraries.txt', 'vndksp.libraries.txt'
54    ]
55
56    # TODO(b/70312118): Parse from soong build system
57    RELATIVE_INSTALL_PATHS = {'android.hidl.memory@1.0-impl.so': 'hw'}
58
59    def __init__(self, install_dir, vndk_version):
60        """GenBuildFile constructor.
61
62        Args:
63          install_dir: string, absolute path to the prebuilts/vndk/v{version}
64            directory where the build files will be generated.
65          vndk_version: int, VNDK snapshot version (e.g., 27, 28)
66        """
67        self._install_dir = install_dir
68        self._vndk_version = vndk_version
69        self._etc_paths = self._get_etc_paths()
70        self._snapshot_variants = utils.get_snapshot_variants(install_dir)
71        self._mkfile = os.path.join(install_dir, utils.ANDROID_MK_PATH)
72        self._vndk_core = self._parse_lib_list('vndkcore.libraries.txt')
73        self._vndk_sp = self._parse_lib_list(
74            os.path.basename(self._etc_paths['vndksp.libraries.txt']))
75        self._vndk_private = self._parse_lib_list('vndkprivate.libraries.txt')
76
77    def _get_etc_paths(self):
78        """Returns a map of relative file paths for each ETC module."""
79
80        etc_paths = dict()
81        for etc_module in self.ETC_MODULES:
82            etc_pattern = '{}*'.format(os.path.splitext(etc_module)[0])
83            etc_path = glob.glob(
84                os.path.join(self._install_dir, utils.CONFIG_DIR_PATH_PATTERN,
85                             etc_pattern))[0]
86            rel_etc_path = etc_path.replace(self._install_dir, '')[1:]
87            etc_paths[etc_module] = rel_etc_path
88        return etc_paths
89
90    def _parse_lib_list(self, txt_filename):
91        """Returns a map of VNDK library lists per VNDK snapshot variant.
92
93        Args:
94          txt_filename: string, name of snapshot config file
95
96        Returns:
97          dict, e.g. {'arm64': ['libfoo.so', 'libbar.so', ...], ...}
98        """
99        lib_map = dict()
100        for txt_path in utils.find(self._install_dir, [txt_filename]):
101            variant = utils.variant_from_path(txt_path)
102            abs_path_of_txt = os.path.join(self._install_dir, txt_path)
103            with open(abs_path_of_txt, 'r') as f:
104                lib_map[variant] = f.read().strip().split('\n')
105        return lib_map
106
107    def generate_android_mk(self):
108        """Autogenerates Android.mk."""
109
110        etc_buildrules = []
111        for prebuilt in self.ETC_MODULES:
112            etc_buildrules.append(self._gen_etc_prebuilt(prebuilt))
113
114        with open(self._mkfile, 'w') as mkfile:
115            mkfile.write(self._gen_autogen_msg('#'))
116            mkfile.write('\n')
117            mkfile.write('LOCAL_PATH := $(call my-dir)\n')
118            mkfile.write('\n')
119            mkfile.write('\n\n'.join(etc_buildrules))
120            mkfile.write('\n')
121
122    def generate_android_bp(self):
123        """Autogenerates Android.bp file for each VNDK snapshot variant."""
124
125        for variant in self._snapshot_variants:
126            bpfile = os.path.join(self._install_dir, variant, 'Android.bp')
127            vndk_core_buildrules = self._gen_vndk_shared_prebuilts(
128                self._vndk_core[variant], variant, False)
129            vndk_sp_buildrules = self._gen_vndk_shared_prebuilts(
130                self._vndk_sp[variant], variant, True)
131
132            with open(bpfile, 'w') as bpfile:
133                bpfile.write(self._gen_autogen_msg('/'))
134                bpfile.write('\n')
135                bpfile.write(self._gen_bp_phony(variant))
136                bpfile.write('\n')
137                bpfile.write('\n'.join(vndk_core_buildrules))
138                bpfile.write('\n')
139                bpfile.write('\n'.join(vndk_sp_buildrules))
140
141    def _gen_autogen_msg(self, comment_char):
142        return ('{0}{0} THIS FILE IS AUTOGENERATED BY '
143                'development/vndk/snapshot/gen_buildfiles.py\n'
144                '{0}{0} DO NOT EDIT\n'.format(comment_char))
145
146    def _get_versioned_name(self, prebuilt, variant, is_etc):
147        """Returns the VNDK version-specific module name for a given prebuilt.
148
149        The VNDK version-specific module name is defined as follows:
150        For a VNDK shared lib: 'libfoo.so'
151                            -> 'libfoo.vndk.{version}.{variant}.vendor'
152        For an ETC module: 'foo.txt' -> 'foo.{version}.txt'
153
154        Args:
155          prebuilt: string, name of the prebuilt object
156          variant: string, VNDK snapshot variant (e.g. 'arm64')
157          is_etc: bool, True if the LOCAL_MODULE_CLASS of prebuilt is 'ETC'
158        """
159        name, ext = os.path.splitext(prebuilt)
160        if is_etc:
161            versioned_name = '{}.{}{}'.format(name, self._vndk_version, ext)
162        else:
163            versioned_name = '{}.vndk.{}.{}.vendor'.format(
164                name, self._vndk_version, variant)
165
166        return versioned_name
167
168    def _gen_etc_prebuilt(self, prebuilt):
169        """Generates build rule for an ETC prebuilt.
170
171        Args:
172          prebuilt: string, name of ETC prebuilt object
173        """
174        etc_path = self._etc_paths[prebuilt]
175        etc_sub_path = etc_path[etc_path.index('/') + 1:]
176
177        return ('#######################################\n'
178                '# {prebuilt}\n'
179                'include $(CLEAR_VARS)\n'
180                'LOCAL_MODULE := {versioned_name}\n'
181                'LOCAL_SRC_FILES := ../$(TARGET_ARCH)/{etc_sub_path}\n'
182                'LOCAL_MODULE_CLASS := ETC\n'
183                'LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)\n'
184                'LOCAL_MODULE_STEM := $(LOCAL_MODULE)\n'
185                'include $(BUILD_PREBUILT)\n'.format(
186                    prebuilt=prebuilt,
187                    versioned_name=self._get_versioned_name(
188                        prebuilt, None, True),
189                    etc_sub_path=etc_sub_path))
190
191    def _gen_bp_phony(self, variant):
192        """Generates build rule for phony package 'vndk_v{ver}_{variant}'.
193
194        Args:
195          variant: string, VNDK snapshot variant (e.g. 'arm64')
196        """
197        required = []
198        for prebuilts in (self._vndk_core[variant], self._vndk_sp[variant]):
199            for prebuilt in prebuilts:
200                required.append(
201                    self._get_versioned_name(prebuilt, variant, False))
202
203        for prebuilt in self.ETC_MODULES:
204            required.append(self._get_versioned_name(prebuilt, None, True))
205
206        required_str = ['"{}",'.format(prebuilt) for prebuilt in required]
207        required_formatted = '\n{ind}{ind}'.format(
208            ind=self.INDENT).join(required_str)
209        required_buildrule = ('{ind}required: [\n'
210                              '{ind}{ind}{required_formatted}\n'
211                              '{ind}],\n'.format(
212                                  ind=self.INDENT,
213                                  required_formatted=required_formatted))
214
215        return ('phony {{\n'
216                '{ind}name: "vndk_v{ver}_{variant}",\n'
217                '{required_buildrule}'
218                '}}\n'.format(
219                    ind=self.INDENT,
220                    ver=self._vndk_version,
221                    variant=variant,
222                    required_buildrule=required_buildrule))
223
224    def _gen_vndk_shared_prebuilts(self, prebuilts, variant, is_vndk_sp):
225        """Returns list of build rules for given prebuilts.
226
227        Args:
228          prebuilts: list of VNDK shared prebuilts
229          variant: string, VNDK snapshot variant (e.g. 'arm64')
230          is_vndk_sp: bool, True if prebuilts are VNDK_SP libs
231        """
232        build_rules = []
233        for prebuilt in prebuilts:
234            build_rules.append(
235                self._gen_vndk_shared_prebuilt(prebuilt, variant, is_vndk_sp))
236        return build_rules
237
238    def _gen_vndk_shared_prebuilt(self, prebuilt, variant, is_vndk_sp):
239        """Returns build rule for given prebuilt.
240
241        Args:
242          prebuilt: string, name of prebuilt object
243          variant: string, VNDK snapshot variant (e.g. 'arm64')
244          is_vndk_sp: bool, True if prebuilt is a VNDK_SP lib
245        """
246
247        def get_notice_file(prebuilt):
248            """Returns build rule for notice file (attribute 'notice').
249
250            Args:
251              prebuilt: string, name of prebuilt object
252            """
253            notice = ''
254            notice_file_name = '{}.txt'.format(prebuilt)
255            notices_dir = os.path.join(self._install_dir,
256                                       utils.NOTICE_FILES_DIR_PATH)
257            notice_files = utils.find(notices_dir, [notice_file_name])
258            if len(notice_files) > 0:
259                notice = '{ind}notice: "{notice_file_path}",\n'.format(
260                    ind=self.INDENT,
261                    notice_file_path=os.path.join(
262                        '..', utils.NOTICE_FILES_DIR_PATH, notice_files[0]))
263            return notice
264
265        def get_rel_install_path(prebuilt):
266            """Returns build rule for 'relative_install_path'.
267
268            Args:
269              prebuilt: string, name of prebuilt object
270            """
271            rel_install_path = ''
272            if prebuilt in self.RELATIVE_INSTALL_PATHS:
273                path = self.RELATIVE_INSTALL_PATHS[prebuilt]
274                rel_install_path += ('{ind}relative_install_path: "{path}",\n'
275                                     .format(ind=self.INDENT, path=path))
276            return rel_install_path
277
278        def get_arch_srcs(prebuilt, variant):
279            """Returns build rule for arch specific srcs.
280
281            e.g.,
282                arch: {
283                    arm: {
284                        srcs: ["..."]
285                    },
286                    arm64: {
287                        srcs: ["..."]
288                    },
289                }
290
291            Args:
292              prebuilt: string, name of prebuilt object
293              variant: string, VNDK snapshot variant (e.g. 'arm64')
294            """
295            arch_srcs = '{ind}arch: {{\n'.format(ind=self.INDENT)
296            variant_path = os.path.join(self._install_dir, variant)
297            src_paths = utils.find(variant_path, [prebuilt])
298            for src in sorted(src_paths):
299                arch_srcs += ('{ind}{ind}{arch}: {{\n'
300                              '{ind}{ind}{ind}srcs: ["{src}"],\n'
301                              '{ind}{ind}}},\n'.format(
302                                  ind=self.INDENT,
303                                  arch=utils.arch_from_path(
304                                      os.path.join(variant, src)),
305                                  src=src))
306            arch_srcs += '{ind}}},\n'.format(ind=self.INDENT)
307            return arch_srcs
308
309        name = os.path.splitext(prebuilt)[0]
310        vendor_available = str(
311            prebuilt not in self._vndk_private[variant]).lower()
312        if is_vndk_sp:
313            vndk_sp = '{ind}{ind}support_system_process: true,\n'.format(
314                ind=self.INDENT)
315        else:
316            vndk_sp = ''
317        notice = get_notice_file(prebuilt)
318        rel_install_path = get_rel_install_path(prebuilt)
319        arch_srcs = get_arch_srcs(prebuilt, variant)
320
321        return ('vndk_prebuilt_shared {{\n'
322                '{ind}name: "{name}",\n'
323                '{ind}version: "{ver}",\n'
324                '{ind}target_arch: "{target_arch}",\n'
325                '{ind}vendor_available: {vendor_available},\n'
326                '{ind}vndk: {{\n'
327                '{ind}{ind}enabled: true,\n'
328                '{vndk_sp}'
329                '{ind}}},\n'
330                '{notice}'
331                '{rel_install_path}'
332                '{arch_srcs}'
333                '}}\n'.format(
334                    ind=self.INDENT,
335                    name=name,
336                    ver=self._vndk_version,
337                    vendor_available=vendor_available,
338                    target_arch=variant,
339                    vndk_sp=vndk_sp,
340                    notice=notice,
341                    rel_install_path=rel_install_path,
342                    arch_srcs=arch_srcs))
343
344
345def main():
346    """For local testing purposes.
347
348    Note: VNDK snapshot must be already installed under
349      prebuilts/vndk/v{version}.
350    """
351    ANDROID_BUILD_TOP = utils.get_android_build_top()
352    PREBUILTS_VNDK_DIR = utils.join_realpath(ANDROID_BUILD_TOP,
353                                             'prebuilts/vndk')
354
355    vndk_version = 27  # set appropriately
356    install_dir = os.path.join(PREBUILTS_VNDK_DIR, 'v{}'.format(vndk_version))
357
358    buildfile_generator = GenBuildFile(install_dir, vndk_version)
359    buildfile_generator.generate_android_mk()
360    buildfile_generator.generate_android_bp()
361
362
363if __name__ == '__main__':
364    main()
365