# Protocol Buffers - Google's data interchange format # Copyright 2024 Google Inc. All rights reserved. # # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file or at # https://developers.google.com/open-source/licenses/bsd # """Bazel's implementation of cc_proto_library""" load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain") load("//bazel/common:proto_common.bzl", "proto_common") load("//bazel/common:proto_info.bzl", "ProtoInfo") load("//bazel/private:cc_proto_support.bzl", "cc_proto_compile_and_link") load("//bazel/private:toolchain_helpers.bzl", "toolchains") _CC_PROTO_TOOLCHAIN = "//bazel/private:cc_toolchain_type" _ProtoCcFilesInfo = provider(fields = ["files"], doc = "Provide cc proto files.") _ProtoCcHeaderInfo = provider(fields = ["headers"], doc = "Provide cc proto headers.") def _get_output_files(actions, proto_info, suffixes): result = [] for suffix in suffixes: result.extend(proto_common.declare_generated_files( actions = actions, proto_info = proto_info, extension = suffix, )) return result # TODO: Make this code actually work. def _get_strip_include_prefix(ctx, proto_info): proto_root = proto_info.proto_source_root if proto_root == "." or proto_root == ctx.label.workspace_root: return "" strip_include_prefix = "" if proto_root.startswith(ctx.bin_dir.path): proto_root = proto_root[len(ctx.bin_dir.path) + 1:] elif proto_root.startswith(ctx.genfiles_dir.path): proto_root = proto_root[len(ctx.genfiles_dir.path) + 1:] if proto_root.startswith(ctx.label.workspace_root): proto_root = proto_root[len(ctx.label.workspace_root):] strip_include_prefix = "//" + proto_root return strip_include_prefix def _aspect_impl(target, ctx): proto_info = target[ProtoInfo] proto_configuration = ctx.fragments.proto sources = [] headers = [] textual_hdrs = [] proto_toolchain = toolchains.find_toolchain(ctx, "_aspect_cc_proto_toolchain", _CC_PROTO_TOOLCHAIN) should_generate_code = proto_common.experimental_should_generate_code(proto_info, proto_toolchain, "cc_proto_library", target.label) if should_generate_code: if len(proto_info.direct_sources) != 0: # Bazel 7 didn't expose cc_proto_library_source_suffixes used by Kythe # gradually falling back to .pb.cc if type(proto_configuration.cc_proto_library_source_suffixes) == "builtin_function_or_method": source_suffixes = [".pb.cc"] header_suffixes = [".pb.h"] else: source_suffixes = proto_configuration.cc_proto_library_source_suffixes header_suffixes = proto_configuration.cc_proto_library_header_suffixes sources = _get_output_files(ctx.actions, proto_info, source_suffixes) headers = _get_output_files(ctx.actions, proto_info, header_suffixes) header_provider = _ProtoCcHeaderInfo(headers = depset(headers)) else: # If this proto_library doesn't have sources, it provides the combined headers of all its # direct dependencies. Thus, if a direct dependency does have sources, the generated files # are also provided by this library. If a direct dependency does not have sources, it will # do the same thing, so that effectively this library looks through all source-less # proto_libraries and provides all generated headers of the proto_libraries with sources # that it depends on. transitive_headers = [] for dep in getattr(ctx.rule.attr, "deps", []): if _ProtoCcHeaderInfo in dep: textual_hdrs.extend(dep[_ProtoCcHeaderInfo].headers.to_list()) transitive_headers.append(dep[_ProtoCcHeaderInfo].headers) header_provider = _ProtoCcHeaderInfo(headers = depset(transitive = transitive_headers)) else: # shouldn't generate code header_provider = _ProtoCcHeaderInfo(headers = depset()) proto_common.compile( actions = ctx.actions, proto_info = proto_info, proto_lang_toolchain_info = proto_toolchain, generated_files = sources + headers, experimental_output_files = "multiple", ) deps = [] if proto_toolchain.runtime: deps = [proto_toolchain.runtime] deps.extend(getattr(ctx.rule.attr, "deps", [])) cc_info, libraries, temps = cc_proto_compile_and_link( ctx = ctx, deps = deps, sources = sources, headers = headers, textual_hdrs = textual_hdrs, strip_include_prefix = _get_strip_include_prefix(ctx, proto_info), ) return [ cc_info, _ProtoCcFilesInfo(files = depset(sources + headers + libraries)), OutputGroupInfo(temp_files_INTERNAL_ = temps), header_provider, ] cc_proto_aspect = aspect( implementation = _aspect_impl, attr_aspects = ["deps"], fragments = ["cpp", "proto"], required_providers = [ProtoInfo], provides = [CcInfo], attrs = toolchains.if_legacy_toolchain({"_aspect_cc_proto_toolchain": attr.label( default = configuration_field(fragment = "proto", name = "proto_toolchain_for_cc"), )}), toolchains = use_cc_toolchain() + toolchains.use_toolchain(_CC_PROTO_TOOLCHAIN), ) def _cc_proto_library_impl(ctx): if len(ctx.attr.deps) != 1: fail( "'deps' attribute must contain exactly one label " + "(we didn't name it 'dep' for consistency). " + "The main use-case for multiple deps is to create a rule that contains several " + "other targets. This makes dependency bloat more likely. It also makes it harder" + "to remove unused deps.", attr = "deps", ) dep = ctx.attr.deps[0] proto_toolchain = toolchains.find_toolchain(ctx, "_aspect_cc_proto_toolchain", _CC_PROTO_TOOLCHAIN) proto_common.check_collocated(ctx.label, dep[ProtoInfo], proto_toolchain) return [DefaultInfo(files = dep[_ProtoCcFilesInfo].files), dep[CcInfo], dep[OutputGroupInfo]] cc_proto_library = rule( implementation = _cc_proto_library_impl, doc = """

cc_proto_library generates C++ code from .proto files.

deps must point to proto_library rules.

Example:


cc_library(
    name = "lib",
    deps = [":foo_cc_proto"],
)

cc_proto_library(
    name = "foo_cc_proto",
    deps = [":foo_proto"],
)

proto_library(
    name = "foo_proto",
)

""", attrs = { "deps": attr.label_list( aspects = [cc_proto_aspect], allow_rules = ["proto_library"], allow_files = False, doc = """ The list of proto_library rules to generate C++ code for.""", ), } | toolchains.if_legacy_toolchain({ "_aspect_cc_proto_toolchain": attr.label( default = configuration_field(fragment = "proto", name = "proto_toolchain_for_cc"), ), }), provides = [CcInfo], toolchains = toolchains.use_toolchain(_CC_PROTO_TOOLCHAIN), )