| #!/usr/bin/env python |
| # 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 functools |
| import json |
| import os |
| import re |
| import sys |
| import textwrap |
| |
| import gn_utils |
| |
| # Copyright header for generated code. |
| header = """# 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. |
| """.format(__file__) |
| |
| # Arguments for the GN output directory. |
| # host_os="linux" is to generate the right build files from Mac OS. |
| gn_args = 'target_os="linux" is_debug=false host_os="linux"' |
| |
| # Default targets to translate to the blueprint file. |
| default_targets = [ |
| '//:trace_processor_shell', |
| '//src/protozero:libprotozero', |
| '//src/trace_processor:trace_processor', |
| '//tools/trace_to_text:trace_to_text_host(//gn/standalone/toolchain:gcc_like_host)', |
| '//tools/trace_to_text:libpprofbuilder', |
| '//protos/perfetto/config:merged_config_gen', |
| '//protos/perfetto/trace:merged_trace_gen', |
| '//protos/perfetto/trace_processor:lite_gen', |
| '//protos/perfetto/metrics:lite_gen' |
| ] |
| |
| # Aliases to add to the BUILD file |
| alias_targets = { |
| '//src/protozero:libprotozero': 'libprotozero', |
| '//src/trace_processor:trace_processor': 'trace_processor', |
| '//tools/trace_to_text:trace_to_text_host': 'trace_to_text', |
| } |
| |
| |
| def enable_sqlite(module): |
| module.deps.add(Label('//third_party/sqlite')) |
| module.deps.add(Label('//third_party/sqlite:sqlite_ext_percentile')) |
| |
| |
| def enable_jsoncpp(module): |
| module.deps.add(Label('//third_party/perfetto/google:jsoncpp')) |
| |
| |
| def enable_linenoise(module): |
| module.deps.add(Label('//third_party/perfetto/google:linenoise')) |
| |
| |
| def enable_gtest_prod(module): |
| module.deps.add(Label('//third_party/perfetto/google:gtest_prod')) |
| |
| |
| def enable_protobuf_full(module): |
| module.deps.add(Label('//third_party/protobuf:libprotoc_legacy')) |
| module.deps.add(Label('//third_party/protobuf:protobuf_legacy')) |
| |
| |
| def enable_perfetto_version(module): |
| module.deps.add(Label('//third_party/perfetto/google:perfetto_version')) |
| |
| |
| def enable_zlib(module): |
| module.deps.add(Label('//third_party/zlib:zlibsystem')) |
| |
| |
| def disable_module(module): |
| pass |
| |
| |
| # Internal equivalents for third-party libraries that the upstream project |
| # depends on. |
| builtin_deps = { |
| '//buildtools:linenoise': enable_linenoise, |
| '//buildtools:protobuf_full': enable_protobuf_full, |
| '//buildtools:protobuf_lite': disable_module, |
| '//buildtools:protoc': disable_module, |
| '//buildtools:sqlite': enable_sqlite, |
| '//buildtools:zlib': enable_zlib, |
| '//gn:default_deps': disable_module, |
| '//gn:jsoncpp': enable_jsoncpp, |
| '//gn:protoc_lib': enable_protobuf_full, |
| '//gn/standalone:gen_git_revision': enable_perfetto_version, |
| } |
| |
| # ---------------------------------------------------------------------------- |
| # End of configuration. |
| # ---------------------------------------------------------------------------- |
| |
| |
| class Error(Exception): |
| pass |
| |
| |
| def is_public_header(label): |
| """ |
| Returns if this is a c++ header file that is part of the API. |
| Args: |
| label: Label to evaluate |
| """ |
| return label.endswith('.h') and label.startswith('//include/perfetto/') |
| |
| |
| @functools.total_ordering |
| class Label(object): |
| """Represents a label in BUILD file terminology. This class wraps a string |
| label to allow for correct comparision of labels for sorting. |
| |
| Args: |
| label: The string rerepsentation of the label. |
| """ |
| |
| def __init__(self, label): |
| self.label = label |
| |
| def is_absolute(self): |
| return self.label.startswith('//') |
| |
| def dirname(self): |
| return self.label.split(':')[0] if ':' in self.label else self.label |
| |
| def basename(self): |
| return self.label.split(':')[1] if ':' in self.label else '' |
| |
| def __eq__(self, other): |
| return self.label == other.label |
| |
| def __lt__(self, other): |
| return ( |
| self.is_absolute(), |
| self.dirname(), |
| self.basename() |
| ) < ( |
| other.is_absolute(), |
| other.dirname(), |
| other.basename() |
| ) |
| |
| def __str__(self): |
| return self.label |
| |
| def __hash__(self): |
| return hash(self.label) |
| |
| |
| class Writer(object): |
| def __init__(self, output, width=79): |
| self.output = output |
| self.width = width |
| |
| def comment(self, text): |
| for line in textwrap.wrap(text, |
| self.width - 2, |
| break_long_words=False, |
| break_on_hyphens=False): |
| self.output.write('# {}\n'.format(line)) |
| |
| def newline(self): |
| self.output.write('\n') |
| |
| def line(self, s, indent=0): |
| self.output.write(' ' * indent + s + '\n') |
| |
| def variable(self, key, value, sort=True): |
| if value is None: |
| return |
| if isinstance(value, set) or isinstance(value, list): |
| if len(value) == 0: |
| return |
| self.line('{} = ['.format(key), indent=1) |
| for v in sorted(list(value)) if sort else value: |
| self.line('"{}",'.format(v), indent=2) |
| self.line('],', indent=1) |
| elif isinstance(value, basestring): |
| self.line('{} = "{}",'.format(key, value), indent=1) |
| else: |
| self.line('{} = {},'.format(key, value), indent=1) |
| |
| def header(self): |
| self.output.write(header) |
| |
| |
| class Target(object): |
| """In-memory representation of a BUILD target.""" |
| |
| def __init__(self, type, name, gn_name=None): |
| assert type in ('cc_binary', 'cc_library', 'cc_proto_library', |
| 'proto_library', 'filegroup', 'alias', |
| 'pbzero_cc_proto_library', 'genrule', |
| 'transitive_descriptor_set', 'java_proto_library' ) |
| self.type = type |
| self.name = name |
| self.srcs = set() |
| self.hdrs = set() |
| self.deps = set() |
| self.visibility = set() |
| self.gn_name = gn_name |
| self.cc_proto_fields = False |
| self.src_proto_library = None |
| self.outs = set() |
| self.cmd = None |
| self.tools = set() |
| |
| def write(self, writer): |
| if self.gn_name: |
| writer.comment('GN target: {}'.format(self.gn_name)) |
| |
| writer.line('{}('.format(self.type)) |
| writer.variable('name', self.name) |
| writer.variable('srcs', self.srcs) |
| writer.variable('hdrs', self.hdrs) |
| |
| if self.cc_proto_fields: |
| assert(self.type == 'proto_library') |
| if self.srcs: |
| writer.variable('has_services', 1) |
| writer.variable('cc_api_version', 2) |
| if self.srcs: |
| writer.variable('cc_generic_services', 1) |
| |
| writer.variable('src_proto_library', self.src_proto_library) |
| |
| writer.variable('outs', self.outs) |
| writer.variable('cmd', self.cmd) |
| writer.variable('tools', self.tools) |
| |
| # Keep visibility and deps last. |
| writer.variable('visibility', self.visibility) |
| |
| if type != 'filegroup': |
| writer.variable('deps', self.deps) |
| |
| writer.line(')') |
| |
| |
| class Build(object): |
| """In-memory representation of a BUILD file.""" |
| |
| def __init__(self, public, header_lines=[]): |
| self.targets = {} |
| self.public = public |
| self.header_lines = header_lines |
| |
| def add_target(self, target): |
| self.targets[target.name] = target |
| |
| def write(self, writer): |
| writer.header() |
| writer.newline() |
| for line in self.header_lines: |
| writer.line(line) |
| if self.header_lines: |
| writer.newline() |
| if self.public: |
| writer.line( |
| 'package(default_visibility = ["//visibility:public"])') |
| else: |
| writer.line( |
| 'package(default_visibility = ["//third_party/perfetto:__subpackages__"])') |
| writer.newline() |
| writer.line('licenses(["notice"]) # Apache 2.0') |
| writer.newline() |
| writer.line('exports_files(["LICENSE"])') |
| writer.newline() |
| |
| sorted_targets = sorted( |
| self.targets.itervalues(), key=lambda m: m.name) |
| for target in sorted_targets[:-1]: |
| target.write(writer) |
| writer.newline() |
| |
| # BUILD files shouldn't have a trailing new line. |
| sorted_targets[-1].write(writer) |
| |
| |
| class BuildGenerator(object): |
| def __init__(self, desc): |
| self.desc = desc |
| self.action_generated_files = set() |
| |
| for target in self.desc.itervalues(): |
| if target['type'] == 'action': |
| self.action_generated_files.update(target['outputs']) |
| |
| |
| def create_build_for_targets(self, targets): |
| """Generate a BUILD for a list of GN targets and aliases.""" |
| self.build = Build(public=True) |
| |
| proto_cc_import = 'load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library")' |
| descriptor_set_import = 'load("//tools/build_defs/proto:descriptor_set.bzl", "transitive_descriptor_set")' |
| pbzero_cc_import = 'load("//third_party/perfetto/google:build_defs.bzl", "pbzero_cc_proto_library")' |
| self.proto_build = Build(public=False, |
| header_lines=[ |
| proto_cc_import, |
| descriptor_set_import, |
| pbzero_cc_import, |
| ]) |
| |
| for target in targets: |
| self.create_target(target) |
| |
| return (self.build, self.proto_build) |
| |
| |
| def resolve_dependencies(self, target_name): |
| """Return the set of direct dependent-on targets for a GN target. |
| |
| Args: |
| desc: JSON GN description. |
| target_name: Name of target |
| |
| Returns: |
| A set of transitive dependencies in the form of GN targets. |
| """ |
| |
| if gn_utils.label_without_toolchain(target_name) in builtin_deps: |
| return set() |
| target = self.desc[target_name] |
| resolved_deps = set() |
| for dep in target.get('deps', []): |
| resolved_deps.add(dep) |
| return resolved_deps |
| |
| |
| def apply_module_sources_to_target(self, target, module_desc): |
| """ |
| Args: |
| target: Module to which dependencies should be added. |
| module_desc: JSON GN description of the module. |
| visibility: Whether the module is visible with respect to the target. |
| """ |
| for src in module_desc.get('sources', []): |
| label = Label(gn_utils.label_to_path(src)) |
| if target.type == 'cc_library' and is_public_header(src): |
| target.hdrs.add(label) |
| else: |
| target.srcs.add(label) |
| |
| |
| def apply_module_dependency(self, target, dep_name): |
| """ |
| Args: |
| build: BUILD instance which is being generated. |
| proto_build: BUILD instance which is being generated to hold protos. |
| desc: JSON GN description. |
| target: Module to which dependencies should be added. |
| dep_name: GN target of the dependency. |
| """ |
| # If the dependency refers to a library which we can replace with an internal |
| # equivalent, stop recursing and patch the dependency in. |
| dep_name_no_toolchain = gn_utils.label_without_toolchain(dep_name) |
| if dep_name_no_toolchain in builtin_deps: |
| builtin_deps[dep_name_no_toolchain](target) |
| return |
| |
| dep_desc = self.desc[dep_name] |
| if dep_desc['type'] == 'source_set': |
| for inner_name in self.resolve_dependencies(dep_name): |
| self.apply_module_dependency(target, inner_name) |
| |
| # Any source set which has a source generated by an action doesn't need |
| # to be depended on as we will depend on the action directly. |
| sources = dep_desc.get('sources', []) |
| if any(src in self.action_generated_files for src in sources): |
| return |
| |
| self.apply_module_sources_to_target(target, dep_desc) |
| elif dep_desc['type'] == 'action': |
| args = dep_desc['args'] |
| if "gen_merged_sql_metrics" in dep_name: |
| dep_target = self.create_merged_sql_metrics_target(dep_name) |
| target.deps.add(Label("//third_party/perfetto:" + dep_target.name)) |
| |
| if target.type == 'cc_library' or target.type == 'cc_binary': |
| target.srcs.update(dep_target.outs) |
| elif '/protoc' in args[0]: |
| result = self.create_proto_target(dep_name); |
| if result is None: |
| return |
| (proto_target, cc_target) = result |
| if target.type == 'proto_library': |
| dep_target_name = proto_target.name |
| else: |
| dep_target_name = cc_target.name |
| target.deps.add( |
| Label("//third_party/perfetto/protos:" + dep_target_name)) |
| else: |
| raise Error('Unsupported action in target %s: %s' % (dep_target_name, |
| args)) |
| elif dep_desc['type'] == 'static_library': |
| dep_target = self.create_target(dep_name) |
| target.deps.add(Label("//third_party/perfetto:" + dep_target.name)) |
| elif dep_desc['type'] == 'group': |
| for inner_name in self.resolve_dependencies(dep_name): |
| self.apply_module_dependency(target, inner_name) |
| elif dep_desc['type'] == 'executable': |
| # Just create the dep target but don't add it as a dep because it's an |
| # executable. |
| self.create_target(dep_name) |
| else: |
| raise Error('Unknown target name %s with type: %s' % |
| (dep_name, dep_desc['type'])) |
| |
| def create_merged_sql_metrics_target(self, gn_target_name): |
| target_desc = self.desc[gn_target_name] |
| gn_target_name_no_toolchain = gn_utils.label_without_toolchain( |
| gn_target_name) |
| target = Target( |
| 'genrule', |
| 'gen_merged_sql_metrics', |
| gn_name=gn_target_name_no_toolchain, |
| ) |
| target.outs.update( |
| Label(src[src.index('gen/') + len('gen/'):]) |
| for src in target_desc.get('outputs', []) |
| ) |
| target.cmd = '$(location gen_merged_sql_metrics_py) --cpp_out=$@ $(SRCS)' |
| target.tools.update([ |
| 'gen_merged_sql_metrics_py', |
| ]) |
| target.srcs.update( |
| Label(gn_utils.label_to_path(src)) |
| for src in target_desc.get('inputs', []) |
| if src not in self.action_generated_files |
| ) |
| self.build.add_target(target) |
| return target |
| |
| def create_proto_target(self, gn_target_name): |
| target_desc = self.desc[gn_target_name] |
| args = target_desc['args'] |
| |
| gn_target_name_no_toolchain = gn_utils.label_without_toolchain( |
| gn_target_name) |
| stripped_path = gn_target_name_no_toolchain.replace("protos/perfetto/", "") |
| |
| is_descriptor = any('descriptor_set_out' in arg for arg in args) |
| if is_descriptor: |
| return None |
| |
| is_pbzero = any("pbzero" in arg for arg in args) |
| is_proto_lite = not is_pbzero and not is_descriptor |
| |
| pretty_target_name = gn_utils.label_to_target_name_with_path(stripped_path) |
| pretty_target_name = pretty_target_name.replace("_lite_gen", "") |
| pretty_target_name = pretty_target_name.replace("_zero_gen", "_zero") |
| proto_target_name = pretty_target_name |
| |
| proto_target = Target( |
| 'proto_library', |
| proto_target_name, |
| gn_name=gn_target_name_no_toolchain |
| ) |
| proto_target.cc_proto_fields = is_proto_lite |
| proto_target.srcs.update([ |
| Label(gn_utils.label_to_path(src).replace('protos/', '')) |
| for src in target_desc.get('sources', []) |
| ]) |
| if is_proto_lite: |
| proto_target.visibility.add("//visibility:public") |
| self.proto_build.add_target(proto_target) |
| |
| for dep_name in self.resolve_dependencies(gn_target_name): |
| self.apply_module_dependency(proto_target, dep_name) |
| |
| if is_pbzero: |
| # Remove all the protozero srcs from the proto_library. |
| proto_target.srcs.difference_update( |
| [src for src in proto_target.srcs if not src.label.endswith('.proto')]) |
| |
| # Remove all the non-proto deps from the proto_library and add to the cc |
| # library. |
| cc_deps = [ |
| dep for dep in proto_target.deps |
| if not dep.label.startswith('//third_party/perfetto/protos') |
| ] |
| proto_target.deps.difference_update(cc_deps) |
| |
| cc_target_name = proto_target.name + "_cc_proto" |
| cc_target = Target('pbzero_cc_proto_library', cc_target_name, |
| gn_name=gn_target_name_no_toolchain) |
| |
| cc_target.deps.add(Label('//third_party/perfetto:libprotozero')) |
| cc_target.deps.update(cc_deps) |
| |
| # Add the proto_library to the cc_target. |
| cc_target.src_proto_library = \ |
| "//third_party/perfetto/protos:" + proto_target.name |
| |
| self.proto_build.add_target(cc_target) |
| else: |
| cc_target_name = proto_target.name + "_cc_proto" |
| cc_target = Target('cc_proto_library', |
| cc_target_name, gn_name=gn_target_name_no_toolchain) |
| cc_target.visibility.add("//visibility:public") |
| cc_target.deps.add( |
| Label("//third_party/perfetto/protos:" + proto_target.name)) |
| self.proto_build.add_target(cc_target) |
| |
| java_target_name = proto_target.name + "_java_proto" |
| java_target = Target('java_proto_library', |
| java_target_name, |
| gn_name=gn_target_name_no_toolchain) |
| java_target.visibility.add("//visibility:public") |
| java_target.deps.add( |
| Label("//third_party/perfetto/protos:" + proto_target.name)) |
| self.proto_build.add_target(java_target) |
| |
| return (proto_target, cc_target) |
| |
| |
| def create_target(self, gn_target_name): |
| """Generate module(s) for a given GN target. |
| |
| Given a GN target name, generate one or more corresponding modules into a |
| build file. |
| |
| Args: |
| build: Build instance which is being generated. |
| desc: JSON GN description. |
| gn_target_name: GN target name for module generation. |
| """ |
| |
| target_desc = self.desc[gn_target_name] |
| if target_desc['type'] == 'action': |
| args = target_desc['args'] |
| if '/protoc' in args[0]: |
| return self.create_proto_target(gn_target_name) |
| else: |
| raise Error('Unsupported action in target %s: %s' % (gn_target_name, |
| args)) |
| elif target_desc['type'] == 'executable': |
| target_type = 'cc_binary' |
| elif target_desc['type'] == 'static_library': |
| target_type = 'cc_library' |
| elif target_desc['type'] == 'source_set': |
| target_type = 'filegroup' |
| else: |
| raise Error('Unknown target type: %s' % target_desc['type']) |
| |
| label_no_toolchain = gn_utils.label_without_toolchain(gn_target_name) |
| target_name_path = gn_utils.label_to_target_name_with_path(label_no_toolchain) |
| target_name = alias_targets.get(label_no_toolchain, target_name_path) |
| target = Target(target_type, target_name, gn_name=label_no_toolchain) |
| target.srcs.update( |
| Label(gn_utils.label_to_path(src)) |
| for src in target_desc.get('sources', []) |
| if src not in self.action_generated_files |
| ) |
| |
| for dep_name in self.resolve_dependencies(gn_target_name): |
| self.apply_module_dependency(target, dep_name) |
| |
| self.build.add_target(target) |
| return target |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description='Generate BUILD from a GN description.') |
| 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) |
| |
| build_generator = BuildGenerator(desc) |
| build, proto_build = build_generator.create_build_for_targets( |
| args.targets or default_targets) |
| with open(args.output, 'w') as f: |
| writer = Writer(f) |
| build.write(writer) |
| writer.newline() |
| |
| with open(args.extras, 'r') as r: |
| for line in r: |
| writer.line(line.rstrip("\n\r")) |
| |
| with open(args.output_proto, 'w') as f: |
| proto_build.write(Writer(f)) |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |