1#!/usr/bin/env python3 2 3import json 4import os 5import sys 6 7PRINT_ORIGINAL_FULL = False 8 9# This flags are augmented with flags added to the json files but not present in .gn or .gni files 10IGNORED_FLAGS = [ 11 '-D_DEBUG', 12 '-Wall', # already globally enabled 13 '-Werror', 14 '-Wextra', # already globally enabled 15 '-Wglobal-constructors', # disabled in DEFAULT_CFLAGS below 16 '-Xclang', 17 '-march=armv8-a', # TARGET_ARCH_VARIANT may be newer 18 '-target-feature', 19 '+crc', 20 '+crypto', 21] 22IGNORED_DEFINES = [ 23 'HAVE_ARM64_CRC32C=1' 24] 25DEFAULT_CFLAGS = [ 26 '-DABSL_ALLOCATOR_NOTHROW=1', 27 '-DHAVE_ARM64_CRC32C=0', 28 '-DUSE_AURA=1', 29 '-DUSE_GLIB=1', 30 '-DUSE_NSS_CERTS=1', 31 '-DUSE_UDEV', 32 '-DUSE_X11=1', 33 '-DWEBRTC_ANDROID_PLATFORM_BUILD=1', 34 '-DWEBRTC_APM_DEBUG_DUMP=0', 35 '-D_FILE_OFFSET_BITS=64', 36 '-D_GNU_SOURCE', 37 '-D_LARGEFILE64_SOURCE', 38 '-D_LARGEFILE_SOURCE', 39 '-Wno-global-constructors', 40 '-Wno-implicit-const-int-float-conversion', 41 '-Wno-missing-field-initializers', 42 '-Wno-unreachable-code-aggressive', 43 '-Wno-unreachable-code-break', 44 "-Wno-unused-parameter", 45] 46 47DEFAULT_CFLAGS_BY_ARCH = { 48 'x86': ['-mavx2', '-mfma', '-msse2', '-msse3'], 49 'x64': ['-mavx2', '-mfma', '-msse2', '-msse3'], 50 'arm': ['-mthumb'], 51 'arm64': [], 52 'riscv64': [], 53 } 54 55FLAGS = ['cflags', 'cflags_c', 'cflags_cc', 'asmflags'] 56FLAG_NAME_MAP = { 57 'cflags': 'cflags', 58 'asmflags': 'asflags', 59 'cflags_cc': 'cppflags', 60 'cflags_c': 'conlyflags', 61} 62 63ARCH_NAME_MAP = {n: n for n in DEFAULT_CFLAGS_BY_ARCH.keys()} 64ARCH_NAME_MAP['x64'] = 'x86_64' 65 66ARCHS = sorted(ARCH_NAME_MAP.keys()) 67 68def FormatList(l): 69 return json.dumps(sorted(list(l))) 70 71def IsInclude(name): 72 return name.endswith('.h') or name.endswith('.inc') 73 74def FilterIncludes(l): 75 return filter(lambda x: not IsInclude(x), l) 76 77def PrintOrigin(target): 78 print('/* From target:') 79 if PRINT_ORIGINAL_FULL: 80 print(json.dumps(target, sort_keys = True, indent = 4)) 81 else: 82 print(target['original_name']) 83 print('*/') 84 85def MakeRelatives(l): 86 return map(lambda x: x.split('//').pop(), l) 87 88def FormatName(name): 89 return 'webrtc_' + name.split('/').pop().replace(':', '__') 90 91def FormatNames(target): 92 target['original_name'] = target['name'] 93 target['name'] = FormatName(target['name']) 94 target['deps'] = sorted([FormatName(d) for d in target['deps']]) 95 return target 96 97def FilterFlags(flags, to_skip = set()): 98 skipped_opts = set(IGNORED_FLAGS).union(to_skip) 99 return [x for x in flags if not any([x.startswith(y) for y in skipped_opts])] 100 101def PrintHeader(): 102 print('package {') 103 print(' default_applicable_licenses: ["external_webrtc_license"],') 104 print('}') 105 print('') 106 print('// Added automatically by a large-scale-change that took the approach of') 107 print('// \'apply every license found to every target\'. While this makes sure we respect') 108 print('// every license restriction, it may not be entirely correct.') 109 print('//') 110 print('// e.g. GPL in an MIT project might only apply to the contrib/ directory.') 111 print('//') 112 print('// Please consider splitting the single license below into multiple licenses,') 113 print('// taking care not to lose any license_kind information, and overriding the') 114 print('// default license using the \'licenses: [...]\' property on targets as needed.') 115 print('//') 116 print('// For unused files, consider creating a \'fileGroup\' with "//visibility:private"') 117 print('// to attach the license to, and including a comment whether the files may be') 118 print('// used in the current project.') 119 print('//') 120 print('// large-scale-change included anything that looked like it might be a license') 121 print('// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc.') 122 print('//') 123 print('// Please consider removing redundant or irrelevant files from \'license_text:\'.') 124 print('// See: http://go/android-license-faq') 125 print('') 126 print('///////////////////////////////////////////////////////////////////////////////') 127 print('// Do not edit this file directly, it\'s automatically generated by a script. //') 128 print('// Modify android_tools/generate_android_bp.py and run that instead. //') 129 print('///////////////////////////////////////////////////////////////////////////////') 130 print('') 131 print('license {') 132 print(' name: "external_webrtc_license",') 133 print(' visibility: [":__subpackages__"],') 134 print(' license_kinds: [') 135 print(' "SPDX-license-identifier-Apache-2.0",') 136 print(' "SPDX-license-identifier-BSD",') 137 print(' "SPDX-license-identifier-MIT",') 138 print(' "SPDX-license-identifier-Zlib",') 139 print(' "legacy_notice",') 140 print(' "legacy_unencumbered",') 141 print(' ],') 142 print(' license_text: [') 143 print(' "LICENSE",') 144 print(' "PATENTS",') 145 print(' "license_template.txt",') 146 print(' ],') 147 print('}') 148 149 150 151def GatherDefaultFlags(targets_by_arch): 152 # Iterate through all of the targets for each architecture collecting the flags that 153 # are the same for all targets in that architecture. Use a list instead of a set 154 # to maintain the flag ordering, which may be significant (e.g. -Wno-shadow has to 155 # come after -Wshadow). 156 arch_default_flags = {} 157 for arch, targets in targets_by_arch.items(): 158 arch_default_flags[arch] = {} 159 for target in targets.values(): 160 typ = target['type'] 161 if typ != 'static_library': 162 continue 163 for flag_type in FLAGS: 164 if not flag_type in arch_default_flags: 165 arch_default_flags[arch][flag_type] = target[flag_type] 166 else: 167 target_flags = set(target[flag_type]) 168 flags = arch_default_flags[arch][flag_type] 169 flags[:] = [ x for x in flags if x in target_flags ] 170 for flag_type, flags in arch_default_flags[arch].items(): 171 arch_default_flags[arch][flag_type] = FilterFlags(flags) 172 # Add in the hardcoded extra default cflags 173 arch_default_flags[arch]['cflags'] += DEFAULT_CFLAGS_BY_ARCH.get(arch, []) 174 175 # Iterate through all of the architectures collecting the flags that are the same 176 # for all targets in all architectures. 177 default_flags = {} 178 for arch, flagset in arch_default_flags.items(): 179 for flag_type, arch_flags in flagset.items(): 180 if not flag_type in default_flags: 181 default_flags[flag_type] = arch_flags.copy() 182 else: 183 flags = default_flags[flag_type] 184 flags[:] = [ x for x in flags if x in arch_flags ] 185 # Add in the hardcoded extra default cflags 186 default_flags['cflags'] += DEFAULT_CFLAGS 187 188 # Remove the global default flags from the per-architecture default flags 189 for arch, flagset in arch_default_flags.items(): 190 for flag_type in flagset.keys(): 191 flags = flagset[flag_type] 192 flags[:] = [ x for x in flags if x not in default_flags[flag_type] ] 193 194 default_flags['arch'] = arch_default_flags 195 return default_flags 196 197def GenerateDefault(targets_by_arch): 198 in_default = GatherDefaultFlags(targets_by_arch) 199 print('cc_defaults {') 200 print(' name: "webrtc_defaults",') 201 print(' local_include_dirs: [') 202 print(' ".",') 203 print(' "webrtc",') 204 print(' "third_party/crc32c/src/include",') 205 print(' ],') 206 for typ in sorted(in_default.keys() - {'arch'}): 207 flags = in_default[typ] 208 if len(flags) > 0: 209 print(' {0}: ['.format(FLAG_NAME_MAP[typ])) 210 for flag in flags: 211 print(' "{0}",'.format(flag.replace('"', '\\"'))) 212 print(' ],') 213 print(' static_libs: [') 214 print(' "libabsl",') 215 print(' "libaom",') 216 print(' "libevent",') 217 print(' "libopus",') 218 print(' "libsrtp2",') 219 print(' "libvpx",') 220 print(' "libyuv",') 221 print(' "libpffft",') 222 print(' "rnnoise_rnn_vad",') 223 print(' ],') 224 print(' shared_libs: [') 225 print(' "libcrypto",') 226 print(' "libprotobuf-cpp-full",') 227 print(' "libprotobuf-cpp-lite",') 228 print(' "libssl",') 229 print(' ],') 230 print(' host_supported: true,') 231 print(' // vendor needed for libpreprocessing effects.') 232 print(' vendor: true,') 233 print(' target: {') 234 print(' darwin: {') 235 print(' enabled: false,') 236 print(' },') 237 print(' },') 238 print(' arch: {') 239 for a in ARCHS: 240 print(' {0}: {{'.format(ARCH_NAME_MAP[a])) 241 for typ in FLAGS: 242 flags = in_default['arch'].get(a, {}).get(typ, []) 243 if len(flags) > 0: 244 print(' {0}: ['.format(FLAG_NAME_MAP[typ])) 245 for flag in flags: 246 print(' "{0}",'.format(flag.replace('"', '\\"'))) 247 print(' ],') 248 print(' },') 249 print(' },') 250 print(' visibility: [') 251 print(' "//frameworks/av/media/libeffects/preprocessing:__subpackages__",') 252 print(' "//device/google/cuttlefish/host/frontend/webrtc:__subpackages__",') 253 print(' ],') 254 print('}') 255 256 # The flags in the default entry can be safely removed from the targets 257 for arch, targets in targets_by_arch.items(): 258 for flag_type in FLAGS: 259 default_flags = set(in_default[flag_type]) | set(in_default['arch'][arch][flag_type]) 260 for target in targets.values(): 261 target[flag_type] = FilterFlags(target.get(flag_type, []), default_flags) 262 if len(target[flag_type]) == 0: 263 target.pop(flag_type) 264 265 return in_default 266 267 268def TransitiveDependencies(name, dep_type, targets): 269 target = targets[name] 270 field = 'transitive_' + dep_type 271 if field in target.keys(): 272 return target[field] 273 target[field] = {'global': set()} 274 for a in ARCHS: 275 target[field][a] = set() 276 if target['type'] == dep_type: 277 target[field]['global'].add(name) 278 for d in target.get('deps', []): 279 if targets[d]['type'] == dep_type: 280 target[field]['global'].add(d) 281 tDeps = TransitiveDependencies(d, dep_type, targets) 282 target[field]['global'] |= tDeps['global'] 283 for a in ARCHS: 284 target[field][a] |= tDeps[a] 285 if 'arch' in target: 286 for a, x in target['arch'].items(): 287 for d in x.get('deps', []): 288 tDeps = TransitiveDependencies(d, dep_type, targets) 289 target[field][a] |= tDeps['global'] | tDeps[a] 290 target[field][a] -= target[field]['global'] 291 292 return target[field] 293 294def GenerateGroup(target): 295 # PrintOrigin(target) 296 pass 297 298def GenerateStaticLib(target, targets): 299 PrintOrigin(target) 300 name = target['name'] 301 print('cc_library_static {') 302 print(' name: "{0}",'.format(name)) 303 print(' defaults: ["webrtc_defaults"],') 304 sources = target.get('sources', []) 305 print(' srcs: {0},'.format(FormatList(sources))) 306 print(' host_supported: true,') 307 if 'asmflags' in target.keys(): 308 asmflags = target['asmflags'] 309 if len(asmflags) > 0: 310 print(' asflags: {0},'.format(FormatList(asmflags))) 311 if 'cflags' in target.keys(): 312 cflags = target['cflags'] 313 print(' cflags: {0},'.format(FormatList(cflags))) 314 if 'cflags_c' in target.keys(): 315 cflags_c = target['cflags_c'] 316 if len(cflags_c) > 0: 317 print(' conlyflags: {0},'.format(FormatList(cflags_c))) 318 if 'cflags_cc' in target.keys(): 319 cflags_cc = target['cflags_cc'] 320 if len(cflags_cc) > 0: 321 print(' cppflags: {0},'.format(FormatList(cflags_cc))) 322 if 'arch' in target: 323 print(' arch: {') 324 for arch_name in ARCHS: 325 if arch_name not in target['arch'].keys(): 326 continue 327 arch = target['arch'][arch_name] 328 print(' ' + ARCH_NAME_MAP[arch_name] + ': {') 329 if 'cflags' in arch.keys(): 330 cflags = arch['cflags'] 331 print(' cflags: {0},'.format(FormatList(cflags))) 332 if 'cflags_c' in arch.keys(): 333 cflags_c = arch['cflags_c'] 334 if len(cflags_c) > 0: 335 print(' conlyflags: {0},'.format(FormatList(cflags_c))) 336 if 'cflags_cc' in arch.keys(): 337 cflags_cc = arch['cflags_cc'] 338 if len(cflags_cc) > 0: 339 print(' cppflags: {0},'.format(FormatList(cflags_cc))) 340 if 'sources' in arch: 341 sources = arch['sources'] 342 print(' srcs: {0},'.format(FormatList(sources))) 343 if 'enabled' in arch: 344 print(' enabled: {0},'.format(arch['enabled'])) 345 print(' },') 346 print(' },') 347 print('}') 348 return name 349 350def DFS(seed, targets): 351 visited = set() 352 stack = [seed] 353 while len(stack) > 0: 354 nxt = stack.pop() 355 if nxt in visited: 356 continue 357 visited.add(nxt) 358 stack += targets[nxt]['deps'] 359 if 'arch' not in targets[nxt]: 360 continue 361 for arch in targets[nxt]['arch']: 362 if 'deps' in arch: 363 stack += arch['deps'] 364 return visited 365 366def Preprocess(project): 367 targets = {} 368 for name, target in project['targets'].items(): 369 target['name'] = name 370 targets[name] = target 371 if target['type'] == 'shared_library': 372 # Don't bother creating shared libraries 373 target['type'] = 'static_library' 374 if target['type'] == 'source_set': 375 # Convert source_sets to static libraires to avoid recompiling sources multiple times. 376 target['type'] = 'static_library' 377 if 'defines' in target: 378 target['cflags'] = target.get('cflags', []) + ['-D{0}'.format(d) for d in target['defines'] if d not in IGNORED_DEFINES] 379 target.pop('defines') 380 if 'sources' not in target: 381 continue 382 sources = list(MakeRelatives(FilterIncludes(target['sources']))) 383 if len(sources) > 0: 384 target['sources'] = sources 385 else: 386 target.pop('sources') 387 388 # These dependencies are provided by aosp 389 ignored_targets = { 390 '//third_party/libaom:libaom', 391 '//third_party/libevent:libevent', 392 '//third_party/opus:opus', 393 '//third_party/libsrtp:libsrtp', 394 '//third_party/libvpx:libvpx', 395 '//third_party/libyuv:libyuv', 396 '//third_party/pffft:pffft', 397 '//third_party/rnnoise:rnn_vad', 398 '//third_party/boringssl:boringssl', 399 '//third_party/android_ndk:cpu_features', 400 '//buildtools/third_party/libunwind:libunwind', 401 '//buildtools/third_party/libc++:libc++', 402 } 403 for name, target in targets.items(): 404 # Skip all "action" targets 405 if target['type'] in {'action', 'action_foreach'}: 406 ignored_targets.add(name) 407 408 def is_ignored(target): 409 if target.startswith('//third_party/abseil-cpp'): 410 return True 411 return target in ignored_targets 412 413 targets = {name: target for name, target in targets.items() if not is_ignored(name)} 414 415 for target in targets.values(): 416 # Don't depend on ignored targets 417 target['deps'] = [d for d in target['deps'] if not is_ignored(d) ] 418 419 # Ignore empty static libraries 420 empty_libs = set() 421 for name, target in targets.items(): 422 if target['type'] == 'static_library' and 'sources' not in target and name != '//:webrtc': 423 empty_libs.add(name) 424 for empty_lib in empty_libs: 425 empty_lib_deps = targets[empty_lib].get('deps', []) 426 for target in targets.values(): 427 target['deps'] = FlattenEmptyLibs(target['deps'], empty_lib, empty_lib_deps) 428 for s in empty_libs: 429 targets.pop(s) 430 431 # Select libwebrtc, libaudio_processing and its dependencies 432 selected = set() 433 selected |= DFS('//:webrtc', targets) 434 selected |= DFS('//modules/audio_processing:audio_processing', targets) 435 436 return {FormatName(n): FormatNames(targets[n]) for n in selected} 437 438def _FlattenEmptyLibs(deps, empty_lib, empty_lib_deps): 439 for x in deps: 440 if x == empty_lib: 441 yield from empty_lib_deps 442 else: 443 yield x 444 445def FlattenEmptyLibs(deps, empty_lib, empty_lib_deps): 446 return list(_FlattenEmptyLibs(deps, empty_lib, empty_lib_deps)) 447 448def NonNoneFrom(l): 449 for a in l: 450 if a is not None: 451 return a 452 return None 453 454def MergeListField(target, f, target_by_arch): 455 set_by_arch = {} 456 for a, t in target_by_arch.items(): 457 if len(t) == 0: 458 # We only care about enabled archs 459 continue 460 set_by_arch[a] = set(t.get(f, [])) 461 462 union = set() 463 for _, s in set_by_arch.items(): 464 union |= s 465 466 common = union 467 for a, s in set_by_arch.items(): 468 common &= s 469 470 not_common = {a: s - common for a,s in set_by_arch.items()} 471 472 if len(common) > 0: 473 target[f] = list(common) 474 for a, s in not_common.items(): 475 if len(s) > 0: 476 target['arch'][a][f] = sorted(list(s)) 477 478def Merge(target_by_arch): 479 # The new target shouldn't have the transitive dependencies memoization fields 480 # or have the union of those fields from all 4 input targets. 481 target = {} 482 for f in ['original_name', 'name', 'type']: 483 target[f] = NonNoneFrom([t.get(f) for _,t in target_by_arch.items()]) 484 485 target['arch'] = {} 486 for a, t in target_by_arch.items(): 487 target['arch'][a] = {} 488 if len(t) == 0: 489 target['arch'][a]['enabled'] = 'false' 490 491 list_fields = ['sources', 492 'deps', 493 'cflags', 494 'cflags_c', 495 'cflags_cc', 496 'asmflags'] 497 for lf in list_fields: 498 MergeListField(target, lf, target_by_arch) 499 500 # Static libraries should be depended on at the root level and disabled for 501 # the corresponding architectures. 502 for arch in target['arch'].values(): 503 if 'deps' not in arch: 504 continue 505 deps = arch['deps'] 506 if 'deps' not in target: 507 target['deps'] = [] 508 target['deps'] += deps 509 arch.pop('deps') 510 if 'deps' in target: 511 target['deps'] = sorted(target['deps']) 512 513 # Remove empty sets 514 for a in ARCHS: 515 if len(target['arch'][a]) == 0: 516 target['arch'].pop(a) 517 if len(target['arch']) == 0: 518 target.pop('arch') 519 520 return target 521 522def DisabledArchs4Target(target): 523 ret = set() 524 for a in ARCHS: 525 if a not in target.get('arch', {}): 526 continue 527 if target['arch'][a].get('enabled', 'true') == 'false': 528 ret.add(a) 529 return ret 530 531 532def HandleDisabledArchs(targets): 533 for n, t in targets.items(): 534 if 'arch' not in t: 535 continue 536 disabledArchs = DisabledArchs4Target(t) 537 if len(disabledArchs) == 0: 538 continue 539 # Fix targets that depend on this one 540 for t in targets.values(): 541 if DisabledArchs4Target(t) == disabledArchs: 542 # With the same disabled archs there is no need to move dependencies 543 continue 544 if 'deps' in t and n in t['deps']: 545 # Remove the dependency from the high level list 546 t['deps'] = sorted(set(t['deps']) - {n}) 547 if 'arch' not in t: 548 t['arch'] = {} 549 for a in ARCHS: 550 if a in disabledArchs: 551 continue 552 if a not in t['arch']: 553 t['arch'][a] = {} 554 if 'deps' not in t['arch'][a]: 555 t['arch'][a]['deps'] = [] 556 t['arch'][a]['deps'] += [n] 557 558def MergeAll(targets_by_arch): 559 names = set() 560 for t in targets_by_arch.values(): 561 names |= t.keys() 562 targets = {} 563 for name in names: 564 targets[name] = Merge({a: t.get(name, {}) for a,t in targets_by_arch.items()}) 565 566 HandleDisabledArchs(targets) 567 568 return targets 569 570def GatherAllFlags(obj): 571 if type(obj) != type({}): 572 # not a dictionary 573 return set() 574 ret = set() 575 for f in FLAGS: 576 ret |= set(obj.get(f, [])) 577 for v in obj.values(): 578 ret |= GatherAllFlags(v) 579 return ret 580 581def FilterFlagsInUse(flags, directory): 582 unused = [] 583 for f in flags: 584 nf = f 585 if nf.startswith("-D"): 586 nf = nf[2:] 587 i = nf.find('=') 588 if i > 0: 589 nf = nf[:i] 590 c = os.system(f"find {directory} -name '*.gn*' | xargs grep -q -s -e '{nf}'") 591 if c != 0: 592 # couldn't find the flag in *.gn or *.gni 593 unused.append(f) 594 return unused 595 596if len(sys.argv) != 2: 597 print('wrong number of arguments', file = sys.stderr) 598 exit(1) 599 600dir = sys.argv[1] 601 602targets_by_arch = {} 603flags = set() 604for arch in ARCHS: 605 path = "{0}/project_{1}.json".format(dir, arch) 606 json_file = open(path, 'r') 607 targets_by_arch[arch] = Preprocess(json.load(json_file)) 608 flags |= GatherAllFlags(targets_by_arch[arch]) 609 610unusedFlags = FilterFlagsInUse(flags, f"{dir}/..") 611IGNORED_FLAGS = sorted(set(IGNORED_FLAGS) | set(unusedFlags)) 612 613PrintHeader() 614 615GenerateDefault(targets_by_arch) 616 617targets = MergeAll(targets_by_arch) 618 619print('\n\n') 620 621for name, target in sorted(targets.items()): 622 typ = target['type'] 623 if typ == 'static_library': 624 GenerateStaticLib(target, targets) 625 elif typ == 'group': 626 GenerateGroup(target) 627 else: 628 print('Unknown type: {0} ({1})'.format(typ, target['name']), file = sys.stderr) 629 exit(1) 630 print('\n\n') 631 632webrtc_libs = TransitiveDependencies(FormatName('//:webrtc'), 'static_library', targets) 633print('cc_library_static {') 634print(' name: "libwebrtc",') 635print(' defaults: ["webrtc_defaults"],') 636print(' export_include_dirs: ["."],') 637print(' whole_static_libs: {0},'.format(FormatList(sorted(webrtc_libs['global']) + ['libpffft', 'rnnoise_rnn_vad']))) 638print(' arch: {') 639for a in ARCHS: 640 if len(webrtc_libs[a]) > 0: 641 print(' {0}: {{'.format(ARCH_NAME_MAP[a])) 642 print(' whole_static_libs: {0},'.format(FormatList(sorted(webrtc_libs[a])))) 643 print(' },') 644print(' },') 645print('}') 646 647print('\n\n') 648 649audio_proc_libs = TransitiveDependencies(FormatName('//modules/audio_processing:audio_processing'), 'static_library', targets) 650print('cc_library_static {') 651print(' name: "webrtc_audio_processing",') 652print(' defaults: ["webrtc_defaults"],') 653print(' export_include_dirs: [') 654print(' ".",') 655print(' "modules/include",') 656print(' "modules/audio_processing/include",') 657print(' ],') 658print(' whole_static_libs: {0},'.format(FormatList(sorted(audio_proc_libs['global']) + ['libpffft', 'rnnoise_rnn_vad']))) 659print(' arch: {') 660for a in ARCHS: 661 if len(audio_proc_libs[a]) > 0: 662 print(' {0}: {{'.format(ARCH_NAME_MAP[a])) 663 print(' whole_static_libs: {0},'.format(FormatList(sorted(audio_proc_libs[a])))) 664 print(' },') 665print(' },') 666print('}') 667