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