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