1# Copyright The ANGLE Project Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4# 5# Generates an Android.bp file from the json output of a 'gn desc' command. 6# Example usage: 7# gn desc out/Android --format=json "*" > desc.json 8# python scripts/generate_android_bp.py desc.json > Android.bp 9 10import json 11import sys 12import re 13import os 14import argparse 15 16root_targets = [ 17 "//:libGLESv2", 18 "//:libGLESv1_CM", 19 "//:libEGL", 20] 21 22sdk_version = '28' 23stl = 'libc++_static' 24 25abi_arm = 'arm' 26abi_arm64 = 'arm64' 27abi_x86 = 'x86' 28abi_x64 = 'x86_64' 29 30abi_targets = [abi_arm, abi_arm64, abi_x86, abi_x64] 31 32 33def tabs(indent): 34 return ' ' * (indent * 4) 35 36 37def has_child_values(value): 38 # Elements of the blueprint can be pruned if they are empty lists or dictionaries of empty 39 # lists 40 if isinstance(value, list): 41 return len(value) > 0 42 if isinstance(value, dict): 43 for (item, item_value) in value.items(): 44 if has_child_values(item_value): 45 return True 46 return False 47 48 # This is a value leaf node 49 return True 50 51 52def write_blueprint_key_value(output, name, value, indent=1): 53 if not has_child_values(value): 54 return 55 56 if isinstance(value, set) or isinstance(value, list): 57 value = list(sorted(set(value))) 58 59 if isinstance(value, list): 60 output.append(tabs(indent) + '%s: [' % name) 61 for item in value: 62 output.append(tabs(indent + 1) + '"%s",' % item) 63 output.append(tabs(indent) + '],') 64 return 65 if isinstance(value, dict): 66 if not value: 67 return 68 output.append(tabs(indent) + '%s: {' % name) 69 for (item, item_value) in value.items(): 70 write_blueprint_key_value(output, item, item_value, indent + 1) 71 output.append(tabs(indent) + '},') 72 return 73 if isinstance(value, bool): 74 output.append(tabs(indent) + '%s: %s,' % (name, 'true' if value else 'false')) 75 return 76 output.append(tabs(indent) + '%s: "%s",' % (name, value)) 77 78 79def write_blueprint(output, target_type, values): 80 if target_type == 'license': 81 comment = """ 82// Added automatically by a large-scale-change that took the approach of 83// 'apply every license found to every target'. While this makes sure we respect 84// every license restriction, it may not be entirely correct. 85// 86// e.g. GPL in an MIT project might only apply to the contrib/ directory. 87// 88// Please consider splitting the single license below into multiple licenses, 89// taking care not to lose any license_kind information, and overriding the 90// default license using the 'licenses: [...]' property on targets as needed. 91// 92// For unused files, consider creating a 'fileGroup' with "//visibility:private" 93// to attach the license to, and including a comment whether the files may be 94// used in the current project. 95// See: http://go/android-license-faq""" 96 output.append(comment) 97 98 output.append('%s {' % target_type) 99 for (key, value) in values.items(): 100 write_blueprint_key_value(output, key, value) 101 output.append('}') 102 103 104def gn_target_to_blueprint_target(target, target_info): 105 if 'output_name' in target_info: 106 return target_info['output_name'] 107 108 # Split the gn target name (in the form of //gn_file_path:target_name) into gn_file_path and 109 # target_name 110 target_regex = re.compile(r"^//([a-zA-Z0-9\-\+_/]*):([a-zA-Z0-9\-\+_.]+)$") 111 match = re.match(target_regex, target) 112 assert match is not None 113 114 gn_file_path = match.group(1) 115 target_name = match.group(2) 116 assert len(target_name) > 0 117 118 # Clean up the gn file path to be a valid blueprint target name. 119 gn_file_path = gn_file_path.replace("/", "_").replace(".", "_").replace("-", "_") 120 121 # Generate a blueprint target name by merging the gn path and target so each target is unique. 122 # Prepend the 'angle' prefix to all targets in the root path (empty gn_file_path). 123 # Skip this step if the target name already starts with 'angle' to avoid target names such as 'angle_angle_common'. 124 root_prefix = "angle" 125 if len(gn_file_path) == 0 and not target_name.startswith(root_prefix): 126 gn_file_path = root_prefix 127 128 # Avoid names such as _angle_common if the gn_file_path is empty. 129 if len(gn_file_path) > 0: 130 gn_file_path += "_" 131 132 return gn_file_path + target_name 133 134 135def remap_gn_path(path): 136 # TODO: pass the gn gen folder as an arg so it is future proof. b/150457277 137 remap_folders = [ 138 ('out/Android/gen/angle/', ''), 139 ('out/Android/gen/', ''), 140 ] 141 142 remapped_path = path 143 for (remap_source, remap_dest) in remap_folders: 144 remapped_path = remapped_path.replace(remap_source, remap_dest) 145 146 return remapped_path 147 148 149def gn_path_to_blueprint_path(source): 150 # gn uses '//' to indicate the root directory, blueprint uses the .bp file's location 151 return remap_gn_path(re.sub(r'^//?', '', source)) 152 153 154def gn_paths_to_blueprint_paths(paths): 155 rebased_paths = [] 156 for path in paths: 157 rebased_paths.append(gn_path_to_blueprint_path(path)) 158 return rebased_paths 159 160 161def gn_sources_to_blueprint_sources(sources): 162 # Blueprints only list source files in the sources list. Headers are only referenced though 163 # include paths. 164 file_extension_allowlist = [ 165 '.c', 166 '.cc', 167 '.cpp', 168 ] 169 170 rebased_sources = [] 171 for source in sources: 172 if os.path.splitext(source)[1] in file_extension_allowlist: 173 rebased_sources.append(gn_path_to_blueprint_path(source)) 174 return rebased_sources 175 176 177target_blockist = [ 178 '//build/config:shared_library_deps', 179 '//third_party/vulkan-validation-layers/src:vulkan_clean_old_validation_layer_objects', 180] 181 182third_party_target_allowlist = [ 183 '//third_party/abseil-cpp', 184 '//third_party/vulkan-deps', 185 '//third_party/vulkan_memory_allocator', 186 '//third_party/zlib', 187] 188 189include_blocklist = [ 190 '//buildtools/third_party/libc++/', 191 '//out/Android/gen/third_party/vulkan-deps/glslang/src/include/', 192 '//third_party/android_ndk/sources/android/cpufeatures/', 193] 194 195 196def gn_deps_to_blueprint_deps(target_info, build_info): 197 static_libs = [] 198 shared_libs = [] 199 defaults = [] 200 generated_headers = [] 201 header_libs = [] 202 if 'deps' not in target_info: 203 return static_libs, defaults 204 205 for dep in target_info['deps']: 206 if dep not in target_blockist and (not dep.startswith('//third_party') or any( 207 dep.startswith(substring) for substring in third_party_target_allowlist)): 208 dep_info = build_info[dep] 209 blueprint_dep_name = gn_target_to_blueprint_target(dep, dep_info) 210 211 # Depending on the dep type, blueprints reference it differently. 212 gn_dep_type = dep_info['type'] 213 if gn_dep_type == 'static_library': 214 static_libs.append(blueprint_dep_name) 215 elif gn_dep_type == 'shared_library': 216 shared_libs.append(blueprint_dep_name) 217 elif gn_dep_type == 'source_set' or gn_dep_type == 'group': 218 defaults.append(blueprint_dep_name) 219 elif gn_dep_type == 'action': 220 generated_headers.append(blueprint_dep_name) 221 222 # Blueprints do not chain linking of static libraries. 223 (child_static_libs, _, _, child_generated_headers, _) = gn_deps_to_blueprint_deps( 224 dep_info, build_info) 225 226 # Each target needs to link all child static library dependencies. 227 static_libs += child_static_libs 228 229 # Each blueprint target runs genrules in a different output directory unlike GN. If a 230 # target depends on another's genrule, it wont find the outputs. Propogate generated 231 # headers up the dependency stack. 232 generated_headers += child_generated_headers 233 elif dep == '//third_party/android_ndk:cpu_features': 234 # chrome_zlib needs cpufeatures from the Android NDK. Rather than including the 235 # entire NDK is a dep in the ANGLE checkout, use the library that's already part 236 # of Android. 237 dep_info = build_info[dep] 238 blueprint_dep_name = gn_target_to_blueprint_target(dep, dep_info) 239 static_libs.append('cpufeatures') 240 241 return static_libs, shared_libs, defaults, generated_headers, header_libs 242 243 244def gn_libs_to_blueprint_shared_libraries(target_info): 245 lib_blockist = [ 246 'android_support', 247 'unwind', 248 ] 249 250 result = [] 251 if 'libs' in target_info: 252 for lib in target_info['libs']: 253 if lib not in lib_blockist: 254 android_lib = lib if '@' in lib else 'lib' + lib 255 result.append(android_lib) 256 return result 257 258 259def gn_include_dirs_to_blueprint_include_dirs(target_info): 260 result = [] 261 if 'include_dirs' in target_info: 262 for include_dir in target_info['include_dirs']: 263 if len(include_dir) > 0 and include_dir not in include_blocklist: 264 result.append(gn_path_to_blueprint_path(include_dir)) 265 return result 266 267 268def escape_quotes(string): 269 return string.replace("\"", "\\\"").replace("\'", "\\\'") 270 271 272def gn_cflags_to_blueprint_cflags(target_info): 273 result = [] 274 275 # regexs of allowlisted cflags 276 cflag_allowlist = [ 277 r'^-Wno-.*$', # forward cflags that disable warnings 278 r'-mpclmul' # forward "-mpclmul" (used by zlib) 279 ] 280 281 for cflag_type in ['cflags', 'cflags_c', 'cflags_cc']: 282 if cflag_type in target_info: 283 for cflag in target_info[cflag_type]: 284 for allowlisted_cflag in cflag_allowlist: 285 if re.search(allowlisted_cflag, cflag): 286 result.append(cflag) 287 288 # Chrome and Android use different versions of Clang which support differnt warning options. 289 # Ignore errors about unrecognized warning flags. 290 result.append('-Wno-unknown-warning-option') 291 292 # Override AOSP build flags to match ANGLE's CQ testing and reduce binary size 293 result.append('-Oz') 294 result.append('-fno-unwind-tables') 295 296 if 'defines' in target_info: 297 for define in target_info['defines']: 298 # Don't emit ANGLE's CPU-bits define here, it will be part of the arch-specific 299 # information later 300 result.append('-D%s' % escape_quotes(define)) 301 302 return result 303 304 305blueprint_library_target_types = { 306 "static_library": "cc_library_static", 307 "shared_library": "cc_library_shared", 308 "source_set": "cc_defaults", 309 "group": "cc_defaults", 310} 311 312 313def merge_bps(bps_for_abis): 314 common_bp = {} 315 for abi in abi_targets: 316 for key in bps_for_abis[abi]: 317 if isinstance(bps_for_abis[abi][key], list): 318 # Find list values that are common to all ABIs 319 for value in bps_for_abis[abi][key]: 320 value_in_all_abis = True 321 for abi2 in abi_targets: 322 if key == 'defaults': 323 # arch-specific defaults are not supported 324 break 325 value_in_all_abis = value_in_all_abis and (key in bps_for_abis[abi2].keys( 326 )) and (value in bps_for_abis[abi2][key]) 327 if value_in_all_abis: 328 if key in common_bp.keys(): 329 common_bp[key].append(value) 330 else: 331 common_bp[key] = [value] 332 else: 333 if 'arch' not in common_bp.keys(): 334 # Make sure there is an 'arch' entry to hold ABI-specific values 335 common_bp['arch'] = {} 336 for abi3 in abi_targets: 337 common_bp['arch'][abi3] = {} 338 if key in common_bp['arch'][abi].keys(): 339 common_bp['arch'][abi][key].append(value) 340 else: 341 common_bp['arch'][abi][key] = [value] 342 else: 343 # Assume everything that's not a list is common to all ABIs 344 common_bp[key] = bps_for_abis[abi][key] 345 346 return common_bp 347 348 349def library_target_to_blueprint(target, build_info): 350 bps_for_abis = {} 351 blueprint_type = "" 352 for abi in abi_targets: 353 if target not in build_info[abi].keys(): 354 bps_for_abis[abi] = {} 355 continue 356 357 target_info = build_info[abi][target] 358 359 blueprint_type = blueprint_library_target_types[target_info['type']] 360 361 bp = {'name': gn_target_to_blueprint_target(target, target_info)} 362 363 if 'sources' in target_info: 364 bp['srcs'] = gn_sources_to_blueprint_sources(target_info['sources']) 365 366 (bp['static_libs'], bp['shared_libs'], bp['defaults'], bp['generated_headers'], 367 bp['header_libs']) = gn_deps_to_blueprint_deps(target_info, build_info[abi]) 368 bp['shared_libs'] += gn_libs_to_blueprint_shared_libraries(target_info) 369 370 bp['local_include_dirs'] = gn_include_dirs_to_blueprint_include_dirs(target_info) 371 372 bp['cflags'] = gn_cflags_to_blueprint_cflags(target_info) 373 374 bp['sdk_version'] = sdk_version 375 bp['stl'] = stl 376 if target in root_targets: 377 bp['vendor'] = True 378 bp['target'] = {'android': {'relative_install_path': 'egl'}} 379 bps_for_abis[abi] = bp 380 381 common_bp = merge_bps(bps_for_abis) 382 383 return blueprint_type, common_bp 384 385 386def gn_action_args_to_blueprint_args(blueprint_inputs, blueprint_outputs, args): 387 # TODO: pass the gn gen folder as an arg so we know how to get from the gen path to the root 388 # path. b/150457277 389 remap_folders = [ 390 # Specific special-cases first, since the other will strip the prefixes. 391 ('gen/third_party/vulkan-deps/glslang/src/include/glslang/build_info.h', 392 'glslang/build_info.h'), 393 ('third_party/vulkan-deps/glslang/src', 394 'external/angle/third_party/vulkan-deps/glslang/src'), 395 ('../../', ''), 396 ('gen/', ''), 397 ] 398 399 result_args = [] 400 for arg in args: 401 # Attempt to find if this arg is a path to one of the inputs. If it is, use the blueprint 402 # $(location <path>) argument instead so the path gets remapped properly to the location 403 # that the script is run from 404 remapped_path_arg = arg 405 for (remap_source, remap_dest) in remap_folders: 406 remapped_path_arg = remapped_path_arg.replace(remap_source, remap_dest) 407 408 if remapped_path_arg in blueprint_inputs or remapped_path_arg in blueprint_outputs: 409 result_args.append('$(location %s)' % remapped_path_arg) 410 elif os.path.basename(remapped_path_arg) in blueprint_outputs: 411 result_args.append('$(location %s)' % os.path.basename(remapped_path_arg)) 412 else: 413 result_args.append(remapped_path_arg) 414 415 return result_args 416 417 418blueprint_gen_types = { 419 "action": "cc_genrule", 420} 421 422 423inputs_blocklist = [ 424 '//.git/HEAD', 425] 426 427outputs_remap = { 428 'build_info.h': 'glslang/build_info.h', 429} 430 431 432def is_input_in_tool_files(tool_files, input): 433 return input in tool_files 434 435 436def action_target_to_blueprint(target, build_info): 437 target_info = build_info[target] 438 blueprint_type = blueprint_gen_types[target_info['type']] 439 440 bp = {'name': gn_target_to_blueprint_target(target, target_info)} 441 442 # Blueprints use only one 'srcs', merge all gn inputs into one list. 443 gn_inputs = [] 444 if 'inputs' in target_info: 445 for input in target_info['inputs']: 446 if input not in inputs_blocklist: 447 gn_inputs.append(input) 448 if 'sources' in target_info: 449 gn_inputs += target_info['sources'] 450 # Filter out the 'script' entry since Android.bp doesn't like the duplicate entries 451 if 'script' in target_info: 452 gn_inputs = [ 453 input for input in gn_inputs 454 if not is_input_in_tool_files(target_info['script'], input) 455 ] 456 bp_srcs = gn_paths_to_blueprint_paths(gn_inputs) 457 458 bp['srcs'] = bp_srcs 459 460 # genrules generate the output right into the 'root' directory. Strip any path before the 461 # file name. 462 bp_outputs = [] 463 for gn_output in target_info['outputs']: 464 output = os.path.basename(gn_output) 465 if output in outputs_remap.keys(): 466 output = outputs_remap[output] 467 bp_outputs.append(output) 468 469 bp['out'] = bp_outputs 470 471 bp['tool_files'] = [gn_path_to_blueprint_path(target_info['script'])] 472 473 # Generate the full command, $(location) refers to tool_files[0], the script 474 cmd = ['$(location)'] + gn_action_args_to_blueprint_args(bp_srcs, bp_outputs, 475 target_info['args']) 476 bp['cmd'] = ' '.join(cmd) 477 478 bp['sdk_version'] = sdk_version 479 480 return blueprint_type, bp 481 482 483def gn_target_to_blueprint(target, build_info): 484 for abi in abi_targets: 485 gn_type = build_info[abi][target]['type'] 486 if gn_type in blueprint_library_target_types: 487 return library_target_to_blueprint(target, build_info) 488 elif gn_type in blueprint_gen_types: 489 return action_target_to_blueprint(target, build_info[abi]) 490 else: 491 # Target is not used by this ABI 492 continue 493 494 495def get_gn_target_dependencies(output_dependencies, build_info, target): 496 if target not in output_dependencies: 497 output_dependencies.insert(0, target) 498 499 for dep in build_info[target]['deps']: 500 if dep in target_blockist: 501 # Blocklisted dep 502 continue 503 if dep not in build_info: 504 # No info for this dep, skip it 505 continue 506 507 # Recurse 508 get_gn_target_dependencies(output_dependencies, build_info, dep) 509 510 511def main(): 512 parser = argparse.ArgumentParser( 513 description='Generate Android blueprints from gn descriptions.') 514 515 for abi in abi_targets: 516 fixed_abi = abi 517 if abi == abi_x64: 518 fixed_abi = 'x64' # gn uses x64, rather than x86_64 519 parser.add_argument( 520 'gn_json_' + fixed_abi, 521 help=fixed_abi + 522 'gn desc in json format. Generated with \'gn desc <out_dir> --format=json "*"\'.') 523 args = vars(parser.parse_args()) 524 525 build_info = {} 526 for abi in abi_targets: 527 fixed_abi = abi 528 if abi == abi_x64: 529 fixed_abi = 'x64' # gn uses x64, rather than x86_64 530 with open(args['gn_json_' + fixed_abi], 'r') as f: 531 build_info[abi] = json.load(f) 532 533 targets_to_write = [] 534 for abi in abi_targets: 535 for root_target in root_targets: 536 get_gn_target_dependencies(targets_to_write, build_info[abi], root_target) 537 538 blueprint_targets = [] 539 for target in targets_to_write: 540 blueprint_targets.append(gn_target_to_blueprint(target, build_info)) 541 542 # Add license build rules 543 blueprint_targets.append(('package', { 544 'default_applicable_licenses': ['external_angle_license'], 545 })) 546 blueprint_targets.append(('license', { 547 'name': 548 'external_angle_license', 549 'visibility': [':__subpackages__'], 550 'license_kinds': [ 551 'SPDX-license-identifier-Apache-2.0', 552 'SPDX-license-identifier-BSD', 553 'SPDX-license-identifier-LGPL', 554 'SPDX-license-identifier-MIT', 555 'SPDX-license-identifier-Zlib', 556 'legacy_unencumbered', 557 ], 558 'license_text': [ 559 'LICENSE', 'third_party/abseil-cpp/LICENSE', 'third_party/vulkan-deps/LICENSE', 560 'third_party/vulkan_memory_allocator/LICENSE.txt', 'third_party/zlib/LICENSE' 561 ], 562 })) 563 564 # Add APKs with all of the root libraries 565 blueprint_targets.append(( 566 'filegroup', 567 { 568 'name': 'ANGLE_srcs', 569 # Only add EmptyMainActivity.java since we just need to be able to reply to the intent 570 # android.app.action.ANGLE_FOR_ANDROID to indicate ANGLE is present on the device. 571 'srcs': ['src/android_system_settings/src/com/android/angle/EmptyMainActivity.java'], 572 })) 573 blueprint_targets.append(( 574 'java_defaults', 575 { 576 'name': 'ANGLE_java_defaults', 577 'sdk_version': 'system_current', 578 'min_sdk_version': sdk_version, 579 'compile_multilib': 'both', 580 'use_embedded_native_libs': True, 581 'jni_libs': [ 582 # hack: assume abi_arm 583 gn_target_to_blueprint_target(target, build_info[abi_arm][target]) 584 for target in root_targets 585 ], 586 'aaptflags': [ 587 # Don't compress *.json files 588 '-0 .json', 589 ], 590 'srcs': [':ANGLE_srcs'], 591 'plugins': ['java_api_finder',], 592 'privileged': True, 593 'product_specific': True, 594 'owner': 'google', 595 })) 596 597 blueprint_targets.append(('android_app', { 598 'name': 'ANGLE', 599 'defaults': ['ANGLE_java_defaults'], 600 'manifest': 'android/AndroidManifest.xml', 601 'asset_dirs': ['src/android_system_settings/assets',], 602 })) 603 604 output = [ 605 """// GENERATED FILE - DO NOT EDIT. 606// Generated by %s 607// 608// Copyright 2020 The ANGLE Project Authors. All rights reserved. 609// Use of this source code is governed by a BSD-style license that can be 610// found in the LICENSE file. 611// 612""" % sys.argv[0] 613 ] 614 for (blueprint_type, blueprint_data) in blueprint_targets: 615 write_blueprint(output, blueprint_type, blueprint_data) 616 617 print('\n'.join(output)) 618 619 620if __name__ == '__main__': 621 sys.exit(main()) 622