• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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