Skip to content

Commit 5254448

Browse files
Move cc_proto_library from Bazel repository
Bazel 6 falls back to native rules, because of ProtoInfo differences. Bazel 7 is slightly degraded: Kythe flags don't work, DebugContext is left out from CcInfo and temporary files generated by the C++ compiler (but it's only useful for debugging). Tests will be submitted in separate PRs. PiperOrigin-RevId: 674030212
1 parent 782e8ad commit 5254448

6 files changed

+356
-2
lines changed

MODULE.bazel

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ bazel_dep(
8585

8686
bazel_dep(
8787
name = "bazel_features",
88-
version = "1.13.0",
88+
version = "1.16.0",
8989
repo_name = "proto_bazel_features",
9090
)
9191

bazel/cc_proto_library.bzl

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
"""cc_proto_library rule"""
22

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

bazel/private/cc_proto_support.bzl

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
"""Supporting C++ compilation of generated code"""
9+
10+
load("@proto_bazel_features//:features.bzl", "bazel_features")
11+
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
12+
13+
def get_feature_configuration(ctx, has_sources, extra_requested_features = []):
14+
"""Returns C++ feature configuration for compiling and linking generated C++ files.
15+
16+
Args:
17+
ctx: (RuleCtx) rule context.
18+
has_sources: (bool) Has the proto_library sources.
19+
extra_requested_features: (list[str]) Additionally requested features.
20+
Returns:
21+
(FeatureConfiguration) C++ feature configuration
22+
"""
23+
cc_toolchain = find_cc_toolchain(ctx)
24+
requested_features = ctx.features + extra_requested_features
25+
26+
# TODO: Remove LAYERING_CHECK once we have verified that there are direct
27+
# dependencies for all generated #includes.
28+
unsupported_features = ctx.disabled_features + ["parse_headers", "layering_check"]
29+
if has_sources:
30+
requested_features.append("header_modules")
31+
else:
32+
unsupported_features.append("header_modules")
33+
return cc_common.configure_features(
34+
ctx = ctx,
35+
cc_toolchain = cc_toolchain,
36+
requested_features = requested_features,
37+
unsupported_features = unsupported_features,
38+
)
39+
40+
def _get_libraries_from_linking_outputs(linking_outputs, feature_configuration):
41+
library_to_link = linking_outputs.library_to_link
42+
if not library_to_link:
43+
return []
44+
outputs = []
45+
if library_to_link.static_library:
46+
outputs.append(library_to_link.static_library)
47+
if library_to_link.pic_static_library:
48+
outputs.append(library_to_link.pic_static_library)
49+
50+
# On Windows, dynamic library is not built by default, so don't add them to files_to_build.
51+
if not cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "targets_windows"):
52+
if library_to_link.resolved_symlink_dynamic_library:
53+
outputs.append(library_to_link.resolved_symlink_dynamic_library)
54+
elif library_to_link.dynamic_library:
55+
outputs.append(library_to_link.dynamic_library)
56+
if library_to_link.resolved_symlink_interface_library:
57+
outputs.append(library_to_link.resolved_symlink_interface_library)
58+
elif library_to_link.interface_library:
59+
outputs.append(library_to_link.interface_library)
60+
return outputs
61+
62+
def cc_proto_compile_and_link(ctx, deps, sources, headers, disallow_dynamic_library = None, feature_configuration = None, alwayslink = False, **kwargs):
63+
"""Creates C++ compilation and linking actions for C++ proto sources.
64+
65+
Args:
66+
ctx: rule context
67+
deps: (list[CcInfo]) List of libraries to be added as dependencies to compilation and linking
68+
actions.
69+
sources:(list[File]) List of C++ sources files.
70+
headers: list(File] List of C++ headers files.
71+
disallow_dynamic_library: (bool) Are dynamic libraries disallowed.
72+
feature_configuration: (FeatureConfiguration) feature configuration to use.
73+
alwayslink: (bool) Should the library be always linked.
74+
**kwargs: Additional arguments passed to the compilation. See cc_common.compile.
75+
76+
Returns:
77+
(CcInfo, list[File], list[File])
78+
- CcInfo provider with compilation context and linking context
79+
- A list of linked libraries related to this proto
80+
- A list of temporary files generated durind compilation
81+
"""
82+
cc_toolchain = find_cc_toolchain(ctx)
83+
feature_configuration = feature_configuration or get_feature_configuration(ctx, bool(sources))
84+
if disallow_dynamic_library == None:
85+
# TODO: Configure output artifact with action_config
86+
# once proto compile action is configurable from the crosstool.
87+
disallow_dynamic_library = not cc_common.is_enabled(
88+
feature_name = "supports_dynamic_linker",
89+
feature_configuration = feature_configuration,
90+
)
91+
92+
(compilation_context, compilation_outputs) = cc_common.compile(
93+
actions = ctx.actions,
94+
feature_configuration = feature_configuration,
95+
cc_toolchain = cc_toolchain,
96+
srcs = sources,
97+
public_hdrs = headers,
98+
compilation_contexts = [dep[CcInfo].compilation_context for dep in deps],
99+
name = ctx.label.name,
100+
# Don't instrument the generated C++ files even when --collect_code_coverage is set.
101+
# If we actually start generating coverage instrumentation for .proto files based on coverage
102+
# data from the generated C++ files, this will have to be removed. Currently, the work done
103+
# to instrument those files and execute the instrumentation is all for nothing, and it can
104+
# be quite a bit of extra computation even when that's not made worse by performance bugs,
105+
# as in b/64963386.
106+
# code_coverage_enabled = False (cc_common.compile disables code_coverage by default)
107+
**kwargs
108+
)
109+
110+
if sources:
111+
linking_context, linking_outputs = cc_common.create_linking_context_from_compilation_outputs(
112+
actions = ctx.actions,
113+
feature_configuration = feature_configuration,
114+
cc_toolchain = cc_toolchain,
115+
compilation_outputs = compilation_outputs,
116+
linking_contexts = [dep[CcInfo].linking_context for dep in deps],
117+
name = ctx.label.name,
118+
disallow_dynamic_library = disallow_dynamic_library,
119+
alwayslink = alwayslink,
120+
)
121+
libraries = _get_libraries_from_linking_outputs(linking_outputs, feature_configuration)
122+
else:
123+
linking_context = cc_common.merge_linking_contexts(
124+
linking_contexts = [dep[CcInfo].linking_context for dep in deps if CcInfo in dep],
125+
)
126+
libraries = []
127+
128+
debug_context = None
129+
temps = []
130+
if bazel_features.cc.protobuf_on_allowlist:
131+
debug_context = cc_common.merge_debug_context(
132+
[cc_common.create_debug_context(compilation_outputs)] +
133+
[dep[CcInfo].debug_context() for dep in deps if CcInfo in dep],
134+
)
135+
temps = compilation_outputs.temps()
136+
137+
return CcInfo(
138+
compilation_context = compilation_context,
139+
linking_context = linking_context,
140+
debug_context = debug_context,
141+
), libraries, temps

