| #!/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()) |