| # 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("@rules_cc//cc/common:cc_info.bzl", "CcInfo") |
| 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 = Label("//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 = """ |
| <p> |
| <code>cc_proto_library</code> generates C++ code from <code>.proto</code> files. |
| </p> |
| |
| <p> |
| <code>deps</code> must point to <a href="protocol-buffer.html#proto_library"><code>proto_library |
| </code></a> rules. |
| </p> |
| |
| <p> |
| Example: |
| </p> |
| |
| <pre> |
| <code class="lang-starlark"> |
| cc_library( |
| name = "lib", |
| deps = [":foo_cc_proto"], |
| ) |
| |
| cc_proto_library( |
| name = "foo_cc_proto", |
| deps = [":foo_proto"], |
| ) |
| |
| proto_library( |
| name = "foo_proto", |
| ) |
| </code> |
| </pre> |
| """, |
| attrs = { |
| "deps": attr.label_list( |
| aspects = [cc_proto_aspect], |
| allow_rules = ["proto_library"], |
| allow_files = False, |
| doc = """ |
| The list of <a href="protocol-buffer.html#proto_library"><code>proto_library</code></a> |
| 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), |
| ) |