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