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