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