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