1# Protocol Buffers - Google's data interchange format 2# Copyright 2024 Google Inc. All rights reserved. 3# 4# Use of this source code is governed by a BSD-style 5# license that can be found in the LICENSE file or at 6# https://developers.google.com/open-source/licenses/bsd 7# 8"""Bazel's implementation of cc_proto_library""" 9 10load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain") 11load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") 12load("//bazel/common:proto_common.bzl", "proto_common") 13load("//bazel/common:proto_info.bzl", "ProtoInfo") 14load("//bazel/private:cc_proto_support.bzl", "cc_proto_compile_and_link") 15load("//bazel/private:toolchain_helpers.bzl", "toolchains") 16 17_CC_PROTO_TOOLCHAIN = Label("//bazel/private:cc_toolchain_type") 18 19_ProtoCcFilesInfo = provider(fields = ["files"], doc = "Provide cc proto files.") 20_ProtoCcHeaderInfo = provider(fields = ["headers"], doc = "Provide cc proto headers.") 21 22def _get_output_files(actions, proto_info, suffixes): 23 result = [] 24 for suffix in suffixes: 25 result.extend(proto_common.declare_generated_files( 26 actions = actions, 27 proto_info = proto_info, 28 extension = suffix, 29 )) 30 return result 31 32# TODO: Make this code actually work. 33def _get_strip_include_prefix(ctx, proto_info): 34 proto_root = proto_info.proto_source_root 35 if proto_root == "." or proto_root == ctx.label.workspace_root: 36 return "" 37 strip_include_prefix = "" 38 if proto_root.startswith(ctx.bin_dir.path): 39 proto_root = proto_root[len(ctx.bin_dir.path) + 1:] 40 elif proto_root.startswith(ctx.genfiles_dir.path): 41 proto_root = proto_root[len(ctx.genfiles_dir.path) + 1:] 42 43 if proto_root.startswith(ctx.label.workspace_root): 44 proto_root = proto_root[len(ctx.label.workspace_root):] 45 46 strip_include_prefix = "//" + proto_root 47 return strip_include_prefix 48 49def _aspect_impl(target, ctx): 50 proto_info = target[ProtoInfo] 51 proto_configuration = ctx.fragments.proto 52 53 sources = [] 54 headers = [] 55 textual_hdrs = [] 56 57 proto_toolchain = toolchains.find_toolchain(ctx, "_aspect_cc_proto_toolchain", _CC_PROTO_TOOLCHAIN) 58 should_generate_code = proto_common.experimental_should_generate_code(proto_info, proto_toolchain, "cc_proto_library", target.label) 59 60 if should_generate_code: 61 if len(proto_info.direct_sources) != 0: 62 # Bazel 7 didn't expose cc_proto_library_source_suffixes used by Kythe 63 # gradually falling back to .pb.cc 64 if type(proto_configuration.cc_proto_library_source_suffixes) == "builtin_function_or_method": 65 source_suffixes = [".pb.cc"] 66 header_suffixes = [".pb.h"] 67 else: 68 source_suffixes = proto_configuration.cc_proto_library_source_suffixes 69 header_suffixes = proto_configuration.cc_proto_library_header_suffixes 70 sources = _get_output_files(ctx.actions, proto_info, source_suffixes) 71 headers = _get_output_files(ctx.actions, proto_info, header_suffixes) 72 header_provider = _ProtoCcHeaderInfo(headers = depset(headers)) 73 else: 74 # If this proto_library doesn't have sources, it provides the combined headers of all its 75 # direct dependencies. Thus, if a direct dependency does have sources, the generated files 76 # are also provided by this library. If a direct dependency does not have sources, it will 77 # do the same thing, so that effectively this library looks through all source-less 78 # proto_libraries and provides all generated headers of the proto_libraries with sources 79 # that it depends on. 80 transitive_headers = [] 81 for dep in getattr(ctx.rule.attr, "deps", []): 82 if _ProtoCcHeaderInfo in dep: 83 textual_hdrs.extend(dep[_ProtoCcHeaderInfo].headers.to_list()) 84 transitive_headers.append(dep[_ProtoCcHeaderInfo].headers) 85 header_provider = _ProtoCcHeaderInfo(headers = depset(transitive = transitive_headers)) 86 87 else: # shouldn't generate code 88 header_provider = _ProtoCcHeaderInfo(headers = depset()) 89 90 proto_common.compile( 91 actions = ctx.actions, 92 proto_info = proto_info, 93 proto_lang_toolchain_info = proto_toolchain, 94 generated_files = sources + headers, 95 experimental_output_files = "multiple", 96 ) 97 98 deps = [] 99 if proto_toolchain.runtime: 100 deps = [proto_toolchain.runtime] 101 deps.extend(getattr(ctx.rule.attr, "deps", [])) 102 103 cc_info, libraries, temps = cc_proto_compile_and_link( 104 ctx = ctx, 105 deps = deps, 106 sources = sources, 107 headers = headers, 108 textual_hdrs = textual_hdrs, 109 strip_include_prefix = _get_strip_include_prefix(ctx, proto_info), 110 ) 111 112 return [ 113 cc_info, 114 _ProtoCcFilesInfo(files = depset(sources + headers + libraries)), 115 OutputGroupInfo(temp_files_INTERNAL_ = temps), 116 header_provider, 117 ] 118 119cc_proto_aspect = aspect( 120 implementation = _aspect_impl, 121 attr_aspects = ["deps"], 122 fragments = ["cpp", "proto"], 123 required_providers = [ProtoInfo], 124 provides = [CcInfo], 125 attrs = toolchains.if_legacy_toolchain({"_aspect_cc_proto_toolchain": attr.label( 126 default = configuration_field(fragment = "proto", name = "proto_toolchain_for_cc"), 127 )}), 128 toolchains = use_cc_toolchain() + toolchains.use_toolchain(_CC_PROTO_TOOLCHAIN), 129) 130 131def _cc_proto_library_impl(ctx): 132 if len(ctx.attr.deps) != 1: 133 fail( 134 "'deps' attribute must contain exactly one label " + 135 "(we didn't name it 'dep' for consistency). " + 136 "The main use-case for multiple deps is to create a rule that contains several " + 137 "other targets. This makes dependency bloat more likely. It also makes it harder" + 138 "to remove unused deps.", 139 attr = "deps", 140 ) 141 dep = ctx.attr.deps[0] 142 143 proto_toolchain = toolchains.find_toolchain(ctx, "_aspect_cc_proto_toolchain", _CC_PROTO_TOOLCHAIN) 144 proto_common.check_collocated(ctx.label, dep[ProtoInfo], proto_toolchain) 145 146 return [DefaultInfo(files = dep[_ProtoCcFilesInfo].files), dep[CcInfo], dep[OutputGroupInfo]] 147 148cc_proto_library = rule( 149 implementation = _cc_proto_library_impl, 150 doc = """ 151<p> 152<code>cc_proto_library</code> generates C++ code from <code>.proto</code> files. 153</p> 154 155<p> 156<code>deps</code> must point to <a href="protocol-buffer.html#proto_library"><code>proto_library 157</code></a> rules. 158</p> 159 160<p> 161Example: 162</p> 163 164<pre> 165<code class="lang-starlark"> 166cc_library( 167 name = "lib", 168 deps = [":foo_cc_proto"], 169) 170 171cc_proto_library( 172 name = "foo_cc_proto", 173 deps = [":foo_proto"], 174) 175 176proto_library( 177 name = "foo_proto", 178) 179</code> 180</pre> 181""", 182 attrs = { 183 "deps": attr.label_list( 184 aspects = [cc_proto_aspect], 185 allow_rules = ["proto_library"], 186 allow_files = False, 187 doc = """ 188The list of <a href="protocol-buffer.html#proto_library"><code>proto_library</code></a> 189rules to generate C++ code for.""", 190 ), 191 } | toolchains.if_legacy_toolchain({ 192 "_aspect_cc_proto_toolchain": attr.label( 193 default = configuration_field(fragment = "proto", name = "proto_toolchain_for_cc"), 194 ), 195 }), 196 provides = [CcInfo], 197 toolchains = toolchains.use_toolchain(_CC_PROTO_TOOLCHAIN), 198) 199