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