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 argparse
19import glob
20import json
21import logging
22import os
23import sys
24
25import utils
26
27
28class GenBuildFile(object):
29    """Generates Android.bp for VNDK snapshot.
30
31    VNDK snapshot directory structure under prebuilts/vndk/v{version}:
32        Android.bp
33        {SNAPSHOT_ARCH}/
34            Android.bp
35            arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT}/
36                shared/
37                    vndk-core/
38                        (VNDK-core libraries, e.g. libbinder.so)
39                    vndk-sp/
40                        (VNDK-SP libraries, e.g. libc++.so)
41            arch-{TARGET_2ND_ARCH}-{TARGET_2ND_ARCH_VARIANT}/
42                shared/
43                    vndk-core/
44                        (VNDK-core libraries, e.g. libbinder.so)
45                    vndk-sp/
46                        (VNDK-SP libraries, e.g. libc++.so)
47            binder32/
48                (This directory is newly introduced in v28 (Android P) to hold
49                prebuilts built for 32-bit binder interface.)
50                Android.bp
51                arch-{TARGET_ARCH}-{TARGE_ARCH_VARIANT}/
52                    ...
53            configs/
54                (various *.txt configuration files, e.g. ld.config.*.txt)
55        ... (other {SNAPSHOT_ARCH}/ directories)
56        common/
57            Android.bp
58            NOTICE_FILES/
59                (license files, e.g. libfoo.so.txt)
60    """
61    INDENT = '    '
62    ETC_MODULES = [
63        'ld.config.txt',
64        'llndk.libraries.txt',
65        'vndksp.libraries.txt',
66        'vndkcore.libraries.txt',
67        'vndkprivate.libraries.txt'
68    ]
69
70    def __init__(self, install_dir, vndk_version):
71        """GenBuildFile constructor.
72
73        Args:
74          install_dir: string, absolute path to the prebuilts/vndk/v{version}
75            directory where the build files will be generated.
76          vndk_version: int, VNDK snapshot version (e.g., 27, 28)
77        """
78        self._install_dir = install_dir
79        self._vndk_version = vndk_version
80        self._etc_paths = self._get_etc_paths()
81        self._snapshot_archs = utils.get_snapshot_archs(install_dir)
82        self._root_bpfile = os.path.join(install_dir, utils.ROOT_BP_PATH)
83        self._common_bpfile = os.path.join(install_dir, utils.COMMON_BP_PATH)
84        self._vndk_core = self._parse_lib_list(
85            os.path.basename(self._etc_paths['vndkcore.libraries.txt']))
86        self._vndk_sp = self._parse_lib_list(
87            os.path.basename(self._etc_paths['vndksp.libraries.txt']))
88        self._vndk_private = self._parse_lib_list(
89            os.path.basename(self._etc_paths['vndkprivate.libraries.txt']))
90        self._modules_with_notice = self._get_modules_with_notice()
91
92    def _get_etc_paths(self):
93        """Returns a map of relative file paths for each ETC module."""
94
95        etc_paths = dict()
96        for etc_module in self.ETC_MODULES:
97            etc_pattern = '{}*'.format(os.path.splitext(etc_module)[0])
98            globbed = glob.glob(
99                os.path.join(self._install_dir, utils.CONFIG_DIR_PATH_PATTERN,
100                             etc_pattern))
101            if len(globbed) > 0:
102                rel_etc_path = globbed[0].replace(self._install_dir, '')[1:]
103                etc_paths[etc_module] = rel_etc_path
104        return etc_paths
105
106    def _parse_lib_list(self, txt_filename):
107        """Returns a map of VNDK library lists per VNDK snapshot arch.
108
109        Args:
110          txt_filename: string, name of snapshot config file
111
112        Returns:
113          dict, e.g. {'arm64': ['libfoo.so', 'libbar.so', ...], ...}
114        """
115        lib_map = dict()
116        for txt_path in utils.find(self._install_dir, [txt_filename]):
117            arch = utils.snapshot_arch_from_path(txt_path)
118            abs_path_of_txt = os.path.join(self._install_dir, txt_path)
119            with open(abs_path_of_txt, 'r') as f:
120                lib_map[arch] = f.read().strip().split('\n')
121        return lib_map
122
123    def _get_modules_with_notice(self):
124        """Returns a list of modules that have associated notice files. """
125        notice_paths = glob.glob(
126            os.path.join(self._install_dir, utils.NOTICE_FILES_DIR_PATH,
127                         '*.txt'))
128        return [os.path.splitext(os.path.basename(p))[0] for p in notice_paths]
129
130    def generate_root_android_bp(self):
131        """Autogenerates Android.bp."""
132
133        logging.info('Generating Android.bp for snapshot v{}'.format(
134            self._vndk_version))
135        etc_buildrules = []
136        for prebuilt in self.ETC_MODULES:
137            # ld.config.VER.txt is not installed as a prebuilt but is built and
138            # installed from thesource tree at the time the VNDK snapshot is
139            # installed to the system.img.
140            if prebuilt == 'ld.config.txt':
141                continue
142            etc_buildrules.append(self._gen_etc_prebuilt(prebuilt))
143
144        with open(self._root_bpfile, 'w') as bpfile:
145            bpfile.write(self._gen_autogen_msg('/'))
146            bpfile.write('\n')
147            bpfile.write('\n'.join(etc_buildrules))
148            bpfile.write('\n')
149
150        logging.info('Successfully generated {}'.format(self._root_bpfile))
151
152    def generate_common_android_bp(self):
153        """Autogenerates common/Android.bp."""
154
155        logging.info('Generating common/Android.bp for snapshot v{}'.format(
156            self._vndk_version))
157        with open(self._common_bpfile, 'w') as bpfile:
158            bpfile.write(self._gen_autogen_msg('/'))
159            for module in self._modules_with_notice:
160                bpfile.write('\n')
161                bpfile.write(self._gen_notice_filegroup(module))
162
163    def generate_android_bp(self):
164        """Autogenerates Android.bp."""
165
166        def gen_for_variant(arch, is_binder32=False):
167            """Generates Android.bp file for specified VNDK snapshot variant.
168
169            A VNDK snapshot variant is defined by the TARGET_ARCH and binder
170            bitness. Example snapshot variants:
171                vndk_v{ver}_arm:            {arch: arm, binder: 64-bit}
172                vndk_v{ver}_arm_binder32:   {arch: arm, binder: 32-bit}
173
174            Args:
175              arch: string, VNDK snapshot arch (e.g. 'arm64')
176              is_binder32: bool, True if binder interface is 32-bit
177            """
178            binder32_suffix = '_{}'.format(
179                utils.BINDER32) if is_binder32 else ''
180            logging.info('Generating Android.bp for vndk_v{}_{}{}'.format(
181                self._vndk_version, arch, binder32_suffix))
182
183            src_root = os.path.join(self._install_dir, arch)
184            module_names_txt = os.path.join(
185                src_root, "configs", "module_names.txt")
186            module_names = dict()
187            try:
188                with open(module_names_txt, 'r') as f:
189                    # Remove empty lines from module_names_txt
190                    module_list = filter(None, f.read().split('\n'))
191                for module in module_list:
192                    lib, name = module.split(' ')
193                    module_names[lib] = name
194            except IOError:
195                # If module_names.txt doesn't exist, ignore it and parse
196                # module names out from .so filenames. (old snapshot)
197                pass
198
199            variant_subpath = arch
200            # For O-MR1 snapshot (v27), 32-bit binder prebuilts are not
201            # isolated in separate 'binder32' subdirectory.
202            if is_binder32 and self._vndk_version >= 28:
203                variant_subpath = os.path.join(arch, utils.BINDER32)
204            variant_path = os.path.join(self._install_dir, variant_subpath)
205            bpfile_path = os.path.join(variant_path, 'Android.bp')
206
207            vndk_core_buildrules = self._gen_vndk_shared_prebuilts(
208                self._vndk_core[arch],
209                arch,
210                is_vndk_sp=False,
211                is_binder32=is_binder32,
212                module_names=module_names)
213            vndk_sp_buildrules = self._gen_vndk_shared_prebuilts(
214                self._vndk_sp[arch],
215                arch,
216                is_vndk_sp=True,
217                is_binder32=is_binder32,
218                module_names=module_names)
219
220            with open(bpfile_path, 'w') as bpfile:
221                bpfile.write(self._gen_autogen_msg('/'))
222                bpfile.write('\n')
223                bpfile.write(self._gen_bp_phony(arch, is_binder32, module_names))
224                bpfile.write('\n')
225                bpfile.write('\n'.join(vndk_core_buildrules))
226                bpfile.write('\n')
227                bpfile.write('\n'.join(vndk_sp_buildrules))
228
229            variant_include_path = os.path.join(variant_path, 'include')
230            include_path = os.path.join(self._install_dir, arch, 'include')
231            if os.path.isdir(include_path) and variant_include_path != include_path:
232                os.symlink(os.path.relpath(include_path, variant_path),
233                    variant_include_path)
234
235            logging.info('Successfully generated {}'.format(bpfile_path))
236
237        if self._vndk_version == 27:
238            # For O-MR1 snapshot (v27), 32-bit binder prebuilts are not
239            # isolated in separate 'binder32' subdirectory.
240            for arch in self._snapshot_archs:
241                if arch in ('arm', 'x86'):
242                    gen_for_variant(arch, is_binder32=True)
243                else:
244                    gen_for_variant(arch)
245            return
246
247        for arch in self._snapshot_archs:
248            if os.path.isdir(
249                    os.path.join(self._install_dir, arch, utils.BINDER32)):
250                gen_for_variant(arch, is_binder32=True)
251            gen_for_variant(arch)
252
253    def _gen_autogen_msg(self, comment_char):
254        return ('{0}{0} THIS FILE IS AUTOGENERATED BY '
255                'development/vndk/snapshot/gen_buildfiles.py\n'
256                '{0}{0} DO NOT EDIT\n'.format(comment_char))
257
258    def _get_versioned_name(self,
259                            prebuilt,
260                            arch,
261                            is_etc=False,
262                            is_binder32=False,
263                            module_names=None):
264        """Returns the VNDK version-specific module name for a given prebuilt.
265
266        The VNDK version-specific module name is defined as follows:
267        For a VNDK shared lib: 'libfoo.so'
268            if binder is 32-bit:
269                'libfoo.vndk.{version}.{arch}.binder32.vendor'
270            else:
271                'libfoo.vndk.{version}.{arch}.vendor'
272        For an ETC module: 'foo.txt' -> 'foo.{version}.txt'
273
274        Args:
275          prebuilt: string, name of the prebuilt object
276          arch: string, VNDK snapshot arch (e.g. 'arm64')
277          is_etc: bool, True if the LOCAL_MODULE_CLASS of prebuilt is 'ETC'
278          is_binder32: bool, True if binder interface is 32-bit
279          module_names: dict, module names for given prebuilts
280        """
281        if is_etc:
282            name, ext = os.path.splitext(prebuilt)
283            versioned_name = '{}.{}{}'.format(name, self._vndk_version, ext)
284        else:
285            module_names = module_names or dict()
286            if prebuilt in module_names:
287                name = module_names[prebuilt]
288            else:
289                name = os.path.splitext(prebuilt)[0]
290            binder_suffix = '.{}'.format(utils.BINDER32) if is_binder32 else ''
291            versioned_name = '{}.vndk.{}.{}{}.vendor'.format(
292                name, self._vndk_version, arch, binder_suffix)
293
294        return versioned_name
295
296    def _gen_etc_prebuilt(self, prebuilt):
297        """Generates build rule for an ETC prebuilt.
298
299        Args:
300          prebuilt: string, name of ETC prebuilt object
301        """
302        etc_path = self._etc_paths[prebuilt]
303        etc_sub_path = etc_path[etc_path.index('/') + 1:]
304
305        prebuilt_etc = ('prebuilt_etc {{\n'
306                        '{ind}name: "{versioned_name}",\n'
307                        '{ind}target: {{\n'.format(
308                            ind=self.INDENT,
309                            versioned_name=self._get_versioned_name(
310                                prebuilt, None, is_etc=True)))
311        for arch in self._snapshot_archs:
312            prebuilt_etc += ('{ind}{ind}android_{arch}: {{\n'
313                             '{ind}{ind}{ind}src: "{arch}/{etc_sub_path}",\n'
314                             '{ind}{ind}}},\n'.format(
315                                 ind=self.INDENT,
316                                 arch=arch,
317                                 etc_sub_path=etc_sub_path))
318        prebuilt_etc += ('{ind}}},\n'
319                         '}}\n'.format(ind=self.INDENT))
320        return prebuilt_etc
321
322    def _gen_notice_filegroup(self, module):
323        """Generates a notice filegroup build rule for a given module.
324
325        Args:
326          notice: string, module name
327        """
328        return ('filegroup {{\n'
329                '{ind}name: "{filegroup_name}",\n'
330                '{ind}srcs: ["{notice_dir}/{module}.txt"],\n'
331                '}}\n'.format(
332                    ind=self.INDENT,
333                    filegroup_name=self._get_notice_filegroup_name(module),
334                    module=module,
335                    notice_dir=utils.NOTICE_FILES_DIR_NAME))
336
337    def _get_notice_filegroup_name(self, module):
338        """ Gets a notice filegroup module name for a given module.
339
340        Args:
341          notice: string, module name.
342        """
343        return 'vndk-v{ver}-{module}-notice'.format(
344            ver=self._vndk_version, module=module)
345
346    def _gen_bp_phony(self, arch, is_binder32, module_names):
347        """Generates build rule for phony package 'vndk_v{ver}_{arch}'.
348
349        Args:
350          arch: string, VNDK snapshot arch (e.g. 'arm64')
351          is_binder32: bool, True if binder interface is 32-bit
352          module_names: dict, module names for given prebuilts
353        """
354
355        required = []
356        for prebuilts in (self._vndk_core[arch], self._vndk_sp[arch]):
357            for prebuilt in prebuilts:
358                required.append(
359                    self._get_versioned_name(
360                        prebuilt,
361                        arch,
362                        is_binder32=is_binder32,
363                        module_names=module_names))
364
365        for prebuilt in self.ETC_MODULES:
366            required.append(
367                self._get_versioned_name(
368                    prebuilt,
369                    None,
370                    is_etc=True,
371                    is_binder32=is_binder32,
372                    module_names=module_names))
373
374        required_str = ['"{}",'.format(prebuilt) for prebuilt in required]
375        required_formatted = '\n{ind}{ind}'.format(
376            ind=self.INDENT).join(required_str)
377        required_buildrule = ('{ind}required: [\n'
378                              '{ind}{ind}{required_formatted}\n'
379                              '{ind}],\n'.format(
380                                  ind=self.INDENT,
381                                  required_formatted=required_formatted))
382        binder_suffix = '_{}'.format(utils.BINDER32) if is_binder32 else ''
383
384        return ('phony {{\n'
385                '{ind}name: "vndk_v{ver}_{arch}{binder_suffix}",\n'
386                '{required_buildrule}'
387                '}}\n'.format(
388                    ind=self.INDENT,
389                    ver=self._vndk_version,
390                    arch=arch,
391                    binder_suffix=binder_suffix,
392                    required_buildrule=required_buildrule))
393
394    def _gen_vndk_shared_prebuilts(self,
395                                   prebuilts,
396                                   arch,
397                                   is_vndk_sp,
398                                   is_binder32,
399                                   module_names):
400        """Returns list of build rules for given prebuilts.
401
402        Args:
403          prebuilts: list of VNDK shared prebuilts
404          arch: string, VNDK snapshot arch (e.g. 'arm64')
405          is_vndk_sp: bool, True if prebuilts are VNDK_SP libs
406          is_binder32: bool, True if binder interface is 32-bit
407          module_names: dict, module names for given prebuilts
408        """
409
410        build_rules = []
411        for prebuilt in prebuilts:
412            build_rules.append(
413                self._gen_vndk_shared_prebuilt(
414                    prebuilt,
415                    arch,
416                    is_vndk_sp=is_vndk_sp,
417                    is_binder32=is_binder32,
418                    module_names=module_names))
419        return build_rules
420
421    def _gen_vndk_shared_prebuilt(self,
422                                  prebuilt,
423                                  arch,
424                                  is_vndk_sp,
425                                  is_binder32,
426                                  module_names):
427        """Returns build rule for given prebuilt.
428
429        Args:
430          prebuilt: string, name of prebuilt object
431          arch: string, VNDK snapshot arch (e.g. 'arm64')
432          is_vndk_sp: bool, True if prebuilt is a VNDK_SP lib
433          is_binder32: bool, True if binder interface is 32-bit
434          module_names: dict, module names for given prebuilts
435        """
436
437        def get_notice_file(prebuilt):
438            """Returns build rule for notice file (attribute 'notice').
439
440            Args:
441              prebuilt: string, name of prebuilt object
442            """
443            notice = ''
444            if prebuilt in self._modules_with_notice:
445                notice = '{ind}notice: ":{notice_filegroup}",\n'.format(
446                    ind=self.INDENT,
447                    notice_filegroup=self._get_notice_filegroup_name(prebuilt))
448            return notice
449
450        def get_arch_props(prebuilt, arch):
451            """Returns build rule for arch specific srcs.
452
453            e.g.,
454                arch: {
455                    arm: {
456                        export_include_dirs: ["..."],
457                        export_system_include_dirs: ["..."],
458                        export_flags: ["..."],
459                        relative_install_path: "...",
460                        srcs: ["..."]
461                    },
462                    arm64: {
463                        export_include_dirs: ["..."],
464                        export_system_include_dirs: ["..."],
465                        export_flags: ["..."],
466                        relative_install_path: "...",
467                        srcs: ["..."]
468                    },
469                }
470
471            Args:
472              prebuilt: string, name of prebuilt object
473              arch: string, VNDK snapshot arch (e.g. 'arm64')
474            """
475            arch_props = '{ind}arch: {{\n'.format(ind=self.INDENT)
476            src_paths = utils.find(src_root, [prebuilt])
477            # filter out paths under 'binder32' subdirectory
478            src_paths = filter(lambda src: not src.startswith(utils.BINDER32),
479                               src_paths)
480
481            def list_to_prop_value(l, name):
482                if len(l) == 0:
483                    return ''
484                dirs=',\n{ind}{ind}{ind}{ind}'.format(
485                    ind=self.INDENT).join(['"%s"' % d for d in l])
486                return ('{ind}{ind}{ind}{name}: [\n'
487                        '{ind}{ind}{ind}{ind}{dirs},\n'
488                        '{ind}{ind}{ind}],\n'.format(
489                            ind=self.INDENT,
490                            dirs=dirs,
491                            name=name))
492
493            for src in sorted(src_paths):
494                include_dirs = ''
495                system_include_dirs = ''
496                flags = ''
497                relative_install_path = ''
498                prop_path = os.path.join(src_root, src+'.json')
499                props = dict()
500                try:
501                    with open(prop_path, 'r') as f:
502                        props = json.loads(f.read())
503                    os.unlink(prop_path)
504                except:
505                    # TODO(b/70312118): Parse from soong build system
506                    if prebuilt == 'android.hidl.memory@1.0-impl.so':
507                        props['RelativeInstallPath'] = 'hw'
508                if 'ExportedDirs' in props:
509                    l = ['include/%s' % d for d in props['ExportedDirs']]
510                    include_dirs = list_to_prop_value(l, 'export_include_dirs')
511                if 'ExportedSystemDirs' in props:
512                    l = ['include/%s' % d for d in props['ExportedSystemDirs']]
513                    system_include_dirs = list_to_prop_value(l, 'export_system_include_dirs')
514                if 'ExportedFlags' in props:
515                    flags = list_to_prop_value(props['ExportedFlags'], 'export_flags')
516                if 'RelativeInstallPath' in props:
517                    relative_install_path = ('{ind}{ind}{ind}'
518                        'relative_install_path: "{path}",\n').format(
519                            ind=self.INDENT,
520                            path=props['RelativeInstallPath'])
521
522                arch_props += ('{ind}{ind}{arch}: {{\n'
523                               '{include_dirs}'
524                               '{system_include_dirs}'
525                               '{flags}'
526                               '{relative_install_path}'
527                               '{ind}{ind}{ind}srcs: ["{src}"],\n'
528                               '{ind}{ind}}},\n').format(
529                                  ind=self.INDENT,
530                                  arch=utils.prebuilt_arch_from_path(
531                                      os.path.join(arch, src)),
532                                  include_dirs=include_dirs,
533                                  system_include_dirs=system_include_dirs,
534                                  flags=flags,
535                                  relative_install_path=relative_install_path,
536                                  src=src)
537            arch_props += '{ind}}},\n'.format(ind=self.INDENT)
538            return arch_props
539
540        src_root = os.path.join(self._install_dir, arch)
541        # For O-MR1 snapshot (v27), 32-bit binder prebuilts are not
542        # isolated in separate 'binder32' subdirectory.
543        if is_binder32 and self._vndk_version >= 28:
544            src_root = os.path.join(src_root, utils.BINDER32)
545
546        if prebuilt in module_names:
547            name = module_names[prebuilt]
548        else:
549            name = os.path.splitext(prebuilt)[0]
550        vendor_available = str(
551            prebuilt not in self._vndk_private[arch]).lower()
552
553        vndk_sp = ''
554        if is_vndk_sp:
555            vndk_sp = '{ind}{ind}support_system_process: true,\n'.format(
556                ind=self.INDENT)
557
558        notice = get_notice_file(prebuilt)
559        arch_props = get_arch_props(prebuilt, arch)
560
561        binder32bit = ''
562        if is_binder32:
563            binder32bit = '{ind}binder32bit: true,\n'.format(ind=self.INDENT)
564
565        return ('vndk_prebuilt_shared {{\n'
566                '{ind}name: "{name}",\n'
567                '{ind}version: "{ver}",\n'
568                '{ind}target_arch: "{target_arch}",\n'
569                '{binder32bit}'
570                '{ind}vendor_available: {vendor_available},\n'
571                '{ind}vndk: {{\n'
572                '{ind}{ind}enabled: true,\n'
573                '{vndk_sp}'
574                '{ind}}},\n'
575                '{notice}'
576                '{arch_props}'
577                '}}\n'.format(
578                    ind=self.INDENT,
579                    name=name,
580                    ver=self._vndk_version,
581                    target_arch=arch,
582                    binder32bit=binder32bit,
583                    vendor_available=vendor_available,
584                    vndk_sp=vndk_sp,
585                    notice=notice,
586                    arch_props=arch_props))
587
588
589def get_args():
590    parser = argparse.ArgumentParser()
591    parser.add_argument(
592        'vndk_version',
593        type=int,
594        help='VNDK snapshot version to install, e.g. "27".')
595    parser.add_argument(
596        '-v',
597        '--verbose',
598        action='count',
599        default=0,
600        help='Increase output verbosity, e.g. "-v", "-vv".')
601    return parser.parse_args()
602
603
604def main():
605    """For local testing purposes.
606
607    Note: VNDK snapshot must be already installed under
608      prebuilts/vndk/v{version}.
609    """
610    ANDROID_BUILD_TOP = utils.get_android_build_top()
611    PREBUILTS_VNDK_DIR = utils.join_realpath(ANDROID_BUILD_TOP,
612                                             'prebuilts/vndk')
613
614    args = get_args()
615    vndk_version = args.vndk_version
616    install_dir = os.path.join(PREBUILTS_VNDK_DIR, 'v{}'.format(vndk_version))
617    if not os.path.isdir(install_dir):
618        raise ValueError(
619            'Please provide valid VNDK version. {} does not exist.'
620            .format(install_dir))
621    utils.set_logging_config(args.verbose)
622
623    buildfile_generator = GenBuildFile(install_dir, vndk_version)
624    buildfile_generator.generate_root_android_bp()
625    buildfile_generator.generate_common_android_bp()
626    buildfile_generator.generate_android_bp()
627
628    logging.info('Done.')
629
630
631if __name__ == '__main__':
632    main()
633