• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15
16root_targets = [
17    "//:libGLESv2",
18    "//:libGLESv1_CM",
19    "//:libEGL",
20]
21
22sdk_version = '28'
23stl = 'libc++_static'
24
25abi_arm = 'arm'
26abi_arm64 = 'arm64'
27abi_x86 = 'x86'
28abi_x64 = 'x86_64'
29
30abi_targets = [abi_arm, abi_arm64, abi_x86, abi_x64]
31
32
33def tabs(indent):
34    return ' ' * (indent * 4)
35
36
37def has_child_values(value):
38    # Elements of the blueprint can be pruned if they are empty lists or dictionaries of empty
39    # lists
40    if isinstance(value, list):
41        return len(value) > 0
42    if isinstance(value, dict):
43        for (item, item_value) in value.items():
44            if has_child_values(item_value):
45                return True
46        return False
47
48    # This is a value leaf node
49    return True
50
51
52def write_blueprint_key_value(output, name, value, indent=1):
53    if not has_child_values(value):
54        return
55
56    if isinstance(value, set) or isinstance(value, list):
57        value = list(sorted(set(value)))
58
59    if isinstance(value, list):
60        output.append(tabs(indent) + '%s: [' % name)
61        for item in value:
62            output.append(tabs(indent + 1) + '"%s",' % item)
63        output.append(tabs(indent) + '],')
64        return
65    if isinstance(value, dict):
66        if not value:
67            return
68        output.append(tabs(indent) + '%s: {' % name)
69        for (item, item_value) in value.items():
70            write_blueprint_key_value(output, item, item_value, indent + 1)
71        output.append(tabs(indent) + '},')
72        return
73    if isinstance(value, bool):
74        output.append(tabs(indent) + '%s: %s,' % (name, 'true' if value else 'false'))
75        return
76    output.append(tabs(indent) + '%s: "%s",' % (name, value))
77
78
79def write_blueprint(output, target_type, values):
80    if target_type == 'license':
81        comment = """
82// Added automatically by a large-scale-change that took the approach of
83// 'apply every license found to every target'. While this makes sure we respect
84// every license restriction, it may not be entirely correct.
85//
86// e.g. GPL in an MIT project might only apply to the contrib/ directory.
87//
88// Please consider splitting the single license below into multiple licenses,
89// taking care not to lose any license_kind information, and overriding the
90// default license using the 'licenses: [...]' property on targets as needed.
91//
92// For unused files, consider creating a 'fileGroup' with "//visibility:private"
93// to attach the license to, and including a comment whether the files may be
94// used in the current project.
95// See: http://go/android-license-faq"""
96        output.append(comment)
97
98    output.append('%s {' % target_type)
99    for (key, value) in values.items():
100        write_blueprint_key_value(output, key, value)
101    output.append('}')
102
103
104def gn_target_to_blueprint_target(target, target_info):
105    if 'output_name' in target_info:
106        return target_info['output_name']
107
108    # Split the gn target name (in the form of //gn_file_path:target_name) into gn_file_path and
109    # target_name
110    target_regex = re.compile(r"^//([a-zA-Z0-9\-\+_/]*):([a-zA-Z0-9\-\+_.]+)$")
111    match = re.match(target_regex, target)
112    assert match is not None
113
114    gn_file_path = match.group(1)
115    target_name = match.group(2)
116    assert len(target_name) > 0
117
118    # Clean up the gn file path to be a valid blueprint target name.
119    gn_file_path = gn_file_path.replace("/", "_").replace(".", "_").replace("-", "_")
120
121    # Generate a blueprint target name by merging the gn path and target so each target is unique.
122    # Prepend the 'angle' prefix to all targets in the root path (empty gn_file_path).
123    # Skip this step if the target name already starts with 'angle' to avoid target names such as 'angle_angle_common'.
124    root_prefix = "angle"
125    if len(gn_file_path) == 0 and not target_name.startswith(root_prefix):
126        gn_file_path = root_prefix
127
128    # Avoid names such as _angle_common if the gn_file_path is empty.
129    if len(gn_file_path) > 0:
130        gn_file_path += "_"
131
132    return gn_file_path + target_name
133
134
135def remap_gn_path(path):
136    # TODO: pass the gn gen folder as an arg so it is future proof. b/150457277
137    remap_folders = [
138        ('out/Android/gen/angle/', ''),
139        ('out/Android/gen/', ''),
140    ]
141
142    remapped_path = path
143    for (remap_source, remap_dest) in remap_folders:
144        remapped_path = remapped_path.replace(remap_source, remap_dest)
145
146    return remapped_path
147
148
149def gn_path_to_blueprint_path(source):
150    # gn uses '//' to indicate the root directory, blueprint uses the .bp file's location
151    return remap_gn_path(re.sub(r'^//?', '', source))
152
153
154def gn_paths_to_blueprint_paths(paths):
155    rebased_paths = []
156    for path in paths:
157        rebased_paths.append(gn_path_to_blueprint_path(path))
158    return rebased_paths
159
160
161def gn_sources_to_blueprint_sources(sources):
162    # Blueprints only list source files in the sources list. Headers are only referenced though
163    # include paths.
164    file_extension_allowlist = [
165        '.c',
166        '.cc',
167        '.cpp',
168    ]
169
170    rebased_sources = []
171    for source in sources:
172        if os.path.splitext(source)[1] in file_extension_allowlist:
173            rebased_sources.append(gn_path_to_blueprint_path(source))
174    return rebased_sources
175
176
177target_blockist = [
178    '//build/config:shared_library_deps',
179    '//third_party/vulkan-validation-layers/src:vulkan_clean_old_validation_layer_objects',
180]
181
182third_party_target_allowlist = [
183    '//third_party/abseil-cpp',
184    '//third_party/vulkan-deps',
185    '//third_party/vulkan_memory_allocator',
186    '//third_party/zlib',
187]
188
189include_blocklist = [
190    '//buildtools/third_party/libc++/',
191    '//out/Android/gen/third_party/vulkan-deps/glslang/src/include/',
192    '//third_party/android_ndk/sources/android/cpufeatures/',
193]
194
195
196def gn_deps_to_blueprint_deps(target_info, build_info):
197    static_libs = []
198    shared_libs = []
199    defaults = []
200    generated_headers = []
201    header_libs = []
202    if 'deps' not in target_info:
203        return static_libs, defaults
204
205    for dep in target_info['deps']:
206        if dep not in target_blockist and (not dep.startswith('//third_party') or any(
207                dep.startswith(substring) for substring in third_party_target_allowlist)):
208            dep_info = build_info[dep]
209            blueprint_dep_name = gn_target_to_blueprint_target(dep, dep_info)
210
211            # Depending on the dep type, blueprints reference it differently.
212            gn_dep_type = dep_info['type']
213            if gn_dep_type == 'static_library':
214                static_libs.append(blueprint_dep_name)
215            elif gn_dep_type == 'shared_library':
216                shared_libs.append(blueprint_dep_name)
217            elif gn_dep_type == 'source_set' or gn_dep_type == 'group':
218                defaults.append(blueprint_dep_name)
219            elif gn_dep_type == 'action':
220                generated_headers.append(blueprint_dep_name)
221
222            # Blueprints do not chain linking of static libraries.
223            (child_static_libs, _, _, child_generated_headers, _) = gn_deps_to_blueprint_deps(
224                dep_info, build_info)
225
226            # Each target needs to link all child static library dependencies.
227            static_libs += child_static_libs
228
229            # Each blueprint target runs genrules in a different output directory unlike GN. If a
230            # target depends on another's genrule, it wont find the outputs. Propogate generated
231            # headers up the dependency stack.
232            generated_headers += child_generated_headers
233        elif dep == '//third_party/android_ndk:cpu_features':
234            # chrome_zlib needs cpufeatures from the Android NDK. Rather than including the
235            # entire NDK is a dep in the ANGLE checkout, use the library that's already part
236            # of Android.
237            dep_info = build_info[dep]
238            blueprint_dep_name = gn_target_to_blueprint_target(dep, dep_info)
239            static_libs.append('cpufeatures')
240
241    return static_libs, shared_libs, defaults, generated_headers, header_libs
242
243
244def gn_libs_to_blueprint_shared_libraries(target_info):
245    lib_blockist = [
246        'android_support',
247        'unwind',
248    ]
249
250    result = []
251    if 'libs' in target_info:
252        for lib in target_info['libs']:
253            if lib not in lib_blockist:
254                android_lib = lib if '@' in lib else 'lib' + lib
255                result.append(android_lib)
256    return result
257
258
259def gn_include_dirs_to_blueprint_include_dirs(target_info):
260    result = []
261    if 'include_dirs' in target_info:
262        for include_dir in target_info['include_dirs']:
263            if len(include_dir) > 0 and include_dir not in include_blocklist:
264                result.append(gn_path_to_blueprint_path(include_dir))
265    return result
266
267
268def escape_quotes(string):
269    return string.replace("\"", "\\\"").replace("\'", "\\\'")
270
271
272def gn_cflags_to_blueprint_cflags(target_info):
273    result = []
274
275    # regexs of allowlisted cflags
276    cflag_allowlist = [
277        r'^-Wno-.*$',  # forward cflags that disable warnings
278        r'-mpclmul'  # forward "-mpclmul" (used by zlib)
279    ]
280
281    for cflag_type in ['cflags', 'cflags_c', 'cflags_cc']:
282        if cflag_type in target_info:
283            for cflag in target_info[cflag_type]:
284                for allowlisted_cflag in cflag_allowlist:
285                    if re.search(allowlisted_cflag, cflag):
286                        result.append(cflag)
287
288    # Chrome and Android use different versions of Clang which support differnt warning options.
289    # Ignore errors about unrecognized warning flags.
290    result.append('-Wno-unknown-warning-option')
291
292    # Override AOSP build flags to match ANGLE's CQ testing and reduce binary size
293    result.append('-Oz')
294    result.append('-fno-unwind-tables')
295
296    if 'defines' in target_info:
297        for define in target_info['defines']:
298            # Don't emit ANGLE's CPU-bits define here, it will be part of the arch-specific
299            # information later
300            result.append('-D%s' % escape_quotes(define))
301
302    return result
303
304
305blueprint_library_target_types = {
306    "static_library": "cc_library_static",
307    "shared_library": "cc_library_shared",
308    "source_set": "cc_defaults",
309    "group": "cc_defaults",
310}
311
312
313def merge_bps(bps_for_abis):
314    common_bp = {}
315    for abi in abi_targets:
316        for key in bps_for_abis[abi]:
317            if isinstance(bps_for_abis[abi][key], list):
318                # Find list values that are common to all ABIs
319                for value in bps_for_abis[abi][key]:
320                    value_in_all_abis = True
321                    for abi2 in abi_targets:
322                        if key == 'defaults':
323                            # arch-specific defaults are not supported
324                            break
325                        value_in_all_abis = value_in_all_abis and (key in bps_for_abis[abi2].keys(
326                        )) and (value in bps_for_abis[abi2][key])
327                    if value_in_all_abis:
328                        if key in common_bp.keys():
329                            common_bp[key].append(value)
330                        else:
331                            common_bp[key] = [value]
332                    else:
333                        if 'arch' not in common_bp.keys():
334                            # Make sure there is an 'arch' entry to hold ABI-specific values
335                            common_bp['arch'] = {}
336                            for abi3 in abi_targets:
337                                common_bp['arch'][abi3] = {}
338                        if key in common_bp['arch'][abi].keys():
339                            common_bp['arch'][abi][key].append(value)
340                        else:
341                            common_bp['arch'][abi][key] = [value]
342            else:
343                # Assume everything that's not a list is common to all ABIs
344                common_bp[key] = bps_for_abis[abi][key]
345
346    return common_bp
347
348
349def library_target_to_blueprint(target, build_info):
350    bps_for_abis = {}
351    blueprint_type = ""
352    for abi in abi_targets:
353        if target not in build_info[abi].keys():
354            bps_for_abis[abi] = {}
355            continue
356
357        target_info = build_info[abi][target]
358
359        blueprint_type = blueprint_library_target_types[target_info['type']]
360
361        bp = {'name': gn_target_to_blueprint_target(target, target_info)}
362
363        if 'sources' in target_info:
364            bp['srcs'] = gn_sources_to_blueprint_sources(target_info['sources'])
365
366        (bp['static_libs'], bp['shared_libs'], bp['defaults'], bp['generated_headers'],
367         bp['header_libs']) = gn_deps_to_blueprint_deps(target_info, build_info[abi])
368        bp['shared_libs'] += gn_libs_to_blueprint_shared_libraries(target_info)
369
370        bp['local_include_dirs'] = gn_include_dirs_to_blueprint_include_dirs(target_info)
371
372        bp['cflags'] = gn_cflags_to_blueprint_cflags(target_info)
373
374        bp['sdk_version'] = sdk_version
375        bp['stl'] = stl
376        if target in root_targets:
377            bp['vendor'] = True
378            bp['target'] = {'android': {'relative_install_path': 'egl'}}
379        bps_for_abis[abi] = bp
380
381    common_bp = merge_bps(bps_for_abis)
382
383    return blueprint_type, common_bp
384
385
386def gn_action_args_to_blueprint_args(blueprint_inputs, blueprint_outputs, args):
387    # TODO: pass the gn gen folder as an arg so we know how to get from the gen path to the root
388    # path. b/150457277
389    remap_folders = [
390        # Specific special-cases first, since the other will strip the prefixes.
391        ('gen/third_party/vulkan-deps/glslang/src/include/glslang/build_info.h',
392         'glslang/build_info.h'),
393        ('third_party/vulkan-deps/glslang/src',
394         'external/angle/third_party/vulkan-deps/glslang/src'),
395        ('../../', ''),
396        ('gen/', ''),
397    ]
398
399    result_args = []
400    for arg in args:
401        # Attempt to find if this arg is a path to one of the inputs. If it is, use the blueprint
402        # $(location <path>) argument instead so the path gets remapped properly to the location
403        # that the script is run from
404        remapped_path_arg = arg
405        for (remap_source, remap_dest) in remap_folders:
406            remapped_path_arg = remapped_path_arg.replace(remap_source, remap_dest)
407
408        if remapped_path_arg in blueprint_inputs or remapped_path_arg in blueprint_outputs:
409            result_args.append('$(location %s)' % remapped_path_arg)
410        elif os.path.basename(remapped_path_arg) in blueprint_outputs:
411            result_args.append('$(location %s)' % os.path.basename(remapped_path_arg))
412        else:
413            result_args.append(remapped_path_arg)
414
415    return result_args
416
417
418blueprint_gen_types = {
419    "action": "cc_genrule",
420}
421
422
423inputs_blocklist = [
424    '//.git/HEAD',
425]
426
427outputs_remap = {
428    'build_info.h': 'glslang/build_info.h',
429}
430
431
432def is_input_in_tool_files(tool_files, input):
433    return input in tool_files
434
435
436def action_target_to_blueprint(target, build_info):
437    target_info = build_info[target]
438    blueprint_type = blueprint_gen_types[target_info['type']]
439
440    bp = {'name': gn_target_to_blueprint_target(target, target_info)}
441
442    # Blueprints use only one 'srcs', merge all gn inputs into one list.
443    gn_inputs = []
444    if 'inputs' in target_info:
445        for input in target_info['inputs']:
446            if input not in inputs_blocklist:
447                gn_inputs.append(input)
448    if 'sources' in target_info:
449        gn_inputs += target_info['sources']
450    # Filter out the 'script' entry since Android.bp doesn't like the duplicate entries
451    if 'script' in target_info:
452        gn_inputs = [
453            input for input in gn_inputs
454            if not is_input_in_tool_files(target_info['script'], input)
455        ]
456    bp_srcs = gn_paths_to_blueprint_paths(gn_inputs)
457
458    bp['srcs'] = bp_srcs
459
460    # genrules generate the output right into the 'root' directory. Strip any path before the
461    # file name.
462    bp_outputs = []
463    for gn_output in target_info['outputs']:
464        output = os.path.basename(gn_output)
465        if output in outputs_remap.keys():
466            output = outputs_remap[output]
467        bp_outputs.append(output)
468
469    bp['out'] = bp_outputs
470
471    bp['tool_files'] = [gn_path_to_blueprint_path(target_info['script'])]
472
473    # Generate the full command, $(location) refers to tool_files[0], the script
474    cmd = ['$(location)'] + gn_action_args_to_blueprint_args(bp_srcs, bp_outputs,
475                                                             target_info['args'])
476    bp['cmd'] = ' '.join(cmd)
477
478    bp['sdk_version'] = sdk_version
479
480    return blueprint_type, bp
481
482
483def gn_target_to_blueprint(target, build_info):
484    for abi in abi_targets:
485        gn_type = build_info[abi][target]['type']
486        if gn_type in blueprint_library_target_types:
487            return library_target_to_blueprint(target, build_info)
488        elif gn_type in blueprint_gen_types:
489            return action_target_to_blueprint(target, build_info[abi])
490        else:
491            # Target is not used by this ABI
492            continue
493
494
495def get_gn_target_dependencies(output_dependencies, build_info, target):
496    if target not in output_dependencies:
497        output_dependencies.insert(0, target)
498
499    for dep in build_info[target]['deps']:
500        if dep in target_blockist:
501            # Blocklisted dep
502            continue
503        if dep not in build_info:
504            # No info for this dep, skip it
505            continue
506
507        # Recurse
508        get_gn_target_dependencies(output_dependencies, build_info, dep)
509
510
511def main():
512    parser = argparse.ArgumentParser(
513        description='Generate Android blueprints from gn descriptions.')
514
515    for abi in abi_targets:
516        fixed_abi = abi
517        if abi == abi_x64:
518            fixed_abi = 'x64'  # gn uses x64, rather than x86_64
519        parser.add_argument(
520            'gn_json_' + fixed_abi,
521            help=fixed_abi +
522            'gn desc in json format. Generated with \'gn desc <out_dir> --format=json "*"\'.')
523    args = vars(parser.parse_args())
524
525    build_info = {}
526    for abi in abi_targets:
527        fixed_abi = abi
528        if abi == abi_x64:
529            fixed_abi = 'x64'  # gn uses x64, rather than x86_64
530        with open(args['gn_json_' + fixed_abi], 'r') as f:
531            build_info[abi] = json.load(f)
532
533    targets_to_write = []
534    for abi in abi_targets:
535        for root_target in root_targets:
536            get_gn_target_dependencies(targets_to_write, build_info[abi], root_target)
537
538    blueprint_targets = []
539    for target in targets_to_write:
540        blueprint_targets.append(gn_target_to_blueprint(target, build_info))
541
542    # Add license build rules
543    blueprint_targets.append(('package', {
544        'default_applicable_licenses': ['external_angle_license'],
545    }))
546    blueprint_targets.append(('license', {
547        'name':
548            'external_angle_license',
549        'visibility': [':__subpackages__'],
550        'license_kinds': [
551            'SPDX-license-identifier-Apache-2.0',
552            'SPDX-license-identifier-BSD',
553            'SPDX-license-identifier-LGPL',
554            'SPDX-license-identifier-MIT',
555            'SPDX-license-identifier-Zlib',
556            'legacy_unencumbered',
557        ],
558        'license_text': [
559            'LICENSE', 'third_party/abseil-cpp/LICENSE', 'third_party/vulkan-deps/LICENSE',
560            'third_party/vulkan_memory_allocator/LICENSE.txt', 'third_party/zlib/LICENSE'
561        ],
562    }))
563
564    # Add APKs with all of the root libraries
565    blueprint_targets.append((
566        'filegroup',
567        {
568            'name': 'ANGLE_srcs',
569            # Only add EmptyMainActivity.java since we just need to be able to reply to the intent
570            # android.app.action.ANGLE_FOR_ANDROID to indicate ANGLE is present on the device.
571            'srcs': ['src/android_system_settings/src/com/android/angle/EmptyMainActivity.java'],
572        }))
573    blueprint_targets.append((
574        'java_defaults',
575        {
576            'name': 'ANGLE_java_defaults',
577            'sdk_version': 'system_current',
578            'min_sdk_version': sdk_version,
579            'compile_multilib': 'both',
580            'use_embedded_native_libs': True,
581            'jni_libs': [
582                # hack: assume abi_arm
583                gn_target_to_blueprint_target(target, build_info[abi_arm][target])
584                for target in root_targets
585            ],
586            'aaptflags': [
587                # Don't compress *.json files
588                '-0 .json',
589            ],
590            'srcs': [':ANGLE_srcs'],
591            'plugins': ['java_api_finder',],
592            'privileged': True,
593            'product_specific': True,
594            'owner': 'google',
595        }))
596
597    blueprint_targets.append(('android_app', {
598        'name': 'ANGLE',
599        'defaults': ['ANGLE_java_defaults'],
600        'manifest': 'android/AndroidManifest.xml',
601        'asset_dirs': ['src/android_system_settings/assets',],
602    }))
603
604    output = [
605        """// GENERATED FILE - DO NOT EDIT.
606// Generated by %s
607//
608// Copyright 2020 The ANGLE Project Authors. All rights reserved.
609// Use of this source code is governed by a BSD-style license that can be
610// found in the LICENSE file.
611//
612""" % sys.argv[0]
613    ]
614    for (blueprint_type, blueprint_data) in blueprint_targets:
615        write_blueprint(output, blueprint_type, blueprint_data)
616
617    print('\n'.join(output))
618
619
620if __name__ == '__main__':
621    sys.exit(main())
622