• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
28import argparse
29import errno
30import json
31import os
32import re
33import shutil
34import subprocess
35import sys
36
37# Default targets to translate to the blueprint file.
38default_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.
55target_initrc = {
56    '//:traced': 'perfetto.rc',
57    '//:heapprofd': 'heapprofd.rc',
58}
59
60target_host_supported = [
61    '//:perfetto_trace_protos',
62]
63
64target_host_only = [
65    '//:trace_to_text',
66]
67
68# Arguments for the GN output directory.
69gn_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.
72module_prefix = 'perfetto_'
73
74# Shared libraries which are directly translated to Android system equivalents.
75library_whitelist = [
76    "android.hardware.atrace@1.0",
77    'android.hardware.health@2.0',
78    "android.hardware.power.stats@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.
93defaults_module = module_prefix + 'defaults'
94
95# Location of the project in the Android source tree.
96tree_path = 'external/perfetto'
97
98# Compiler flags which are passed through to the blueprint.
99cflag_whitelist = r'^-DPERFETTO.*$'
100
101# Compiler defines which are passed through to the blueprint.
102define_whitelist = r'^(GOOGLE_PROTO.*)|(PERFETTO_BUILD_WITH_ANDROID)|(ZLIB_.*)|(USE_MMAP)|(HAVE_HIDDEN)$'
103
104# Shared libraries which are not in PDK.
105library_not_in_pdk = {
106    'libandroid',
107    'libservices',
108}
109
110# Additional arguments to apply to Android.bp rules.
111additional_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
125def enable_gmock(module):
126    module.static_libs.append('libgmock')
127
128
129def enable_gtest_prod(module):
130    module.static_libs.append('libgtest_prod')
131
132
133def enable_gtest(module):
134    assert module.type == 'cc_test'
135
136
137def enable_protobuf_full(module):
138    module.shared_libs.append('libprotobuf-cpp-full')
139
140
141def enable_protobuf_lite(module):
142    module.shared_libs.append('libprotobuf-cpp-lite')
143
144
145def enable_protoc_lib(module):
146    module.shared_libs.append('libprotoc')
147
148def enable_libunwindstack(module):
149    module.shared_libs.append('libunwindstack')
150    module.shared_libs.append('libprocinfo')
151    module.shared_libs.append('libbase')
152
153def enable_libunwind(module):
154    # libunwind is disabled on Darwin so we cannot depend on it.
155    pass
156
157def enable_sqlite(module):
158    module.static_libs.append('libsqlite')
159
160def enable_zlib(module):
161    module.shared_libs.append('libz')
162
163# Android equivalents for third-party libraries that the upstream project
164# depends on.
165builtin_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
184class Error(Exception):
185    pass
186
187
188class 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
197class 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
282class 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
302def 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
308def 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
317def 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
326def 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
331def 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
343def 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@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
403def 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
520def 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
545def _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
553def 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
616def 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
645def 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
668def 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
675def 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
695def 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
753if __name__ == '__main__':
754    sys.exit(main())
755