• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
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 json
30import os
31import re
32import sys
33from typing import Dict
34from typing import List
35from typing import Optional
36
37import gn_utils
38from gn_utils import GnParser
39
40from compat import itervalues
41
42ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
43
44# Arguments for the GN output directory.
45gn_args = ' '.join([
46    'is_debug=false',
47    'is_perfetto_build_generator=true',
48    'perfetto_build_with_android=true',
49    'target_cpu="arm"',
50    'target_os="android"',
51])
52
53# Default targets to translate to the blueprint file.
54default_targets = [
55    '//:libperfetto_client_experimental',
56    '//:libperfetto',
57    '//:perfetto_integrationtests',
58    '//:perfetto_unittests',
59    '//protos/perfetto/trace:perfetto_trace_protos',
60    '//src/android_internal:libperfetto_android_internal',
61    '//src/base:perfetto_base_default_platform',
62    '//src/perfetto_cmd:perfetto',
63    '//src/perfetto_cmd:trigger_perfetto',
64    '//src/profiling/memory:heapprofd_client',
65    '//src/profiling/memory:heapprofd_client_api',
66    '//src/profiling/memory:heapprofd_api_noop',
67    '//src/profiling/memory:heapprofd',
68    '//src/profiling/memory:heapprofd_standalone_client',
69    '//src/profiling/perf:traced_perf',
70    '//src/traced/probes:traced_probes',
71    '//src/traced/service:traced',
72    '//src/trace_processor:trace_processor_shell',
73    '//test/cts:perfetto_cts_deps',
74    '//test/cts:perfetto_cts_jni_deps',
75    '//test:perfetto_gtest_logcat_printer',
76    '//test:perfetto_end_to_end_integrationtests',
77    '//test/vts:perfetto_vts_deps',
78]
79
80# Host targets
81ipc_plugin = '//src/ipc/protoc_plugin:ipc_plugin(%s)' % gn_utils.HOST_TOOLCHAIN
82protozero_plugin = '//src/protozero/protoc_plugin:protozero_plugin(%s)' % (
83    gn_utils.HOST_TOOLCHAIN)
84cppgen_plugin = '//src/protozero/protoc_plugin:cppgen_plugin(%s)' % (
85    gn_utils.HOST_TOOLCHAIN)
86
87default_targets += [
88    '//src/traceconv:traceconv(%s)' % gn_utils.HOST_TOOLCHAIN,
89    protozero_plugin,
90    ipc_plugin,
91]
92
93# Defines a custom init_rc argument to be applied to the corresponding output
94# blueprint target.
95target_initrc = {
96    '//src/traced/service:traced': {'perfetto.rc'},
97    '//src/profiling/memory:heapprofd': {'heapprofd.rc'},
98    '//src/profiling/perf:traced_perf': {'traced_perf.rc'},
99}
100
101target_host_supported = [
102    '//:libperfetto',
103    '//:libperfetto_client_experimental',
104    '//protos/perfetto/trace:perfetto_trace_protos',
105    '//src/trace_processor:demangle',
106    '//src/trace_processor:trace_processor_shell',
107]
108
109target_vendor_available = [
110    '//:libperfetto_client_experimental',
111]
112
113# Proto target groups which will be made public.
114proto_groups = {
115    'trace': [
116        '//protos/perfetto/trace:non_minimal_source_set',
117        '//protos/perfetto/trace:minimal_source_set'
118    ],
119}
120
121needs_libfts = [
122    '//:perfetto_unittests',
123    '//src/trace_processor:trace_processor_shell',
124    '//src/traceconv:traceconv',
125]
126
127# All module names are prefixed with this string to avoid collisions.
128module_prefix = 'perfetto_'
129
130# Shared libraries which are directly translated to Android system equivalents.
131shared_library_allowlist = [
132    'android',
133    'android.hardware.atrace@1.0',
134    'android.hardware.health@2.0',
135    'android.hardware.health-V2-ndk',
136    'android.hardware.power.stats@1.0',
137    'android.hardware.power.stats-V1-cpp',
138    'base',
139    'binder',
140    'binder_ndk',
141    'cutils',
142    'hidlbase',
143    'hidltransport',
144    'hwbinder',
145    'incident',
146    'log',
147    'services',
148    'statssocket',
149    'tracingproxy',
150    'utils',
151    'statspull',
152]
153
154# Static libraries which are directly translated to Android system equivalents.
155static_library_allowlist = [
156    'statslog_perfetto',
157]
158
159# Name of the module which settings such as compiler flags for all other
160# modules.
161defaults_module = module_prefix + 'defaults'
162
163# Location of the project in the Android source tree.
164tree_path = 'external/perfetto'
165
166# Path for the protobuf sources in the standalone build.
167buildtools_protobuf_src = '//buildtools/protobuf/src'
168
169# Location of the protobuf src dir in the Android source tree.
170android_protobuf_src = 'external/protobuf/src'
171
172# Compiler flags which are passed through to the blueprint.
173cflag_allowlist = r'^-DPERFETTO.*$'
174
175# Compiler defines which are passed through to the blueprint.
176define_allowlist = r'^(GOOGLE_PROTO.*)|(ZLIB_.*)|(USE_MMAP)$'
177
178# The directory where the generated perfetto_build_flags.h will be copied into.
179buildflags_dir = 'include/perfetto/base/build_configs/android_tree'
180
181
182def enumerate_data_deps():
183  with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f:
184    lines = f.readlines()
185  for line in (line.strip() for line in lines if not line.startswith('#')):
186    assert os.path.exists(line), 'file %s should exist' % line
187    if line.startswith('test/data/'):
188      # Skip test data files that require GCS. They are only for benchmarks.
189      # We don't run benchmarks in the android tree.
190      continue
191    if line.endswith('/.'):
192      yield line[:-1] + '**/*'
193    else:
194      yield line
195
196
197# Additional arguments to apply to Android.bp rules.
198additional_args = {
199    'heapprofd_client_api': [
200        ('static_libs', {'libasync_safe'}),
201        # heapprofd_client_api MUST NOT have global constructors. Because it
202        # is loaded in an __attribute__((constructor)) of libc, we cannot
203        # guarantee that the global constructors get run before it is used.
204        ('cflags', {'-Wglobal-constructors', '-Werror=global-constructors'}),
205        ('version_script', 'src/profiling/memory/heapprofd_client_api.map.txt'),
206        ('stubs', {
207            'versions': ['S'],
208            'symbol_file': 'src/profiling/memory/heapprofd_client_api.map.txt',
209        }),
210        ('export_include_dirs', {'src/profiling/memory/include'}),
211    ],
212    'heapprofd_api_noop': [
213        ('version_script', 'src/profiling/memory/heapprofd_client_api.map.txt'),
214        ('stubs', {
215            'versions': ['S'],
216            'symbol_file': 'src/profiling/memory/heapprofd_client_api.map.txt',
217        }),
218        ('export_include_dirs', {'src/profiling/memory/include'}),
219    ],
220    'heapprofd_client': [
221        ('include_dirs', {'bionic/libc'}),
222        ('static_libs', {'libasync_safe'}),
223    ],
224    'heapprofd_standalone_client': [
225        ('static_libs', {'libasync_safe'}),
226        ('version_script', 'src/profiling/memory/heapprofd_client_api.map.txt'),
227        ('export_include_dirs', {'src/profiling/memory/include'}),
228        ('stl', 'libc++_static'),
229    ],
230    'perfetto_unittests': [
231        ('data', set(enumerate_data_deps())),
232        ('include_dirs', {'bionic/libc/kernel'}),
233    ],
234    'perfetto_integrationtests': [
235        ('test_suites', {'general-tests'}),
236        ('test_config', 'PerfettoIntegrationTests.xml'),
237    ],
238    'traced_probes': [('required', {
239        'libperfetto_android_internal', 'trigger_perfetto', 'traced_perf',
240        'mm_events'
241    }),],
242    'libperfetto_android_internal': [('static_libs', {'libhealthhalutils'}),],
243    'trace_processor_shell': [
244        ('strip', {
245            'all': True
246        }),
247        ('host', {
248            'stl': 'libc++_static',
249            'dist': {
250                'targets': ['sdk_repo']
251            },
252        }),
253    ],
254    'libperfetto_client_experimental': [
255        ('apex_available', {
256            '//apex_available:platform', 'com.android.art',
257            'com.android.art.debug', 'com.android.tethering'
258        }),
259        ('min_sdk_version', '30'),
260        ('shared_libs', {'liblog'}),
261        ('export_include_dirs', {'include', buildflags_dir}),
262    ],
263    'perfetto_trace_protos': [
264        ('apex_available', {
265            '//apex_available:platform', 'com.android.art',
266            'com.android.art.debug'
267        }),
268        ('min_sdk_version', 'S'),
269    ],
270    'libperfetto': [('export_include_dirs', {'include', buildflags_dir}),],
271}
272
273
274def enable_base_platform(module):
275  module.srcs.add(':perfetto_base_default_platform')
276
277
278def enable_gtest_and_gmock(module):
279  module.static_libs.add('libgmock')
280  module.static_libs.add('libgtest')
281  if module.name != 'perfetto_gtest_logcat_printer':
282    module.whole_static_libs.add('perfetto_gtest_logcat_printer')
283
284
285def enable_protobuf_full(module):
286  if module.type == 'cc_binary_host':
287    module.static_libs.add('libprotobuf-cpp-full')
288  elif module.host_supported:
289    module.host.static_libs.add('libprotobuf-cpp-full')
290    module.android.shared_libs.add('libprotobuf-cpp-full')
291  else:
292    module.shared_libs.add('libprotobuf-cpp-full')
293
294
295def enable_protobuf_lite(module):
296  module.shared_libs.add('libprotobuf-cpp-lite')
297
298
299def enable_protoc_lib(module):
300  if module.type == 'cc_binary_host':
301    module.static_libs.add('libprotoc')
302  else:
303    module.shared_libs.add('libprotoc')
304
305
306def enable_libunwindstack(module):
307  if module.name != 'heapprofd_standalone_client':
308    module.shared_libs.add('libunwindstack')
309    module.shared_libs.add('libprocinfo')
310    module.shared_libs.add('libbase')
311  else:
312    module.static_libs.add('libunwindstack')
313    module.static_libs.add('libprocinfo')
314    module.static_libs.add('libbase')
315    module.static_libs.add('liblzma')
316    module.static_libs.add('libdexfile_support')
317    module.runtime_libs.add('libdexfile')  # libdexfile_support dependency
318
319
320def enable_libunwind(module):
321  # libunwind is disabled on Darwin so we cannot depend on it.
322  pass
323
324
325def enable_sqlite(module):
326  if module.type == 'cc_binary_host':
327    module.static_libs.add('libsqlite')
328    module.static_libs.add('sqlite_ext_percentile')
329  elif module.host_supported:
330    # Copy what the sqlite3 command line tool does.
331    module.android.shared_libs.add('libsqlite')
332    module.android.shared_libs.add('libicu')
333    module.android.shared_libs.add('liblog')
334    module.android.shared_libs.add('libutils')
335    module.android.static_libs.add('sqlite_ext_percentile')
336    module.host.static_libs.add('libsqlite')
337    module.host.static_libs.add('sqlite_ext_percentile')
338  else:
339    module.shared_libs.add('libsqlite')
340    module.shared_libs.add('libicu')
341    module.shared_libs.add('liblog')
342    module.shared_libs.add('libutils')
343    module.static_libs.add('sqlite_ext_percentile')
344
345
346def enable_zlib(module):
347  if module.type == 'cc_binary_host':
348    module.static_libs.add('libz')
349  elif module.host_supported:
350    module.android.shared_libs.add('libz')
351    module.host.static_libs.add('libz')
352  else:
353    module.shared_libs.add('libz')
354
355
356def enable_uapi_headers(module):
357  module.include_dirs.add('bionic/libc/kernel')
358
359
360def enable_bionic_libc_platform_headers_on_android(module):
361  module.header_libs.add('bionic_libc_platform_headers')
362
363
364# Android equivalents for third-party libraries that the upstream project
365# depends on.
366builtin_deps = {
367    '//gn:default_deps':
368        lambda x: None,
369    '//gn:gtest_main':
370        lambda x: None,
371    '//gn:protoc':
372        lambda x: None,
373    '//gn:base_platform':
374        enable_base_platform,
375    '//gn:gtest_and_gmock':
376        enable_gtest_and_gmock,
377    '//gn:libunwind':
378        enable_libunwind,
379    '//gn:protobuf_full':
380        enable_protobuf_full,
381    '//gn:protobuf_lite':
382        enable_protobuf_lite,
383    '//gn:protoc_lib':
384        enable_protoc_lib,
385    '//gn:libunwindstack':
386        enable_libunwindstack,
387    '//gn:sqlite':
388        enable_sqlite,
389    '//gn:zlib':
390        enable_zlib,
391    '//gn:bionic_kernel_uapi_headers':
392        enable_uapi_headers,
393    '//src/profiling/memory:bionic_libc_platform_headers_on_android':
394        enable_bionic_libc_platform_headers_on_android,
395}
396
397# ----------------------------------------------------------------------------
398# End of configuration.
399# ----------------------------------------------------------------------------
400
401
402class Error(Exception):
403  pass
404
405
406class ThrowingArgumentParser(argparse.ArgumentParser):
407
408  def __init__(self, context):
409    super(ThrowingArgumentParser, self).__init__()
410    self.context = context
411
412  def error(self, message):
413    raise Error('%s: %s' % (self.context, message))
414
415
416def write_blueprint_key_value(output, name, value, sort=True):
417  """Writes a Blueprint key-value pair to the output"""
418
419  if isinstance(value, bool):
420    if value:
421      output.append('    %s: true,' % name)
422    else:
423      output.append('    %s: false,' % name)
424    return
425  if not value:
426    return
427  if isinstance(value, set):
428    value = sorted(value)
429  if isinstance(value, list):
430    output.append('    %s: [' % name)
431    for item in sorted(value) if sort else value:
432      output.append('        "%s",' % item)
433    output.append('    ],')
434    return
435  if isinstance(value, Target):
436    value.to_string(output)
437    return
438  if isinstance(value, dict):
439    kv_output = []
440    for k, v in value.items():
441      write_blueprint_key_value(kv_output, k, v)
442
443    output.append('    %s: {' % name)
444    for line in kv_output:
445      output.append('    %s' % line)
446    output.append('    },')
447    return
448  output.append('    %s: "%s",' % (name, value))
449
450
451class Target(object):
452  """A target-scoped part of a module"""
453
454  def __init__(self, name):
455    self.name = name
456    self.shared_libs = set()
457    self.static_libs = set()
458    self.whole_static_libs = set()
459    self.cflags = set()
460    self.dist = dict()
461    self.strip = dict()
462    self.stl = None
463
464  def to_string(self, output):
465    nested_out = []
466    self._output_field(nested_out, 'shared_libs')
467    self._output_field(nested_out, 'static_libs')
468    self._output_field(nested_out, 'whole_static_libs')
469    self._output_field(nested_out, 'cflags')
470    self._output_field(nested_out, 'stl')
471    self._output_field(nested_out, 'dist')
472    self._output_field(nested_out, 'strip')
473
474    if nested_out:
475      output.append('    %s: {' % self.name)
476      for line in nested_out:
477        output.append('    %s' % line)
478      output.append('    },')
479
480  def _output_field(self, output, name, sort=True):
481    value = getattr(self, name)
482    return write_blueprint_key_value(output, name, value, sort)
483
484
485class Module(object):
486  """A single module (e.g., cc_binary, cc_test) in a blueprint."""
487
488  def __init__(self, mod_type, name, gn_target):
489    assert (gn_target)
490    self.type = mod_type
491    self.gn_target = gn_target
492    self.name = name
493    self.srcs = set()
494    self.main: Optional[str] = None
495    self.comment = 'GN: ' + gn_utils.label_without_toolchain(gn_target)
496    self.shared_libs = set()
497    self.static_libs = set()
498    self.whole_static_libs = set()
499    self.runtime_libs = set()
500    self.tools = set()
501    self.cmd: Optional[str] = None
502    self.host_supported = False
503    self.vendor_available = False
504    self.init_rc = set()
505    self.out = set()
506    self.export_include_dirs = set()
507    self.generated_headers = set()
508    self.export_generated_headers = set()
509    self.defaults = set()
510    self.cflags = set()
511    self.include_dirs = set()
512    self.header_libs = set()
513    self.required = set()
514    self.user_debug_flag = False
515    self.tool_files: Optional[List[str]] = None
516    self.android = Target('android')
517    self.host = Target('host')
518    self.musl = Target('musl')
519    self.lto: Optional[bool] = None
520    self.stl = None
521    self.dist = dict()
522    self.strip = dict()
523    self.data = set()
524    self.apex_available = set()
525    self.min_sdk_version = None
526    self.proto = dict()
527    # The genrule_XXX below are properties that must to be propagated back
528    # on the module(s) that depend on the genrule.
529    self.genrule_headers = set()
530    self.genrule_srcs = set()
531    self.genrule_shared_libs = set()
532    self.version_script = None
533    self.test_suites = set()
534    self.test_config = None
535    self.stubs = {}
536
537  def to_string(self, output):
538    if self.comment:
539      output.append('// %s' % self.comment)
540    output.append('%s {' % self.type)
541    self._output_field(output, 'name')
542    self._output_field(output, 'srcs')
543    self._output_field(output, 'shared_libs')
544    self._output_field(output, 'static_libs')
545    self._output_field(output, 'whole_static_libs')
546    self._output_field(output, 'runtime_libs')
547    self._output_field(output, 'tools')
548    self._output_field(output, 'cmd', sort=False)
549    if self.host_supported:
550      self._output_field(output, 'host_supported')
551    if self.vendor_available:
552      self._output_field(output, 'vendor_available')
553    self._output_field(output, 'init_rc')
554    self._output_field(output, 'out')
555    self._output_field(output, 'export_include_dirs')
556    self._output_field(output, 'generated_headers')
557    self._output_field(output, 'export_generated_headers')
558    self._output_field(output, 'defaults')
559    self._output_field(output, 'cflags')
560    self._output_field(output, 'include_dirs')
561    self._output_field(output, 'header_libs')
562    self._output_field(output, 'required')
563    self._output_field(output, 'dist')
564    self._output_field(output, 'strip')
565    self._output_field(output, 'tool_files')
566    self._output_field(output, 'data')
567    self._output_field(output, 'stl')
568    self._output_field(output, 'apex_available')
569    self._output_field(output, 'min_sdk_version')
570    self._output_field(output, 'version_script')
571    self._output_field(output, 'test_suites')
572    self._output_field(output, 'test_config')
573    self._output_field(output, 'stubs')
574    self._output_field(output, 'proto')
575    self._output_field(output, 'main')
576
577    target_out = []
578    self._output_field(target_out, 'android')
579    self._output_field(target_out, 'host')
580    self._output_field(target_out, 'musl')
581    if target_out:
582      output.append('    target: {')
583      for line in target_out:
584        output.append('    %s' % line)
585      output.append('    },')
586
587    if self.user_debug_flag:
588      output.append('    product_variables: {')
589      output.append('        debuggable: {')
590      output.append(
591          '            cflags: ["-DPERFETTO_BUILD_WITH_ANDROID_USERDEBUG"],')
592      output.append('        },')
593      output.append('    },')
594    if self.lto is not None:
595      output.append('    target: {')
596      output.append('        android: {')
597      output.append('            lto: {')
598      output.append('                thin: %s,' %
599                    'true' if self.lto else 'false')
600      output.append('            },')
601      output.append('        },')
602      output.append('    },')
603    output.append('}')
604    output.append('')
605
606  def add_android_static_lib(self, lib):
607    if self.type == 'cc_binary_host':
608      raise Exception('Adding Android static lib for host tool is unsupported')
609    elif self.host_supported:
610      self.android.static_libs.add(lib)
611    else:
612      self.static_libs.add(lib)
613
614  def add_android_shared_lib(self, lib):
615    if self.type == 'cc_binary_host':
616      raise Exception('Adding Android shared lib for host tool is unsupported')
617    elif self.host_supported:
618      self.android.shared_libs.add(lib)
619    else:
620      self.shared_libs.add(lib)
621
622  def _output_field(self, output, name, sort=True):
623    value = getattr(self, name)
624    return write_blueprint_key_value(output, name, value, sort)
625
626
627class Blueprint(object):
628  """In-memory representation of an Android.bp file."""
629
630  def __init__(self):
631    self.modules: Dict[str, Module] = {}
632    self.gn_target_to_module: Dict[str, Module] = {}
633
634  def add_module(self, module: Module):
635    """Adds a new module to the blueprint, replacing any existing module
636        with the same name.
637
638        Args:
639            module: Module instance.
640        """
641    self.modules[module.name] = module
642    self.gn_target_to_module[module.gn_target] = module
643
644  def to_string(self, output):
645    for m in sorted(itervalues(self.modules), key=lambda m: m.name):
646      m.to_string(output)
647
648
649def label_to_module_name(label: str):
650  """Turn a GN label (e.g., //:perfetto_tests) into a module name."""
651  # If the label is explicibly listed in the default target list, don't prefix
652  # its name and return just the target name. This is so tools like
653  # "traceconv" stay as such in the Android tree.
654  label_without_toolchain = gn_utils.label_without_toolchain(label)
655  if label in default_targets or label_without_toolchain in default_targets:
656    return label_without_toolchain.split(':')[-1]
657
658  module = re.sub(r'^//:?', '', label_without_toolchain)
659  module = re.sub(r'[^a-zA-Z0-9_]', '_', module)
660  if not module.startswith(module_prefix):
661    return module_prefix + module
662  return module
663
664
665def is_supported_source_file(name: str):
666  """Returns True if |name| can appear in a 'srcs' list."""
667  return os.path.splitext(name)[1] in ['.c', '.cc', '.proto']
668
669
670def create_proto_modules(blueprint: Blueprint, gn: GnParser,
671                         target: GnParser.Target):
672  """Generate genrules for a proto GN target.
673
674    GN actions are used to dynamically generate files during the build. The
675    Soong equivalent is a genrule. This function turns a specific kind of
676    genrule which turns .proto files into source and header files into a pair
677    equivalent genrules.
678
679    Args:
680        blueprint: Blueprint instance which is being generated.
681        target: gn_utils.Target object.
682
683    Returns:
684        The source_genrule module.
685    """
686  assert (target.type == 'proto_library')
687
688  # We don't generate any targets for source_set proto modules because
689  # they will be inlined into other modules if required.
690  if target.proto_plugin == 'source_set':
691    return None
692
693  tools = {'aprotoc'}
694  cpp_out_dir = '$(genDir)/%s/' % tree_path
695  target_module_name = label_to_module_name(target.name)
696
697  # In GN builds the proto path is always relative to the output directory
698  # (out/tmp.xxx).
699  cmd = ['mkdir -p %s &&' % cpp_out_dir, '$(location aprotoc)']
700  cmd += ['--proto_path=%s' % tree_path]
701
702  if buildtools_protobuf_src in target.proto_paths:
703    cmd += ['--proto_path=%s' % android_protobuf_src]
704
705  # Descriptor targets only generate a single target.
706  if target.proto_plugin == 'descriptor':
707    out = '{}.bin'.format(target_module_name)
708
709    cmd += ['--descriptor_set_out=$(out)']
710    cmd += ['$(in)']
711
712    descriptor_module = Module('genrule', target_module_name, target.name)
713    descriptor_module.cmd = ' '.join(cmd)
714    descriptor_module.out.add(out)
715    descriptor_module.tools = tools
716    blueprint.add_module(descriptor_module)
717
718    # Recursively extract the .proto files of all the dependencies and
719    # add them to srcs.
720    descriptor_module.srcs.update(
721        gn_utils.label_to_path(src) for src in target.sources)
722    for dep in target.transitive_proto_deps():
723      current_target = gn.get_target(dep.name)
724      descriptor_module.srcs.update(
725          gn_utils.label_to_path(src) for src in current_target.sources)
726
727    return descriptor_module
728
729  # We create two genrules for each proto target: one for the headers and
730  # another for the sources. This is because the module that depends on the
731  # generated files needs to declare two different types of dependencies --
732  # source files in 'srcs' and headers in 'generated_headers' -- and it's not
733  # valid to generate .h files from a source dependency and vice versa.
734  source_module_name = target_module_name + '_gen'
735  source_module = Module('genrule', source_module_name, target.name)
736  blueprint.add_module(source_module)
737  source_module.srcs.update(
738      gn_utils.label_to_path(src) for src in target.sources)
739
740  header_module = Module('genrule', source_module_name + '_headers',
741                         target.name)
742  blueprint.add_module(header_module)
743  header_module.srcs = set(source_module.srcs)
744
745  # TODO(primiano): at some point we should remove this. This was introduced
746  # by aosp/1108421 when adding "protos/" to .proto include paths, in order to
747  # avoid doing multi-repo changes and allow old clients in the android tree
748  # to still do the old #include "perfetto/..." rather than
749  # #include "protos/perfetto/...".
750  header_module.export_include_dirs = {'.', 'protos'}
751
752  source_module.genrule_srcs.add(':' + source_module.name)
753  source_module.genrule_headers.add(header_module.name)
754
755  if target.proto_plugin == 'proto':
756    suffixes = ['pb']
757    source_module.genrule_shared_libs.add('libprotobuf-cpp-lite')
758    cmd += ['--cpp_out=lite=true:' + cpp_out_dir]
759  elif target.proto_plugin == 'protozero':
760    suffixes = ['pbzero']
761    plugin = create_modules_from_target(blueprint, gn, protozero_plugin)
762    assert (plugin)
763    tools.add(plugin.name)
764    cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
765    cmd += ['--plugin_out=wrapper_namespace=pbzero:' + cpp_out_dir]
766  elif target.proto_plugin == 'cppgen':
767    suffixes = ['gen']
768    plugin = create_modules_from_target(blueprint, gn, cppgen_plugin)
769    assert (plugin)
770    tools.add(plugin.name)
771    cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
772    cmd += ['--plugin_out=wrapper_namespace=gen:' + cpp_out_dir]
773  elif target.proto_plugin == 'ipc':
774    suffixes = ['ipc']
775    plugin = create_modules_from_target(blueprint, gn, ipc_plugin)
776    assert (plugin)
777    tools.add(plugin.name)
778    cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
779    cmd += ['--plugin_out=wrapper_namespace=gen:' + cpp_out_dir]
780  else:
781    raise Error('Unsupported proto plugin: %s' % target.proto_plugin)
782
783  cmd += ['$(in)']
784  source_module.cmd = ' '.join(cmd)
785  header_module.cmd = source_module.cmd
786  source_module.tools = tools
787  header_module.tools = tools
788
789  for sfx in suffixes:
790    source_module.out.update('%s/%s' %
791                             (tree_path, src.replace('.proto', '.%s.cc' % sfx))
792                             for src in source_module.srcs)
793    header_module.out.update('%s/%s' %
794                             (tree_path, src.replace('.proto', '.%s.h' % sfx))
795                             for src in header_module.srcs)
796  return source_module
797
798
799def create_tp_tables_module(blueprint: Blueprint, gn: GnParser,
800                            target: GnParser.Target):
801  bp_module_name = label_to_module_name(target.name)
802  bp_binary_module_name = f'{bp_module_name}_binary'
803  srcs = [gn_utils.label_to_path(src) for src in target.sources]
804
805  binary_module = Module('python_binary_host', bp_binary_module_name,
806                         target.name)
807  for dep in target.non_proto_or_source_set_deps():
808    binary_module.srcs.update([
809        gn_utils.label_to_path(src) for src in gn.get_target(dep.name).sources
810    ])
811  binary_module.srcs.update(srcs)
812  binary_module.srcs.add('tools/gen_tp_table_headers.py')
813  binary_module.main = 'tools/gen_tp_table_headers.py'
814  blueprint.add_module(binary_module)
815
816  module = Module('genrule', bp_module_name, target.name)
817  module.tools = set([
818      bp_binary_module_name,
819  ])
820  module.cmd = ' '.join([
821      f'$(location {bp_binary_module_name})',
822      '--gen-dir=$(genDir)',
823      '--relative-input-dir=external/perfetto',
824      '--inputs',
825      '$(in)',
826  ])
827  module.out.update(target.outputs)
828  module.genrule_headers.add(module.name)
829  module.srcs.update(srcs)
830  blueprint.add_module(module)
831
832  return module
833
834
835def create_amalgamated_sql_module(blueprint: Blueprint, gn: GnParser,
836                                  target: GnParser.Target):
837  bp_module_name = label_to_module_name(target.name)
838
839  def find_arg(name):
840    for i, arg in enumerate(target.args):
841      if arg.startswith(f'--{name}'):
842        return target.args[i + 1]
843
844  namespace = find_arg('namespace')
845
846  module = Module('genrule', bp_module_name, target.name)
847  module.tool_files = [
848      'tools/gen_amalgamated_sql.py',
849  ]
850  module.cmd = ' '.join([
851      '$(location tools/gen_amalgamated_sql.py)',
852      f'--namespace={namespace}',
853      '--cpp-out=$(out)',
854      '$(in)',
855  ])
856  module.genrule_headers.add(module.name)
857  module.out.update(target.outputs)
858
859  for dep in target.transitive_deps:
860    module.srcs.update(
861        [gn_utils.label_to_path(src) for src in gn.get_target(dep.name).inputs])
862  blueprint.add_module(module)
863  return module
864
865
866def create_cc_proto_descriptor_module(blueprint: Blueprint,
867                                      target: GnParser.Target):
868  bp_module_name = label_to_module_name(target.name)
869  module = Module('genrule', bp_module_name, target.name)
870  module.tool_files = [
871      'tools/gen_cc_proto_descriptor.py',
872  ]
873  module.cmd = ' '.join([
874      '$(location tools/gen_cc_proto_descriptor.py)', '--gen_dir=$(genDir)',
875      '--cpp_out=$(out)', '$(in)'
876  ])
877  module.genrule_headers.add(module.name)
878  module.srcs.update(
879      ':' + label_to_module_name(dep.name) for dep in target.proto_deps())
880  module.srcs.update(
881      gn_utils.label_to_path(src)
882      for src in target.inputs
883      if "tmp.gn_utils" not in src)
884  module.out.update(target.outputs)
885  blueprint.add_module(module)
886  return module
887
888
889def create_gen_version_module(blueprint: Blueprint, target: GnParser.Target,
890                              bp_module_name: str):
891  module = Module('genrule', bp_module_name, gn_utils.GEN_VERSION_TARGET)
892  script_path = gn_utils.label_to_path(target.script)
893  module.genrule_headers.add(bp_module_name)
894  module.tool_files = [script_path]
895  module.out.update(target.outputs)
896  module.srcs.update(gn_utils.label_to_path(src) for src in target.inputs)
897  module.cmd = ' '.join([
898      'python3 $(location %s)' % script_path, '--no_git',
899      '--changelog=$(location CHANGELOG)', '--cpp_out=$(out)'
900  ])
901  blueprint.add_module(module)
902  return module
903
904
905def create_proto_group_modules(blueprint, gn: GnParser, module_name: str,
906                               target_names):
907  # TODO(lalitm): today, we're only adding a Java lite module because that's
908  # the only one used in practice. In the future, if we need other target types
909  # (e.g. C++, Java full etc.) add them here.
910  bp_module_name = label_to_module_name(module_name) + '_java_protos'
911  module = Module('java_library', bp_module_name, bp_module_name)
912  module.comment = f'''GN: [{', '.join(target_names)}]'''
913  module.proto = {'type': 'lite', 'canonical_path_from_root': False}
914
915  for name in target_names:
916    target = gn.get_target(name)
917    module.srcs.update(gn_utils.label_to_path(src) for src in target.sources)
918    for dep_label in target.transitive_proto_deps():
919      dep = gn.get_target(dep_label.name)
920      module.srcs.update(gn_utils.label_to_path(src) for src in dep.sources)
921
922  blueprint.add_module(module)
923
924
925def _get_cflags(target: GnParser.Target):
926  cflags = {flag for flag in target.cflags if re.match(cflag_allowlist, flag)}
927  cflags |= set("-D%s" % define
928                for define in target.defines
929                if re.match(define_allowlist, define))
930  return cflags
931
932
933def create_modules_from_target(blueprint: Blueprint, gn: GnParser,
934                               gn_target_name: str) -> Optional[Module]:
935  """Generate module(s) for a given GN target.
936
937    Given a GN target name, generate one or more corresponding modules into a
938    blueprint. The only case when this generates >1 module is proto libraries.
939
940    Args:
941        blueprint: Blueprint instance which is being generated.
942        gn: gn_utils.GnParser object.
943        gn_target_name: GN target for module generation.
944    """
945  if gn_target_name in blueprint.gn_target_to_module:
946    return blueprint.gn_target_to_module[gn_target_name]
947
948  target = gn.get_target(gn_target_name)
949  bp_module_name = label_to_module_name(gn_target_name)
950  name_without_toolchain = gn_utils.label_without_toolchain(target.name)
951  if target.type == 'executable':
952    if target.toolchain == gn_utils.HOST_TOOLCHAIN:
953      module_type = 'cc_binary_host'
954    elif target.testonly:
955      module_type = 'cc_test'
956    else:
957      module_type = 'cc_binary'
958    module = Module(module_type, bp_module_name, gn_target_name)
959  elif target.type == 'static_library':
960    module = Module('cc_library_static', bp_module_name, gn_target_name)
961  elif target.type == 'shared_library':
962    module = Module('cc_library_shared', bp_module_name, gn_target_name)
963  elif target.type == 'source_set':
964    module = Module('filegroup', bp_module_name, gn_target_name)
965  elif target.type == 'group':
966    # "group" targets are resolved recursively by gn_utils.get_target().
967    # There's nothing we need to do at this level for them.
968    return None
969  elif target.type == 'proto_library':
970    module = create_proto_modules(blueprint, gn, target)
971    if module is None:
972      return None
973  elif target.type == 'action':
974    if target.custom_action_type == 'sql_amalgamation':
975      return create_amalgamated_sql_module(blueprint, gn, target)
976    if target.custom_action_type == 'tp_tables':
977      return create_tp_tables_module(blueprint, gn, target)
978
979    if target.custom_action_type == 'cc_proto_descriptor':
980      module = create_cc_proto_descriptor_module(blueprint, target)
981    elif name_without_toolchain == gn_utils.GEN_VERSION_TARGET:
982      module = create_gen_version_module(blueprint, target, bp_module_name)
983    else:
984      raise Error('Unhandled action: {}'.format(target.name))
985  else:
986    raise Error('Unknown target %s (%s)' % (target.name, target.type))
987
988  blueprint.add_module(module)
989  module.host_supported = (name_without_toolchain in target_host_supported)
990  module.vendor_available = (name_without_toolchain in target_vendor_available)
991  module.init_rc.update(target_initrc.get(target.name, []))
992  module.srcs.update(
993      gn_utils.label_to_path(src)
994      for src in target.sources
995      if is_supported_source_file(src))
996
997  if name_without_toolchain in needs_libfts:
998    module.musl.static_libs.add('libfts')
999
1000  if target.type in gn_utils.LINKER_UNIT_TYPES:
1001    module.cflags.update(_get_cflags(target))
1002
1003  module_is_compiled = module.type not in ('genrule', 'filegroup')
1004  if module_is_compiled:
1005    # Don't try to inject library/source dependencies into genrules or
1006    # filegroups because they are not compiled in the traditional sense.
1007    module.defaults.update([defaults_module])
1008    for lib in target.libs:
1009      # Generally library names should be mangled as 'libXXX', unless they
1010      # are HAL libraries (e.g., android.hardware.health@2.0) or AIDL c++ / NDK
1011      # libraries (e.g. "android.hardware.power.stats-V1-cpp")
1012      android_lib = lib if '@' in lib or "-cpp" in lib or "-ndk" in lib \
1013        else 'lib' + lib
1014      if lib in shared_library_allowlist:
1015        module.add_android_shared_lib(android_lib)
1016      if lib in static_library_allowlist:
1017        module.add_android_static_lib(android_lib)
1018
1019  # If the module is a static library, export all the generated headers.
1020  if module.type == 'cc_library_static':
1021    module.export_generated_headers = module.generated_headers
1022
1023  # Merge in additional hardcoded arguments.
1024  for key, add_val in additional_args.get(module.name, []):
1025    curr = getattr(module, key)
1026    if add_val and isinstance(add_val, set) and isinstance(curr, set):
1027      curr.update(add_val)
1028    elif isinstance(add_val, str) and (not curr or isinstance(curr, str)):
1029      setattr(module, key, add_val)
1030    elif isinstance(add_val, bool) and (not curr or isinstance(curr, bool)):
1031      setattr(module, key, add_val)
1032    elif isinstance(add_val, dict) and isinstance(curr, dict):
1033      curr.update(add_val)
1034    elif isinstance(add_val, dict) and isinstance(curr, Target):
1035      curr.__dict__.update(add_val)
1036    else:
1037      raise Error('Unimplemented type %r of additional_args: %r' %
1038                  (type(add_val), key))
1039
1040  # dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)).
1041  all_deps = target.non_proto_or_source_set_deps()
1042  all_deps |= target.transitive_source_set_deps()
1043  all_deps |= target.transitive_proto_deps()
1044  for dep in all_deps:
1045    # If the dependency refers to a library which we can replace with an
1046    # Android equivalent, stop recursing and patch the dependency in.
1047    # Don't recurse into //buildtools, builtin_deps are intercepted at
1048    # the //gn:xxx level.
1049    dep_name = dep.name
1050    if dep_name.startswith('//buildtools'):
1051      continue
1052
1053    # Ignore the dependency on the gen_buildflags genrule. That is run
1054    # separately in this generator and the generated file is copied over
1055    # into the repo (see usage of |buildflags_dir| in this script).
1056    if dep_name.startswith(gn_utils.BUILDFLAGS_TARGET):
1057      continue
1058
1059    dep_module = create_modules_from_target(blueprint, gn, dep_name)
1060
1061    # For filegroups and genrule, recurse but don't apply the deps.
1062    if not module_is_compiled:
1063      continue
1064
1065    # |builtin_deps| override GN deps with Android-specific ones. See the
1066    # config in the top of this file.
1067    if gn_utils.label_without_toolchain(dep_name) in builtin_deps:
1068      builtin_deps[gn_utils.label_without_toolchain(dep_name)](module)
1069      continue
1070
1071    # Don't recurse in any other //gn dep if not handled by builtin_deps.
1072    if dep_name.startswith('//gn:'):
1073      continue
1074
1075    if dep_module is None:
1076      continue
1077    if dep_module.type == 'cc_library_shared':
1078      module.shared_libs.add(dep_module.name)
1079    elif dep_module.type == 'cc_library_static':
1080      module.static_libs.add(dep_module.name)
1081    elif dep_module.type == 'filegroup':
1082      module.srcs.add(':' + dep_module.name)
1083    elif dep_module.type == 'genrule':
1084      module.generated_headers.update(dep_module.genrule_headers)
1085      module.srcs.update(dep_module.genrule_srcs)
1086      module.shared_libs.update(dep_module.genrule_shared_libs)
1087    elif dep_module.type == 'cc_binary':
1088      continue  # Ignore executables deps (used by cmdline integration tests).
1089    else:
1090      raise Error('Unknown dep %s (%s) for target %s' %
1091                  (dep_module.name, dep_module.type, module.name))
1092
1093  return module
1094
1095
1096def create_blueprint_for_targets(gn: GnParser, targets: List[str]):
1097  """Generate a blueprint for a list of GN targets."""
1098  blueprint = Blueprint()
1099
1100  # Default settings used by all modules.
1101  defaults = Module('cc_defaults', defaults_module, '//gn:default_deps')
1102
1103  # We have to use include_dirs passing the path relative to the android tree.
1104  # This is because: (i) perfetto_cc_defaults is used also by
1105  # test/**/Android.bp; (ii) if we use local_include_dirs instead, paths
1106  # become relative to the Android.bp that *uses* cc_defaults (not the one
1107  # that defines it).s
1108  defaults.include_dirs = {
1109      tree_path, tree_path + '/include', tree_path + '/' + buildflags_dir,
1110      tree_path + '/src/profiling/memory/include'
1111  }
1112  defaults.cflags.update([
1113      '-Wno-error=return-type',
1114      '-Wno-sign-compare',
1115      '-Wno-sign-promo',
1116      '-Wno-unused-parameter',
1117      '-fvisibility=hidden',
1118      '-O2',
1119  ])
1120  defaults.user_debug_flag = True
1121  defaults.lto = True
1122
1123  blueprint.add_module(defaults)
1124  for target in targets:
1125    create_modules_from_target(blueprint, gn, target)
1126  return blueprint
1127
1128
1129def main():
1130  parser = argparse.ArgumentParser(
1131      description='Generate Android.bp from a GN description.')
1132  parser.add_argument(
1133      '--check-only',
1134      help='Don\'t keep the generated files',
1135      action='store_true')
1136  parser.add_argument(
1137      '--desc',
1138      help='GN description (e.g., gn desc out --format=json --all-toolchains "//*"'
1139  )
1140  parser.add_argument(
1141      '--extras',
1142      help='Extra targets to include at the end of the Blueprint file',
1143      default=os.path.join(gn_utils.repo_root(), 'Android.bp.extras'),
1144  )
1145  parser.add_argument(
1146      '--output',
1147      help='Blueprint file to create',
1148      default=os.path.join(gn_utils.repo_root(), 'Android.bp'),
1149  )
1150  parser.add_argument(
1151      'targets',
1152      nargs=argparse.REMAINDER,
1153      help='Targets to include in the blueprint (e.g., "//:perfetto_tests")')
1154  args = parser.parse_args()
1155
1156  if args.desc:
1157    with open(args.desc) as f:
1158      desc = json.load(f)
1159  else:
1160    desc = gn_utils.create_build_description(gn_args)
1161
1162  gn = gn_utils.GnParser(desc)
1163  blueprint = create_blueprint_for_targets(gn, args.targets or default_targets)
1164  project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
1165  tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
1166
1167  # TODO(primiano): enable this on Android after the TODO in
1168  # perfetto_component.gni is fixed.
1169  # Check for ODR violations
1170  # for target_name in default_targets:
1171  # checker = gn_utils.ODRChecker(gn, target_name)
1172
1173  # Add any proto groups to the blueprint.
1174  for l_name, t_names in proto_groups.items():
1175    create_proto_group_modules(blueprint, gn, l_name, t_names)
1176
1177  output = [
1178      """// Copyright (C) 2017 The Android Open Source Project
1179//
1180// Licensed under the Apache License, Version 2.0 (the "License");
1181// you may not use this file except in compliance with the License.
1182// You may obtain a copy of the License at
1183//
1184//      http://www.apache.org/licenses/LICENSE-2.0
1185//
1186// Unless required by applicable law or agreed to in writing, software
1187// distributed under the License is distributed on an "AS IS" BASIS,
1188// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1189// See the License for the specific language governing permissions and
1190// limitations under the License.
1191//
1192// This file is automatically generated by %s. Do not edit.
1193""" % (tool_name)
1194  ]
1195  blueprint.to_string(output)
1196  with open(args.extras, 'r') as r:
1197    for line in r:
1198      output.append(line.rstrip("\n\r"))
1199
1200  out_files = []
1201
1202  # Generate the Android.bp file.
1203  out_files.append(args.output + '.swp')
1204  with open(out_files[-1], 'w') as f:
1205    f.write('\n'.join(output))
1206    # Text files should have a trailing EOL.
1207    f.write('\n')
1208
1209  # Generate the perfetto_build_flags.h file.
1210  out_files.append(os.path.join(buildflags_dir, 'perfetto_build_flags.h.swp'))
1211  gn_utils.gen_buildflags(gn_args, out_files[-1])
1212
1213  # Either check the contents or move the files to their final destination.
1214  return gn_utils.check_or_commit_generated_files(out_files, args.check_only)
1215
1216
1217if __name__ == '__main__':
1218  sys.exit(main())
1219