| # 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 |
| |
| root_targets = [ |
| "//:libGLESv2", |
| "//:libGLESv1_CM", |
| "//:libEGL", |
| ] |
| |
| sdk_version = '28' |
| stl = 'libc++_static' |
| |
| abi_arm = 'arm' |
| abi_arm64 = 'arm64' |
| abi_x86 = 'x86' |
| abi_x64 = 'x86_64' |
| |
| abi_targets = [abi_arm, abi_arm64, abi_x86, abi_x64] |
| |
| |
| 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): |
| if target_type == 'license': |
| comment = """ |
| // Added automatically by a large-scale-change that took the approach of |
| // 'apply every license found to every target'. While this makes sure we respect |
| // every license restriction, it may not be entirely correct. |
| // |
| // e.g. GPL in an MIT project might only apply to the contrib/ directory. |
| // |
| // Please consider splitting the single license below into multiple licenses, |
| // taking care not to lose any license_kind information, and overriding the |
| // default license using the 'licenses: [...]' property on targets as needed. |
| // |
| // For unused files, consider creating a 'fileGroup' with "//visibility:private" |
| // to attach the license to, and including a comment whether the files may be |
| // used in the current project. |
| // See: http://go/android-license-faq""" |
| output.append(comment) |
| |
| 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 is not 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_allowlist = [ |
| '.c', |
| '.cc', |
| '.cpp', |
| ] |
| |
| rebased_sources = [] |
| for source in sources: |
| if os.path.splitext(source)[1] in file_extension_allowlist: |
| rebased_sources.append(gn_path_to_blueprint_path(source)) |
| return rebased_sources |
| |
| |
| target_blockist = [ |
| '//build/config:shared_library_deps', |
| '//third_party/vulkan-validation-layers/src:vulkan_clean_old_validation_layer_objects', |
| ] |
| |
| third_party_target_allowlist = [ |
| '//third_party/abseil-cpp', |
| '//third_party/vulkan-deps', |
| '//third_party/vulkan_memory_allocator', |
| '//third_party/zlib', |
| ] |
| |
| include_blocklist = [ |
| '//buildtools/third_party/libc++/', |
| '//out/Android/gen/third_party/vulkan-deps/glslang/src/include/', |
| '//third_party/android_ndk/sources/android/cpufeatures/', |
| ] |
| |
| |
| def gn_deps_to_blueprint_deps(target_info, build_info): |
| static_libs = [] |
| shared_libs = [] |
| defaults = [] |
| generated_headers = [] |
| header_libs = [] |
| if 'deps' not in target_info: |
| return static_libs, defaults |
| |
| for dep in target_info['deps']: |
| if dep not in target_blockist and (not dep.startswith('//third_party') or any( |
| dep.startswith(substring) for substring in third_party_target_allowlist)): |
| 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 |
| elif dep == '//third_party/android_ndk:cpu_features': |
| # chrome_zlib needs cpufeatures from the Android NDK. Rather than including the |
| # entire NDK is a dep in the ANGLE checkout, use the library that's already part |
| # of Android. |
| dep_info = build_info[dep] |
| blueprint_dep_name = gn_target_to_blueprint_target(dep, dep_info) |
| static_libs.append('cpufeatures') |
| |
| return static_libs, shared_libs, defaults, generated_headers, header_libs |
| |
| |
| def gn_libs_to_blueprint_shared_libraries(target_info): |
| lib_blockist = [ |
| 'android_support', |
| 'unwind', |
| ] |
| |
| result = [] |
| if 'libs' in target_info: |
| for lib in target_info['libs']: |
| if lib not in lib_blockist: |
| 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 len(include_dir) > 0 and include_dir not in include_blocklist: |
| result.append(gn_path_to_blueprint_path(include_dir)) |
| return result |
| |
| |
| def escape_quotes(string): |
| return string.replace("\"", "\\\"").replace("\'", "\\\'") |
| |
| |
| def gn_cflags_to_blueprint_cflags(target_info): |
| result = [] |
| |
| # regexs of allowlisted cflags |
| cflag_allowlist = [ |
| r'^-Wno-.*$', # forward cflags that disable warnings |
| r'-mpclmul' # forward "-mpclmul" (used by zlib) |
| ] |
| |
| for cflag_type in ['cflags', 'cflags_c', 'cflags_cc']: |
| if cflag_type in target_info: |
| for cflag in target_info[cflag_type]: |
| for allowlisted_cflag in cflag_allowlist: |
| if re.search(allowlisted_cflag, 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') |
| |
| # Override AOSP build flags to match ANGLE's CQ testing and reduce binary size |
| result.append('-Os') |
| result.append('-fno-unwind-tables') |
| |
| 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 |
| result.append('-D%s' % escape_quotes(define)) |
| |
| return result |
| |
| |
| blueprint_library_target_types = { |
| "static_library": "cc_library_static", |
| "shared_library": "cc_library_shared", |
| "source_set": "cc_defaults", |
| "group": "cc_defaults", |
| } |
| |
| |
| def merge_bps(bps_for_abis): |
| common_bp = {} |
| for abi in abi_targets: |
| for key in bps_for_abis[abi]: |
| if isinstance(bps_for_abis[abi][key], list): |
| # Find list values that are common to all ABIs |
| for value in bps_for_abis[abi][key]: |
| value_in_all_abis = True |
| for abi2 in abi_targets: |
| if key == 'defaults': |
| # arch-specific defaults are not supported |
| break |
| value_in_all_abis = value_in_all_abis and (key in bps_for_abis[abi2].keys( |
| )) and (value in bps_for_abis[abi2][key]) |
| if value_in_all_abis: |
| if key in common_bp.keys(): |
| common_bp[key].append(value) |
| else: |
| common_bp[key] = [value] |
| else: |
| if 'arch' not in common_bp.keys(): |
| # Make sure there is an 'arch' entry to hold ABI-specific values |
| common_bp['arch'] = {} |
| for abi3 in abi_targets: |
| common_bp['arch'][abi3] = {} |
| if key in common_bp['arch'][abi].keys(): |
| common_bp['arch'][abi][key].append(value) |
| else: |
| common_bp['arch'][abi][key] = [value] |
| else: |
| # Assume everything that's not a list is common to all ABIs |
| common_bp[key] = bps_for_abis[abi][key] |
| |
| return common_bp |
| |
| |
| def library_target_to_blueprint(target, build_info): |
| bps_for_abis = {} |
| blueprint_type = "" |
| for abi in abi_targets: |
| if target not in build_info[abi].keys(): |
| bps_for_abis[abi] = {} |
| continue |
| |
| target_info = build_info[abi][target] |
| |
| blueprint_type = blueprint_library_target_types[target_info['type']] |
| |
| 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[abi]) |
| 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['sdk_version'] = sdk_version |
| bp['stl'] = stl |
| if target in root_targets: |
| bp['vendor'] = True |
| bp['target'] = {'android': {'relative_install_path': 'egl'}} |
| bps_for_abis[abi] = bp |
| |
| common_bp = merge_bps(bps_for_abis) |
| |
| return blueprint_type, common_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 = [ |
| # Specific special-cases first, since the other will strip the prefixes. |
| ('gen/third_party/vulkan-deps/glslang/src/include/glslang/build_info.h', |
| 'glslang/build_info.h'), |
| ('third_party/vulkan-deps/glslang/src', |
| 'external/angle/third_party/vulkan-deps/glslang/src'), |
| ('../../', ''), |
| ('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", |
| } |
| |
| |
| inputs_blocklist = [ |
| '//.git/HEAD', |
| ] |
| |
| outputs_remap = { |
| 'build_info.h': 'glslang/build_info.h', |
| } |
| |
| |
| def is_input_in_tool_files(tool_files, input): |
| return input in tool_files |
| |
| |
| def action_target_to_blueprint(target, build_info): |
| target_info = build_info[target] |
| blueprint_type = blueprint_gen_types[target_info['type']] |
| |
| 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: |
| for input in target_info['inputs']: |
| if input not in inputs_blocklist: |
| gn_inputs.append(input) |
| if 'sources' in target_info: |
| gn_inputs += target_info['sources'] |
| # Filter out the 'script' entry since Android.bp doesn't like the duplicate entries |
| if 'script' in target_info: |
| gn_inputs = [ |
| input for input in gn_inputs |
| if not is_input_in_tool_files(target_info['script'], input) |
| ] |
| 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']: |
| output = os.path.basename(gn_output) |
| if output in outputs_remap.keys(): |
| output = outputs_remap[output] |
| bp_outputs.append(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): |
| for abi in abi_targets: |
| gn_type = build_info[abi][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[abi]) |
| else: |
| # Target is not used by this ABI |
| continue |
| |
| |
| def get_gn_target_dependencies(output_dependencies, build_info, target): |
| if target not in output_dependencies: |
| output_dependencies.insert(0, target) |
| |
| for dep in build_info[target]['deps']: |
| if dep in target_blockist: |
| # Blocklisted dep |
| continue |
| if dep not 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.') |
| |
| for abi in abi_targets: |
| fixed_abi = abi |
| if abi == abi_x64: |
| fixed_abi = 'x64' # gn uses x64, rather than x86_64 |
| parser.add_argument( |
| 'gn_json_' + fixed_abi, |
| help=fixed_abi + |
| 'gn desc in json format. Generated with \'gn desc <out_dir> --format=json "*"\'.') |
| args = vars(parser.parse_args()) |
| |
| build_info = {} |
| for abi in abi_targets: |
| fixed_abi = abi |
| if abi == abi_x64: |
| fixed_abi = 'x64' # gn uses x64, rather than x86_64 |
| with open(args['gn_json_' + fixed_abi], 'r') as f: |
| build_info[abi] = json.load(f) |
| |
| targets_to_write = [] |
| for abi in abi_targets: |
| for root_target in root_targets: |
| get_gn_target_dependencies(targets_to_write, build_info[abi], root_target) |
| |
| blueprint_targets = [] |
| for target in targets_to_write: |
| blueprint_targets.append(gn_target_to_blueprint(target, build_info)) |
| |
| # Add license build rules |
| blueprint_targets.append(('package', { |
| 'default_applicable_licenses': ['external_angle_license'], |
| })) |
| blueprint_targets.append(('license', { |
| 'name': |
| 'external_angle_license', |
| 'visibility': [':__subpackages__'], |
| 'license_kinds': [ |
| 'SPDX-license-identifier-Apache-2.0', |
| 'SPDX-license-identifier-BSD', |
| 'SPDX-license-identifier-GPL', |
| 'SPDX-license-identifier-GPL-2.0', |
| 'SPDX-license-identifier-GPL-3.0', |
| 'SPDX-license-identifier-LGPL', |
| 'SPDX-license-identifier-MIT', |
| 'SPDX-license-identifier-Zlib', |
| 'legacy_unencumbered', |
| ], |
| 'license_text': [ |
| 'LICENSE', |
| 'src/common/third_party/smhasher/LICENSE', |
| 'src/common/third_party/xxhash/LICENSE', |
| 'src/libANGLE/renderer/vulkan/shaders/src/third_party/ffx_spd/LICENSE', |
| 'src/tests/test_utils/third_party/LICENSE', |
| 'src/third_party/libXNVCtrl/LICENSE', |
| 'src/third_party/volk/LICENSE.md', |
| 'third_party/abseil-cpp/LICENSE', |
| 'third_party/android_system_sdk/LICENSE', |
| 'third_party/bazel/LICENSE', |
| 'third_party/colorama/LICENSE', |
| 'third_party/proguard/LICENSE', |
| 'third_party/r8/LICENSE', |
| 'third_party/turbine/LICENSE', |
| 'third_party/vulkan-deps/glslang/LICENSE', |
| 'third_party/vulkan-deps/glslang/src/LICENSE.txt', |
| 'third_party/vulkan-deps/LICENSE', |
| 'third_party/vulkan-deps/spirv-headers/LICENSE', |
| 'third_party/vulkan-deps/spirv-headers/src/LICENSE', |
| 'third_party/vulkan-deps/spirv-tools/LICENSE', |
| 'third_party/vulkan-deps/spirv-tools/src/LICENSE', |
| 'third_party/vulkan-deps/spirv-tools/src/utils/vscode/src/lsp/LICENSE', |
| 'third_party/vulkan-deps/vulkan-headers/LICENSE.txt', |
| 'third_party/vulkan-deps/vulkan-headers/src/LICENSE.txt', |
| 'third_party/vulkan_memory_allocator/LICENSE.txt', |
| 'third_party/zlib/LICENSE', |
| 'tools/flex-bison/third_party/m4sugar/LICENSE', |
| 'tools/flex-bison/third_party/skeletons/LICENSE', |
| 'util/windows/third_party/StackWalker/LICENSE', |
| ], |
| })) |
| |
| # Add APKs with all of the root libraries |
| blueprint_targets.append(( |
| 'filegroup', |
| { |
| 'name': 'ANGLE_srcs', |
| # Only add EmptyMainActivity.java since we just need to be able to reply to the intent |
| # android.app.action.ANGLE_FOR_ANDROID to indicate ANGLE is present on the device. |
| 'srcs': ['src/android_system_settings/src/com/android/angle/EmptyMainActivity.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': [ |
| # hack: assume abi_arm |
| gn_target_to_blueprint_target(target, build_info[abi_arm][target]) |
| for target in root_targets |
| ], |
| 'aaptflags': [ |
| # Don't compress *.json files |
| '-0 .json', |
| ], |
| 'srcs': [':ANGLE_srcs'], |
| 'plugins': ['java_api_finder',], |
| 'privileged': True, |
| 'product_specific': True, |
| 'owner': 'google', |
| })) |
| |
| blueprint_targets.append(('android_app', { |
| 'name': 'ANGLE', |
| 'defaults': ['ANGLE_java_defaults'], |
| 'manifest': 'android/AndroidManifest.xml', |
| 'asset_dirs': ['src/android_system_settings/assets',], |
| })) |
| |
| output = [ |
| """// GENERATED FILE - DO NOT EDIT. |
| // Generated by %s |
| // |
| // Copyright 2020 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] |
| ] |
| 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()) |