Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 # Copyright (C) 2017 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 
     16 # This tool translates a collection of BUILD.gn files into a mostly equivalent
     17 # Android.bp file for the Android Soong build system. The input to the tool is a
     18 # JSON description of the GN build definition generated with the following
     19 # command:
     20 #
     21 #   gn desc out --format=json --all-toolchains "//*" > desc.json
     22 #
     23 # The tool is then given a list of GN labels for which to generate Android.bp
     24 # build rules. The dependencies for the GN labels are squashed to the generated
     25 # Android.bp target, except for actions which get their own genrule. Some
     26 # libraries are also mapped to their Android equivalents -- see |builtin_deps|.
     27 
     28 import argparse
     29 import errno
     30 import json
     31 import os
     32 import re
     33 import shutil
     34 import subprocess
     35 import sys
     36 
     37 # Default targets to translate to the blueprint file.
     38 default_targets = [
     39     '//:libperfetto',
     40     '//:libperfetto_android_internal',
     41     '//:perfetto_integrationtests',
     42     '//:perfetto_trace_protos',
     43     '//:perfetto_unittests',
     44     '//:perfetto',
     45     '//:traced',
     46     '//:traced_probes',
     47     '//:trace_to_text',
     48     '//:heapprofd_client',
     49     '//:heapprofd',
     50     '//:trigger_perfetto',
     51 ]
     52 
     53 # Defines a custom init_rc argument to be applied to the corresponding output
     54 # blueprint target.
     55 target_initrc = {
     56     '//:traced': 'perfetto.rc',
     57     '//:heapprofd': 'heapprofd.rc',
     58 }
     59 
     60 target_host_supported = [
     61     '//:perfetto_trace_protos',
     62 ]
     63 
     64 target_host_only = [
     65     '//:trace_to_text',
     66 ]
     67 
     68 # Arguments for the GN output directory.
     69 gn_args = 'target_os="android" target_cpu="arm" is_debug=false perfetto_build_with_android=true'
     70 
     71 # All module names are prefixed with this string to avoid collisions.
     72 module_prefix = 'perfetto_'
     73 
     74 # Shared libraries which are directly translated to Android system equivalents.
     75 library_whitelist = [
     76     "android.hardware.atrace (at] 1.0",
     77     'android.hardware.health (at] 2.0',
     78     "android.hardware.power.stats (at] 1.0",
     79     'android',
     80     'base',
     81     'binder',
     82     'hidlbase',
     83     'hidltransport',
     84     'hwbinder',
     85     'incident',
     86     'log',
     87     'services',
     88     'utils',
     89 ]
     90 
     91 # Name of the module which settings such as compiler flags for all other
     92 # modules.
     93 defaults_module = module_prefix + 'defaults'
     94 
     95 # Location of the project in the Android source tree.
     96 tree_path = 'external/perfetto'
     97 
     98 # Compiler flags which are passed through to the blueprint.
     99 cflag_whitelist = r'^-DPERFETTO.*$'
    100 
    101 # Compiler defines which are passed through to the blueprint.
    102 define_whitelist = r'^(GOOGLE_PROTO.*)|(PERFETTO_BUILD_WITH_ANDROID)|(ZLIB_.*)|(USE_MMAP)|(HAVE_HIDDEN)$'
    103 
    104 # Shared libraries which are not in PDK.
    105 library_not_in_pdk = {
    106     'libandroid',
    107     'libservices',
    108 }
    109 
    110 # Additional arguments to apply to Android.bp rules.
    111 additional_args = {
    112     'heapprofd_client': [
    113         ('include_dirs', ['bionic/libc']),
    114         ('static_libs', ['libasync_safe']),
    115     ],
    116     'traced_probes': [
    117       ('required', ['libperfetto_android_internal', 'trigger_perfetto']),
    118     ],
    119     'libperfetto_android_internal': [
    120       ('static_libs', ['libhealthhalutils']),
    121     ],
    122 }
    123 
    124 
    125 def enable_gmock(module):
    126     module.static_libs.append('libgmock')
    127 
    128 
    129 def enable_gtest_prod(module):
    130     module.static_libs.append('libgtest_prod')
    131 
    132 
    133 def enable_gtest(module):
    134     assert module.type == 'cc_test'
    135 
    136 
    137 def enable_protobuf_full(module):
    138     module.shared_libs.append('libprotobuf-cpp-full')
    139 
    140 
    141 def enable_protobuf_lite(module):
    142     module.shared_libs.append('libprotobuf-cpp-lite')
    143 
    144 
    145 def enable_protoc_lib(module):
    146     module.shared_libs.append('libprotoc')
    147 
    148 def enable_libunwindstack(module):
    149     module.shared_libs.append('libunwindstack')
    150     module.shared_libs.append('libprocinfo')
    151     module.shared_libs.append('libbase')
    152 
    153 def enable_libunwind(module):
    154     # libunwind is disabled on Darwin so we cannot depend on it.
    155     pass
    156 
    157 def enable_sqlite(module):
    158     module.static_libs.append('libsqlite')
    159 
    160 def enable_zlib(module):
    161     module.shared_libs.append('libz')
    162 
    163 # Android equivalents for third-party libraries that the upstream project
    164 # depends on.
    165 builtin_deps = {
    166     '//buildtools:gmock': enable_gmock,
    167     '//buildtools:gtest': enable_gtest,
    168     '//gn:gtest_prod_config': enable_gtest_prod,
    169     '//buildtools:gtest_main': enable_gtest,
    170     '//buildtools:libunwind': enable_libunwind,
    171     '//buildtools:protobuf_full': enable_protobuf_full,
    172     '//buildtools:protobuf_lite': enable_protobuf_lite,
    173     '//buildtools:protoc_lib': enable_protoc_lib,
    174     '//buildtools:libunwindstack': enable_libunwindstack,
    175     '//buildtools:sqlite': enable_sqlite,
    176     '//buildtools:zlib': enable_zlib,
    177 }
    178 
    179 # ----------------------------------------------------------------------------
    180 # End of configuration.
    181 # ----------------------------------------------------------------------------
    182 
    183 
    184 class Error(Exception):
    185     pass
    186 
    187 
    188 class ThrowingArgumentParser(argparse.ArgumentParser):
    189     def __init__(self, context):
    190         super(ThrowingArgumentParser, self).__init__()
    191         self.context = context
    192 
    193     def error(self, message):
    194         raise Error('%s: %s' % (self.context, message))
    195 
    196 
    197 class Module(object):
    198     """A single module (e.g., cc_binary, cc_test) in a blueprint."""
    199 
    200     def __init__(self, mod_type, name):
    201         self.type = mod_type
    202         self.name = name
    203         self.srcs = []
    204         self.comment = None
    205         self.shared_libs = []
    206         self.static_libs = []
    207         self.tools = []
    208         self.cmd = None
    209         self.host_supported = False
    210         self.init_rc = []
    211         self.out = []
    212         self.export_include_dirs = []
    213         self.generated_headers = []
    214         self.export_generated_headers = []
    215         self.defaults = []
    216         self.cflags = set()
    217         self.local_include_dirs = []
    218         self.include_dirs = []
    219         self.required = []
    220         self.user_debug_flag = False
    221         self.tool_files = None
    222 
    223     def to_string(self, output):
    224         if self.comment:
    225             output.append('// %s' % self.comment)
    226         output.append('%s {' % self.type)
    227         self._output_field(output, 'name')
    228         self._output_field(output, 'srcs')
    229         self._output_field(output, 'shared_libs')
    230         self._output_field(output, 'static_libs')
    231         self._output_field(output, 'tools')
    232         self._output_field(output, 'cmd', sort=False)
    233         self._output_field(output, 'host_supported')
    234         self._output_field(output, 'init_rc')
    235         self._output_field(output, 'out')
    236         self._output_field(output, 'export_include_dirs')
    237         self._output_field(output, 'generated_headers')
    238         self._output_field(output, 'export_generated_headers')
    239         self._output_field(output, 'defaults')
    240         self._output_field(output, 'cflags')
    241         self._output_field(output, 'local_include_dirs')
    242         self._output_field(output, 'include_dirs')
    243         self._output_field(output, 'required')
    244         self._output_field(output, 'tool_files')
    245 
    246         disable_pdk = any(name in library_not_in_pdk for name in self.shared_libs)
    247         if self.user_debug_flag or disable_pdk:
    248             output.append('  product_variables: {')
    249             if disable_pdk:
    250                 output.append('    pdk: {')
    251                 output.append('      enabled: false,')
    252                 output.append('    },')
    253             if self.user_debug_flag:
    254                 output.append('    debuggable: {')
    255                 output.append('      cflags: ["-DPERFETTO_BUILD_WITH_ANDROID_USERDEBUG"],')
    256                 output.append('    },')
    257             output.append('  },')
    258         output.append('}')
    259         output.append('')
    260 
    261     def _output_field(self, output, name, sort=True):
    262         value = getattr(self, name)
    263         return self._write_value(output, name, value, sort)
    264 
    265     def _write_value(self, output, name, value, sort=True):
    266         if not value:
    267             return
    268         if isinstance(value, set):
    269             value = sorted(value)
    270         if isinstance(value, list):
    271             output.append('  %s: [' % name)
    272             for item in sorted(value) if sort else value:
    273                 output.append('    "%s",' % item)
    274             output.append('  ],')
    275             return
    276         if isinstance(value, bool):
    277            output.append('  %s: true,' % name)
    278            return
    279         output.append('  %s: "%s",' % (name, value))
    280 
    281 
    282 class Blueprint(object):
    283     """In-memory representation of an Android.bp file."""
    284 
    285     def __init__(self):
    286         self.modules = {}
    287 
    288     def add_module(self, module):
    289         """Adds a new module to the blueprint, replacing any existing module
    290         with the same name.
    291 
    292         Args:
    293             module: Module instance.
    294         """
    295         self.modules[module.name] = module
    296 
    297     def to_string(self, output):
    298         for m in sorted(self.modules.itervalues(), key=lambda m: m.name):
    299             m.to_string(output)
    300 
    301 
    302 def label_to_path(label):
    303     """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
    304     assert label.startswith('//')
    305     return label[2:]
    306 
    307 
    308 def label_to_module_name(label):
    309     """Turn a GN label (e.g., //:perfetto_tests) into a module name."""
    310     module = re.sub(r'^//:?', '', label)
    311     module = re.sub(r'[^a-zA-Z0-9_]', '_', module)
    312     if not module.startswith(module_prefix) and label not in default_targets:
    313         return module_prefix + module
    314     return module
    315 
    316 
    317 def label_without_toolchain(label):
    318     """Strips the toolchain from a GN label.
    319 
    320     Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain:
    321     gcc_like_host) without the parenthesised toolchain part.
    322     """
    323     return label.split('(')[0]
    324 
    325 
    326 def is_supported_source_file(name):
    327     """Returns True if |name| can appear in a 'srcs' list."""
    328     return os.path.splitext(name)[1] in ['.c', '.cc', '.proto']
    329 
    330 
    331 def is_generated_by_action(desc, label):
    332     """Checks if a label is generated by an action.
    333 
    334     Returns True if a GN output label |label| is an output for any action,
    335     i.e., the file is generated dynamically.
    336     """
    337     for target in desc.itervalues():
    338         if target['type'] == 'action' and label in target['outputs']:
    339             return True
    340     return False
    341 
    342 
    343 def apply_module_dependency(blueprint, desc, module, dep_name):
    344     """Recursively collect dependencies for a given module.
    345 
    346     Walk the transitive dependencies for a GN target and apply them to a given
    347     module. This effectively flattens the dependency tree so that |module|
    348     directly contains all the sources, libraries, etc. in the corresponding GN
    349     dependency tree.
    350 
    351     Args:
    352         blueprint: Blueprint instance which is being generated.
    353         desc: JSON GN description.
    354         module: Module to which dependencies should be added.
    355         dep_name: GN target of the dependency.
    356     """
    357     # If the dependency refers to a library which we can replace with an Android
    358     # equivalent, stop recursing and patch the dependency in.
    359     if label_without_toolchain(dep_name) in builtin_deps:
    360         builtin_deps[label_without_toolchain(dep_name)](module)
    361         return
    362 
    363     # Similarly some shared libraries are directly mapped to Android
    364     # equivalents.
    365     target = desc[dep_name]
    366     for lib in target.get('libs', []):
    367         # Generally library names sould be mangled as 'libXXX', unless they are
    368         # HAL libraries (e.g., android.hardware.health (at] 2.0).
    369         android_lib = lib if '@' in lib else 'lib' + lib
    370         if lib in library_whitelist and not android_lib in module.shared_libs:
    371             module.shared_libs.append(android_lib)
    372 
    373     type = target['type']
    374     if type == 'action':
    375         if "gen_merged_sql_metrics" in dep_name:
    376           dep_mod = create_merged_sql_metrics_target(blueprint, desc, dep_name)
    377           module.generated_headers.append(dep_mod.name)
    378         else:
    379           create_modules_from_target(blueprint, desc, dep_name)
    380           # Depend both on the generated sources and headers -- see
    381           # make_genrules_for_action.
    382           module.srcs.append(':' + label_to_module_name(dep_name))
    383           module.generated_headers.append(
    384               label_to_module_name(dep_name) + '_headers')
    385     elif type == 'static_library' and label_to_module_name(
    386             dep_name) != module.name:
    387         create_modules_from_target(blueprint, desc, dep_name)
    388         module.static_libs.append(label_to_module_name(dep_name))
    389     elif type == 'shared_library' and label_to_module_name(
    390             dep_name) != module.name:
    391         module.shared_libs.append(label_to_module_name(dep_name))
    392     elif type in ['group', 'source_set', 'executable', 'static_library'
    393                   ] and 'sources' in target:
    394         # Ignore source files that are generated by actions since they will be
    395         # implicitly added by the genrule dependencies.
    396         module.srcs.extend(
    397             label_to_path(src) for src in target['sources']
    398             if is_supported_source_file(src)
    399             and not is_generated_by_action(desc, src))
    400     module.cflags |= _get_cflags(target)
    401 
    402 
    403 def make_genrules_for_action(blueprint, desc, target_name):
    404     """Generate genrules for a GN action.
    405 
    406     GN actions are used to dynamically generate files during the build. The
    407     Soong equivalent is a genrule. This function turns a specific kind of
    408     genrule which turns .proto files into source and header files into a pair
    409     equivalent genrules.
    410 
    411     Args:
    412         blueprint: Blueprint instance which is being generated.
    413         desc: JSON GN description.
    414         target_name: GN target for genrule generation.
    415 
    416     Returns:
    417         A (source_genrule, header_genrule) module tuple.
    418     """
    419     target = desc[target_name]
    420 
    421     # We only support genrules which call protoc (with or without a plugin) to
    422     # turn .proto files into header and source files.
    423     args = target['args']
    424     if not args[0].endswith('/protoc'):
    425         raise Error('Unsupported action in target %s: %s' % (target_name,
    426                                                              target['args']))
    427     parser = ThrowingArgumentParser('Action in target %s (%s)' %
    428                                     (target_name, ' '.join(target['args'])))
    429     parser.add_argument('--proto_path')
    430     parser.add_argument('--cpp_out')
    431     parser.add_argument('--plugin')
    432     parser.add_argument('--plugin_out')
    433     parser.add_argument('--descriptor_set_out')
    434     parser.add_argument('--include_imports', action='store_true')
    435     parser.add_argument('protos', nargs=argparse.REMAINDER)
    436     args = parser.parse_args(args[1:])
    437 
    438     # Depending on whether we are using the default protoc C++ generator or the
    439     # protozero plugin, the output dir is passed as:
    440     # --cpp_out=gen/xxx or
    441     # --plugin_out=:gen/xxx or
    442     # --plugin_out=wrapper_namespace=pbzero:gen/xxx
    443     gen_dir = args.cpp_out if args.cpp_out else args.plugin_out.split(':')[1]
    444     assert gen_dir.startswith('gen/')
    445     gen_dir = gen_dir[4:]
    446     cpp_out_dir = ('$(genDir)/%s/%s' % (tree_path, gen_dir)).rstrip('/')
    447 
    448     # TODO(skyostil): Is there a way to avoid hardcoding the tree path here?
    449     # TODO(skyostil): Find a way to avoid creating the directory.
    450     cmd = [
    451         'mkdir -p %s &&' % cpp_out_dir,
    452         '$(location aprotoc)',
    453         '--cpp_out=%s' % cpp_out_dir
    454     ]
    455 
    456     # We create two genrules for each action: one for the protobuf headers and
    457     # another for the sources. This is because the module that depends on the
    458     # generated files needs to declare two different types of dependencies --
    459     # source files in 'srcs' and headers in 'generated_headers' -- and it's not
    460     # valid to generate .h files from a source dependency and vice versa.
    461     source_module = Module('genrule', label_to_module_name(target_name))
    462     source_module.srcs.extend(label_to_path(src) for src in target['sources'])
    463     source_module.tools = ['aprotoc']
    464 
    465     header_module = Module('genrule',
    466                            label_to_module_name(target_name) + '_headers')
    467     header_module.srcs = source_module.srcs[:]
    468     header_module.tools = source_module.tools[:]
    469     header_module.export_include_dirs = [gen_dir or '.']
    470 
    471     # In GN builds the proto path is always relative to the output directory
    472     # (out/tmp.xxx).
    473     assert args.proto_path.startswith('../../')
    474     cmd += [ '--proto_path=%s/%s' % (tree_path, args.proto_path[6:])]
    475 
    476     namespaces = ['pb']
    477     if args.plugin:
    478         _, plugin = os.path.split(args.plugin)
    479         # TODO(skyostil): Can we detect this some other way?
    480         if plugin == 'ipc_plugin':
    481             namespaces.append('ipc')
    482         elif plugin == 'protoc_plugin':
    483             namespaces = ['pbzero']
    484         for dep in target['deps']:
    485             if desc[dep]['type'] != 'executable':
    486                 continue
    487             _, executable = os.path.split(desc[dep]['outputs'][0])
    488             if executable == plugin:
    489                 cmd += [
    490                     '--plugin=protoc-gen-plugin=$(location %s)' %
    491                     label_to_module_name(dep)
    492                 ]
    493                 source_module.tools.append(label_to_module_name(dep))
    494                 # Also make sure the module for the tool is generated.
    495                 create_modules_from_target(blueprint, desc, dep)
    496                 break
    497         else:
    498             raise Error('Unrecognized protoc plugin in target %s: %s' %
    499                         (target_name, args[i]))
    500     if args.plugin_out:
    501         plugin_args = args.plugin_out.split(':')[0]
    502         cmd += ['--plugin_out=%s:%s' % (plugin_args, cpp_out_dir)]
    503 
    504     cmd += ['$(in)']
    505     source_module.cmd = ' '.join(cmd)
    506     header_module.cmd = source_module.cmd
    507     header_module.tools = source_module.tools[:]
    508 
    509     for ns in namespaces:
    510         source_module.out += [
    511             '%s/%s' % (tree_path, src.replace('.proto', '.%s.cc' % ns))
    512             for src in source_module.srcs
    513         ]
    514         header_module.out += [
    515             '%s/%s' % (tree_path, src.replace('.proto', '.%s.h' % ns))
    516             for src in header_module.srcs
    517         ]
    518     return source_module, header_module
    519 
    520 def create_merged_sql_metrics_target(blueprint, desc, gn_target_name):
    521     target_desc = desc[gn_target_name]
    522     module = Module(
    523         'genrule',
    524         'gen_merged_sql_metrics',
    525     )
    526     module.tool_files = [
    527         'tools/gen_merged_sql_metrics.py',
    528     ]
    529     module.cmd = ' '.join([
    530         '$(location tools/gen_merged_sql_metrics.py)',
    531         '--cpp_out=$(out)',
    532         '$(in)',
    533     ])
    534     module.out = set(
    535         src[src.index('gen/') + len('gen/'):]
    536         for src in target_desc.get('outputs', [])
    537     )
    538     module.srcs.extend(
    539       label_to_path(src)
    540       for src in target_desc.get('inputs', [])
    541     )
    542     blueprint.add_module(module)
    543     return module
    544 
    545 def _get_cflags(target):
    546     cflags = set(flag for flag in target.get('cflags', [])
    547         if re.match(cflag_whitelist, flag))
    548     cflags |= set("-D%s" % define for define in target.get('defines', [])
    549                   if re.match(define_whitelist, define))
    550     return cflags
    551 
    552 
    553 def create_modules_from_target(blueprint, desc, target_name):
    554     """Generate module(s) for a given GN target.
    555 
    556     Given a GN target name, generate one or more corresponding modules into a
    557     blueprint.
    558 
    559     Args:
    560         blueprint: Blueprint instance which is being generated.
    561         desc: JSON GN description.
    562         target_name: GN target for module generation.
    563     """
    564     target = desc[target_name]
    565     if target['type'] == 'executable':
    566         if 'host' in target['toolchain'] or target_name in target_host_only:
    567             module_type = 'cc_binary_host'
    568         elif target.get('testonly'):
    569             module_type = 'cc_test'
    570         else:
    571             module_type = 'cc_binary'
    572         modules = [Module(module_type, label_to_module_name(target_name))]
    573     elif target['type'] == 'action':
    574         modules = make_genrules_for_action(blueprint, desc, target_name)
    575     elif target['type'] == 'static_library':
    576         module = Module('cc_library_static', label_to_module_name(target_name))
    577         module.export_include_dirs = ['include']
    578         modules = [module]
    579     elif target['type'] == 'shared_library':
    580         modules = [
    581             Module('cc_library_shared', label_to_module_name(target_name))
    582         ]
    583     else:
    584         raise Error('Unknown target type: %s' % target['type'])
    585 
    586     for module in modules:
    587         module.comment = 'GN target: %s' % target_name
    588         if target_name in target_initrc:
    589           module.init_rc = [target_initrc[target_name]]
    590         if target_name in target_host_supported:
    591           module.host_supported = True
    592 
    593         # Don't try to inject library/source dependencies into genrules because
    594         # they are not compiled in the traditional sense.
    595         if module.type != 'genrule':
    596             module.defaults = [defaults_module]
    597             apply_module_dependency(blueprint, desc, module, target_name)
    598             for dep in resolve_dependencies(desc, target_name):
    599                 apply_module_dependency(blueprint, desc, module, dep)
    600 
    601         # If the module is a static library, export all the generated headers.
    602         if module.type == 'cc_library_static':
    603             module.export_generated_headers = module.generated_headers
    604 
    605         # Merge in additional hardcoded arguments.
    606         for key, add_val in additional_args.get(module.name, []):
    607           curr = getattr(module, key)
    608           if add_val and isinstance(add_val, list) and isinstance(curr, list):
    609             curr.extend(add_val)
    610           else:
    611             raise Error('Unimplemented type of additional_args')
    612 
    613         blueprint.add_module(module)
    614 
    615 
    616 def resolve_dependencies(desc, target_name):
    617     """Return the transitive set of dependent-on targets for a GN target.
    618 
    619     Args:
    620         blueprint: Blueprint instance which is being generated.
    621         desc: JSON GN description.
    622 
    623     Returns:
    624         A set of transitive dependencies in the form of GN targets.
    625     """
    626 
    627     if label_without_toolchain(target_name) in builtin_deps:
    628         return set()
    629     target = desc[target_name]
    630     resolved_deps = set()
    631     for dep in target.get('deps', []):
    632         resolved_deps.add(dep)
    633         # Ignore the transitive dependencies of actions because they are
    634         # explicitly converted to genrules.
    635         if desc[dep]['type'] == 'action':
    636             continue
    637         # Dependencies on shared libraries shouldn't propagate any transitive
    638         # dependencies but only depend on the shared library target
    639         if desc[dep]['type'] == 'shared_library':
    640             continue
    641         resolved_deps.update(resolve_dependencies(desc, dep))
    642     return resolved_deps
    643 
    644 
    645 def create_blueprint_for_targets(desc, targets):
    646     """Generate a blueprint for a list of GN targets."""
    647     blueprint = Blueprint()
    648 
    649     # Default settings used by all modules.
    650     defaults = Module('cc_defaults', defaults_module)
    651     defaults.local_include_dirs = ['include']
    652     defaults.cflags = [
    653         '-Wno-error=return-type',
    654         '-Wno-sign-compare',
    655         '-Wno-sign-promo',
    656         '-Wno-unused-parameter',
    657         '-fvisibility=hidden',
    658         '-Oz',
    659     ]
    660     defaults.user_debug_flag = True
    661 
    662     blueprint.add_module(defaults)
    663     for target in targets:
    664         create_modules_from_target(blueprint, desc, target)
    665     return blueprint
    666 
    667 
    668 def repo_root():
    669     """Returns an absolute path to the repository root."""
    670 
    671     return os.path.join(
    672         os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
    673 
    674 
    675 def create_build_description():
    676     """Creates the JSON build description by running GN."""
    677 
    678     out = os.path.join(repo_root(), 'out', 'tmp.gen_android_bp')
    679     try:
    680         try:
    681             os.makedirs(out)
    682         except OSError as e:
    683             if e.errno != errno.EEXIST:
    684                 raise
    685         subprocess.check_output(
    686             ['gn', 'gen', out, '--args=%s' % gn_args], cwd=repo_root())
    687         desc = subprocess.check_output(
    688             ['gn', 'desc', out, '--format=json', '--all-toolchains', '//*'],
    689             cwd=repo_root())
    690         return json.loads(desc)
    691     finally:
    692         shutil.rmtree(out)
    693 
    694 
    695 def main():
    696     parser = argparse.ArgumentParser(
    697         description='Generate Android.bp from a GN description.')
    698     parser.add_argument(
    699         '--desc',
    700         help=
    701         'GN description (e.g., gn desc out --format=json --all-toolchains "//*"'
    702     )
    703     parser.add_argument(
    704         '--extras',
    705         help='Extra targets to include at the end of the Blueprint file',
    706         default=os.path.join(repo_root(), 'Android.bp.extras'),
    707     )
    708     parser.add_argument(
    709         '--output',
    710         help='Blueprint file to create',
    711         default=os.path.join(repo_root(), 'Android.bp'),
    712     )
    713     parser.add_argument(
    714         'targets',
    715         nargs=argparse.REMAINDER,
    716         help='Targets to include in the blueprint (e.g., "//:perfetto_tests")')
    717     args = parser.parse_args()
    718 
    719     if args.desc:
    720         with open(args.desc) as f:
    721             desc = json.load(f)
    722     else:
    723         desc = create_build_description()
    724 
    725     blueprint = create_blueprint_for_targets(desc, args.targets or
    726                                              default_targets)
    727     output = [
    728         """// Copyright (C) 2017 The Android Open Source Project
    729 //
    730 // Licensed under the Apache License, Version 2.0 (the "License");
    731 // you may not use this file except in compliance with the License.
    732 // You may obtain a copy of the License at
    733 //
    734 //      http://www.apache.org/licenses/LICENSE-2.0
    735 //
    736 // Unless required by applicable law or agreed to in writing, software
    737 // distributed under the License is distributed on an "AS IS" BASIS,
    738 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    739 // See the License for the specific language governing permissions and
    740 // limitations under the License.
    741 //
    742 // This file is automatically generated by %s. Do not edit.
    743 """ % (__file__)
    744     ]
    745     blueprint.to_string(output)
    746     with open(args.extras, 'r') as r:
    747         for line in r:
    748             output.append(line.rstrip("\n\r"))
    749     with open(args.output, 'w') as f:
    750         f.write('\n'.join(output))
    751 
    752 
    753 if __name__ == '__main__':
    754     sys.exit(main())
    755