• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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