• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright (C) 2018 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# BUILD file for the Bazel 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 Bazel
24# build rules.
25
26from __future__ import print_function
27import argparse
28import json
29import os
30import re
31import sys
32from typing import Any
33from typing import Dict
34from typing import List
35from typing import Optional
36from typing import Union
37
38from gn_utils import GnParser
39import gn_utils
40
41from compat import itervalues, iteritems, basestring
42
43# Visibility option for targets which we want an allowlist of public targets
44# which can depend on it.
45ALLOWLIST_PUBLIC_VISIBILITY = 'PERFETTO_CONFIG.public_visibility'
46
47# Arguments for the GN output directory.
48# host_os="linux" is to generate the right build files from Mac OS.
49gn_args_base = {
50    'host_os': '"linux"',
51    'is_debug': 'false',
52    'is_perfetto_build_generator': 'true',
53    'monolithic_binaries': 'true',
54    'target_os': '"linux"',
55    'enable_perfetto_heapprofd': 'false',
56    'enable_perfetto_traced_perf': 'false',
57    'perfetto_force_dcheck': '"off"',
58    'enable_perfetto_llvm_demangle': 'true',
59}
60gn_args = ' '.join((map(lambda x:'='.join(x), gn_args_base.items())))
61
62# We generate a second set of build rules for Windows, in order to
63# conditionally filter out targets which are gated behind !is_win
64# in the GN build files.
65gn_args_win_base = {
66    **gn_args_base,
67    'host_os': '"win"',
68    'host_cpu': '"x86"',
69    'target_os': '"win"',
70    'target_cpu': '"x64"',
71    'is_cross_compiling': 'false',
72}
73gn_args_win = ' '.join((map(lambda x:'='.join(x), gn_args_win_base.items())))
74
75gn_args_android_base = {
76    **gn_args_base,
77    'target_os': '"android"',
78    'enable_perfetto_android_java_sdk': 'true',
79    'is_cross_compiling': 'false',
80}
81gn_args_android = ' '.join(
82    (map(lambda x: '='.join(x), gn_args_android_base.items())))
83
84# Default targets to translate to the blueprint file.
85
86# These targets will be exported with public visibility in the generated BUILD.
87public_targets = [
88    '//:libperfetto_client_experimental',
89    '//src/perfetto_cmd:perfetto',
90    '//src/traced/probes:traced_probes',
91    '//src/traced/service:traced',
92    '//src/trace_processor:trace_processor_shell',
93    '//src/trace_processor:trace_processor',
94    '//src/traceconv:traceconv',
95]
96
97# These targets will be exported with visibility only to our allowlist.
98allowlist_public_targets = [
99    '//src/shared_lib:libperfetto_c',
100    '//src/traceconv:libpprofbuilder',
101]
102
103# These targets are required by internal build rules but don't need to be
104# exported publicly.
105default_targets = [
106    '//src/base:perfetto_base_default_platform',
107    '//src/ipc:perfetto_ipc',
108    '//src/ipc/protoc_plugin:ipc_plugin',
109    '//src/protozero:protozero',
110    '//src/protozero/protoc_plugin:cppgen_plugin',
111    '//src/protozero/protoc_plugin:protozero_plugin',
112    '//src/tools/proto_filter:proto_filter',
113    '//src/tools/proto_merger:proto_merger',
114    '//src/trace_processor/rpc:trace_processor_rpc',
115    '//test:client_api_example',
116] + public_targets + allowlist_public_targets
117
118default_android_targets = [
119    '//src/java_sdk/main:perfetto_java_sdk_app',
120    '//src/java_sdk/test:perfetto_java_sdk_test_app',
121    '//src/java_sdk/test:perfetto_java_sdk_instrumentation_test',
122    "//src/android_sdk/java/main:perfetto_trace_lib",
123    "//src/android_sdk/java/test:perfetto_trace_instrumentation_test",
124]
125
126# Proto target groups which will be made public.
127proto_groups = {
128    'config': {
129        'sources': ['//protos/perfetto/config:source_set'],
130        'visibility': ['//visibility:public'],
131    },
132    'trace': {
133        'sources': [
134            '//protos/perfetto/trace:non_minimal_source_set',
135            '//protos/perfetto/trace:minimal_source_set'
136        ],
137        'visibility': ALLOWLIST_PUBLIC_VISIBILITY,
138    },
139    'metrics': {
140        'sources': ['//protos/perfetto/metrics:source_set',],
141        'visibility': ['//visibility:public'],
142    },
143    'chromium': {
144        'sources': ['//protos/third_party/chromium:source_set',],
145        'visibility': ALLOWLIST_PUBLIC_VISIBILITY,
146    },
147    'chrome_metrics': {
148        'sources': ['//protos/perfetto/metrics/chrome:source_set',],
149        'visibility': ALLOWLIST_PUBLIC_VISIBILITY,
150    },
151    'trace_processor': {
152        'sources': ['//protos/perfetto/trace_processor:source_set',],
153        'visibility': [],
154    },
155    'trace_summary': {
156        'sources': ['//protos/perfetto/trace_summary:source_set',],
157        'visibility': ['//visibility:public'],
158    },
159}
160
161# Path for the protobuf sources in the standalone build.
162buildtools_protobuf_src = '//buildtools/protobuf/src'
163
164# The directory where the generated perfetto_build_flags.h will be copied into.
165buildflags_dir = 'include/perfetto/base/build_configs/bazel'
166
167# Internal equivalents for third-party libraries that the upstream project
168# depends on.
169external_deps = {
170    '//gn:default_deps': [],
171    '//gn:base_platform': ['PERFETTO_CONFIG.deps.base_platform'],
172    '//gn:expat': ['PERFETTO_CONFIG.deps.expat'],
173    '//gn:jsoncpp': ['PERFETTO_CONFIG.deps.jsoncpp'],
174    '//gn:linenoise': ['PERFETTO_CONFIG.deps.linenoise'],
175    '//gn:open_csd': ['PERFETTO_CONFIG.deps.open_csd'],
176    '//gn:protobuf_full': ['PERFETTO_CONFIG.deps.protobuf_full'],
177    '//gn:protobuf_lite': ['PERFETTO_CONFIG.deps.protobuf_lite'],
178    '//gn:protoc_lib': ['PERFETTO_CONFIG.deps.protoc_lib'],
179    '//gn:protoc': ['PERFETTO_CONFIG.deps.protoc'],
180    '//gn:sqlite': [
181        'PERFETTO_CONFIG.deps.sqlite',
182        'PERFETTO_CONFIG.deps.sqlite_ext_percentile'
183    ],
184    '//gn:zlib': ['PERFETTO_CONFIG.deps.zlib'],
185    '//gn:llvm_demangle': ['PERFETTO_CONFIG.deps.llvm_demangle'],
186    '//src/trace_processor:demangle': ['PERFETTO_CONFIG.deps.demangle_wrapper'],
187    gn_utils.GEN_VERSION_TARGET: ['PERFETTO_CONFIG.deps.version_header'],
188}
189
190# These are Python targets which are exposed with public visibility.
191public_python_targets = [
192    '//python:batch_trace_processor',
193    '//python:trace_processor_py',
194    '//python:trace_processor_py_no_resolvers',
195]
196
197# These are Python targets which are exposed by default.
198default_python_targets = [
199    '//python:batch_trace_processor',
200    '//python:experimental_slice_breakdown_bin',
201    '//python:trace_processor_table_generator',
202    '//python:trace_processor_py_example',
203    '//python:sql_processing',
204]
205
206# Internal equivalents for third-party Python libraries.
207external_python_deps: Dict[str, List[str]] = {
208    '//gn:pandas_py': ['PERFETTO_CONFIG.deps.pandas_py'],
209    '//gn:protobuf_py': ['PERFETTO_CONFIG.deps.protobuf_py'],
210    '//gn:tp_vendor_py': ['PERFETTO_CONFIG.deps.tp_vendor_py'],
211    '//gn:tp_resolvers_py': ['PERFETTO_CONFIG.deps.tp_resolvers_py'],
212}
213
214external_java_android_sdk_deps: Dict[str, List[str]] = {
215    '//gn:android_test_common': ['PERFETTO_CONFIG.deps.android_test_common'],
216}
217
218# Map from GN targets to the list of Bazel deps labels
219additional_java_android_sdk_deps: Dict[str, List[str]] = {
220    '//src/android_sdk/java/test:perfetto_trace_test_lib': [
221        ':trace_java_proto_lite'],
222}
223
224# Additional arguments
225target_overrides = {
226  '//src/trace_processor/perfetto_sql/stdlib/chrome:chrome_sql': {
227    'srcs': 'glob(["src/trace_processor/perfetto_sql/stdlib/chrome/**/*.sql"])'
228  }
229}
230
231# Defines required for Bazel.
232bazel_required_defines = [
233  "PERFETTO_SHLIB_SDK_IMPLEMENTATION"
234]
235
236# Targets with the "avoid_dep" tag;
237avoid_dep_targets = set([
238  '//python:trace_processor_py_no_resolvers',
239])
240
241# Filter defines that appear in the bazel build file to only those that bazel requires.
242def filter_defines(defines):
243  return [d for d in defines if d in bazel_required_defines]
244
245
246def gen_version_header(target):
247  label = BazelLabel(get_bazel_label_name(target.name), 'perfetto_genrule')
248  label.srcs += [gn_utils.label_to_path(x) for x in sorted(target.inputs)]
249  label.outs += target.outputs
250  label.cmd = r'$(location gen_version_header_py)'
251  label.cmd += r' --cpp_out=$@ --changelog=$(location CHANGELOG)'
252  label.tools += [':gen_version_header_py']
253  return [label]
254
255
256custom_actions = {
257    gn_utils.GEN_VERSION_TARGET: gen_version_header,
258}
259
260# ------------------------------------------------------------------------------
261# End of configuration.
262# ------------------------------------------------------------------------------
263
264
265class PythonBuildGenerator:
266  '''Generator of the BUILD file in the python folder.
267
268  This code is split into its own class to avoid polluting
269  the generation of the main build file with Python related
270  content.
271  '''
272
273  def populate_python_deps(self, target: GnParser.Target, label: 'BazelLabel'):
274    '''Populates deps for a GN target into Bazel Python label.'''
275    for dep in sorted(target.non_proto_or_source_set_deps()):
276      if dep.name in external_python_deps:
277        assert (isinstance(external_python_deps[dep.name], list))
278        label.external_deps += external_python_deps[dep.name]
279      else:
280        label.deps += [':' + get_bazel_python_label_name(dep.name)]
281
282  def python_label_to_path(self, gn_name: str):
283    """Converts a Python GN path label into a Bazel path."""
284    return re.sub(r'^python/', '', gn_utils.label_to_path(gn_name))
285
286  def python_data_to_path(self, gn_name: str):
287    """Converts a Python GN data label into a Bazel data label."""
288    return re.sub(r'^\.\.(.*)', r'PERFETTO_CONFIG.root + "\1"', gn_name)
289
290  def gen_python_library(self, target: GnParser.Target):
291    """Creates a Bazel target for a Python library GN target."""
292    label = BazelLabel(
293        get_bazel_python_label_name(target.name), 'perfetto_py_library')
294    label.comment = target.name
295    label.srcs += (self.python_label_to_path(x) for x in target.sources)
296    label.data += (self.python_data_to_path(x) for x in target.data)
297    self.populate_python_deps(target, label)
298    if target.name in public_python_targets:
299      label.visibility = ['//visibility:public']
300    if target.name in avoid_dep_targets:
301      label.tags += ['avoid_dep']
302    return [label]
303
304  def gen_python_binary(self, target: GnParser.Target):
305    """Creates a Bazel target for a Python binary GN target."""
306    label = BazelLabel(
307        get_bazel_python_label_name(target.name), 'perfetto_py_binary')
308    label.comment = target.name
309    label.srcs += (self.python_label_to_path(x) for x in target.sources)
310    label.data += (self.python_data_to_path(x) for x in target.data)
311    label.main = target.python_main
312    label.python_version = 'PY3'
313    if target.name in public_python_targets:
314      label.visibility = ['//visibility:public']
315
316    self.populate_python_deps(target, label)
317    return [label]
318
319  def gen_target(self, gn_target: GnParser.Target):
320    """Creates a Bazel target for a Python GN target."""
321    assert (gn_target.type == 'action')
322    if gn_target.name in external_python_deps:
323      return []
324    if gn_target.custom_action_type == 'python_library':
325      return self.gen_python_library(gn_target)
326    if gn_target.custom_action_type == 'python_binary':
327      return self.gen_python_binary(gn_target)
328    assert (False)
329
330  def gen_target_str(self, gn_target: GnParser.Target):
331    """Creates a Bazel target string for a Python GN target."""
332    return ''.join(str(x) for x in self.gen_target(gn_target))
333
334  def generate(self, gn_desc):
335    """Creates a Python BUILD file for the GN description."""
336    gn = gn_utils.GnParser(gn_desc)
337    project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
338    tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
339    tool_name = tool_name.replace('\\', '/')
340    res = '''
341# Copyright (C) 2022 The Android Open Source Project
342#
343# Licensed under the Apache License, Version 2.0 (the "License");
344# you may not use this file except in compliance with the License.
345# You may obtain a copy of the License at
346#
347#      http://www.apache.org/licenses/LICENSE-2.0
348#
349# Unless required by applicable law or agreed to in writing, software
350# distributed under the License is distributed on an "AS IS" BASIS,
351# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
352# See the License for the specific language governing permissions and
353# limitations under the License.
354#
355# This file is automatically generated by {}. Do not edit.
356
357load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
358load(
359    "@perfetto//bazel:rules.bzl",
360    "perfetto_py_binary",
361    "perfetto_py_library",
362)
363
364licenses(["notice"])
365
366package(default_visibility = [PERFETTO_CONFIG.root + ":__subpackages__"])
367
368'''.format(tool_name).lstrip()
369
370    # Find all the targets in the //python folder.
371    for target_name in default_python_targets:
372      target = gn.get_target(target_name)
373      res += self.gen_target_str(target)
374
375    # Generate all the intermediate targets.
376    for target in sorted(itervalues(gn.all_targets)):
377      if target.name in default_python_targets:
378        continue
379      res += self.gen_target_str(target)
380
381    return res
382
383
384class Error(Exception):
385  pass
386
387
388class BazelLabel(object):
389
390  def __init__(self, name, type):
391    self.comment: Optional[str] = None
392    self.name = name
393    self.type = type
394    self.visibility: Union[List[str], str] = []
395    self.srcs: Union[List[str], str] = []
396    self.win_srcs: Union[List[str], str] = None
397    self.hdrs = []
398    self.defines = []
399    self.data = []
400    self.deps = []
401    self.external_deps = []
402    self.tools = []
403    self.outs = []
404    self.exports = []
405    self.main = None
406    self.cmd: Optional[str] = None
407    self.python_version: Optional[str] = None
408    self.root_dir: Optional[str] = None
409    self.namespace: Optional[str] = None
410    self.tags: List[str] = []
411    self.binary_name: Optional[str] = None
412    self.linkopts: List[str] = []
413    self.manifest: Optional[str] = None
414    self.resource_files: Optional[str] = None
415    self.instruments: Optional[str] = None
416    self.app: Optional[str] = None
417    self.test_app: Optional[str] = None
418    self.testonly: Optional[bool] = None
419
420  def __lt__(self, other):
421    if isinstance(other, self.__class__):
422      return self.name < other.name
423    raise TypeError(
424        '\'<\' not supported between instances of \'%s\' and \'%s\'' %
425        (type(self).__name__, type(other).__name__))
426
427  def __str__(self):
428    """Converts the object into a Bazel Starlark label."""
429    res = ''
430    res += ('# GN target: %s\n' % self.comment) if self.comment else ''
431    res += '%s(\n' % self.type
432    any_deps = len(self.deps) + len(self.external_deps) > 0
433    ORD = [
434        'name', 'testonly', 'srcs', 'binary_name', 'linkopts', 'manifest',
435        'resource_files', 'instruments', 'hdrs', 'defines', 'visibility',
436        'data', 'deps', 'outs', 'cmd', 'tools', 'exports', 'main',
437        'python_version'
438    ]
439    hasher = lambda x: sum((99,) + tuple(ord(c) for c in x))
440    key_sorter = lambda kv: ORD.index(kv[0]) if kv[0] in ORD else hasher(kv[0])
441    for k, v in sorted(iteritems(self.__dict__), key=key_sorter):
442      if k in ('type', 'comment',
443               'external_deps', 'win_srcs') or v is None or (v == [] and
444                                                 (k != 'deps' or not any_deps)):
445        continue
446      res += '    %s = ' % k
447      if isinstance(v, basestring):
448        if v.startswith('PERFETTO_CONFIG.') or v.startswith('glob'):
449          res += '%s,\n' % v
450        else:
451          res += '"%s",\n' % v
452      elif isinstance(v, bool):
453        res += '%s,\n' % v
454      elif isinstance(v, list):
455        res += '[\n'
456        if k == 'deps' and len(self.external_deps) > 1:
457          indent = '           '
458        else:
459          indent = '    '
460
461        non_win_srcs = {}
462        if k == "srcs" and self.win_srcs is not None:
463          non_win_srcs = sorted(set(self.srcs) - set(self.win_srcs))
464
465        for entry in sorted(v):
466          if entry in non_win_srcs:
467            continue
468          if entry.startswith('PERFETTO_CONFIG.'):
469            res += '%s    %s,\n' % (indent, entry)
470          else:
471            res += '%s    "%s",\n' % (indent, entry)
472        if non_win_srcs:
473          res += '    ] + select({\n'
474          res += '        "@platforms//os:windows": [],\n'
475          res += '        "//conditions:default": [\n'
476          for win_src in non_win_srcs:
477            res += '            "%s",\n' % win_src
478          res += '        ],\n'
479          res += '    }),\n'
480        else:
481          res += '%s]' % indent
482          if k == 'deps' and self.external_deps:
483            res += ' + %s' % self.external_deps[0]
484            for edep in self.external_deps[1:]:
485              if isinstance(edep, list):
486                res += ' + [\n'
487                for inner_dep in edep:
488                  res += '        "%s",\n' % inner_dep
489                res += '    ]'
490              else:
491                res += ' +\n%s%s' % (indent, edep)
492          res += ',\n'
493      else:
494        raise Error('Unsupported value %s', type(v))
495    res += ')\n\n'
496    return res
497
498
499def get_bazel_label_name(gn_name: str):
500  """Converts a GN target name into a Bazel label name.
501
502  If target is in the public target list, returns only the GN target name,
503  e.g.: //src/ipc:perfetto_ipc -> perfetto_ipc
504
505  Otherwise, in the case of an intermediate target, returns a mangled path.
506  e.g.:  //include/perfetto/base:base -> include_perfetto_base_base.
507  """
508  if gn_name in default_targets:
509    return gn_utils.label_without_toolchain(gn_name).split(':')[1]
510  return gn_utils.label_to_target_name_with_path(gn_name)
511
512
513def get_bazel_python_label_name(gn_name: str):
514  """Converts a Python GN label into a Bazel label."""
515  name = re.sub(r'^//python:?', '', gn_name)
516  return gn_utils.label_to_target_name_with_path(name)
517
518
519def get_bazel_proto_sources_label(target_name: str):
520  """Converts a GN target name into a Bazel proto label name."""
521  return re.sub('_(lite|zero|cpp|ipc|source_set|descriptor)$', '',
522                get_bazel_label_name(target_name)) + '_protos'
523
524
525def gen_proto_label(target: GnParser.Target):
526  """ Generates the xx_proto_library label for proto targets."""
527  assert (target.type == 'proto_library')
528
529  sources_label_name = get_bazel_proto_sources_label(target.name)
530
531  # For 'source_set' plugins, we don't want to generate any plugin-dependent
532  # targets so just return the label of the proto sources only.
533  if target.proto_plugin == 'source_set':
534    sources_label = BazelLabel(sources_label_name, 'perfetto_proto_library')
535    sources_label.comment = target.name
536    assert (all(x.startswith('//') for x in target.sources))
537    assert (all(x.endswith('.proto') for x in target.sources))
538    sources_label.srcs = sorted([x[2:] for x in target.sources])  # Strip //.
539    sources_label.deps = sorted([
540        ':' + get_bazel_proto_sources_label(x.name)
541        for x in target.transitive_proto_deps()
542    ])
543
544    # In Bazel, proto_paths are not a supported concept becauase strong
545    # dependency checking is enabled. Instead, we need to depend on the target
546    # which includes the proto we want to depend on.
547    # For example, we include the proto_path |buildtools_protobuf_src| because
548    # we want to depend on the "google/protobuf/descriptor.proto" proto file.
549    # This will be exposed by the |protobuf_descriptor_proto| dep.
550    if buildtools_protobuf_src in target.proto_paths:
551      sources_label.external_deps = [
552          'PERFETTO_CONFIG.deps.protobuf_descriptor_proto'
553      ]
554
555    sources_label.visibility = ['PERFETTO_CONFIG.proto_library_visibility']
556
557    sources_label.exports = sorted(
558        [':' + get_bazel_proto_sources_label(d) for d in target.proto_exports])
559    return sources_label
560
561  # For all other types of plugins, we need to generate
562  if target.proto_plugin == 'proto':
563    plugin_label_type = 'perfetto_cc_proto_library'
564  elif target.proto_plugin == 'protozero':
565    plugin_label_type = 'perfetto_cc_protozero_library'
566  elif target.proto_plugin == 'cppgen':
567    plugin_label_type = 'perfetto_cc_protocpp_library'
568  elif target.proto_plugin == 'ipc':
569    plugin_label_type = 'perfetto_cc_ipc_library'
570  elif target.proto_plugin == 'descriptor':
571    plugin_label_type = 'perfetto_proto_descriptor'
572  else:
573    raise Error('Unknown proto plugin: %s' % target.proto_plugin)
574  plugin_label_name = get_bazel_label_name(target.name)
575  plugin_label = BazelLabel(plugin_label_name, plugin_label_type)
576  plugin_label.comment = target.name
577
578  # When using the plugins we need to pass down also the transitive deps.
579  # For instance consider foo.proto including common.proto. The generated
580  # foo.cc will #include "common.gen.h". Hence the generated cc_protocpp_library
581  # rule need to pass down the dependency on the target that generates
582  # common.gen.{cc,h}.
583  if target.proto_plugin in ('cppgen', 'ipc', 'protozero'):
584    plugin_label.deps += [
585        ':' + get_bazel_label_name(x.name)
586        for x in target.transitive_proto_deps()
587    ]
588
589  # Add any dependencies on source_set targets (i.e. targets containing proto
590  # files). For descriptors, we will have an explicit edge between the
591  # descriptor and source set wheras for other plugin types, this edge is
592  # implicit.
593  if target.proto_plugin == 'descriptor':
594    plugin_label.deps += [
595        ':' + get_bazel_proto_sources_label(x.name)
596        for x in target.proto_deps()
597    ]
598  else:
599    plugin_label.deps += [':' + sources_label_name]
600
601  # Since the descriptor generates an explicit output file which can be
602  # referenced by other targets, we specify a name for it.
603  if target.proto_plugin == 'descriptor':
604    plugin_label.outs = [plugin_label_name + '.bin']
605
606  return plugin_label
607
608
609def gen_proto_group_target(gn: GnParser, name: str, desc: Dict[str, Any]):
610  # Get a recursive list of the proto_library targets rooted here which
611  # have src.
612  deps_set = set(desc['sources'])
613  for target_name in desc['sources']:
614    target = gn.get_target(target_name)
615    deps_set.update(d.name for d in target.transitive_proto_deps())
616
617  # First, create a root source set target which references all the child
618  # source set targets. We publish this as well as depending on this in all
619  # subsequent targets.
620  sources_label = BazelLabel(name + '_proto', 'perfetto_proto_library')
621  sources_label.deps = [
622      ':' + get_bazel_proto_sources_label(name)
623      for name in sorted(list(deps_set))
624  ]
625  sources_label.comment = f'''[{', '.join(desc['sources'])}]'''
626
627  cc_label = BazelLabel(name + '_cc_proto', 'perfetto_cc_proto_library')
628  cc_label.deps = [':' + sources_label.name]
629  cc_label.comment = sources_label.comment
630
631  java_label = BazelLabel(name + '_java_proto', 'perfetto_java_proto_library')
632  java_label.deps = [':' + sources_label.name]
633  java_label.comment = sources_label.comment
634
635  lite_name = name + '_java_proto_lite'
636  java_lite_label = BazelLabel(lite_name, 'perfetto_java_lite_proto_library')
637  java_lite_label.deps = [':' + sources_label.name]
638  java_lite_label.comment = sources_label.comment
639
640  py_label = BazelLabel(name + '_py_pb2', 'perfetto_py_proto_library')
641  py_label.deps = [':' + sources_label.name]
642  py_label.comment = sources_label.comment
643
644  visibility = desc['visibility']
645  if visibility:
646    sources_label.visibility = visibility
647    cc_label.visibility = visibility
648    java_label.visibility = visibility
649    java_lite_label.visibility = visibility
650    py_label.visibility = visibility
651
652  return [sources_label, cc_label, java_label, java_lite_label, py_label]
653
654
655def gen_cc_proto_descriptor(target: GnParser.Target):
656  label = BazelLabel(
657      get_bazel_label_name(target.name), 'perfetto_cc_proto_descriptor')
658  label.comment = target.name
659  label.deps += [
660      ':' + get_bazel_label_name(x.name) for x in target.proto_deps()
661  ]
662  label.deps += [
663      gn_utils.label_to_path(src)
664      for src in target.inputs
665      if "tmp.gn_utils" not in src
666  ]
667
668  label.outs += target.outputs
669  return [label]
670
671
672def gen_cc_amalgamated_sql(target: GnParser.Target):
673  label = BazelLabel(
674      get_bazel_label_name(target.name), 'perfetto_cc_amalgamated_sql')
675
676  def find_arg(name):
677    for i, arg in enumerate(target.args):
678      if arg.startswith(f'--{name}'):
679        return target.args[i + 1]
680
681  label.comment = target.name
682  label.namespace = find_arg('namespace')
683
684  label.deps += sorted(
685      ':' + get_bazel_label_name(x.name) for x in target.transitive_deps)
686  label.outs += target.outputs
687  return [label]
688
689
690def gen_sql_source_set(target: GnParser.Target):
691  label = BazelLabel(get_bazel_label_name(target.name), 'perfetto_filegroup')
692  label.comment = target.name
693  label.srcs += (gn_utils.label_to_path(x) for x in target.inputs)
694  return [label]
695
696
697def gen_cc_tp_tables(target: GnParser.Target):
698  label = BazelLabel(get_bazel_label_name(target.name), 'perfetto_cc_tp_tables')
699  label.comment = target.name
700  label.srcs += (gn_utils.label_to_path(x) for x in target.sources)
701  label.deps += sorted(':' + get_bazel_label_name(x.name)
702                       for x in target.transitive_deps
703                       if x.name not in default_python_targets)
704  label.outs += target.outputs
705  return [label]
706
707
708def _populate_android_library_or_binary_deps(target: GnParser.Target,
709    label: BazelLabel):
710  for dep in sorted(target.non_proto_or_source_set_deps()):
711    if dep.name in external_java_android_sdk_deps:
712      list_of_deps = external_java_android_sdk_deps[dep.name]
713      assert (isinstance(list_of_deps, list))
714      label.external_deps += list_of_deps
715    else:
716      if target.name in additional_java_android_sdk_deps:
717        list_of_deps = additional_java_android_sdk_deps[target.name]
718        assert (isinstance(list_of_deps, list))
719        label.deps += list_of_deps
720
721      label.deps += [':' + get_bazel_label_name(dep.name)]
722
723def gen_perfetto_android_library(target: GnParser.Target):
724  label = BazelLabel(get_bazel_label_name(target.name),
725                     'perfetto_android_library')
726  label.comment = target.name
727  label.srcs += (gn_utils.label_to_path(x) for x in target.sources)
728  _populate_android_library_or_binary_deps(target, label)
729  if target.manifest:
730    label.manifest = gn_utils.label_to_path(target.manifest)
731  label.tags += ['notap'] # This tag is needed to build in google3.
732  if target.testonly:
733    label.testonly = True
734  return [label]
735
736def gen_perfetto_android_binary(target: GnParser.Target):
737  label = BazelLabel(get_bazel_label_name(target.name),
738                     'perfetto_android_binary')
739  label.comment = target.name
740  label.srcs += (gn_utils.label_to_path(x) for x in target.sources)
741  _populate_android_library_or_binary_deps(target, label)
742  if target.manifest:
743    label.manifest = gn_utils.label_to_path(target.manifest)
744  if target.resource_files:
745    label.resource_files = 'glob(["%s"])' % gn_utils.label_to_path(
746      target.resource_files)
747  if target.instruments:
748    label.instruments = ':' + get_bazel_label_name(target.instruments)
749  if target.testonly:
750    label.testonly = True
751  return [label]
752
753def gen_perfetto_android_instrumentation_test(target: GnParser.Target):
754  label = BazelLabel(get_bazel_label_name(target.name),
755                     'perfetto_android_instrumentation_test')
756  label.comment = target.name
757  label.app = get_bazel_label_name(target.a_i_t_app)
758  label.test_app = get_bazel_label_name(target.a_i_t_test_app)
759  return [label]
760
761def gen_target_helper(gn_target: GnParser.Target, win_target: GnParser.Target = None):
762  if gn_target.type == 'proto_library':
763    return [gen_proto_label(gn_target)]
764  elif gn_target.type == 'action':
765    if gn_target.name in custom_actions:
766      return custom_actions[gn_target.name](gn_target)
767    if gn_target.custom_action_type == 'sql_amalgamation':
768      return gen_cc_amalgamated_sql(gn_target)
769    if gn_target.custom_action_type == 'sql_source_set':
770      return gen_sql_source_set(gn_target)
771    if gn_target.custom_action_type == 'cc_proto_descriptor':
772      return gen_cc_proto_descriptor(gn_target)
773    if gn_target.custom_action_type == 'tp_tables':
774      return gen_cc_tp_tables(gn_target)
775    if gn_target.custom_action_type == 'perfetto_android_library':
776      return gen_perfetto_android_library(gn_target)
777    if gn_target.custom_action_type == 'perfetto_android_app':
778      return gen_perfetto_android_binary(gn_target)
779    if gn_target.custom_action_type == 'perfetto_android_instrumentation_test':
780      return gen_perfetto_android_instrumentation_test(gn_target)
781    return []
782  elif gn_target.type == 'group':
783    return []
784  elif gn_target.type == 'executable':
785    bazel_type = 'perfetto_cc_binary'
786  elif gn_target.type == 'shared_library':
787    bazel_type = 'perfetto_cc_library'
788  elif gn_target.type == 'static_library':
789    bazel_type = 'perfetto_cc_library'
790  elif gn_target.type == 'source_set':
791    bazel_type = 'perfetto_filegroup'
792  elif gn_target.type == 'generated_file':
793    return []
794  else:
795    raise Error('target type not supported: %s' % gn_target.type)
796
797  custom_bazel_type = gn_target.custom_target_type()
798  if custom_bazel_type:
799    if custom_bazel_type == 'perfetto_android_jni_library':
800      bazel_type = custom_bazel_type
801    else:
802      raise Error('custom target type not supported: %s' % custom_bazel_type)
803
804  label = BazelLabel(get_bazel_label_name(gn_target.name), bazel_type)
805  label.comment = gn_target.name
806
807  # Supporting 'public' on source_sets would require not converting them to
808  # filegroups in bazel.
809  if gn_target.public_headers:
810    if bazel_type == 'perfetto_cc_library':
811      label.hdrs += [x[2:] for x in gn_target.public_headers]
812    else:
813      raise Error('%s: \'public\' currently supported only for cc_library' %
814                  gn_target.name)
815
816  raw_srcs = [x[2:] for x in gn_target.sources]
817  raw_srcs += [x[2:] for x in gn_target.inputs]
818  if bazel_type == 'perfetto_cc_library':
819    label.srcs += [x for x in raw_srcs if not x.startswith('include')]
820    label.hdrs += [x for x in raw_srcs if x.startswith('include')]
821
822    # Most Perfetto libraries cannot be dynamically linked as they would
823    # cause ODR violations.
824    label.__dict__['linkstatic'] = True
825  else:
826    label.srcs = raw_srcs
827
828  is_public = gn_target.name in public_targets
829  is_public_for_allowlist = gn_target.name in allowlist_public_targets
830  if is_public and is_public_for_allowlist:
831    raise Error('Target %s in both public_targets and allowlist_public_targets', gn.target.name)
832  elif is_public:
833    label.visibility = ['//visibility:public']
834  elif is_public_for_allowlist:
835    label.visibility = ALLOWLIST_PUBLIC_VISIBILITY
836
837  if win_target:
838    label.win_srcs = list(set(label.srcs) & {s[2:] for s in win_target.sources | win_target.inputs})
839
840  if bazel_type == 'perfetto_android_jni_library':
841    label.tags += ['notap'] # This tag is needed to build in google3.
842    label.binary_name = gn_target.binary_name()
843    label.linkopts = gn_target.linkopts()
844
845  if gn_target.type in gn_utils.LINKER_UNIT_TYPES:
846    # |source_sets| contains the transitive set of source_set deps.
847    for trans_dep in gn_target.transitive_source_set_deps():
848      name = ':' + get_bazel_label_name(trans_dep.name)
849      if name.startswith(
850          ':include_perfetto_') and gn_target.type != 'executable':
851        label.hdrs += [name]
852      else:
853        if win_target:
854          win_target_names = [target.name for target in win_target.transitive_source_set_deps()]
855          if trans_dep.name in win_target_names:
856            label.win_srcs += [name]
857
858        label.srcs += [name]
859
860      # Add defines from all transitive dependencies.
861      label.defines += trans_dep.defines
862
863    for dep in sorted(gn_target.non_proto_or_source_set_deps()):
864      dep_name = dep.name
865      if dep_name.startswith('//gn:'):
866        assert (dep_name in external_deps), dep
867
868      # tp_tables produces a filegroup not a cc_library so should end up srcs
869      # not deps.
870      if dep.custom_action_type == 'tp_tables':
871        label_name = ':' + get_bazel_label_name(dep_name)
872        win_target_names = [target.name for target in win_target.non_proto_or_source_set_deps()]
873        if win_target and dep_name in win_target_names:
874          label.win_srcs += [label_name]
875        label.srcs += [label_name]
876      elif dep_name in external_deps:
877        assert (isinstance(external_deps[dep_name], list))
878        label.external_deps += external_deps[dep_name]
879      else:
880        label.deps += [':' + get_bazel_label_name(dep_name)]
881
882    label.deps += [
883        ':' + get_bazel_label_name(x.name)
884        for x in gn_target.transitive_cpp_proto_deps()
885    ]
886
887  # All items starting with : need to be sorted to the end of the list.
888  # However, Python makes specifying a comparator function hard so cheat
889  # instead and make everything start with : sort as if it started with |
890  # As | > all other normal ASCII characters, this will lead to all : targets
891  # starting with : to be sorted to the end.
892  label.srcs = sorted(label.srcs, key=lambda x: x.replace(':', '|'))
893  if win_target:
894    label.win_srcs = sorted(label.win_srcs, key=lambda x: x.replace(':', '|'))
895
896  label.deps = sorted(label.deps)
897  label.hdrs = sorted(label.hdrs)
898  label.defines = sorted(filter_defines(set(label.defines)))
899  return [label]
900
901
902def gen_target(gn_target: GnParser.Target, win_target: GnParser.Target = None):
903  targets = gen_target_helper(gn_target, win_target)
904  if gn_target.name not in target_overrides:
905    return targets
906  for target in targets:
907    for key, add_val in target_overrides[gn_target.name].items():
908      setattr(target, key, add_val)
909  return targets
910
911
912def gen_target_str(gn_target, win_target: GnParser.Target = None):
913  return ''.join(str(x) for x in gen_target(gn_target, win_target))
914
915
916def generate_build(gn_desc, gn_desc_win, gn_desc_android, targets, extras):
917  gn = gn_utils.GnParser(gn_desc)
918  gn_win = gn_utils.GnParser(gn_desc_win)
919  gn_android = gn_utils.GnParser(gn_desc_android)
920  project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
921  tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
922  tool_name = tool_name.replace('\\', '/')
923  res = '''
924# Copyright (C) 2019 The Android Open Source Project
925#
926# Licensed under the Apache License, Version 2.0 (the "License");
927# you may not use this file except in compliance with the License.
928# You may obtain a copy of the License at
929#
930#      http://www.apache.org/licenses/LICENSE-2.0
931#
932# Unless required by applicable law or agreed to in writing, software
933# distributed under the License is distributed on an "AS IS" BASIS,
934# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
935# See the License for the specific language governing permissions and
936# limitations under the License.
937#
938# This file is automatically generated by {}. Do not edit.
939
940load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
941load(
942    "@perfetto//bazel:rules.bzl",
943    "perfetto_build_config_cc_library",
944    "perfetto_cc_amalgamated_sql",
945    "perfetto_cc_binary",
946    "perfetto_cc_ipc_library",
947    "perfetto_cc_library",
948    "perfetto_cc_proto_descriptor",
949    "perfetto_cc_proto_library",
950    "perfetto_cc_protocpp_library",
951    "perfetto_cc_protozero_library",
952    "perfetto_cc_tp_tables",
953    "perfetto_filegroup",
954    "perfetto_genrule",
955    "perfetto_go_proto_library",
956    "perfetto_java_lite_proto_library",
957    "perfetto_java_proto_library",
958    "perfetto_proto_descriptor",
959    "perfetto_proto_library",
960    "perfetto_py_binary",
961    "perfetto_py_library",
962    "perfetto_py_proto_library",
963    "perfetto_jspb_proto_library",
964    "perfetto_android_binary",
965    "perfetto_android_jni_library",
966    "perfetto_android_library",
967    "perfetto_android_instrumentation_test",
968)
969
970package(default_visibility = [PERFETTO_CONFIG.root + ":__subpackages__"])
971
972licenses(["notice"])
973
974exports_files(["NOTICE"])
975
976'''.format(tool_name).lstrip()
977
978  # Public targets need to be computed at the beginning (to figure out the
979  # intermediate deps) but printed at the end (because declaration order matters
980  # in Bazel).
981  public_str = ''
982
983  for target_name in sorted(public_targets):
984    target = gn.get_target(target_name)
985    public_str += gen_target_str(target, gn_win.get_target(target_name))
986
987  res += '''
988# ##############################################################################
989# Internal targets
990# ##############################################################################
991
992'''.lstrip()
993  # Generate the other non-public targets.
994  for target_name in sorted(set(default_targets) - set(public_targets)):
995    gn_win.get_target(target_name)
996
997  for target_name in sorted(set(default_targets) - set(public_targets)):
998    target = gn.get_target(target_name)
999    res += gen_target_str(target, gn_win.get_target(target_name))
1000
1001  # Generate all the intermediate targets.
1002  for target in sorted(itervalues(gn.all_targets)):
1003    if target.name in default_targets or target.name in gn.proto_libs:
1004      continue
1005    res += gen_target_str(target, gn_win.get_target(target.name))
1006
1007  res += '''
1008# ##############################################################################
1009# Android Java SDK targets
1010# ##############################################################################
1011
1012'''.lstrip()
1013
1014  # Populate android deps first.
1015  for target_name in sorted(default_android_targets):
1016    gn_android.get_target(target_name)
1017
1018  android_only_targets = gn_android.all_targets.keys() - gn.all_targets.keys()
1019
1020  android_str = ''
1021  for target_name in sorted(android_only_targets):
1022    target = gn_android.get_target(target_name)
1023    android_str += gen_target_str(target)
1024
1025  res += android_str
1026
1027  res += '''
1028# ##############################################################################
1029# Proto libraries
1030# ##############################################################################
1031
1032'''.lstrip()
1033  # Generate targets for proto groups.
1034  for l_name, t_desc in proto_groups.items():
1035    res += ''.join(str(x) for x in gen_proto_group_target(gn, l_name, t_desc))
1036
1037  # For any non-source set and non-descriptor targets, ensure the source set
1038  # associated to that target is discovered.
1039  for target in sorted(itervalues(gn.all_targets)):
1040    plugin = target.proto_plugin
1041    if plugin is None or plugin == 'source_set' or plugin == 'descriptor':
1042      continue
1043    gn.get_target(re.sub('(lite|zero|cpp|ipc)$', 'source_set', target.name))
1044
1045  # Generate targets for the transitive set of proto targets.
1046  labels = [
1047      l for target in sorted(itervalues(gn.proto_libs))
1048      for l in gen_target(target)
1049  ]
1050  res += ''.join(str(x) for x in sorted(labels))
1051
1052  res += '''
1053# ##############################################################################
1054# Public targets
1055# ##############################################################################
1056
1057'''.lstrip()
1058  res += public_str
1059  res += '# Content from BUILD.extras\n\n'
1060  res += extras
1061
1062  # Check for ODR violations
1063  for target_name in default_targets:
1064    checker = gn_utils.ODRChecker(gn, target_name)
1065
1066  return res
1067
1068
1069def main():
1070  parser = argparse.ArgumentParser(
1071      description='Generate BUILD from a GN description.')
1072  parser.add_argument(
1073      '--check-only',
1074      help='Don\'t keep the generated files',
1075      action='store_true')
1076  parser.add_argument(
1077      '--desc',
1078      help='GN description ' +
1079      '(e.g., gn desc out --format=json --all-toolchains "//*"')
1080  parser.add_argument(
1081      '--repo-root',
1082      help='Standalone Perfetto repository to generate a GN description',
1083      default=gn_utils.repo_root(),
1084  )
1085  parser.add_argument(
1086      '--extras',
1087      help='Extra targets to include at the end of the BUILD file',
1088      default=os.path.join(gn_utils.repo_root(), 'BUILD.extras'),
1089  )
1090  parser.add_argument(
1091      '--output',
1092      help='BUILD file to create',
1093      default=os.path.join(gn_utils.repo_root(), 'BUILD'),
1094  )
1095  parser.add_argument(
1096      '--output-python',
1097      help='Python BUILD file to create',
1098      default=os.path.join(gn_utils.repo_root(), 'python', 'BUILD'),
1099  )
1100  parser.add_argument(
1101      'targets',
1102      nargs=argparse.REMAINDER,
1103      help='Targets to include in the BUILD file (e.g., "//:perfetto_tests")')
1104  args = parser.parse_args()
1105
1106  if args.desc:
1107    with open(args.desc) as f:
1108      desc = json.load(f)
1109  else:
1110    desc = gn_utils.create_build_description(gn_args, args.repo_root)
1111
1112  desc_win = gn_utils.create_build_description(gn_args_win, args.repo_root)
1113
1114  desc_android = gn_utils.create_build_description(gn_args_android,
1115                                                   args.repo_root)
1116  out_files = []
1117
1118  # Generate the main BUILD file.
1119  with open(args.extras, 'r') as extra_f:
1120    extras = extra_f.read()
1121
1122  contents = generate_build(desc, desc_win, desc_android,
1123                            args.targets or default_targets, extras)
1124  out_files.append(args.output + '.swp')
1125  with open(out_files[-1], 'w', newline='\n') as out_f:
1126    out_f.write(contents)
1127
1128  # Generate the python BUILD file.
1129  python_gen = PythonBuildGenerator()
1130  python_contents = python_gen.generate(desc)
1131  out_files.append(args.output_python + '.swp')
1132  with open(out_files[-1], 'w', newline='\n') as out_f:
1133    out_f.write(python_contents)
1134
1135  # Generate the build flags file.
1136  out_files.append(os.path.join(buildflags_dir, 'perfetto_build_flags.h.swp'))
1137  gn_utils.gen_buildflags(gn_args, out_files[-1])
1138
1139  return gn_utils.check_or_commit_generated_files(out_files, args.check_only)
1140
1141
1142if __name__ == '__main__':
1143  sys.exit(main())
1144