普通文本  |  365行  |  13.95 KB

#!/usr/bin/env python
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import glob
import os
import sys

import utils


class GenBuildFile(object):
    """Generates Android.mk and Android.bp for VNDK snapshot.

    VNDK snapshot directory structure under prebuilts/vndk/v{version}:
        {SNAPSHOT_VARIANT}/
            Android.bp
            arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT}/
                shared/
                    vndk-core/
                        (VNDK-core libraries, e.g. libbinder.so)
                    vndk-sp/
                        (VNDK-SP libraries, e.g. libc++.so)
            arch-{TARGET_2ND_ARCH}-{TARGET_2ND_ARCH_VARIANT}/
                shared/
                    vndk-core/
                        (VNDK-core libraries, e.g. libbinder.so)
                    vndk-sp/
                        (VNDK-SP libraries, e.g. libc++.so)
            configs/
                (various *.txt configuration files, e.g. ld.config.*.txt)
        ... (other {SNAPSHOT_VARIANT}/ directories)
        common/
            Android.mk
            NOTICE_FILES/
                (license files, e.g. libfoo.so.txt)
    """
    INDENT = '    '
    ETC_MODULES = [
        'ld.config.txt', 'llndk.libraries.txt', 'vndksp.libraries.txt'
    ]

    # TODO(b/70312118): Parse from soong build system
    RELATIVE_INSTALL_PATHS = {'android.hidl.memory@1.0-impl.so': 'hw'}

    def __init__(self, install_dir, vndk_version):
        """GenBuildFile constructor.

        Args:
          install_dir: string, absolute path to the prebuilts/vndk/v{version}
            directory where the build files will be generated.
          vndk_version: int, VNDK snapshot version (e.g., 27, 28)
        """
        self._install_dir = install_dir
        self._vndk_version = vndk_version
        self._etc_paths = self._get_etc_paths()
        self._snapshot_variants = utils.get_snapshot_variants(install_dir)
        self._mkfile = os.path.join(install_dir, utils.ANDROID_MK_PATH)
        self._vndk_core = self._parse_lib_list('vndkcore.libraries.txt')
        self._vndk_sp = self._parse_lib_list(
            os.path.basename(self._etc_paths['vndksp.libraries.txt']))
        self._vndk_private = self._parse_lib_list('vndkprivate.libraries.txt')

    def _get_etc_paths(self):
        """Returns a map of relative file paths for each ETC module."""

        etc_paths = dict()
        for etc_module in self.ETC_MODULES:
            etc_pattern = '{}*'.format(os.path.splitext(etc_module)[0])
            etc_path = glob.glob(
                os.path.join(self._install_dir, utils.CONFIG_DIR_PATH_PATTERN,
                             etc_pattern))[0]
            rel_etc_path = etc_path.replace(self._install_dir, '')[1:]
            etc_paths[etc_module] = rel_etc_path
        return etc_paths

    def _parse_lib_list(self, txt_filename):
        """Returns a map of VNDK library lists per VNDK snapshot variant.

        Args:
          txt_filename: string, name of snapshot config file

        Returns:
          dict, e.g. {'arm64': ['libfoo.so', 'libbar.so', ...], ...}
        """
        lib_map = dict()
        for txt_path in utils.find(self._install_dir, [txt_filename]):
            variant = utils.variant_from_path(txt_path)
            abs_path_of_txt = os.path.join(self._install_dir, txt_path)
            with open(abs_path_of_txt, 'r') as f:
                lib_map[variant] = f.read().strip().split('\n')
        return lib_map

    def generate_android_mk(self):
        """Autogenerates Android.mk."""

        etc_buildrules = []
        for prebuilt in self.ETC_MODULES:
            etc_buildrules.append(self._gen_etc_prebuilt(prebuilt))

        with open(self._mkfile, 'w') as mkfile:
            mkfile.write(self._gen_autogen_msg('#'))
            mkfile.write('\n')
            mkfile.write('LOCAL_PATH := $(call my-dir)\n')
            mkfile.write('\n')
            mkfile.write('\n\n'.join(etc_buildrules))
            mkfile.write('\n')

    def generate_android_bp(self):
        """Autogenerates Android.bp file for each VNDK snapshot variant."""

        for variant in self._snapshot_variants:
            bpfile = os.path.join(self._install_dir, variant, 'Android.bp')
            vndk_core_buildrules = self._gen_vndk_shared_prebuilts(
                self._vndk_core[variant], variant, False)
            vndk_sp_buildrules = self._gen_vndk_shared_prebuilts(
                self._vndk_sp[variant], variant, True)

            with open(bpfile, 'w') as bpfile:
                bpfile.write(self._gen_autogen_msg('/'))
                bpfile.write('\n')
                bpfile.write(self._gen_bp_phony(variant))
                bpfile.write('\n')
                bpfile.write('\n'.join(vndk_core_buildrules))
                bpfile.write('\n')
                bpfile.write('\n'.join(vndk_sp_buildrules))

    def _gen_autogen_msg(self, comment_char):
        return ('{0}{0} THIS FILE IS AUTOGENERATED BY '
                'development/vndk/snapshot/gen_buildfiles.py\n'
                '{0}{0} DO NOT EDIT\n'.format(comment_char))

    def _get_versioned_name(self, prebuilt, variant, is_etc):
        """Returns the VNDK version-specific module name for a given prebuilt.

        The VNDK version-specific module name is defined as follows:
        For a VNDK shared lib: 'libfoo.so'
                            -> 'libfoo.vndk.{version}.{variant}.vendor'
        For an ETC module: 'foo.txt' -> 'foo.{version}.txt'

        Args:
          prebuilt: string, name of the prebuilt object
          variant: string, VNDK snapshot variant (e.g. 'arm64')
          is_etc: bool, True if the LOCAL_MODULE_CLASS of prebuilt is 'ETC'
        """
        name, ext = os.path.splitext(prebuilt)
        if is_etc:
            versioned_name = '{}.{}{}'.format(name, self._vndk_version, ext)
        else:
            versioned_name = '{}.vndk.{}.{}.vendor'.format(
                name, self._vndk_version, variant)

        return versioned_name

    def _gen_etc_prebuilt(self, prebuilt):
        """Generates build rule for an ETC prebuilt.

        Args:
          prebuilt: string, name of ETC prebuilt object
        """
        etc_path = self._etc_paths[prebuilt]
        etc_sub_path = etc_path[etc_path.index('/') + 1:]

        return ('#######################################\n'
                '# {prebuilt}\n'
                'include $(CLEAR_VARS)\n'
                'LOCAL_MODULE := {versioned_name}\n'
                'LOCAL_SRC_FILES := ../$(TARGET_ARCH)/{etc_sub_path}\n'
                'LOCAL_MODULE_CLASS := ETC\n'
                'LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)\n'
                'LOCAL_MODULE_STEM := $(LOCAL_MODULE)\n'
                'include $(BUILD_PREBUILT)\n'.format(
                    prebuilt=prebuilt,
                    versioned_name=self._get_versioned_name(
                        prebuilt, None, True),
                    etc_sub_path=etc_sub_path))

    def _gen_bp_phony(self, variant):
        """Generates build rule for phony package 'vndk_v{ver}_{variant}'.

        Args:
          variant: string, VNDK snapshot variant (e.g. 'arm64')
        """
        required = []
        for prebuilts in (self._vndk_core[variant], self._vndk_sp[variant]):
            for prebuilt in prebuilts:
                required.append(
                    self._get_versioned_name(prebuilt, variant, False))

        for prebuilt in self.ETC_MODULES:
            required.append(self._get_versioned_name(prebuilt, None, True))

        required_str = ['"{}",'.format(prebuilt) for prebuilt in required]
        required_formatted = '\n{ind}{ind}'.format(
            ind=self.INDENT).join(required_str)
        required_buildrule = ('{ind}required: [\n'
                              '{ind}{ind}{required_formatted}\n'
                              '{ind}],\n'.format(
                                  ind=self.INDENT,
                                  required_formatted=required_formatted))

        return ('phony {{\n'
                '{ind}name: "vndk_v{ver}_{variant}",\n'
                '{required_buildrule}'
                '}}\n'.format(
                    ind=self.INDENT,
                    ver=self._vndk_version,
                    variant=variant,
                    required_buildrule=required_buildrule))

    def _gen_vndk_shared_prebuilts(self, prebuilts, variant, is_vndk_sp):
        """Returns list of build rules for given prebuilts.

        Args:
          prebuilts: list of VNDK shared prebuilts
          variant: string, VNDK snapshot variant (e.g. 'arm64')
          is_vndk_sp: bool, True if prebuilts are VNDK_SP libs
        """
        build_rules = []
        for prebuilt in prebuilts:
            build_rules.append(
                self._gen_vndk_shared_prebuilt(prebuilt, variant, is_vndk_sp))
        return build_rules

    def _gen_vndk_shared_prebuilt(self, prebuilt, variant, is_vndk_sp):
        """Returns build rule for given prebuilt.

        Args:
          prebuilt: string, name of prebuilt object
          variant: string, VNDK snapshot variant (e.g. 'arm64')
          is_vndk_sp: bool, True if prebuilt is a VNDK_SP lib
        """

        def get_notice_file(prebuilt):
            """Returns build rule for notice file (attribute 'notice').

            Args:
              prebuilt: string, name of prebuilt object
            """
            notice = ''
            notice_file_name = '{}.txt'.format(prebuilt)
            notices_dir = os.path.join(self._install_dir,
                                       utils.NOTICE_FILES_DIR_PATH)
            notice_files = utils.find(notices_dir, [notice_file_name])
            if len(notice_files) > 0:
                notice = '{ind}notice: "{notice_file_path}",\n'.format(
                    ind=self.INDENT,
                    notice_file_path=os.path.join(
                        '..', utils.NOTICE_FILES_DIR_PATH, notice_files[0]))
            return notice

        def get_rel_install_path(prebuilt):
            """Returns build rule for 'relative_install_path'.

            Args:
              prebuilt: string, name of prebuilt object
            """
            rel_install_path = ''
            if prebuilt in self.RELATIVE_INSTALL_PATHS:
                path = self.RELATIVE_INSTALL_PATHS[prebuilt]
                rel_install_path += ('{ind}relative_install_path: "{path}",\n'
                                     .format(ind=self.INDENT, path=path))
            return rel_install_path

        def get_arch_srcs(prebuilt, variant):
            """Returns build rule for arch specific srcs.

            e.g.,
                arch: {
                    arm: {
                        srcs: ["..."]
                    },
                    arm64: {
                        srcs: ["..."]
                    },
                }

            Args:
              prebuilt: string, name of prebuilt object
              variant: string, VNDK snapshot variant (e.g. 'arm64')
            """
            arch_srcs = '{ind}arch: {{\n'.format(ind=self.INDENT)
            variant_path = os.path.join(self._install_dir, variant)
            src_paths = utils.find(variant_path, [prebuilt])
            for src in sorted(src_paths):
                arch_srcs += ('{ind}{ind}{arch}: {{\n'
                              '{ind}{ind}{ind}srcs: ["{src}"],\n'
                              '{ind}{ind}}},\n'.format(
                                  ind=self.INDENT,
                                  arch=utils.arch_from_path(
                                      os.path.join(variant, src)),
                                  src=src))
            arch_srcs += '{ind}}},\n'.format(ind=self.INDENT)
            return arch_srcs

        name = os.path.splitext(prebuilt)[0]
        vendor_available = str(
            prebuilt not in self._vndk_private[variant]).lower()
        if is_vndk_sp:
            vndk_sp = '{ind}{ind}support_system_process: true,\n'.format(
                ind=self.INDENT)
        else:
            vndk_sp = ''
        notice = get_notice_file(prebuilt)
        rel_install_path = get_rel_install_path(prebuilt)
        arch_srcs = get_arch_srcs(prebuilt, variant)

        return ('vndk_prebuilt_shared {{\n'
                '{ind}name: "{name}",\n'
                '{ind}version: "{ver}",\n'
                '{ind}target_arch: "{target_arch}",\n'
                '{ind}vendor_available: {vendor_available},\n'
                '{ind}vndk: {{\n'
                '{ind}{ind}enabled: true,\n'
                '{vndk_sp}'
                '{ind}}},\n'
                '{notice}'
                '{rel_install_path}'
                '{arch_srcs}'
                '}}\n'.format(
                    ind=self.INDENT,
                    name=name,
                    ver=self._vndk_version,
                    vendor_available=vendor_available,
                    target_arch=variant,
                    vndk_sp=vndk_sp,
                    notice=notice,
                    rel_install_path=rel_install_path,
                    arch_srcs=arch_srcs))


def main():
    """For local testing purposes.

    Note: VNDK snapshot must be already installed under
      prebuilts/vndk/v{version}.
    """
    ANDROID_BUILD_TOP = utils.get_android_build_top()
    PREBUILTS_VNDK_DIR = utils.join_realpath(ANDROID_BUILD_TOP,
                                             'prebuilts/vndk')

    vndk_version = 27  # set appropriately
    install_dir = os.path.join(PREBUILTS_VNDK_DIR, 'v{}'.format(vndk_version))

    buildfile_generator = GenBuildFile(install_dir, vndk_version)
    buildfile_generator.generate_android_mk()
    buildfile_generator.generate_android_bp()


if __name__ == '__main__':
    main()