bazel/private/proto_bazel_features.bzl

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"""Vendored version of bazel_features for protobuf, to keep a one-step setup"""
99

1010
_PROTO_BAZEL_FEATURES = """bazel_features = struct(
11+
cc = struct(
12+
protobuf_on_allowlist = {protobuf_on_allowlist},
13+
),
1114
proto = struct(
1215
starlark_proto_info = {starlark_proto_info},
1316
),
@@ -29,6 +32,8 @@ def _proto_bazel_features_impl(rctx):
2932
starlark_proto_info = major_version_int >= 7
3033
PackageSpecificationInfo = major_version_int > 6 or (major_version_int == 6 and minor_version_int >= 4)
3134

35+
protobuf_on_allowlist = major_version_int > 7
36+
3237
rctx.file("BUILD.bazel", """
3338
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
3439
bzl_library(
@@ -41,6 +46,7 @@ exports_files(["features.bzl"])
4146
rctx.file("features.bzl", _PROTO_BAZEL_FEATURES.format(
4247
starlark_proto_info = repr(starlark_proto_info),
4348
PackageSpecificationInfo = "PackageSpecificationInfo" if PackageSpecificationInfo else "None",
49+
protobuf_on_allowlist = repr(protobuf_on_allowlist),
4450
))
4551

4652
proto_bazel_features = repository_rule(

java/core/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ load("@bazel_skylib//rules:build_test.bzl", "build_test")
22
load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix")
33
load("//:protobuf.bzl", "internal_gen_well_known_protos_java")
44
load("//:protobuf_version.bzl", "PROTOBUF_JAVA_VERSION")
5+
load("//bazel:cc_proto_library.bzl", "cc_proto_library")
56
load("//bazel:java_lite_proto_library.bzl", "java_lite_proto_library")
67
load("//bazel:java_proto_library.bzl", "java_proto_library")
78
load("//bazel:proto_library.bzl", "proto_library")

0 commit comments

Comments
 (0)