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: 674030212pull/18203/head
parent
782e8ad382
commit
52544482bf
6 changed files with 356 additions and 2 deletions
@ -1,3 +1,12 @@ |
||||
"""cc_proto_library rule""" |
||||
|
||||
cc_proto_library = native.cc_proto_library |
||||
load("@proto_bazel_features//:features.bzl", "bazel_features") |
||||
load("//bazel/private:bazel_cc_proto_library.bzl", _cc_proto_library = "cc_proto_library") # buildifier: disable=bzl-visibility |
||||
|
||||
def cc_proto_library(**kwattrs): |
||||
# This condition causes Starlark rules to be used only on Bazel >=7.0.0 |
||||
if bazel_features.proto.starlark_proto_info: |
||||
_cc_proto_library(**kwattrs) |
||||
else: |
||||
# On older Bazel versions keep using native rules, so that mismatch in ProtoInfo doesn't happen |
||||
native.cc_proto_library(**kwattrs) # buildifier: disable=native-cc-proto |
||||
|
@ -0,0 +1,197 @@ |
||||
# 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 = "@rules_cc//cc/proto: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), |
||||
) |
@ -0,0 +1,141 @@ |
||||
# 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 |
||||
# |
||||
"""Supporting C++ compilation of generated code""" |
||||
|
||||
load("@proto_bazel_features//:features.bzl", "bazel_features") |
||||
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain") |
||||
|
||||
def get_feature_configuration(ctx, has_sources, extra_requested_features = []): |
||||
"""Returns C++ feature configuration for compiling and linking generated C++ files. |
||||
|
||||
Args: |
||||
ctx: (RuleCtx) rule context. |
||||
has_sources: (bool) Has the proto_library sources. |
||||
extra_requested_features: (list[str]) Additionally requested features. |
||||
Returns: |
||||
(FeatureConfiguration) C++ feature configuration |
||||
""" |
||||
cc_toolchain = find_cc_toolchain(ctx) |
||||
requested_features = ctx.features + extra_requested_features |
||||
|
||||
# TODO: Remove LAYERING_CHECK once we have verified that there are direct |
||||
# dependencies for all generated #includes. |
||||
unsupported_features = ctx.disabled_features + ["parse_headers", "layering_check"] |
||||
if has_sources: |
||||
requested_features.append("header_modules") |
||||
else: |
||||
unsupported_features.append("header_modules") |
||||
return cc_common.configure_features( |
||||
ctx = ctx, |
||||
cc_toolchain = cc_toolchain, |
||||
requested_features = requested_features, |
||||
unsupported_features = unsupported_features, |
||||
) |
||||
|
||||
def _get_libraries_from_linking_outputs(linking_outputs, feature_configuration): |
||||
library_to_link = linking_outputs.library_to_link |
||||
if not library_to_link: |
||||
return [] |
||||
outputs = [] |
||||
if library_to_link.static_library: |
||||
outputs.append(library_to_link.static_library) |
||||
if library_to_link.pic_static_library: |
||||
outputs.append(library_to_link.pic_static_library) |
||||
|
||||
# On Windows, dynamic library is not built by default, so don't add them to files_to_build. |
||||
if not cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "targets_windows"): |
||||
if library_to_link.resolved_symlink_dynamic_library: |
||||
outputs.append(library_to_link.resolved_symlink_dynamic_library) |
||||
elif library_to_link.dynamic_library: |
||||
outputs.append(library_to_link.dynamic_library) |
||||
if library_to_link.resolved_symlink_interface_library: |
||||
outputs.append(library_to_link.resolved_symlink_interface_library) |
||||
elif library_to_link.interface_library: |
||||
outputs.append(library_to_link.interface_library) |
||||
return outputs |
||||
|
||||
def cc_proto_compile_and_link(ctx, deps, sources, headers, disallow_dynamic_library = None, feature_configuration = None, alwayslink = False, **kwargs): |
||||
"""Creates C++ compilation and linking actions for C++ proto sources. |
||||
|
||||
Args: |
||||
ctx: rule context |
||||
deps: (list[CcInfo]) List of libraries to be added as dependencies to compilation and linking |
||||
actions. |
||||
sources:(list[File]) List of C++ sources files. |
||||
headers: list(File] List of C++ headers files. |
||||
disallow_dynamic_library: (bool) Are dynamic libraries disallowed. |
||||
feature_configuration: (FeatureConfiguration) feature configuration to use. |
||||
alwayslink: (bool) Should the library be always linked. |
||||
**kwargs: Additional arguments passed to the compilation. See cc_common.compile. |
||||
|
||||
Returns: |
||||
(CcInfo, list[File], list[File]) |
||||
- CcInfo provider with compilation context and linking context |
||||
- A list of linked libraries related to this proto |
||||
- A list of temporary files generated durind compilation |
||||
""" |
||||
cc_toolchain = find_cc_toolchain(ctx) |
||||
feature_configuration = feature_configuration or get_feature_configuration(ctx, bool(sources)) |
||||
if disallow_dynamic_library == None: |
||||
# TODO: Configure output artifact with action_config |
||||
# once proto compile action is configurable from the crosstool. |
||||
disallow_dynamic_library = not cc_common.is_enabled( |
||||
feature_name = "supports_dynamic_linker", |
||||
feature_configuration = feature_configuration, |
||||
) |
||||
|
||||
(compilation_context, compilation_outputs) = cc_common.compile( |
||||
actions = ctx.actions, |
||||
feature_configuration = feature_configuration, |
||||
cc_toolchain = cc_toolchain, |
||||
srcs = sources, |
||||
public_hdrs = headers, |
||||
compilation_contexts = [dep[CcInfo].compilation_context for dep in deps], |
||||
name = ctx.label.name, |
||||
# Don't instrument the generated C++ files even when --collect_code_coverage is set. |
||||
# If we actually start generating coverage instrumentation for .proto files based on coverage |
||||
# data from the generated C++ files, this will have to be removed. Currently, the work done |
||||
# to instrument those files and execute the instrumentation is all for nothing, and it can |
||||
# be quite a bit of extra computation even when that's not made worse by performance bugs, |
||||
# as in b/64963386. |
||||
# code_coverage_enabled = False (cc_common.compile disables code_coverage by default) |
||||
**kwargs |
||||
) |
||||
|
||||
if sources: |
||||
linking_context, linking_outputs = cc_common.create_linking_context_from_compilation_outputs( |
||||
actions = ctx.actions, |
||||
feature_configuration = feature_configuration, |
||||
cc_toolchain = cc_toolchain, |
||||
compilation_outputs = compilation_outputs, |
||||
linking_contexts = [dep[CcInfo].linking_context for dep in deps], |
||||
name = ctx.label.name, |
||||
disallow_dynamic_library = disallow_dynamic_library, |
||||
alwayslink = alwayslink, |
||||
) |
||||
libraries = _get_libraries_from_linking_outputs(linking_outputs, feature_configuration) |
||||
else: |
||||
linking_context = cc_common.merge_linking_contexts( |
||||
linking_contexts = [dep[CcInfo].linking_context for dep in deps if CcInfo in dep], |
||||
) |
||||
libraries = [] |
||||
|
||||
debug_context = None |
||||
temps = [] |
||||
if bazel_features.cc.protobuf_on_allowlist: |
||||
debug_context = cc_common.merge_debug_context( |
||||
[cc_common.create_debug_context(compilation_outputs)] + |
||||
[dep[CcInfo].debug_context() for dep in deps if CcInfo in dep], |
||||
) |
||||
temps = compilation_outputs.temps() |
||||
|
||||
return CcInfo( |
||||
compilation_context = compilation_context, |
||||
linking_context = linking_context, |
||||
debug_context = debug_context, |
||||
), libraries, temps |
Loading…
Reference in new issue