Edit

kc3-lang/angle/scripts/generate_android_bp.py

Branch :

  • Show log

    Commit

  • Author : Geoff Lang
    Date : 2020-05-27 12:59:20
    Hash : 0652d0d6
    Message : Fix non-existent required dependency TARGET module ANGLE has non-existent required: privapp_whitelist_com.android.angle Bug: b/7456955 Test: TH presubmit Change-Id: I828f154d2ff8126de8383ef56216436c29677729 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2218687 Reviewed-by: Tim Van Patten <timvp@google.com> Commit-Queue: Geoff Lang <geofflang@chromium.org>

  • scripts/generate_android_bp.py
  • #  Copyright The ANGLE Project Authors. All rights reserved.
    #  Use of this source code is governed by a BSD-style license that can be
    #  found in the LICENSE file.
    #
    # Generates an Android.bp file from the json output of a 'gn desc' command.
    # Example usage:
    #   gn desc out/Android --format=json "*" > desc.json
    #   python scripts/generate_android_bp.py desc.json > Android.bp
    
    import json
    import sys
    import re
    import os
    import argparse
    from datetime import date
    
    root_targets = [
        "//:libGLESv2",
        "//:libGLESv1_CM",
        "//:libEGL",
        "//:libfeature_support",
    ]
    
    sdk_version = '28'
    stl = 'libc++_static'
    
    
    def tabs(indent):
        return ' ' * (indent * 4)
    
    
    def has_child_values(value):
        # Elements of the blueprint can be pruned if they are empty lists or dictionaries of empty
        # lists
        if isinstance(value, list):
            return len(value) > 0
        if isinstance(value, dict):
            for (item, item_value) in value.items():
                if has_child_values(item_value):
                    return True
            return False
    
        # This is a value leaf node
        return True
    
    
    def write_blueprint_key_value(output, name, value, indent=1):
        if not has_child_values(value):
            return
    
        if isinstance(value, set) or isinstance(value, list):
            value = list(sorted(set(value)))
    
        if isinstance(value, list):
            output.append(tabs(indent) + '%s: [' % name)
            for item in value:
                output.append(tabs(indent + 1) + '"%s",' % item)
            output.append(tabs(indent) + '],')
            return
        if isinstance(value, dict):
            if not value:
                return
            output.append(tabs(indent) + '%s: {' % name)
            for (item, item_value) in value.items():
                write_blueprint_key_value(output, item, item_value, indent + 1)
            output.append(tabs(indent) + '},')
            return
        if isinstance(value, bool):
            output.append(tabs(indent) + '%s: %s,' % (name, 'true' if value else 'false'))
            return
        output.append(tabs(indent) + '%s: "%s",' % (name, value))
    
    
    def write_blueprint(output, target_type, values):
        output.append('%s {' % target_type)
        for (key, value) in values.items():
            write_blueprint_key_value(output, key, value)
        output.append('}')
    
    
    def gn_target_to_blueprint_target(target, target_info):
        if 'output_name' in target_info:
            return target_info['output_name']
    
        # Split the gn target name (in the form of //gn_file_path:target_name) into gn_file_path and
        # target_name
        target_regex = re.compile(r"^//([a-zA-Z0-9\-_/]*):([a-zA-Z0-9\-_\.]+)$")
        match = re.match(target_regex, target)
        assert match != None
    
        gn_file_path = match.group(1)
        target_name = match.group(2)
        assert len(target_name) > 0
    
        # Clean up the gn file path to be a valid blueprint target name.
        gn_file_path = gn_file_path.replace("/", "_").replace(".", "_").replace("-", "_")
    
        # Generate a blueprint target name by merging the gn path and target so each target is unique.
        # Prepend the 'angle' prefix to all targets in the root path (empty gn_file_path). Skip this step if the target name already starts with 'angle' to avoid target names such as 'angle_angle_common'.
        root_prefix = "angle"
        if len(gn_file_path) == 0 and not target_name.startswith(root_prefix):
            gn_file_path = root_prefix
    
        # Avoid names such as _angle_common if the gn_file_path is empty.
        if len(gn_file_path) > 0:
            gn_file_path += "_"
    
        return gn_file_path + target_name
    
    
    def remap_gn_path(path):
        # TODO: pass the gn gen folder as an arg so it is future proof. b/150457277
        remap_folders = [
            ('out/Android/gen/angle/', ''),
            ('out/Android/gen/', ''),
        ]
    
        remapped_path = path
        for (remap_source, remap_dest) in remap_folders:
            remapped_path = remapped_path.replace(remap_source, remap_dest)
    
        return remapped_path
    
    
    def gn_path_to_blueprint_path(source):
        # gn uses '//' to indicate the root directory, blueprint uses the .bp file's location
        return remap_gn_path(re.sub(r'^//?', '', source))
    
    
    def gn_paths_to_blueprint_paths(paths):
        rebased_paths = []
        for path in paths:
            rebased_paths.append(gn_path_to_blueprint_path(path))
        return rebased_paths
    
    
    def gn_sources_to_blueprint_sources(sources):
        # Blueprints only list source files in the sources list. Headers are only referenced though
        # include paths.
        file_extension_whitelist = [
            '.c',
            '.cc',
            '.cpp',
        ]
    
        rebased_sources = []
        for source in sources:
            if os.path.splitext(source)[1] in file_extension_whitelist:
                rebased_sources.append(gn_path_to_blueprint_path(source))
        return rebased_sources
    
    
    target_blackist = [
        '//build/config:shared_library_deps',
    ]
    
    include_blacklist = [
    ]
    
    
    def gn_deps_to_blueprint_deps(target_info, build_info):
        static_libs = []
        shared_libs = []
        defaults = []
        generated_headers = []
        header_libs = []
        if not 'deps' in target_info:
            return (static_libs, defaults)
    
        for dep in target_info['deps']:
            if not dep in target_blackist:
                dep_info = build_info[dep]
                blueprint_dep_name = gn_target_to_blueprint_target(dep, dep_info)
    
                # Depending on the dep type, blueprints reference it differently.
                gn_dep_type = dep_info['type']
                if gn_dep_type == 'static_library':
                    static_libs.append(blueprint_dep_name)
                elif gn_dep_type == 'shared_library':
                    shared_libs.append(blueprint_dep_name)
                elif gn_dep_type == 'source_set' or gn_dep_type == 'group':
                    defaults.append(blueprint_dep_name)
                elif gn_dep_type == 'action':
                    generated_headers.append(blueprint_dep_name)
    
                # Blueprints do not chain linking of static libraries.
                (child_static_libs, _, _, child_generated_headers, _) = gn_deps_to_blueprint_deps(
                    dep_info, build_info)
    
                # Each target needs to link all child static library dependencies.
                static_libs += child_static_libs
    
                # Each blueprint target runs genrules in a different output directory unlike GN. If a
                # target depends on another's genrule, it wont find the outputs. Propogate generated
                # headers up the dependency stack.
                generated_headers += child_generated_headers
    
        return (static_libs, shared_libs, defaults, generated_headers, header_libs)
    
    
    def gn_libs_to_blueprint_shared_libraries(target_info):
        lib_blackist = [
            'android_support',
        ]
    
        result = []
        if 'libs' in target_info:
            for lib in target_info['libs']:
                if not lib in lib_blackist:
                    android_lib = lib if '@' in lib else 'lib' + lib
                    result.append(android_lib)
        return result
    
    
    def gn_include_dirs_to_blueprint_include_dirs(target_info):
        result = []
        if 'include_dirs' in target_info:
            for include_dir in target_info['include_dirs']:
                if not include_dir in include_blacklist:
                    result.append(gn_path_to_blueprint_path(include_dir))
        return result
    
    
    def escape_quotes(str):
        return str.replace("\"", "\\\"").replace("\'", "\\\'")
    
    
    angle_cpu_bits_define = r'^ANGLE_IS_[0-9]+_BIT_CPU$'
    
    
    def gn_cflags_to_blueprint_cflags(target_info):
        result = []
    
        # Only forward cflags that disable warnings
        cflag_whitelist = r'^-Wno-.*$'
    
        for cflag_type in ['cflags', 'cflags_c', 'cflags_cc']:
            if cflag_type in target_info:
                for cflag in target_info[cflag_type]:
                    if re.search(cflag_whitelist, cflag):
                        result.append(cflag)
    
        # Chrome and Android use different versions of Clang which support differnt warning options.
        # Ignore errors about unrecognized warning flags.
        result.append('-Wno-unknown-warning-option')
    
        if 'defines' in target_info:
            for define in target_info['defines']:
                # Don't emit ANGLE's CPU-bits define here, it will be part of the arch-specific
                # information later
                if not re.search(angle_cpu_bits_define, define):
                    result.append('-D%s' % escape_quotes(define))
    
        return result
    
    
    def gn_arch_specific_to_blueprint(target_info):
        arch_infos = {
            'arm': {
                'bits': 32
            },
            'arm64': {
                'bits': 64
            },
            'x86': {
                'bits': 32
            },
            'x86_64': {
                'bits': 64
            },
        }
    
        result = {}
        for (arch_name, arch_info) in arch_infos.items():
            result[arch_name] = {'cflags': []}
    
        # If the target has ANGLE's CPU-bits define, replace it with the arch-specific bits here.
        if 'defines' in target_info:
            for define in target_info['defines']:
                if re.search(angle_cpu_bits_define, define):
                    for (arch_name, arch_info) in arch_infos.items():
                        result[arch_name]['cflags'].append('-DANGLE_IS_%d_BIT_CPU' % arch_info['bits'])
    
        return result
    
    
    blueprint_library_target_types = {
        "static_library": "cc_library_static",
        "shared_library": "cc_library_shared",
        "source_set": "cc_defaults",
        "group": "cc_defaults",
    }
    
    
    def library_target_to_blueprint(target, build_info):
        target_info = build_info[target]
    
        blueprint_type = blueprint_library_target_types[target_info['type']]
    
        bp = {}
        bp['name'] = gn_target_to_blueprint_target(target, target_info)
    
        if 'sources' in target_info:
            bp['srcs'] = gn_sources_to_blueprint_sources(target_info['sources'])
    
        (bp['static_libs'], bp['shared_libs'], bp['defaults'], bp['generated_headers'],
         bp['header_libs']) = gn_deps_to_blueprint_deps(target_info, build_info)
        bp['shared_libs'] += gn_libs_to_blueprint_shared_libraries(target_info)
    
        bp['local_include_dirs'] = gn_include_dirs_to_blueprint_include_dirs(target_info)
    
        bp['cflags'] = gn_cflags_to_blueprint_cflags(target_info)
        bp['arch'] = gn_arch_specific_to_blueprint(target_info)
    
        bp['sdk_version'] = sdk_version
        bp['stl'] = stl
    
        return (blueprint_type, bp)
    
    
    def gn_action_args_to_blueprint_args(blueprint_inputs, blueprint_outputs, args):
        # TODO: pass the gn gen folder as an arg so we know how to get from the gen path to the root
        # path. b/150457277
        remap_folders = [
            ('../../', ''),
            ('gen/', ''),
        ]
    
        result_args = []
        for arg in args:
            # Attempt to find if this arg is a path to one of the inputs. If it is, use the blueprint
            # $(location <path>) argument instead so the path gets remapped properly to the location
            # that the script is run from
            remapped_path_arg = arg
            for (remap_source, remap_dest) in remap_folders:
                remapped_path_arg = remapped_path_arg.replace(remap_source, remap_dest)
    
            if remapped_path_arg in blueprint_inputs or remapped_path_arg in blueprint_outputs:
                result_args.append('$(location %s)' % remapped_path_arg)
            elif os.path.basename(remapped_path_arg) in blueprint_outputs:
                result_args.append('$(location %s)' % os.path.basename(remapped_path_arg))
            else:
                result_args.append(remapped_path_arg)
    
        return result_args
    
    
    blueprint_gen_types = {
        "action": "cc_genrule",
    }
    
    
    def action_target_to_blueprint(target, build_info):
        target_info = build_info[target]
        blueprint_type = blueprint_gen_types[target_info['type']]
    
        bp = {}
        bp['name'] = gn_target_to_blueprint_target(target, target_info)
    
        # Blueprints use only one 'srcs', merge all gn inputs into one list.
        gn_inputs = []
        if 'inputs' in target_info:
            gn_inputs += target_info['inputs']
        if 'sources' in target_info:
            gn_inputs += target_info['sources']
        bp_srcs = gn_paths_to_blueprint_paths(gn_inputs)
    
        bp['srcs'] = bp_srcs
    
        # genrules generate the output right into the 'root' directory. Strip any path before the
        # file name.
        bp_outputs = []
        for gn_output in target_info['outputs']:
            bp_outputs.append(os.path.basename(gn_output))
    
        bp['out'] = bp_outputs
    
        bp['tool_files'] = [gn_path_to_blueprint_path(target_info['script'])]
    
        # Generate the full command, $(location) refers to tool_files[0], the script
        cmd = ['$(location)'] + gn_action_args_to_blueprint_args(bp_srcs, bp_outputs,
                                                                 target_info['args'])
        bp['cmd'] = ' '.join(cmd)
    
        bp['sdk_version'] = sdk_version
    
        return (blueprint_type, bp)
    
    
    def gn_target_to_blueprint(target, build_info):
        gn_type = build_info[target]['type']
        if gn_type in blueprint_library_target_types:
            return library_target_to_blueprint(target, build_info)
        elif gn_type in blueprint_gen_types:
            return action_target_to_blueprint(target, build_info)
        else:
            raise RuntimeError("Unknown gn target type: " + gn_type)
    
    
    def get_gn_target_dependencies(output_dependencies, build_info, target):
        output_dependencies.insert(0, target)
        for dep in build_info[target]['deps']:
            if dep in target_blackist:
                # Blacklisted dep
                continue
            if dep in output_dependencies:
                # Already added this dep
                continue
            if not dep in build_info:
                # No info for this dep, skip it
                continue
    
            # Recurse
            get_gn_target_dependencies(output_dependencies, build_info, dep)
    
    
    def main():
        parser = argparse.ArgumentParser(
            description='Generate Android blueprints from gn descriptions.')
        parser.add_argument(
            'gn_json',
            help='gn desc in json format. Generated with \'gn desc <out_dir> --format=json "*"\'.')
        args = parser.parse_args()
    
        with open(args.gn_json, 'r') as f:
            build_info = json.load(f)
    
        targets_to_write = []
        for root_target in root_targets:
            get_gn_target_dependencies(targets_to_write, build_info, root_target)
    
        blueprint_targets = []
    
        for target in targets_to_write:
            blueprint_targets.append(gn_target_to_blueprint(target, build_info))
    
        # Add APKs with all of the root libraries
        blueprint_targets.append(('filegroup', {
            'name': 'ANGLE_srcs',
            'srcs': ['src/**/*.java',],
        }))
    
        blueprint_targets.append((
            'java_defaults',
            {
                'name':
                    'ANGLE_java_defaults',
                'sdk_version':
                    'system_current',
                'min_sdk_version':
                    sdk_version,
                'compile_multilib':
                    'both',
                'use_embedded_native_libs':
                    True,
                'jni_libs': [
                    gn_target_to_blueprint_target(target, build_info[target])
                    for target in root_targets
                ],
                'aaptflags': [
                    # Don't compress *.json files
                    '-0 .json',
                    # Give com.android.angle.common Java files access to the R class
                    '--extra-packages com.android.angle.common',
                ],
                'srcs': [':ANGLE_srcs'],
                'plugins': ['java_api_finder',],
                'privileged':
                    True,
                'owner':
                    'google',
            }))
    
        blueprint_targets.append((
            'android_library',
            {
                'name': 'ANGLE_library',
                'sdk_version': 'system_current',
                'min_sdk_version': sdk_version,
                'resource_dirs': ['src/android_system_settings/res',],
                'asset_dirs': ['src/android_system_settings/assets',],
                'aaptflags': [
                    # Don't compress *.json files
                    '-0 .json',
                ],
                'manifest': 'src/android_system_settings/src/com/android/angle/AndroidManifest.xml',
                'static_libs': ['androidx.preference_preference',],
            }))
    
        blueprint_targets.append(('android_app', {
            'name': 'ANGLE',
            'defaults': ['ANGLE_java_defaults'],
            'static_libs': ['ANGLE_library'],
            'manifest': 'src/android_system_settings/src/com/android/angle/AndroidManifest.xml',
        }))
    
        output = [
            """// GENERATED FILE - DO NOT EDIT.
    // Generated by %s
    //
    // Copyright %s The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    """ % (sys.argv[0], date.today().year)
        ]
        for (blueprint_type, blueprint_data) in blueprint_targets:
            write_blueprint(output, blueprint_type, blueprint_data)
    
        print('\n'.join(output))
    
    
    if __name__ == '__main__':
        sys.exit(main())