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 15from datetime import date 16 17root_targets = [ 18 "//:libGLESv2", 19 "//:libGLESv1_CM", 20 "//:libEGL", 21 "//:libfeature_support", 22] 23 24sdk_version = '28' 25stl = 'libc++_static' 26 27 28def tabs(indent): 29 return ' ' * (indent * 4) 30 31 32def has_child_values(value): 33 # Elements of the blueprint can be pruned if they are empty lists or dictionaries of empty 34 # lists 35 if isinstance(value, list): 36 return len(value) > 0 37 if isinstance(value, dict): 38 for (item, item_value) in value.items(): 39 if has_child_values(item_value): 40 return True 41 return False 42 43 # This is a value leaf node 44 return True 45 46 47def write_blueprint_key_value(output, name, value, indent=1): 48 if not has_child_values(value): 49 return 50 51 if isinstance(value, set) or isinstance(value, list): 52 value = list(sorted(set(value))) 53 54 if isinstance(value, list): 55 output.append(tabs(indent) + '%s: [' % name) 56 for item in value: 57 output.append(tabs(indent + 1) + '"%s",' % item) 58 output.append(tabs(indent) + '],') 59 return 60 if isinstance(value, dict): 61 if not value: 62 return 63 output.append(tabs(indent) + '%s: {' % name) 64 for (item, item_value) in value.items(): 65 write_blueprint_key_value(output, item, item_value, indent + 1) 66 output.append(tabs(indent) + '},') 67 return 68 if isinstance(value, bool): 69 output.append(tabs(indent) + '%s: %s,' % (name, 'true' if value else 'false')) 70 return 71 output.append(tabs(indent) + '%s: "%s",' % (name, value)) 72 73 74def write_blueprint(output, target_type, values): 75 output.append('%s {' % target_type) 76 for (key, value) in values.items(): 77 write_blueprint_key_value(output, key, value) 78 output.append('}') 79 80 81def gn_target_to_blueprint_target(target, target_info): 82 if 'output_name' in target_info: 83 return target_info['output_name'] 84 85 # Split the gn target name (in the form of //gn_file_path:target_name) into gn_file_path and 86 # target_name 87 target_regex = re.compile(r"^//([a-zA-Z0-9\-_/]*):([a-zA-Z0-9\-_\.]+)$") 88 match = re.match(target_regex, target) 89 assert match != None 90 91 gn_file_path = match.group(1) 92 target_name = match.group(2) 93 assert len(target_name) > 0 94 95 # Clean up the gn file path to be a valid blueprint target name. 96 gn_file_path = gn_file_path.replace("/", "_").replace(".", "_").replace("-", "_") 97 98 # Generate a blueprint target name by merging the gn path and target so each target is unique. 99 # 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'. 100 root_prefix = "angle" 101 if len(gn_file_path) == 0 and not target_name.startswith(root_prefix): 102 gn_file_path = root_prefix 103 104 # Avoid names such as _angle_common if the gn_file_path is empty. 105 if len(gn_file_path) > 0: 106 gn_file_path += "_" 107 108 return gn_file_path + target_name 109 110 111def remap_gn_path(path): 112 # TODO: pass the gn gen folder as an arg so it is future proof. b/150457277 113 remap_folders = [ 114 ('out/Android/gen/angle/', ''), 115 ('out/Android/gen/', ''), 116 ] 117 118 remapped_path = path 119 for (remap_source, remap_dest) in remap_folders: 120 remapped_path = remapped_path.replace(remap_source, remap_dest) 121 122 return remapped_path 123 124 125def gn_path_to_blueprint_path(source): 126 # gn uses '//' to indicate the root directory, blueprint uses the .bp file's location 127 return remap_gn_path(re.sub(r'^//?', '', source)) 128 129 130def gn_paths_to_blueprint_paths(paths): 131 rebased_paths = [] 132 for path in paths: 133 rebased_paths.append(gn_path_to_blueprint_path(path)) 134 return rebased_paths 135 136 137def gn_sources_to_blueprint_sources(sources): 138 # Blueprints only list source files in the sources list. Headers are only referenced though 139 # include paths. 140 file_extension_whitelist = [ 141 '.c', 142 '.cc', 143 '.cpp', 144 ] 145 146 rebased_sources = [] 147 for source in sources: 148 if os.path.splitext(source)[1] in file_extension_whitelist: 149 rebased_sources.append(gn_path_to_blueprint_path(source)) 150 return rebased_sources 151 152 153target_blackist = [ 154 '//build/config:shared_library_deps', 155] 156 157include_blacklist = [ 158] 159 160 161def gn_deps_to_blueprint_deps(target_info, build_info): 162 static_libs = [] 163 shared_libs = [] 164 defaults = [] 165 generated_headers = [] 166 header_libs = [] 167 if not 'deps' in target_info: 168 return (static_libs, defaults) 169 170 for dep in target_info['deps']: 171 if not dep in target_blackist: 172 dep_info = build_info[dep] 173 blueprint_dep_name = gn_target_to_blueprint_target(dep, dep_info) 174 175 # Depending on the dep type, blueprints reference it differently. 176 gn_dep_type = dep_info['type'] 177 if gn_dep_type == 'static_library': 178 static_libs.append(blueprint_dep_name) 179 elif gn_dep_type == 'shared_library': 180 shared_libs.append(blueprint_dep_name) 181 elif gn_dep_type == 'source_set' or gn_dep_type == 'group': 182 defaults.append(blueprint_dep_name) 183 elif gn_dep_type == 'action': 184 generated_headers.append(blueprint_dep_name) 185 186 # Blueprints do not chain linking of static libraries. 187 (child_static_libs, _, _, child_generated_headers, _) = gn_deps_to_blueprint_deps( 188 dep_info, build_info) 189 190 # Each target needs to link all child static library dependencies. 191 static_libs += child_static_libs 192 193 # Each blueprint target runs genrules in a different output directory unlike GN. If a 194 # target depends on another's genrule, it wont find the outputs. Propogate generated 195 # headers up the dependency stack. 196 generated_headers += child_generated_headers 197 198 return (static_libs, shared_libs, defaults, generated_headers, header_libs) 199 200 201def gn_libs_to_blueprint_shared_libraries(target_info): 202 lib_blackist = [ 203 'android_support', 204 ] 205 206 result = [] 207 if 'libs' in target_info: 208 for lib in target_info['libs']: 209 if not lib in lib_blackist: 210 android_lib = lib if '@' in lib else 'lib' + lib 211 result.append(android_lib) 212 return result 213 214 215def gn_include_dirs_to_blueprint_include_dirs(target_info): 216 result = [] 217 if 'include_dirs' in target_info: 218 for include_dir in target_info['include_dirs']: 219 if not include_dir in include_blacklist: 220 result.append(gn_path_to_blueprint_path(include_dir)) 221 return result 222 223 224def escape_quotes(str): 225 return str.replace("\"", "\\\"").replace("\'", "\\\'") 226 227 228angle_cpu_bits_define = r'^ANGLE_IS_[0-9]+_BIT_CPU$' 229 230 231def gn_cflags_to_blueprint_cflags(target_info): 232 result = [] 233 234 # Only forward cflags that disable warnings 235 cflag_whitelist = r'^-Wno-.*$' 236 237 for cflag_type in ['cflags', 'cflags_c', 'cflags_cc']: 238 if cflag_type in target_info: 239 for cflag in target_info[cflag_type]: 240 if re.search(cflag_whitelist, cflag): 241 result.append(cflag) 242 243 # Chrome and Android use different versions of Clang which support differnt warning options. 244 # Ignore errors about unrecognized warning flags. 245 result.append('-Wno-unknown-warning-option') 246 247 if 'defines' in target_info: 248 for define in target_info['defines']: 249 # Don't emit ANGLE's CPU-bits define here, it will be part of the arch-specific 250 # information later 251 if not re.search(angle_cpu_bits_define, define): 252 result.append('-D%s' % escape_quotes(define)) 253 254 return result 255 256 257def gn_arch_specific_to_blueprint(target_info): 258 arch_infos = { 259 'arm': { 260 'bits': 32 261 }, 262 'arm64': { 263 'bits': 64 264 }, 265 'x86': { 266 'bits': 32 267 }, 268 'x86_64': { 269 'bits': 64 270 }, 271 } 272 273 result = {} 274 for (arch_name, arch_info) in arch_infos.items(): 275 result[arch_name] = {'cflags': []} 276 277 # If the target has ANGLE's CPU-bits define, replace it with the arch-specific bits here. 278 if 'defines' in target_info: 279 for define in target_info['defines']: 280 if re.search(angle_cpu_bits_define, define): 281 for (arch_name, arch_info) in arch_infos.items(): 282 result[arch_name]['cflags'].append('-DANGLE_IS_%d_BIT_CPU' % arch_info['bits']) 283 284 return result 285 286 287blueprint_library_target_types = { 288 "static_library": "cc_library_static", 289 "shared_library": "cc_library_shared", 290 "source_set": "cc_defaults", 291 "group": "cc_defaults", 292} 293 294 295def library_target_to_blueprint(target, build_info): 296 target_info = build_info[target] 297 298 blueprint_type = blueprint_library_target_types[target_info['type']] 299 300 bp = {} 301 bp['name'] = gn_target_to_blueprint_target(target, target_info) 302 303 if 'sources' in target_info: 304 bp['srcs'] = gn_sources_to_blueprint_sources(target_info['sources']) 305 306 (bp['static_libs'], bp['shared_libs'], bp['defaults'], bp['generated_headers'], 307 bp['header_libs']) = gn_deps_to_blueprint_deps(target_info, build_info) 308 bp['shared_libs'] += gn_libs_to_blueprint_shared_libraries(target_info) 309 310 bp['local_include_dirs'] = gn_include_dirs_to_blueprint_include_dirs(target_info) 311 312 bp['cflags'] = gn_cflags_to_blueprint_cflags(target_info) 313 bp['arch'] = gn_arch_specific_to_blueprint(target_info) 314 315 bp['sdk_version'] = sdk_version 316 bp['stl'] = stl 317 318 return (blueprint_type, bp) 319 320 321def gn_action_args_to_blueprint_args(blueprint_inputs, blueprint_outputs, args): 322 # TODO: pass the gn gen folder as an arg so we know how to get from the gen path to the root 323 # path. b/150457277 324 remap_folders = [ 325 ('../../', ''), 326 ('gen/', ''), 327 ] 328 329 result_args = [] 330 for arg in args: 331 # Attempt to find if this arg is a path to one of the inputs. If it is, use the blueprint 332 # $(location <path>) argument instead so the path gets remapped properly to the location 333 # that the script is run from 334 remapped_path_arg = arg 335 for (remap_source, remap_dest) in remap_folders: 336 remapped_path_arg = remapped_path_arg.replace(remap_source, remap_dest) 337 338 if remapped_path_arg in blueprint_inputs or remapped_path_arg in blueprint_outputs: 339 result_args.append('$(location %s)' % remapped_path_arg) 340 elif os.path.basename(remapped_path_arg) in blueprint_outputs: 341 result_args.append('$(location %s)' % os.path.basename(remapped_path_arg)) 342 else: 343 result_args.append(remapped_path_arg) 344 345 return result_args 346 347 348blueprint_gen_types = { 349 "action": "cc_genrule", 350} 351 352 353def action_target_to_blueprint(target, build_info): 354 target_info = build_info[target] 355 blueprint_type = blueprint_gen_types[target_info['type']] 356 357 bp = {} 358 bp['name'] = gn_target_to_blueprint_target(target, target_info) 359 360 # Blueprints use only one 'srcs', merge all gn inputs into one list. 361 gn_inputs = [] 362 if 'inputs' in target_info: 363 gn_inputs += target_info['inputs'] 364 if 'sources' in target_info: 365 gn_inputs += target_info['sources'] 366 bp_srcs = gn_paths_to_blueprint_paths(gn_inputs) 367 368 bp['srcs'] = bp_srcs 369 370 # genrules generate the output right into the 'root' directory. Strip any path before the 371 # file name. 372 bp_outputs = [] 373 for gn_output in target_info['outputs']: 374 bp_outputs.append(os.path.basename(gn_output)) 375 376 bp['out'] = bp_outputs 377 378 bp['tool_files'] = [gn_path_to_blueprint_path(target_info['script'])] 379 380 # Generate the full command, $(location) refers to tool_files[0], the script 381 cmd = ['$(location)'] + gn_action_args_to_blueprint_args(bp_srcs, bp_outputs, 382 target_info['args']) 383 bp['cmd'] = ' '.join(cmd) 384 385 bp['sdk_version'] = sdk_version 386 387 return (blueprint_type, bp) 388 389 390def gn_target_to_blueprint(target, build_info): 391 gn_type = build_info[target]['type'] 392 if gn_type in blueprint_library_target_types: 393 return library_target_to_blueprint(target, build_info) 394 elif gn_type in blueprint_gen_types: 395 return action_target_to_blueprint(target, build_info) 396 else: 397 raise RuntimeError("Unknown gn target type: " + gn_type) 398 399 400def get_gn_target_dependencies(output_dependencies, build_info, target): 401 output_dependencies.insert(0, target) 402 for dep in build_info[target]['deps']: 403 if dep in target_blackist: 404 # Blacklisted dep 405 continue 406 if dep in output_dependencies: 407 # Already added this dep 408 continue 409 if not dep in build_info: 410 # No info for this dep, skip it 411 continue 412 413 # Recurse 414 get_gn_target_dependencies(output_dependencies, build_info, dep) 415 416 417def main(): 418 parser = argparse.ArgumentParser( 419 description='Generate Android blueprints from gn descriptions.') 420 parser.add_argument( 421 'gn_json', 422 help='gn desc in json format. Generated with \'gn desc <out_dir> --format=json "*"\'.') 423 args = parser.parse_args() 424 425 with open(args.gn_json, 'r') as f: 426 build_info = json.load(f) 427 428 targets_to_write = [] 429 for root_target in root_targets: 430 get_gn_target_dependencies(targets_to_write, build_info, root_target) 431 432 blueprint_targets = [] 433 434 for target in targets_to_write: 435 blueprint_targets.append(gn_target_to_blueprint(target, build_info)) 436 437 # Add APKs with all of the root libraries 438 blueprint_targets.append(('filegroup', { 439 'name': 'ANGLE_srcs', 440 'srcs': ['src/**/*.java',], 441 })) 442 443 blueprint_targets.append(( 444 'java_defaults', 445 { 446 'name': 447 'ANGLE_java_defaults', 448 'sdk_version': 449 'system_current', 450 'min_sdk_version': 451 sdk_version, 452 'compile_multilib': 453 'both', 454 'use_embedded_native_libs': 455 True, 456 'jni_libs': [ 457 gn_target_to_blueprint_target(target, build_info[target]) 458 for target in root_targets 459 ], 460 'aaptflags': [ 461 # Don't compress *.json files 462 '-0 .json', 463 # Give com.android.angle.common Java files access to the R class 464 '--extra-packages com.android.angle.common', 465 ], 466 'srcs': [':ANGLE_srcs'], 467 'plugins': ['java_api_finder',], 468 'privileged': 469 True, 470 'owner': 471 'google', 472 })) 473 474 blueprint_targets.append(( 475 'android_library', 476 { 477 'name': 'ANGLE_library', 478 'sdk_version': 'system_current', 479 'min_sdk_version': sdk_version, 480 'resource_dirs': ['src/android_system_settings/res',], 481 'asset_dirs': ['src/android_system_settings/assets',], 482 'aaptflags': [ 483 # Don't compress *.json files 484 '-0 .json', 485 ], 486 'manifest': 'src/android_system_settings/src/com/android/angle/AndroidManifest.xml', 487 'static_libs': ['androidx.preference_preference',], 488 })) 489 490 blueprint_targets.append(('android_app', { 491 'name': 'ANGLE', 492 'defaults': ['ANGLE_java_defaults'], 493 'static_libs': ['ANGLE_library'], 494 'manifest': 'src/android_system_settings/src/com/android/angle/AndroidManifest.xml', 495 })) 496 497 output = [ 498 """// GENERATED FILE - DO NOT EDIT. 499// Generated by %s 500// 501// Copyright %s The ANGLE Project Authors. All rights reserved. 502// Use of this source code is governed by a BSD-style license that can be 503// found in the LICENSE file. 504// 505""" % (sys.argv[0], date.today().year) 506 ] 507 for (blueprint_type, blueprint_data) in blueprint_targets: 508 write_blueprint(output, blueprint_type, blueprint_data) 509 510 print('\n'.join(output)) 511 512 513if __name__ == '__main__': 514 sys.exit(main()) 515