#!/usr/bin/env python3 # Copyright (C) 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This tool translates a collection of BUILD.gn files into a mostly equivalent # BUILD file for the Bazel build system. The input to the tool is a # JSON description of the GN build definition generated with the following # command: # # gn desc out --format=json --all-toolchains "//*" > desc.json # # The tool is then given a list of GN labels for which to generate Bazel # build rules. from __future__ import print_function import argparse import json import os import re import sys import gn_utils from compat import itervalues, iteritems, basestring # Arguments for the GN output directory. # host_os="linux" is to generate the right build files from Mac OS. gn_args = ' '.join([ 'host_os="linux"', 'is_debug=false', 'is_perfetto_build_generator=true', 'enable_perfetto_watchdog=true', 'monolithic_binaries=true', 'target_os="linux"', ]) # Default targets to translate to the blueprint file. # These targets will be exported with public visibility in the generated BUILD. public_targets = [ '//:libperfetto_client_experimental', '//src/perfetto_cmd:perfetto', '//src/traced/probes:traced_probes', '//src/traced/service:traced', '//src/trace_processor:trace_processor_shell', '//src/trace_processor:trace_processor', '//tools/trace_to_text:trace_to_text', '//tools/trace_to_text:libpprofbuilder', ] # These targets are required by internal build rules but don't need to be # exported publicly. default_targets = [ '//test:client_api_example', '//src/ipc:perfetto_ipc', '//src/ipc/protoc_plugin:ipc_plugin', '//src/protozero:protozero', '//src/protozero/protoc_plugin:protozero_plugin', '//src/protozero/protoc_plugin:cppgen_plugin', ] + public_targets # Root proto targets (to force discovery of intermediate proto targets). # These targets are marked public. proto_targets = [ '//protos/perfetto/trace:merged_trace', '//protos/perfetto/trace:non_minimal_lite', '//protos/perfetto/config:merged_config', '//protos/perfetto/metrics:lite', '//protos/perfetto/metrics/android:lite', '//protos/perfetto/trace:lite', '//protos/perfetto/config:lite', ] # Path for the protobuf sources in the standalone build. buildtools_protobuf_src = '//buildtools/protobuf/src' # The directory where the generated perfetto_build_flags.h will be copied into. buildflags_dir = 'include/perfetto/base/build_configs/bazel' # Internal equivalents for third-party libraries that the upstream project # depends on. external_deps = { '//gn:default_deps': [], '//gn:jsoncpp': ['PERFETTO_CONFIG.deps.jsoncpp'], '//gn:linenoise': ['PERFETTO_CONFIG.deps.linenoise'], '//gn:protobuf_full': ['PERFETTO_CONFIG.deps.protobuf_full'], '//gn:protobuf_lite': ['PERFETTO_CONFIG.deps.protobuf_lite'], '//gn:protoc_lib': ['PERFETTO_CONFIG.deps.protoc_lib'], '//gn:protoc': ['PERFETTO_CONFIG.deps.protoc'], '//gn:sqlite': [ 'PERFETTO_CONFIG.deps.sqlite', 'PERFETTO_CONFIG.deps.sqlite_ext_percentile' ], '//gn:zlib': ['PERFETTO_CONFIG.deps.zlib'], '//src/trace_processor/metrics:gen_merged_sql_metrics': [[ ':cc_merged_sql_metrics' ]], gn_utils.GEN_VERSION_TARGET: ['PERFETTO_CONFIG.deps.version_header'], } def gen_sql_metrics(target): label = BazelLabel(get_bazel_label_name(target.name), 'genrule') label.srcs += [re.sub('^//', '', x) for x in sorted(target.inputs)] label.outs += target.outputs label.cmd = r'$(location gen_merged_sql_metrics_py) --cpp_out=$@ $(SRCS)' label.exec_tools += [':gen_merged_sql_metrics_py'] return [label] def gen_version_header(target): label = BazelLabel(get_bazel_label_name(target.name), 'genrule') label.srcs += [re.sub('^//', '', x) for x in sorted(target.inputs)] label.outs += target.outputs label.cmd = r'$(location gen_version_header_py)' label.cmd += r' --cpp_out=$@ --changelog=$(location CHANGELOG)' label.exec_tools += [':gen_version_header_py'] return [label] def gen_cc_metrics_descriptor(target): label = BazelLabel( get_bazel_label_name(target.name), 'perfetto_cc_proto_descriptor') label.deps += [':' + get_bazel_label_name(x) for x in target.proto_deps] label.outs += target.outputs return [label] custom_actions = { gn_utils.GEN_VERSION_TARGET: gen_version_header, '//src/trace_processor/metrics:gen_merged_sql_metrics': gen_sql_metrics, } # ------------------------------------------------------------------------------ # End of configuration. # ------------------------------------------------------------------------------ class Error(Exception): pass class BazelLabel(object): def __init__(self, name, type): self.comment = None self.name = name self.type = type self.visibility = [] self.srcs = [] self.hdrs = [] self.deps = [] self.external_deps = [] self.tools = [] self.exec_tools = [] self.outs = [] def __lt__(self, other): if isinstance(other, self.__class__): return self.name < other.name raise TypeError('\'<\' not supported between instances of \'%s\' and \'%s\'' % (type(self).__name__, type(other).__name__)) def __str__(self): """Converts the object into a Bazel Starlark label.""" res = '' res += ('# GN target: %s\n' % self.comment) if self.comment else '' res += '%s(\n' % self.type any_deps = len(self.deps) + len(self.external_deps) > 0 ORD = [ 'name','srcs', 'hdrs', 'visibility', 'deps', 'outs', 'cmd', 'tools', 'exec_tools' ] hasher = lambda x: sum((99,) + tuple(ord(c) for c in x)) key_sorter = lambda kv: ORD.index(kv[0]) if kv[0] in ORD else hasher(kv[0]) for k, v in sorted(iteritems(self.__dict__), key=key_sorter): if k in ('type', 'comment', 'external_deps') or v is None or (v == [] and (k != 'deps' or not any_deps)): continue res += ' %s = ' % k if isinstance(v, basestring): if v.startswith('PERFETTO_CONFIG.'): res += '%s,\n' % v else: res += '"%s",\n' % v elif isinstance(v, bool): res += '%s,\n' % v elif isinstance(v, list): res += '[\n' if k == 'deps' and len(self.external_deps) > 1: indent = ' ' else: indent = ' ' for entry in sorted(v): if entry.startswith('PERFETTO_CONFIG.'): res += '%s %s,\n' % (indent, entry) else: res += '%s "%s",\n' % (indent, entry) res += '%s]' % indent if k == 'deps' and self.external_deps: res += ' + %s' % self.external_deps[0] for edep in self.external_deps[1:]: if isinstance(edep, list): res += ' + [\n' for inner_dep in edep: res += ' "%s",\n' % inner_dep res += ' ]' else: res += ' +\n%s%s' % (indent, edep) res += ',\n' else: raise Error('Unsupported value %s', type(v)) res += ')\n\n' return res # Public visibility for targets in Bazel. PUBLIC_VISIBILITY = 'PERFETTO_CONFIG.public_visibility' def get_bazel_label_name(gn_name): """Converts a GN target name into a Bazel label name. If target is in the public target list, returns only the GN target name, e.g.: //src/ipc:perfetto_ipc -> perfetto_ipc Otherwise, in the case of an intermediate target, returns a mangled path. e.g.: //include/perfetto/base:base -> include_perfetto_base_base. """ if gn_name in default_targets: return gn_utils.label_without_toolchain(gn_name).split(':')[1] return gn_utils.label_to_target_name_with_path(gn_name) def gen_proto_labels(target): """ Generates the xx_proto_library label for proto targets. Bazel requires that each protobuf-related target is modeled with two labels: 1. A plugin-agnostic target that defines only the .proto sources and their dependencies. 2. A plugin-dependent target (e.g. cc_library, cc_protozero_library) that has only a dependency on 1 and does NOT refer to any .proto sources. """ assert (target.type == 'proto_library') def get_sources_label(target_name): return re.sub('_(lite|zero|cpp|ipc|source_set|descriptor)$', '', get_bazel_label_name(target_name)) + '_protos' sources_label_name = get_sources_label(target.name) # Generates 1. sources_label = BazelLabel(sources_label_name, 'perfetto_proto_library') sources_label.comment = target.name assert (all(x.startswith('//') for x in target.sources)) assert (all(x.endswith('.proto') for x in target.sources)) sources_label.srcs = sorted([x[2:] for x in target.sources]) # Strip //. deps = [ ':' + get_sources_label(x) for x in target.proto_deps # This is to avoid a dependency-on-self in the case where # protos/perfetto/ipc:ipc depends on protos/perfetto/ipc:cpp and both # targets resolve to "protos_perfetto_ipc_protos". if get_sources_label(x) != sources_label_name ] sources_label.deps = sorted(deps) # In Bazel, proto_paths are not a supported concept becauase strong dependency # checking is enabled. Instead, we need to depend on the target which includes # the proto we want to depend on. # For example, we include the proto_path |buildtools_protobuf_src| because we # want to depend on the "google/protobuf/descriptor.proto" proto file. This # will be exposed by the |protobuf_descriptor_proto| dep. if buildtools_protobuf_src in target.proto_paths: sources_label.external_deps = [ 'PERFETTO_CONFIG.deps.protobuf_descriptor_proto' ] if target.name in proto_targets: sources_label.visibility = PUBLIC_VISIBILITY else: sources_label.visibility = ['PERFETTO_CONFIG.proto_library_visibility'] # For 'source_set' plugins, we don't want to generate any plugin-dependent # targets so just return the label of the proto sources only. if target.proto_plugin == 'source_set': return [sources_label] # Generates 2. if target.proto_plugin == 'proto': plugin_label_type = 'perfetto_cc_proto_library' elif target.proto_plugin == 'protozero': plugin_label_type = 'perfetto_cc_protozero_library' elif target.proto_plugin == 'cppgen': plugin_label_type = 'perfetto_cc_protocpp_library' elif target.proto_plugin == 'ipc': plugin_label_type = 'perfetto_cc_ipc_library' elif target.proto_plugin == 'descriptor': plugin_label_type = 'perfetto_proto_descriptor' else: raise Error('Unknown proto plugin: %s' % target.proto_plugin) plugin_label_name = get_bazel_label_name(target.name) plugin_label = BazelLabel(plugin_label_name, plugin_label_type) plugin_label.comment = target.name plugin_label.deps += [':' + sources_label_name] # When using the plugins we need to pass down also the transitive deps. # For instance consider foo.proto including common.proto. The generated # foo.cc will #include "common.gen.h". Hence the generated cc_protocpp_library # rule need to pass down the dependency on the target that generates # common.gen.{cc,h}. if target.proto_deps and target.proto_plugin in ( 'cppgen', 'ipc', 'protozero'): plugin_label.deps += [ ':' + get_bazel_label_name(x) for x in target.proto_deps ] if target.proto_plugin == 'descriptor': plugin_label.outs = [plugin_label_name + '.bin'] return [sources_label, plugin_label] def gen_target(gn_target): if gn_target.type == 'proto_library': return gen_proto_labels(gn_target) elif gn_target.type == 'action': if gn_target.name in custom_actions: return custom_actions[gn_target.name](gn_target) elif re.match('.*gen_cc_.*_descriptor$', gn_target.name): return gen_cc_metrics_descriptor(gn_target) return [] elif gn_target.type == 'group': return [] elif gn_target.type == 'executable': bazel_type = 'perfetto_cc_binary' elif gn_target.type == 'shared_library': bazel_type = 'perfetto_cc_binary' vars['linkshared'] = True elif gn_target.type == 'static_library': bazel_type = 'perfetto_cc_library' elif gn_target.type == 'source_set': bazel_type = 'filegroup' else: raise Error('target type not supported: %s' % gn_target.type) label = BazelLabel(get_bazel_label_name(gn_target.name), bazel_type) label.comment = gn_target.name # Supporting 'public' on source_sets would require not converting them to # filegroups in bazel. if gn_target.public_headers: if bazel_type == 'perfetto_cc_library': label.hdrs += [x[2:] for x in gn_target.public_headers] else: raise Error('%s: \'public\' currently supported only for cc_library' % gn_target.name) raw_srcs = [x[2:] for x in gn_target.sources] if bazel_type == 'perfetto_cc_library': label.srcs += [x for x in raw_srcs if not x.startswith('include')] label.hdrs += [x for x in raw_srcs if x.startswith('include')] # Most Perfetto libraries cannot by dynamically linked as they would # cause ODR violations. label.__dict__['linkstatic'] = True else: label.srcs = raw_srcs if gn_target.name in public_targets: label.visibility = ['//visibility:public'] if gn_target.type in gn_utils.LINKER_UNIT_TYPES: # |source_sets| contains the transitive set of source_set deps. for trans_dep in gn_target.source_set_deps: name = ':' + get_bazel_label_name(trans_dep) if name.startswith( ':include_perfetto_') and gn_target.type != 'executable': label.hdrs += [name] else: label.srcs += [name] for dep in sorted(gn_target.deps): if dep.startswith('//gn:'): assert (dep in external_deps), dep if dep in external_deps: assert (isinstance(external_deps[dep], list)) label.external_deps += external_deps[dep] else: label.deps += [':' + get_bazel_label_name(dep)] label.deps += [':' + get_bazel_label_name(x) for x in gn_target.proto_deps] # All items starting with : need to be sorted to the end of the list. # However, Python makes specifying a comparator function hard so cheat # instead and make everything start with : sort as if it started with | # As | > all other normal ASCII characters, this will lead to all : targets # starting with : to be sorted to the end. label.srcs = sorted(label.srcs, key=lambda x: x.replace(':', '|')) label.deps = sorted(label.deps) label.hdrs = sorted(label.hdrs) return [label] def gen_target_str(gn_target): return ''.join(str(x) for x in gen_target(gn_target)) def generate_build(gn_desc, targets, extras): gn = gn_utils.GnParser(gn_desc) project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) tool_name = os.path.relpath(os.path.abspath(__file__), project_root) res = ''' # Copyright (C) 2019 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # This file is automatically generated by {}. Do not edit. load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG") load( "@perfetto//bazel:rules.bzl", "perfetto_cc_binary", "perfetto_cc_ipc_library", "perfetto_cc_library", "perfetto_cc_proto_descriptor", "perfetto_cc_proto_library", "perfetto_cc_protocpp_library", "perfetto_cc_protozero_library", "perfetto_java_proto_library", "perfetto_java_lite_proto_library", "perfetto_proto_library", "perfetto_proto_descriptor", "perfetto_py_binary", "perfetto_py_library", "perfetto_gensignature_internal_only", ) package(default_visibility = ["//visibility:private"]) licenses(["notice"]) exports_files(["NOTICE"]) '''.format(tool_name).lstrip() # Public targets need to be computed at the beginning (to figure out the # intermediate deps) but printed at the end (because declaration order matters # in Bazel). public_str = '' for target_name in sorted(public_targets): target = gn.get_target(target_name) public_str += gen_target_str(target) res += ''' # ############################################################################## # Internal targets # ############################################################################## '''.lstrip() # Generate the other non-public targets. for target_name in sorted(set(default_targets) - set(public_targets)): target = gn.get_target(target_name) res += gen_target_str(target) # Generate all the intermediate targets. for target in sorted(itervalues(gn.all_targets)): if target.name in default_targets or target.name in gn.proto_libs: continue res += gen_target_str(target) res += ''' # ############################################################################## # Proto libraries # ############################################################################## '''.lstrip() # Force discovery of explicilty listed root proto targets. for target_name in sorted(proto_targets): gn.get_target(target_name) # Generate targets for the transitive set of proto targets. # TODO explain deduping here. labels = {} for target in sorted(itervalues(gn.proto_libs)): for label in gen_target(target): # Ensure that if the existing target has public visibility, we preserve # that in the new label; this ensures that we don't accidentaly reduce # the visibility of targets which are meant to be public. existing_label = labels.get(label.name) if existing_label and existing_label.visibility == PUBLIC_VISIBILITY: label.visibility = PUBLIC_VISIBILITY labels[label.name] = label res += ''.join(str(x) for x in sorted(itervalues(labels))) res += ''' # ############################################################################## # Public targets # ############################################################################## '''.lstrip() res += public_str res += '# Content from BUILD.extras\n\n' res += extras # Check for ODR violations for target_name in default_targets + proto_targets: checker = gn_utils.ODRChecker(gn, target_name) return res def main(): parser = argparse.ArgumentParser( description='Generate BUILD from a GN description.') parser.add_argument( '--check-only', help='Don\'t keep the generated files', action='store_true') parser.add_argument( '--desc', help='GN description ' + '(e.g., gn desc out --format=json --all-toolchains "//*"') parser.add_argument( '--repo-root', help='Standalone Perfetto repository to generate a GN description', default=gn_utils.repo_root(), ) parser.add_argument( '--extras', help='Extra targets to include at the end of the BUILD file', default=os.path.join(gn_utils.repo_root(), 'BUILD.extras'), ) parser.add_argument( '--output', help='BUILD file to create', default=os.path.join(gn_utils.repo_root(), 'BUILD'), ) parser.add_argument( '--output-proto', help='Proto BUILD file to create', default=os.path.join(gn_utils.repo_root(), 'protos', 'BUILD'), ) parser.add_argument( 'targets', nargs=argparse.REMAINDER, help='Targets to include in the BUILD file (e.g., "//:perfetto_tests")') args = parser.parse_args() if args.desc: with open(args.desc) as f: desc = json.load(f) else: desc = gn_utils.create_build_description(gn_args, args.repo_root) out_files = [] # Generate the main BUILD file. with open(args.extras, 'r') as extra_f: extras = extra_f.read() contents = generate_build(desc, args.targets or default_targets, extras) out_files.append(args.output + '.swp') with open(out_files[-1], 'w') as out_f: out_f.write(contents) # Generate the build flags file. out_files.append(os.path.join(buildflags_dir, 'perfetto_build_flags.h.swp')) gn_utils.gen_buildflags(gn_args, out_files[-1]) return gn_utils.check_or_commit_generated_files(out_files, args.check_only) if __name__ == '__main__': sys.exit(main())