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