1#!/usr/bin/env python 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 32 33import gn_utils 34 35from compat import itervalues, iteritems, basestring 36 37# Arguments for the GN output directory. 38# host_os="linux" is to generate the right build files from Mac OS. 39gn_args = ' '.join([ 40 'host_os="linux"', 41 'is_debug=false', 42 'is_perfetto_build_generator=true', 43 'enable_perfetto_watchdog=true', 44 'monolithic_binaries=true', 45 'target_os="linux"', 46]) 47 48# Default targets to translate to the blueprint file. 49 50# These targets will be exported with public visibility in the generated BUILD. 51public_targets = [ 52 '//:libperfetto_client_experimental', 53 '//src/perfetto_cmd:perfetto', 54 '//src/traced/probes:traced_probes', 55 '//src/traced/service:traced', 56 '//src/trace_processor:trace_processor_shell', 57 '//src/trace_processor:trace_processor', 58 '//tools/trace_to_text:trace_to_text', 59 '//tools/trace_to_text:libpprofbuilder', 60] 61 62# These targets are required by internal build rules but don't need to be 63# exported publicly. 64default_targets = [ 65 '//test:client_api_example', 66 '//src/ipc:perfetto_ipc', 67 '//src/ipc/protoc_plugin:ipc_plugin', 68 '//src/protozero:libprotozero', 69 '//src/protozero/protoc_plugin:protozero_plugin', 70 '//src/protozero/protoc_plugin:cppgen_plugin', 71] + public_targets 72 73# Root proto targets (to force discovery of intermediate proto targets). 74# These targets are marked public. 75proto_targets = [ 76 '//protos/perfetto/trace:merged_trace', 77 '//protos/perfetto/trace:non_minimal_lite', 78 '//protos/perfetto/config:merged_config', 79 '//protos/perfetto/metrics:lite', 80 '//protos/perfetto/metrics/android:lite', 81 '//protos/perfetto/trace:lite', 82 '//protos/perfetto/config:lite', 83] 84 85# The directory where the generated perfetto_build_flags.h will be copied into. 86buildflags_dir = 'include/perfetto/base/build_configs/bazel' 87 88# Internal equivalents for third-party libraries that the upstream project 89# depends on. 90external_deps = { 91 '//gn:default_deps': [], 92 '//gn:jsoncpp': ['PERFETTO_CONFIG.deps.jsoncpp'], 93 '//gn:linenoise': ['PERFETTO_CONFIG.deps.linenoise'], 94 '//gn:protobuf_full': ['PERFETTO_CONFIG.deps.protobuf_full'], 95 '//gn:protobuf_lite': ['PERFETTO_CONFIG.deps.protobuf_lite'], 96 '//gn:protoc_lib': ['PERFETTO_CONFIG.deps.protoc_lib'], 97 '//gn:protoc': ['PERFETTO_CONFIG.deps.protoc'], 98 '//gn:sqlite': [ 99 'PERFETTO_CONFIG.deps.sqlite', 100 'PERFETTO_CONFIG.deps.sqlite_ext_percentile' 101 ], 102 '//gn:zlib': ['PERFETTO_CONFIG.deps.zlib'], 103 '//gn/standalone:gen_git_revision': [], 104 '//src/trace_processor/metrics:gen_merged_sql_metrics': [[ 105 ":cc_merged_sql_metrics" 106 ]] 107} 108 109 110def gen_sql_metrics(target): 111 label = BazelLabel(get_bazel_label_name(target.name), 'genrule') 112 label.srcs += [re.sub('^//', '', x) for x in sorted(target.inputs)] 113 label.outs += target.outputs 114 label.cmd = r'$(location gen_merged_sql_metrics_py) --cpp_out=$@ $(SRCS)' 115 label.tools += [':gen_merged_sql_metrics_py'] 116 return [label] 117 118 119custom_actions = { 120 '//src/trace_processor/metrics:gen_merged_sql_metrics': gen_sql_metrics, 121} 122 123# ------------------------------------------------------------------------------ 124# End of configuration. 125# ------------------------------------------------------------------------------ 126 127 128class Error(Exception): 129 pass 130 131 132class BazelLabel(object): 133 134 def __init__(self, name, type): 135 self.comment = None 136 self.name = name 137 self.type = type 138 self.visibility = [] 139 self.srcs = [] 140 self.hdrs = [] 141 self.deps = [] 142 self.external_deps = [] 143 self.tools = [] 144 self.outs = [] 145 146 def __lt__(self, other): 147 if isinstance(other, self.__class__): 148 return self.name < other.name 149 raise TypeError('\'<\' not supported between instances of \'%s\' and \'%s\'' 150 % (type(self).__name__, type(other).__name__)) 151 152 def __str__(self): 153 """Converts the object into a Bazel Starlark label.""" 154 res = '' 155 res += ('# GN target: %s\n' % self.comment) if self.comment else '' 156 res += '%s(\n' % self.type 157 any_deps = len(self.deps) + len(self.external_deps) > 0 158 ORD = ['name', 'srcs', 'hdrs', 'visibility', 'deps', 'outs', 'cmd', 'tools'] 159 hasher = lambda x: sum((99,) + tuple(ord(c) for c in x)) 160 key_sorter = lambda kv: ORD.index(kv[0]) if kv[0] in ORD else hasher(kv[0]) 161 for k, v in sorted(iteritems(self.__dict__), key=key_sorter): 162 if k in ('type', 'comment', 163 'external_deps') or v is None or (v == [] and 164 (k != 'deps' or not any_deps)): 165 continue 166 res += ' %s = ' % k 167 if isinstance(v, basestring): 168 if v.startswith('PERFETTO_CONFIG.'): 169 res += '%s,\n' % v 170 else: 171 res += '"%s",\n' % v 172 elif isinstance(v, bool): 173 res += '%s,\n' % v 174 elif isinstance(v, list): 175 res += '[\n' 176 if k == 'deps' and len(self.external_deps) > 1: 177 indent = ' ' 178 else: 179 indent = ' ' 180 for entry in v: 181 if entry.startswith('PERFETTO_CONFIG.'): 182 res += '%s %s,\n' % (indent, entry) 183 else: 184 res += '%s "%s",\n' % (indent, entry) 185 res += '%s]' % indent 186 if k == 'deps' and self.external_deps: 187 res += ' + %s' % self.external_deps[0] 188 for edep in self.external_deps[1:]: 189 if isinstance(edep, list): 190 res += ' + [\n' 191 for inner_dep in edep: 192 res += ' "%s",\n' % inner_dep 193 res += ' ]' 194 else: 195 res += ' +\n%s%s' % (indent, edep) 196 res += ',\n' 197 else: 198 raise Error('Unsupported value %s', type(v)) 199 res += ')\n\n' 200 return res 201 202 203# Public visibility for targets in Bazel. 204PUBLIC_VISIBILITY = 'PERFETTO_CONFIG.public_visibility' 205 206 207def get_bazel_label_name(gn_name): 208 """Converts a GN target name into a Bazel label name. 209 210 If target is in the public taraget list, returns only the GN target name, 211 e.g.: //src/ipc:perfetto_ipc -> perfetto_ipc 212 213 Otherwise, in the case of an intermediate taraget, returns a mangled path. 214 e.g.: //include/perfetto/base:base -> include_perfetto_base_base. 215 """ 216 if gn_name in default_targets: 217 return gn_utils.label_without_toolchain(gn_name).split(':')[1] 218 return gn_utils.label_to_target_name_with_path(gn_name) 219 220 221def gen_proto_labels(target): 222 """ Generates the xx_proto_library label for proto targets. 223 224 Bazel requires that each protobuf-related target is modeled with two labels: 225 1. A plugin-dependent target (e.g. cc_library, cc_protozero_library) that has 226 only a dependency on 2 and does NOT refer to any .proto sources. 227 2. A plugin-agnostic target that defines only the .proto sources and their 228 dependencies. 229 """ 230 assert (target.type == 'proto_library') 231 232 def get_sources_label(target_name): 233 return re.sub('_(lite|zero|cpp|ipc)$', '', 234 get_bazel_label_name(target_name)) + '_protos' 235 236 sources_label_name = get_sources_label(target.name) 237 238 # Generates 1. 239 if target.proto_plugin == 'proto': 240 plugin_label_type = 'perfetto_cc_proto_library' 241 elif target.proto_plugin == 'protozero': 242 plugin_label_type = 'perfetto_cc_protozero_library' 243 elif target.proto_plugin == 'cppgen': 244 plugin_label_type = 'perfetto_cc_protocpp_library' 245 elif target.proto_plugin == 'ipc': 246 plugin_label_type = 'perfetto_cc_ipc_library' 247 else: 248 raise Error('Unknown proto plugin: %s' % target.proto_plugin) 249 plugin_label_name = get_bazel_label_name(target.name) 250 plugin_label = BazelLabel(plugin_label_name, plugin_label_type) 251 plugin_label.comment = target.name 252 plugin_label.deps += [':' + sources_label_name] 253 254 # When using the cppgen plugin we need to pass down also the transitive deps. 255 # For instance consider foo.proto including common.proto. The generated 256 # foo.cc will #include "common.gen.h". Hence the generated cc_protocpp_library 257 # rule need to pass down the dependency on the target that generates 258 # common.gen.{cc,h}. This is not needed for protozero because protozero 259 # headers are fully hermetic deps-wise and use only on forward declarations. 260 if target.proto_deps and target.proto_plugin in ('cppgen', 'ipc'): 261 plugin_label.deps += [ 262 ':' + get_bazel_label_name(x) for x in target.proto_deps 263 ] 264 265 # Generates 2. 266 sources_label = BazelLabel(sources_label_name, 'perfetto_proto_library') 267 sources_label.comment = target.name 268 assert (all(x.startswith('//') for x in target.sources)) 269 assert (all(x.endswith('.proto') for x in target.sources)) 270 sources_label.srcs = sorted([x[2:] for x in target.sources]) # Strip //. 271 272 deps = [ 273 ':' + get_sources_label(x) 274 for x in target.proto_deps 275 276 # This is to avoid a dependency-on-self in the case where 277 # protos/perfetto/ipc:ipc depends on protos/perfetto/ipc:cpp and both 278 # targets resolve to "protos_perfetto_ipc_protos". 279 if get_sources_label(x) != sources_label_name 280 ] 281 sources_label.deps = sorted(deps) 282 283 if target.name in proto_targets: 284 sources_label.visibility = PUBLIC_VISIBILITY 285 else: 286 sources_label.visibility = ['PERFETTO_CONFIG.proto_library_visibility'] 287 288 return [plugin_label, sources_label] 289 290 291def gen_target(gn_target): 292 if gn_target.type == 'proto_library': 293 return gen_proto_labels(gn_target) 294 elif gn_target.type == 'action': 295 if gn_target.name in custom_actions: 296 return custom_actions[gn_target.name](gn_target) 297 return [] 298 elif gn_target.type == 'group': 299 return [] 300 elif gn_target.type == 'executable': 301 bazel_type = 'perfetto_cc_binary' 302 elif gn_target.type == 'shared_library': 303 bazel_type = 'perfetto_cc_binary' 304 vars['linkshared'] = True 305 elif gn_target.type == 'static_library': 306 bazel_type = 'perfetto_cc_library' 307 elif gn_target.type == 'source_set': 308 bazel_type = 'filegroup' 309 else: 310 raise Error('target type not supported: %s' % gn_target.type) 311 312 label = BazelLabel(get_bazel_label_name(gn_target.name), bazel_type) 313 label.comment = gn_target.name 314 315 raw_srcs = [x[2:] for x in gn_target.sources] 316 if bazel_type == 'perfetto_cc_library': 317 label.srcs = [x for x in raw_srcs if not x.startswith('include')] 318 label.hdrs = [x for x in raw_srcs if x.startswith('include')] 319 320 # Most Perfetto libraries cannot by dynamically linked as they would 321 # cause ODR violations. 322 label.__dict__['linkstatic'] = True 323 else: 324 label.srcs = raw_srcs 325 326 if gn_target.name in public_targets: 327 label.visibility = ['//visibility:public'] 328 329 if gn_target.type in gn_utils.LINKER_UNIT_TYPES: 330 # |source_sets| contains the transitive set of source_set deps. 331 for trans_dep in gn_target.source_set_deps: 332 name = ':' + get_bazel_label_name(trans_dep) 333 if name.startswith( 334 ':include_perfetto_') and gn_target.type != 'executable': 335 label.hdrs += [name] 336 else: 337 label.srcs += [name] 338 for dep in sorted(gn_target.deps): 339 if dep.startswith('//gn:'): 340 assert (dep in external_deps), dep 341 if dep in external_deps: 342 assert (isinstance(external_deps[dep], list)) 343 label.external_deps += external_deps[dep] 344 else: 345 label.deps += [':' + get_bazel_label_name(dep)] 346 label.deps += [':' + get_bazel_label_name(x) for x in gn_target.proto_deps] 347 348 # All items starting with : need to be sorted to the end of the list. 349 # However, Python makes specifying a comparator function hard so cheat 350 # instead and make everything start with : sort as if it started with | 351 # As | > all other normal ASCII characters, this will lead to all : targets 352 # starting with : to be sorted to the end. 353 label.srcs = sorted(label.srcs, key=lambda x: x.replace(':', '|')) 354 355 label.deps = sorted(label.deps) 356 label.hdrs = sorted(label.hdrs) 357 return [label] 358 359 360def gen_target_str(gn_target): 361 return ''.join(str(x) for x in gen_target(gn_target)) 362 363 364def generate_build(gn_desc, targets, extras): 365 gn = gn_utils.GnParser(gn_desc) 366 res = ''' 367# Copyright (C) 2019 The Android Open Source Project 368# 369# Licensed under the Apache License, Version 2.0 (the "License"); 370# you may not use this file except in compliance with the License. 371# You may obtain a copy of the License at 372# 373# http://www.apache.org/licenses/LICENSE-2.0 374# 375# Unless required by applicable law or agreed to in writing, software 376# distributed under the License is distributed on an "AS IS" BASIS, 377# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 378# See the License for the specific language governing permissions and 379# limitations under the License. 380# 381# This file is automatically generated by {}. Do not edit. 382 383load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG") 384load( 385 "@perfetto//bazel:rules.bzl", 386 "perfetto_cc_binary", 387 "perfetto_cc_ipc_library", 388 "perfetto_cc_library", 389 "perfetto_cc_proto_library", 390 "perfetto_cc_protocpp_library", 391 "perfetto_cc_protozero_library", 392 "perfetto_java_proto_library", 393 "perfetto_java_lite_proto_library", 394 "perfetto_proto_library", 395 "perfetto_py_binary", 396 "perfetto_gensignature_internal_only", 397) 398 399package(default_visibility = ["//visibility:private"]) 400 401licenses(["notice"]) 402 403exports_files(["NOTICE"]) 404 405'''.format(__file__).lstrip() 406 407 # Public targets need to be computed at the beginning (to figure out the 408 # intermediate deps) but printed at the end (because declaration order matters 409 # in Bazel). 410 public_str = '' 411 for target_name in sorted(public_targets): 412 target = gn.get_target(target_name) 413 public_str += gen_target_str(target) 414 415 res += ''' 416# ############################################################################## 417# Internal targets 418# ############################################################################## 419 420'''.lstrip() 421 # Generate the other non-public targets. 422 for target_name in sorted(set(default_targets) - set(public_targets)): 423 target = gn.get_target(target_name) 424 res += gen_target_str(target) 425 426 # Generate all the intermediate targets. 427 for target in sorted(itervalues(gn.all_targets)): 428 if target.name in default_targets or target.name in gn.proto_libs: 429 continue 430 res += gen_target_str(target) 431 432 res += ''' 433# ############################################################################## 434# Proto libraries 435# ############################################################################## 436 437'''.lstrip() 438 # Force discovery of explicilty listed root proto targets. 439 for target_name in sorted(proto_targets): 440 gn.get_target(target_name) 441 442 # Generate targets for the transitive set of proto targets. 443 # TODO explain deduping here. 444 labels = {} 445 for target in sorted(itervalues(gn.proto_libs)): 446 for label in gen_target(target): 447 # Ensure that if the existing target has public visibility, we preserve 448 # that in the new label; this ensures that we don't accidentaly reduce 449 # the visibility of targets which are meant to be public. 450 existing_label = labels.get(label.name) 451 if existing_label and existing_label.visibility == PUBLIC_VISIBILITY: 452 label.visibility = PUBLIC_VISIBILITY 453 labels[label.name] = label 454 455 res += ''.join(str(x) for x in sorted(itervalues(labels))) 456 457 res += ''' 458# ############################################################################## 459# Public targets 460# ############################################################################## 461 462'''.lstrip() 463 res += public_str 464 res += '# Content from BUILD.extras\n\n' 465 res += extras 466 return res 467 468 469def main(): 470 parser = argparse.ArgumentParser( 471 description='Generate BUILD from a GN description.') 472 parser.add_argument( 473 '--check-only', 474 help='Don\'t keep the generated files', 475 action='store_true') 476 parser.add_argument( 477 '--desc', 478 help='GN description ' + 479 '(e.g., gn desc out --format=json --all-toolchains "//*"') 480 parser.add_argument( 481 '--repo-root', 482 help='Standalone Perfetto repository to generate a GN description', 483 default=gn_utils.repo_root(), 484 ) 485 parser.add_argument( 486 '--extras', 487 help='Extra targets to include at the end of the BUILD file', 488 default=os.path.join(gn_utils.repo_root(), 'BUILD.extras'), 489 ) 490 parser.add_argument( 491 '--output', 492 help='BUILD file to create', 493 default=os.path.join(gn_utils.repo_root(), 'BUILD'), 494 ) 495 parser.add_argument( 496 '--output-proto', 497 help='Proto BUILD file to create', 498 default=os.path.join(gn_utils.repo_root(), 'protos', 'BUILD'), 499 ) 500 parser.add_argument( 501 'targets', 502 nargs=argparse.REMAINDER, 503 help='Targets to include in the BUILD file (e.g., "//:perfetto_tests")') 504 args = parser.parse_args() 505 506 if args.desc: 507 with open(args.desc) as f: 508 desc = json.load(f) 509 else: 510 desc = gn_utils.create_build_description(gn_args, args.repo_root) 511 512 out_files = [] 513 514 # Generate the main BUILD file. 515 with open(args.extras, 'r') as extra_f: 516 extras = extra_f.read() 517 518 contents = generate_build(desc, args.targets or default_targets, extras) 519 out_files.append(args.output + '.swp') 520 with open(out_files[-1], 'w') as out_f: 521 out_f.write(contents) 522 523 # Generate the build flags file. 524 out_files.append(os.path.join(buildflags_dir, 'perfetto_build_flags.h.swp')) 525 gn_utils.gen_buildflags(gn_args, out_files[-1]) 526 527 return gn_utils.check_or_commit_generated_files(out_files, args.check_only) 528 529 530if __name__ == '__main__': 531 sys.exit(main()) 532