• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
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
32
33import gn_utils
34
35from compat import itervalues, iteritems, basestring
36
37# Arguments for the GN output directory.
38# host_os="linux" is to generate the right build files from Mac OS.
39gn_args = ' '.join([
40    'host_os="linux"',
41    'is_debug=false',
42    'is_perfetto_build_generator=true',
43    'enable_perfetto_watchdog=true',
44    'monolithic_binaries=true',
45    'target_os="linux"',
46])
47
48# Default targets to translate to the blueprint file.
49
50# These targets will be exported with public visibility in the generated BUILD.
51public_targets = [
52    '//:libperfetto_client_experimental',
53    '//src/perfetto_cmd:perfetto',
54    '//src/traced/probes:traced_probes',
55    '//src/traced/service:traced',
56    '//src/trace_processor:trace_processor_shell',
57    '//src/trace_processor:trace_processor',
58    '//tools/trace_to_text:trace_to_text',
59    '//tools/trace_to_text:libpprofbuilder',
60]
61
62# These targets are required by internal build rules but don't need to be
63# exported publicly.
64default_targets = [
65    '//test:client_api_example',
66    '//src/ipc:perfetto_ipc',
67    '//src/ipc/protoc_plugin:ipc_plugin',
68    '//src/protozero:libprotozero',
69    '//src/protozero/protoc_plugin:protozero_plugin',
70    '//src/protozero/protoc_plugin:cppgen_plugin',
71] + public_targets
72
73# Root proto targets (to force discovery of intermediate proto targets).
74# These targets are marked public.
75proto_targets = [
76    '//protos/perfetto/trace:merged_trace',
77    '//protos/perfetto/trace:non_minimal_lite',
78    '//protos/perfetto/config:merged_config',
79    '//protos/perfetto/metrics:lite',
80    '//protos/perfetto/metrics/android:lite',
81    '//protos/perfetto/trace:lite',
82    '//protos/perfetto/config:lite',
83]
84
85# The directory where the generated perfetto_build_flags.h will be copied into.
86buildflags_dir = 'include/perfetto/base/build_configs/bazel'
87
88# Internal equivalents for third-party libraries that the upstream project
89# depends on.
90external_deps = {
91    '//gn:default_deps': [],
92    '//gn:jsoncpp': ['PERFETTO_CONFIG.deps.jsoncpp'],
93    '//gn:linenoise': ['PERFETTO_CONFIG.deps.linenoise'],
94    '//gn:protobuf_full': ['PERFETTO_CONFIG.deps.protobuf_full'],
95    '//gn:protobuf_lite': ['PERFETTO_CONFIG.deps.protobuf_lite'],
96    '//gn:protoc_lib': ['PERFETTO_CONFIG.deps.protoc_lib'],
97    '//gn:protoc': ['PERFETTO_CONFIG.deps.protoc'],
98    '//gn:sqlite': [
99        'PERFETTO_CONFIG.deps.sqlite',
100        'PERFETTO_CONFIG.deps.sqlite_ext_percentile'
101    ],
102    '//gn:zlib': ['PERFETTO_CONFIG.deps.zlib'],
103    '//gn/standalone:gen_git_revision': [],
104    '//src/trace_processor/metrics:gen_merged_sql_metrics': [[
105        ":cc_merged_sql_metrics"
106    ]]
107}
108
109
110def gen_sql_metrics(target):
111  label = BazelLabel(get_bazel_label_name(target.name), 'genrule')
112  label.srcs += [re.sub('^//', '', x) for x in sorted(target.inputs)]
113  label.outs += target.outputs
114  label.cmd = r'$(location gen_merged_sql_metrics_py) --cpp_out=$@ $(SRCS)'
115  label.tools += [':gen_merged_sql_metrics_py']
116  return [label]
117
118
119custom_actions = {
120    '//src/trace_processor/metrics:gen_merged_sql_metrics': gen_sql_metrics,
121}
122
123# ------------------------------------------------------------------------------
124# End of configuration.
125# ------------------------------------------------------------------------------
126
127
128class Error(Exception):
129  pass
130
131
132class BazelLabel(object):
133
134  def __init__(self, name, type):
135    self.comment = None
136    self.name = name
137    self.type = type
138    self.visibility = []
139    self.srcs = []
140    self.hdrs = []
141    self.deps = []
142    self.external_deps = []
143    self.tools = []
144    self.outs = []
145
146  def __lt__(self, other):
147    if isinstance(other, self.__class__):
148      return self.name < other.name
149    raise TypeError('\'<\' not supported between instances of \'%s\' and \'%s\''
150                    % (type(self).__name__, type(other).__name__))
151
152  def __str__(self):
153    """Converts the object into a Bazel Starlark label."""
154    res = ''
155    res += ('# GN target: %s\n' % self.comment) if self.comment else ''
156    res += '%s(\n' % self.type
157    any_deps = len(self.deps) + len(self.external_deps) > 0
158    ORD = ['name', 'srcs', 'hdrs', 'visibility', 'deps', 'outs', 'cmd', 'tools']
159    hasher = lambda x: sum((99,) + tuple(ord(c) for c in x))
160    key_sorter = lambda kv: ORD.index(kv[0]) if kv[0] in ORD else hasher(kv[0])
161    for k, v in sorted(iteritems(self.__dict__), key=key_sorter):
162      if k in ('type', 'comment',
163               'external_deps') or v is None or (v == [] and
164                                                 (k != 'deps' or not any_deps)):
165        continue
166      res += '    %s = ' % k
167      if isinstance(v, basestring):
168        if v.startswith('PERFETTO_CONFIG.'):
169          res += '%s,\n' % v
170        else:
171          res += '"%s",\n' % v
172      elif isinstance(v, bool):
173        res += '%s,\n' % v
174      elif isinstance(v, list):
175        res += '[\n'
176        if k == 'deps' and len(self.external_deps) > 1:
177          indent = '           '
178        else:
179          indent = '    '
180        for entry in v:
181          if entry.startswith('PERFETTO_CONFIG.'):
182            res += '%s    %s,\n' % (indent, entry)
183          else:
184            res += '%s    "%s",\n' % (indent, entry)
185        res += '%s]' % indent
186        if k == 'deps' and self.external_deps:
187          res += ' + %s' % self.external_deps[0]
188          for edep in self.external_deps[1:]:
189            if isinstance(edep, list):
190              res += ' + [\n'
191              for inner_dep in edep:
192                res += '        "%s",\n' % inner_dep
193              res += '    ]'
194            else:
195              res += ' +\n%s%s' % (indent, edep)
196        res += ',\n'
197      else:
198        raise Error('Unsupported value %s', type(v))
199    res += ')\n\n'
200    return res
201
202
203# Public visibility for targets in Bazel.
204PUBLIC_VISIBILITY = 'PERFETTO_CONFIG.public_visibility'
205
206
207def get_bazel_label_name(gn_name):
208  """Converts a GN target name into a Bazel label name.
209
210  If target is in the public taraget list, returns only the GN target name,
211  e.g.: //src/ipc:perfetto_ipc -> perfetto_ipc
212
213  Otherwise, in the case of an intermediate taraget, returns a mangled path.
214  e.g.:  //include/perfetto/base:base -> include_perfetto_base_base.
215  """
216  if gn_name in default_targets:
217    return gn_utils.label_without_toolchain(gn_name).split(':')[1]
218  return gn_utils.label_to_target_name_with_path(gn_name)
219
220
221def gen_proto_labels(target):
222  """ Generates the xx_proto_library label for proto targets.
223
224  Bazel requires that each protobuf-related target is modeled with two labels:
225  1. A plugin-dependent target (e.g. cc_library, cc_protozero_library) that has
226     only a dependency on 2 and does NOT refer to any .proto sources.
227  2. A plugin-agnostic target that defines only the .proto sources and their
228     dependencies.
229  """
230  assert (target.type == 'proto_library')
231
232  def get_sources_label(target_name):
233    return re.sub('_(lite|zero|cpp|ipc)$', '',
234                  get_bazel_label_name(target_name)) + '_protos'
235
236  sources_label_name = get_sources_label(target.name)
237
238  # Generates 1.
239  if target.proto_plugin == 'proto':
240    plugin_label_type = 'perfetto_cc_proto_library'
241  elif target.proto_plugin == 'protozero':
242    plugin_label_type = 'perfetto_cc_protozero_library'
243  elif target.proto_plugin == 'cppgen':
244    plugin_label_type = 'perfetto_cc_protocpp_library'
245  elif target.proto_plugin == 'ipc':
246    plugin_label_type = 'perfetto_cc_ipc_library'
247  else:
248    raise Error('Unknown proto plugin: %s' % target.proto_plugin)
249  plugin_label_name = get_bazel_label_name(target.name)
250  plugin_label = BazelLabel(plugin_label_name, plugin_label_type)
251  plugin_label.comment = target.name
252  plugin_label.deps += [':' + sources_label_name]
253
254  # When using the cppgen plugin we need to pass down also the transitive deps.
255  # For instance consider foo.proto including common.proto. The generated
256  # foo.cc will #include "common.gen.h". Hence the generated cc_protocpp_library
257  # rule need to pass down the dependency on the target that generates
258  # common.gen.{cc,h}. This is not needed for protozero because protozero
259  # headers are fully hermetic deps-wise and use only on forward declarations.
260  if target.proto_deps and target.proto_plugin in ('cppgen', 'ipc'):
261    plugin_label.deps += [
262        ':' + get_bazel_label_name(x) for x in target.proto_deps
263    ]
264
265  # Generates 2.
266  sources_label = BazelLabel(sources_label_name, 'perfetto_proto_library')
267  sources_label.comment = target.name
268  assert (all(x.startswith('//') for x in target.sources))
269  assert (all(x.endswith('.proto') for x in target.sources))
270  sources_label.srcs = sorted([x[2:] for x in target.sources])  # Strip //.
271
272  deps = [
273      ':' + get_sources_label(x)
274      for x in target.proto_deps
275
276      # This is to avoid a dependency-on-self in the case where
277      # protos/perfetto/ipc:ipc depends on protos/perfetto/ipc:cpp and both
278      # targets resolve to "protos_perfetto_ipc_protos".
279      if get_sources_label(x) != sources_label_name
280  ]
281  sources_label.deps = sorted(deps)
282
283  if target.name in proto_targets:
284    sources_label.visibility = PUBLIC_VISIBILITY
285  else:
286    sources_label.visibility = ['PERFETTO_CONFIG.proto_library_visibility']
287
288  return [plugin_label, sources_label]
289
290
291def gen_target(gn_target):
292  if gn_target.type == 'proto_library':
293    return gen_proto_labels(gn_target)
294  elif gn_target.type == 'action':
295    if gn_target.name in custom_actions:
296      return custom_actions[gn_target.name](gn_target)
297    return []
298  elif gn_target.type == 'group':
299    return []
300  elif gn_target.type == 'executable':
301    bazel_type = 'perfetto_cc_binary'
302  elif gn_target.type == 'shared_library':
303    bazel_type = 'perfetto_cc_binary'
304    vars['linkshared'] = True
305  elif gn_target.type == 'static_library':
306    bazel_type = 'perfetto_cc_library'
307  elif gn_target.type == 'source_set':
308    bazel_type = 'filegroup'
309  else:
310    raise Error('target type not supported: %s' % gn_target.type)
311
312  label = BazelLabel(get_bazel_label_name(gn_target.name), bazel_type)
313  label.comment = gn_target.name
314
315  raw_srcs = [x[2:] for x in gn_target.sources]
316  if bazel_type == 'perfetto_cc_library':
317    label.srcs = [x for x in raw_srcs if not x.startswith('include')]
318    label.hdrs = [x for x in raw_srcs if x.startswith('include')]
319
320    # Most Perfetto libraries cannot by dynamically linked as they would
321    # cause ODR violations.
322    label.__dict__['linkstatic'] = True
323  else:
324    label.srcs = raw_srcs
325
326  if gn_target.name in public_targets:
327    label.visibility = ['//visibility:public']
328
329  if gn_target.type in gn_utils.LINKER_UNIT_TYPES:
330    # |source_sets| contains the transitive set of source_set deps.
331    for trans_dep in gn_target.source_set_deps:
332      name = ':' + get_bazel_label_name(trans_dep)
333      if name.startswith(
334          ':include_perfetto_') and gn_target.type != 'executable':
335        label.hdrs += [name]
336      else:
337        label.srcs += [name]
338    for dep in sorted(gn_target.deps):
339      if dep.startswith('//gn:'):
340        assert (dep in external_deps), dep
341      if dep in external_deps:
342        assert (isinstance(external_deps[dep], list))
343        label.external_deps += external_deps[dep]
344      else:
345        label.deps += [':' + get_bazel_label_name(dep)]
346    label.deps += [':' + get_bazel_label_name(x) for x in gn_target.proto_deps]
347
348  # All items starting with : need to be sorted to the end of the list.
349  # However, Python makes specifying a comparator function hard so cheat
350  # instead and make everything start with : sort as if it started with |
351  # As | > all other normal ASCII characters, this will lead to all : targets
352  # starting with : to be sorted to the end.
353  label.srcs = sorted(label.srcs, key=lambda x: x.replace(':', '|'))
354
355  label.deps = sorted(label.deps)
356  label.hdrs = sorted(label.hdrs)
357  return [label]
358
359
360def gen_target_str(gn_target):
361  return ''.join(str(x) for x in gen_target(gn_target))
362
363
364def generate_build(gn_desc, targets, extras):
365  gn = gn_utils.GnParser(gn_desc)
366  res = '''
367# Copyright (C) 2019 The Android Open Source Project
368#
369# Licensed under the Apache License, Version 2.0 (the "License");
370# you may not use this file except in compliance with the License.
371# You may obtain a copy of the License at
372#
373#      http://www.apache.org/licenses/LICENSE-2.0
374#
375# Unless required by applicable law or agreed to in writing, software
376# distributed under the License is distributed on an "AS IS" BASIS,
377# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
378# See the License for the specific language governing permissions and
379# limitations under the License.
380#
381# This file is automatically generated by {}. Do not edit.
382
383load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
384load(
385    "@perfetto//bazel:rules.bzl",
386    "perfetto_cc_binary",
387    "perfetto_cc_ipc_library",
388    "perfetto_cc_library",
389    "perfetto_cc_proto_library",
390    "perfetto_cc_protocpp_library",
391    "perfetto_cc_protozero_library",
392    "perfetto_java_proto_library",
393    "perfetto_java_lite_proto_library",
394    "perfetto_proto_library",
395    "perfetto_py_binary",
396    "perfetto_gensignature_internal_only",
397)
398
399package(default_visibility = ["//visibility:private"])
400
401licenses(["notice"])
402
403exports_files(["NOTICE"])
404
405'''.format(__file__).lstrip()
406
407  # Public targets need to be computed at the beginning (to figure out the
408  # intermediate deps) but printed at the end (because declaration order matters
409  # in Bazel).
410  public_str = ''
411  for target_name in sorted(public_targets):
412    target = gn.get_target(target_name)
413    public_str += gen_target_str(target)
414
415  res += '''
416# ##############################################################################
417# Internal targets
418# ##############################################################################
419
420'''.lstrip()
421  # Generate the other non-public targets.
422  for target_name in sorted(set(default_targets) - set(public_targets)):
423    target = gn.get_target(target_name)
424    res += gen_target_str(target)
425
426  # Generate all the intermediate targets.
427  for target in sorted(itervalues(gn.all_targets)):
428    if target.name in default_targets or target.name in gn.proto_libs:
429      continue
430    res += gen_target_str(target)
431
432  res += '''
433# ##############################################################################
434# Proto libraries
435# ##############################################################################
436
437'''.lstrip()
438  # Force discovery of explicilty listed root proto targets.
439  for target_name in sorted(proto_targets):
440    gn.get_target(target_name)
441
442  # Generate targets for the transitive set of proto targets.
443  # TODO explain deduping here.
444  labels = {}
445  for target in sorted(itervalues(gn.proto_libs)):
446    for label in gen_target(target):
447      # Ensure that if the existing target has public visibility, we preserve
448      # that in the new label; this ensures that we don't accidentaly reduce
449      # the visibility of targets which are meant to be public.
450      existing_label = labels.get(label.name)
451      if existing_label and existing_label.visibility == PUBLIC_VISIBILITY:
452        label.visibility = PUBLIC_VISIBILITY
453      labels[label.name] = label
454
455  res += ''.join(str(x) for x in sorted(itervalues(labels)))
456
457  res += '''
458# ##############################################################################
459# Public targets
460# ##############################################################################
461
462'''.lstrip()
463  res += public_str
464  res += '# Content from BUILD.extras\n\n'
465  res += extras
466  return res
467
468
469def main():
470  parser = argparse.ArgumentParser(
471      description='Generate BUILD from a GN description.')
472  parser.add_argument(
473      '--check-only',
474      help='Don\'t keep the generated files',
475      action='store_true')
476  parser.add_argument(
477      '--desc',
478      help='GN description ' +
479      '(e.g., gn desc out --format=json --all-toolchains "//*"')
480  parser.add_argument(
481      '--repo-root',
482      help='Standalone Perfetto repository to generate a GN description',
483      default=gn_utils.repo_root(),
484  )
485  parser.add_argument(
486      '--extras',
487      help='Extra targets to include at the end of the BUILD file',
488      default=os.path.join(gn_utils.repo_root(), 'BUILD.extras'),
489  )
490  parser.add_argument(
491      '--output',
492      help='BUILD file to create',
493      default=os.path.join(gn_utils.repo_root(), 'BUILD'),
494  )
495  parser.add_argument(
496      '--output-proto',
497      help='Proto BUILD file to create',
498      default=os.path.join(gn_utils.repo_root(), 'protos', 'BUILD'),
499  )
500  parser.add_argument(
501      'targets',
502      nargs=argparse.REMAINDER,
503      help='Targets to include in the BUILD file (e.g., "//:perfetto_tests")')
504  args = parser.parse_args()
505
506  if args.desc:
507    with open(args.desc) as f:
508      desc = json.load(f)
509  else:
510    desc = gn_utils.create_build_description(gn_args, args.repo_root)
511
512  out_files = []
513
514  # Generate the main BUILD file.
515  with open(args.extras, 'r') as extra_f:
516    extras = extra_f.read()
517
518  contents = generate_build(desc, args.targets or default_targets, extras)
519  out_files.append(args.output + '.swp')
520  with open(out_files[-1], 'w') as out_f:
521    out_f.write(contents)
522
523  # Generate the build flags file.
524  out_files.append(os.path.join(buildflags_dir, 'perfetto_build_flags.h.swp'))
525  gn_utils.gen_buildflags(gn_args, out_files[-1])
526
527  return gn_utils.check_or_commit_generated_files(out_files, args.check_only)
528
529
530if __name__ == '__main__':
531  sys.exit(main())
532