Add Kythe indexing support to HPB proto in C++

PiperOrigin-RevId: 929969615
This commit is contained in:
Protobuf Team Bot 2026-06-10 11:21:46 -07:00 committed by Copybara-Service
parent 3eb9b841a7
commit c5a405c0b7
10 changed files with 434 additions and 81 deletions

View file

@ -9,12 +9,14 @@
- hpb_proto_library()
"""
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "use_cpp_toolchain")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
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("//upb/bazel:upb_proto_library.bzl", "GeneratedSrcsInfo", "UpbWrappedCcInfo", "upb_proto_library_aspect")
load("//bazel/common:proto_lang_toolchain_info.bzl", "ProtoLangToolchainInfo")
load("//upb/bazel:upb_proto_library.bzl", "UpbWrappedCcInfo", "upb_proto_library_aspect")
load("//upb/bazel/private:upb_proto_library_internal/cc_library_func.bzl", "cc_library_func") # buildifier: disable=bzl-visibility
def upb_use_cpp_toolchain():
@ -26,12 +28,40 @@ def _filter_none(elems):
_HpbWrappedCcInfo = provider("Provider for cc_info for hpb", fields = ["cc_info"])
_WrappedCcGeneratedSrcsInfo = provider("Provider for generated sources", fields = ["srcs"])
HpbGeneratedSrcsInfo = provider(
"Provides generated headers, sources and metadata for hpb",
fields = ["srcs", "hdrs", "metadata"],
)
def _get_lang_toolchain(ctx):
return ctx.attr._hpb_lang_toolchain[proto_common.ProtoLangToolchainInfo]
def _compile_hpb_protos(ctx, proto_info, proto_sources):
def _add_annotate_headers_to_flag(out_flag):
eq_idx = out_flag.find("=")
if eq_idx == -1:
return out_flag
param_name = out_flag[:eq_idx]
value = out_flag[eq_idx + 1:]
colon_idx = value.rfind(":")
if colon_idx == -1:
args = ""
output = value
else:
args = value[:colon_idx]
output = value[colon_idx + 1:]
if args:
new_args = args + ",annotate_headers"
else:
new_args = "annotate_headers"
return param_name + "=" + new_args + ":" + output
def _compile_hpb_protos(ctx, proto_info, proto_sources, generate_meta_data):
if len(proto_sources) == 0:
return GeneratedSrcsInfo(srcs = [], hdrs = [])
return HpbGeneratedSrcsInfo(srcs = [], hdrs = [], metadata = [])
srcs = []
srcs += proto_common.declare_generated_files(
@ -47,15 +77,42 @@ def _compile_hpb_protos(ctx, proto_info, proto_sources):
proto_info = proto_info,
)
metadata = []
if generate_meta_data:
metadata = proto_common.declare_generated_files(
ctx.actions,
extension = ".hpb.h.meta",
proto_info = proto_info,
)
toolchain = _get_lang_toolchain(ctx)
if generate_meta_data:
out_flag = _add_annotate_headers_to_flag(toolchain.out_replacement_format_flag)
toolchain = ProtoLangToolchainInfo(
out_replacement_format_flag = out_flag,
output_files = getattr(toolchain, "output_files", "legacy"),
plugin_format_flag = toolchain.plugin_format_flag,
plugin = toolchain.plugin,
runtime = toolchain.runtime,
provided_proto_sources = toolchain.provided_proto_sources,
proto_compiler = toolchain.proto_compiler,
protoc_opts = toolchain.protoc_opts,
progress_message = toolchain.progress_message,
mnemonic = toolchain.mnemonic,
allowlist_different_package = getattr(toolchain, "allowlist_different_package", None),
toolchain_type = getattr(toolchain, "toolchain_type", None),
)
proto_common.compile(
actions = ctx.actions,
proto_info = proto_info,
proto_lang_toolchain_info = _get_lang_toolchain(ctx),
generated_files = srcs + hdrs,
proto_lang_toolchain_info = toolchain,
generated_files = srcs + hdrs + metadata,
experimental_exec_group = "proto_compiler",
)
return GeneratedSrcsInfo(srcs = srcs, hdrs = hdrs)
return HpbGeneratedSrcsInfo(srcs = srcs, hdrs = hdrs, metadata = metadata)
def _hpb_proto_rule_impl(ctx):
if len(ctx.attr.deps) != 1:
@ -80,7 +137,7 @@ def _hpb_proto_rule_impl(ctx):
lib.dynamic_library,
])
return [
DefaultInfo(files = depset(files + srcs.hdrs + srcs.srcs)),
DefaultInfo(files = depset(files + srcs.hdrs + srcs.srcs + srcs.metadata)),
srcs,
cc_info,
]
@ -89,6 +146,7 @@ def _get_proto_deps(ctx):
return [dep for dep in ctx.rule.attr.deps if ProtoInfo in dep]
def _upb_cc_proto_aspect_impl(target, ctx, cc_provider, file_provider):
generate_meta_data = False
deps = _get_proto_deps(ctx) + ctx.attr._upbprotos
dep_ccinfos = [dep[CcInfo] for dep in deps if CcInfo in dep]
dep_ccinfos += [dep[UpbWrappedCcInfo].cc_info for dep in deps if UpbWrappedCcInfo in dep]
@ -104,9 +162,9 @@ def _upb_cc_proto_aspect_impl(target, ctx, cc_provider, file_provider):
# https://bazel.build/versions/6.4.0/reference/be/protocol-buffer#proto_library.srcs
return [cc_provider(
cc_info = cc_common.merge_cc_infos(direct_cc_infos = dep_ccinfos),
), file_provider(srcs = GeneratedSrcsInfo(srcs = [], hdrs = []))]
), file_provider(srcs = HpbGeneratedSrcsInfo(srcs = [], hdrs = [], metadata = []))]
else:
files = _compile_hpb_protos(ctx, proto_info, proto_info.direct_sources)
files = _compile_hpb_protos(ctx, proto_info, proto_info.direct_sources, generate_meta_data)
cc_info = cc_library_func(
ctx = ctx,
name = ctx.rule.attr.name + "_hpb",
@ -114,6 +172,7 @@ def _upb_cc_proto_aspect_impl(target, ctx, cc_provider, file_provider):
srcs = files.srcs,
copts = [],
dep_ccinfos = dep_ccinfos,
non_compilation_additional_inputs = files.metadata,
)
return [cc_provider(cc_info = cc_info), file_provider(srcs = files)]

View file

@ -59,7 +59,7 @@ cc_library(
hdrs = [
"generator.h",
],
visibility = ["//visibility:private"],
visibility = ["//hpb_generator:__subpackages__"],
deps = [
":context",
":gen_utils",
@ -69,6 +69,7 @@ cc_library(
"//src/google/protobuf/compiler:code_generator",
"//src/google/protobuf/compiler:code_generator_lite",
"//src/google/protobuf/compiler:plugin",
"//src/google/protobuf/io:printer",
"//upb_generator:common",
"//upb_generator:file_layout",
"//upb_generator/c:names",
@ -108,7 +109,7 @@ cc_library(
name = "names",
srcs = ["names.cc"],
hdrs = ["names.h"],
visibility = ["//visibility:private"],
visibility = ["//hpb_generator:__subpackages__"],
deps = [
":context",
":keywords",

View file

@ -34,6 +34,7 @@ enum class Backend { UPB, CPP };
struct Options {
Backend backend = Backend::UPB;
bool strip_feature_includes = false;
io::AnnotationCollector* annotation_collector = nullptr;
};
/**
@ -52,7 +53,10 @@ class Context final {
public:
Context(const FileDescriptor* file, io::ZeroCopyOutputStream* stream,
const Options& options)
: stream_(stream), printer_(stream_), options_(options) {
: stream_(stream),
printer_(stream_,
io::Printer::Options('$', options.annotation_collector)),
options_(options) {
BuildDefPool(file);
}

View file

@ -21,6 +21,7 @@
#include "hpb_generator/keywords.h"
#include "hpb_generator/names.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/io/printer.h"
#include "upb_generator/c/names.h"
#include "upb_generator/minitable/names.h"
@ -30,6 +31,7 @@ namespace hpb_generator {
using NameToFieldDescriptorMap =
absl::flat_hash_map<absl::string_view, const google::protobuf::FieldDescriptor*>;
using Sub = ::google::protobuf::io::Printer::Sub;
void WriteFieldAccessorHazzer(const google::protobuf::Descriptor* desc,
const google::protobuf::FieldDescriptor* field,
@ -100,20 +102,31 @@ void WriteFieldAccessorsInHeader(const google::protobuf::Descriptor* desc, Conte
} else {
// non-repeated.
if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_STRING) {
ctx.Emit({{"field_name", resolved_field_name}},
R"cc(
absl::string_view $field_name$() const;
void set_$field_name$(absl::string_view value);
)cc");
ctx.Emit(
{Sub("field_name", resolved_field_name)
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("set_field_name", absl::StrCat("set_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet})},
R"cc(
absl::string_view $field_name$() const;
void $set_field_name$(absl::string_view value);
)cc");
} else if (field->cpp_type() ==
google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
ctx.Emit(
{{"mut_ptr_type", MessagePtrConstType(field, /* const */ false)},
{"const_ptr_type", MessagePtrConstType(field, /* const */ true)},
{"field_name", resolved_field_name}},
Sub("field_name", resolved_field_name)
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("mutable_field_name",
absl::StrCat("mutable_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kAlias}),
Sub("set_alias_field_name",
absl::StrCat("set_alias_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet})},
R"cc(
$const_ptr_type$ $field_name$() const;
$mut_ptr_type$ mutable_$field_name$();
$mut_ptr_type$ $mutable_field_name$();
/**
* Re-points submessage to the given target.
*
@ -121,22 +134,26 @@ void WriteFieldAccessorsInHeader(const google::protobuf::Descriptor* desc, Conte
* - both messages must be in the same arena, or in two
* fused arenas.
*/
void set_alias_$field_name$($mut_ptr_type$ target);
void $set_alias_field_name$($mut_ptr_type$ target);
)cc");
} else {
ctx.Emit({{"cpp_type", CppConstType(field)},
{"field_name", resolved_field_name},
{"upb_msg_name",
upb::generator::CApiMessageType(desc->full_name())},
{"upb_field_name", resolved_upbc_name}},
R"cc(
inline $cpp_type$ $field_name$() const {
return $upb_msg_name$_$upb_field_name$(msg_);
}
inline void set_$field_name$($cpp_type$ value) {
return $upb_msg_name$_set_$upb_field_name$(msg_, value);
}
)cc");
ctx.Emit(
{{"cpp_type", CppConstType(field)},
Sub("field_name", resolved_field_name)
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("set_field_name", absl::StrCat("set_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
{"upb_msg_name",
upb::generator::CApiMessageType(desc->full_name())},
{"upb_field_name", resolved_upbc_name}},
R"cc(
inline $cpp_type$ $field_name$() const {
return $upb_msg_name$_$upb_field_name$(msg_);
}
inline void $set_field_name$($cpp_type$ value) {
return $upb_msg_name$_set_$upb_field_name$(msg_, value);
}
)cc");
}
}
}
@ -151,11 +168,12 @@ void WriteFieldAccessorHazzer(const google::protobuf::Descriptor* desc,
if (field->has_presence()) {
// Has presence.
ctx.Emit(
{{"field_name", resolved_field_name},
{Sub("has_field_name", absl::StrCat("has_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
{"upb_msg_name", upb::generator::CApiMessageType(desc->full_name())},
{"upb_field_name", resolved_upbc_name}},
R"cc(
inline bool has_$field_name$() const {
inline bool $has_field_name$() const {
return $upb_msg_name$_has_$upb_field_name$(msg_);
}
)cc");
@ -169,11 +187,12 @@ void WriteFieldAccessorClear(const google::protobuf::Descriptor* desc,
Context& ctx) {
if (field->has_presence()) {
ctx.Emit(
{{"field_name", resolved_field_name},
{Sub("clear_field_name", absl::StrCat("clear_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
{"upb_field_name", resolved_upbc_name},
{"upb_msg_name", upb::generator::CApiMessageType(desc->full_name())}},
R"cc(
void clear_$field_name$() {
void $clear_field_name$() {
$upb_msg_name$_clear_$upb_field_name$(msg_);
}
)cc");
@ -189,41 +208,58 @@ void WriteMapFieldAccessors(const google::protobuf::Descriptor* desc,
const google::protobuf::FieldDescriptor* key = entry->FindFieldByNumber(1);
const google::protobuf::FieldDescriptor* val = entry->FindFieldByNumber(2);
ctx.Emit(
{{"field_name", resolved_field_name},
{Sub("field_name_size", absl::StrCat(resolved_field_name, "_size"))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("clear_field_name", absl::StrCat("clear_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("delete_field_name", absl::StrCat("delete_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
{"upb_msg_name", upb::generator::CApiMessageType(desc->full_name())},
{"const_key", CppConstType(key)},
{"upb_field_name", resolved_upbc_name}},
R"cc(
inline size_t $field_name$_size() const {
inline size_t $field_name_size$() const {
return $upb_msg_name$_$upb_field_name$_size(msg_);
}
inline void clear_$field_name$() {
inline void $clear_field_name$() {
$upb_msg_name$_clear_$upb_field_name$(msg_);
}
void delete_$field_name$($const_key$ key);
void $delete_field_name$($const_key$ key);
)cc");
if (val->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
ctx.Emit({{"field_name", resolved_field_name},
ctx.Emit({Sub("set_field_name", absl::StrCat("set_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("set_alias_field_name",
absl::StrCat("set_alias_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("get_field_name", absl::StrCat("get_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("get_mutable_field_name",
absl::StrCat("get_mutable_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kAlias}),
{"const_key", CppConstType(key)},
{"const_val", CppConstType(val)},
{"ConstPtr", MessagePtrConstType(val, true)},
{"MutPtr", MessagePtrConstType(val, false)}},
R"cc(
bool set_$field_name$($const_key$ key, $ConstPtr$ value);
bool set_$field_name$($const_key$ key, $MutPtr$ value);
bool set_alias_$field_name$($const_key$ key, $ConstPtr$ value);
bool set_alias_$field_name$($const_key$ key, $MutPtr$ value);
absl::StatusOr<$ConstPtr$> get_$field_name$($const_key$ key);
absl::StatusOr<$MutPtr$> get_mutable_$field_name$($const_key$ key);
bool $set_field_name$($const_key$ key, $ConstPtr$ value);
bool $set_field_name$($const_key$ key, $MutPtr$ value);
bool $set_alias_field_name$($const_key$ key, $ConstPtr$ value);
bool $set_alias_field_name$($const_key$ key, $MutPtr$ value);
absl::StatusOr<$ConstPtr$> $get_field_name$($const_key$ key);
absl::StatusOr<$MutPtr$> $get_mutable_field_name$($const_key$ key);
)cc");
} else {
ctx.Emit({{"field_name", resolved_field_name},
ctx.Emit({Sub("set_field_name", absl::StrCat("set_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("get_field_name", absl::StrCat("get_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
{"const_key", CppConstType(key)},
{"const_val", CppConstType(val)}},
R"cc(
bool set_$field_name$($const_key$ key, $const_val$ value);
absl::StatusOr<$const_val$> get_$field_name$($const_key$ key);
bool $set_field_name$($const_key$ key, $const_val$ value);
absl::StatusOr<$const_val$> $get_field_name$($const_key$ key);
)cc");
}
}

View file

@ -200,7 +200,7 @@ void WriteModelPublicDeclaration(
const std::vector<const google::protobuf::FieldDescriptor*>& file_exts,
const std::vector<const google::protobuf::EnumDescriptor*>& file_enums,
Context& ctx) {
ctx.Emit({{"class_name", ClassName(descriptor)},
ctx.Emit({Sub("class_name", ClassName(descriptor)).AnnotatedAs(descriptor),
{"qualified_class_name", QualifiedClassName(descriptor)}},
R"cc(
class $class_name$ final : private internal::$class_name$Access {

View file

@ -11,6 +11,7 @@
#include <vector>
#include "google/protobuf/descriptor.pb.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "hpb_generator/context.h"
#include "hpb_generator/gen_accessors.h"
@ -19,6 +20,7 @@
#include "hpb_generator/gen_utils.h"
#include "hpb_generator/names.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/io/printer.h"
#include "upb_generator/c/names.h"
#include "upb_generator/common.h"
#include "upb_generator/file_layout.h"
@ -27,6 +29,8 @@ namespace google {
namespace protobuf {
namespace hpb_generator {
using Sub = ::google::protobuf::io::Printer::Sub;
// Adds using accessors to reuse base Access class members from a Proxy/CProxy.
void WriteRepeatedFieldUsingAccessors(const google::protobuf::FieldDescriptor* field,
absl::string_view class_name,
@ -73,10 +77,11 @@ void WriteRepeatedFieldsInMessageHeader(const google::protobuf::Descriptor* desc
Context& ctx) {
ctx.Emit(
{{"upb_msg_name", upb::generator::CApiMessageType(desc->full_name())},
{"field_name", resolved_field_name},
Sub("field_name_size", absl::StrCat(resolved_field_name, "_size"))
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
{"upbc_name", resolved_upbc_name}},
R"cc(
inline size_t $field_name$_size() const {
inline size_t $field_name_size$() const {
size_t len;
$upb_msg_name$_$upbc_name$(msg_, &len);
return len;
@ -87,44 +92,75 @@ void WriteRepeatedFieldsInMessageHeader(const google::protobuf::Descriptor* desc
ctx.Emit(
{{"mut_ptr_type", MessagePtrConstType(field, /* const */ false)},
{"const_ptr_type", MessagePtrConstType(field, /* const */ true)},
{"field_name", resolved_field_name},
Sub("field_name", resolved_field_name)
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("mutable_field_name",
absl::StrCat("mutable_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kAlias}),
Sub("add_field_name", absl::StrCat("add_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("add_alias_field_name",
absl::StrCat("add_alias_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
{"upbc_name", resolved_upbc_name},
{"msg_base_type", MessageBaseType(field, /* maybe_const */ false)}},
R"cc(
$const_ptr_type$ $field_name$(size_t index) const;
const ::hpb::RepeatedField<const $msg_base_type$>::CProxy $field_name$() const;
::hpb::Ptr<::hpb::RepeatedField<$msg_base_type$>> mutable_$field_name$();
absl::StatusOr<$mut_ptr_type$> add_$field_name$();
::hpb::Ptr<::hpb::RepeatedField<$msg_base_type$>> $mutable_field_name$();
absl::StatusOr<$mut_ptr_type$> $add_field_name$();
/**
* Re-points submsg of repeated field to given target.
*
* REQUIRES: both messages must be in the same arena.
*/
bool add_alias_$field_name$($mut_ptr_type$ target);
$mut_ptr_type$ mutable_$field_name$(size_t index) const;
bool $add_alias_field_name$($mut_ptr_type$ target);
$mut_ptr_type$ $mutable_field_name$(size_t index) const;
)cc");
} else if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_STRING) {
ctx.Emit({{"cpp_const_type", CppConstType(field)},
{"field_name", resolved_field_name}},
R"cc(
$cpp_const_type$ $field_name$(size_t index) const;
const ::hpb::RepeatedField<$cpp_const_type$>::CProxy $field_name$() const;
::hpb::Ptr<::hpb::RepeatedField<$cpp_const_type$>> mutable_$field_name$();
bool add_$field_name$($cpp_const_type$ val);
void set_$field_name$(size_t index, $cpp_const_type$ val);
bool resize_$field_name$(size_t len);
)cc");
ctx.Emit(
{{"cpp_const_type", CppConstType(field)},
Sub("field_name", resolved_field_name)
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("mutable_field_name",
absl::StrCat("mutable_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kAlias}),
Sub("add_field_name", absl::StrCat("add_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("set_field_name", absl::StrCat("set_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("resize_field_name", absl::StrCat("resize_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone})},
R"cc(
$cpp_const_type$ $field_name$(size_t index) const;
const ::hpb::RepeatedField<$cpp_const_type$>::CProxy $field_name$() const;
::hpb::Ptr<::hpb::RepeatedField<$cpp_const_type$>> $mutable_field_name$();
bool $add_field_name$($cpp_const_type$ val);
void $set_field_name$(size_t index, $cpp_const_type$ val);
bool $resize_field_name$(size_t len);
)cc");
} else {
ctx.Emit({{"cpp_const_type", CppConstType(field)},
{"field_name", resolved_field_name}},
R"cc(
$cpp_const_type$ $field_name$(size_t index) const;
const ::hpb::RepeatedField<$cpp_const_type$>::CProxy $field_name$() const;
::hpb::Ptr<::hpb::RepeatedField<$cpp_const_type$>> mutable_$field_name$();
bool add_$field_name$($cpp_const_type$ val);
void set_$field_name$(size_t index, $cpp_const_type$ val);
bool resize_$field_name$(size_t len);
)cc");
ctx.Emit(
{{"cpp_const_type", CppConstType(field)},
Sub("field_name", resolved_field_name)
.AnnotatedAs({field, io::AnnotationCollector::kNone}),
Sub("mutable_field_name",
absl::StrCat("mutable_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kAlias}),
Sub("add_field_name", absl::StrCat("add_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("set_field_name", absl::StrCat("set_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kSet}),
Sub("resize_field_name", absl::StrCat("resize_", resolved_field_name))
.AnnotatedAs({field, io::AnnotationCollector::kNone})},
R"cc(
$cpp_const_type$ $field_name$(size_t index) const;
const ::hpb::RepeatedField<$cpp_const_type$>::CProxy $field_name$() const;
::hpb::Ptr<::hpb::RepeatedField<$cpp_const_type$>> $mutable_field_name$();
bool $add_field_name$($cpp_const_type$ val);
void $set_field_name$(size_t index, $cpp_const_type$ val);
bool $resize_field_name$(size_t len);
)cc");
}
}

View file

@ -12,6 +12,7 @@
#include <utility>
#include <vector>
#include "google/protobuf/descriptor.pb.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "google/protobuf/compiler/code_generator.h"
@ -329,6 +330,7 @@ bool Generator::Generate(const google::protobuf::FileDescriptor* file,
std::string* error) const {
{
bool strip_nonfunctional_codegen = false;
bool annotate_headers = false;
Backend backend = Backend::UPB;
std::vector<std::pair<std::string, std::string>> params;
google::protobuf::compiler::ParseGeneratorParameter(parameter, &params);
@ -338,14 +340,27 @@ bool Generator::Generate(const google::protobuf::FileDescriptor* file,
strip_nonfunctional_codegen = true;
} else if (pair.first == "backend" && pair.second == "cpp") {
backend = Backend::CPP;
} else if (pair.first == "annotate_headers") {
annotate_headers = true;
} else {
*error = "Unknown parameter: " + pair.first;
return false;
}
}
std::unique_ptr<GeneratedCodeInfo> annotations;
std::unique_ptr<io::AnnotationCollector> annotation_collector;
if (annotate_headers) {
annotations = std::make_unique<GeneratedCodeInfo>();
annotation_collector =
std::make_unique<io::AnnotationProtoCollector<GeneratedCodeInfo>>(
annotations.get());
}
// Write model.hpb.h
Options options = {.backend = backend,
.strip_feature_includes = strip_nonfunctional_codegen};
.strip_feature_includes = strip_nonfunctional_codegen,
.annotation_collector = annotation_collector.get()};
std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> header_output_stream(
context->Open(CppHeaderFilename(file)));
Context hdr_ctx(file, header_output_stream.get(), options);
@ -356,6 +371,16 @@ bool Generator::Generate(const google::protobuf::FileDescriptor* file,
context->Open(CppSourceFilename(file)));
auto cc_ctx = Context(file, cc_output_stream.get(), options);
WriteSource(file, cc_ctx);
if (annotate_headers) {
std::string meta_filename =
absl::StrCat(compiler::StripProto(file->name()), ".hpb.h.meta");
std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> meta_output_stream(
context->Open(meta_filename));
ABSL_CHECK(
annotations->SerializeToZeroCopyStream(meta_output_stream.get()));
}
return true;
}
}

View file

@ -257,3 +257,22 @@ cc_test(
"@googletest//:gtest_main",
],
)
cc_test(
name = "metadata_test",
srcs = ["metadata_test.cc"],
deps = [
"//hpb_generator:generator",
"//src/google/protobuf",
"//src/google/protobuf/compiler:annotation_test_util",
"//src/google/protobuf/compiler:code_generator_lite",
"//src/google/protobuf/compiler:command_line_interface",
"//src/google/protobuf/testing",
"//src/google/protobuf/testing:file",
"@abseil-cpp//absl/container:flat_hash_map",
"@abseil-cpp//absl/log:absl_check",
"@abseil-cpp//absl/strings",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)

View file

@ -0,0 +1,170 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC. 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
#include <string>
#include <vector>
#include "google/protobuf/testing/file.h"
#include "google/protobuf/testing/file.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/container/flat_hash_map.h"
#include "absl/log/absl_check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/compiler/annotation_test_util.h"
#include "google/protobuf/compiler/code_generator_lite.h"
#include "google/protobuf/compiler/command_line_interface.h"
#include "hpb_generator/generator.h"
#include "google/protobuf/descriptor.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace hpb {
namespace atu = annotation_test_util;
namespace {
using ::testing::IsEmpty;
using Annotation = GeneratedCodeInfo::Annotation;
class HpbMetadataTest : public ::testing::Test {
public:
// Tries to capture a FileDescriptorProto, GeneratedCodeInfo, and output
// code from the previously added file with name `filename`. Returns true on
// success.
bool CaptureMetadata(const std::string& filename, FileDescriptorProto& file,
std::string& hpb_h, GeneratedCodeInfo& hpb_h_info) {
CommandLineInterface cli;
hpb_generator::Generator hpb_generator;
cli.RegisterGenerator("--hpb_out", &hpb_generator, "");
std::string hpb_out =
absl::StrCat("--hpb_out=annotate_headers=true:", ::testing::TempDir());
const bool result = atu::RunProtoCompiler(filename, hpb_out, &cli, &file);
if (!result) {
return result;
}
std::string output_base =
absl::StrCat(::testing::TempDir(), "/", StripProto(filename));
ABSL_CHECK_OK(File::GetContents(absl::StrCat(output_base, ".hpb.h"), &hpb_h,
true));
if (!atu::DecodeMetadata(absl::StrCat(output_base, ".hpb.h.meta"),
&hpb_h_info)) {
return false;
}
return true;
}
// Helper function to get annotations by the provided path and verify that
// these annotations contain a subset of the expected annotations (by
// substring) + semantics.
void ExpectAnnotationsForPathContain(
const GeneratedCodeInfo& info, const std::string& filename,
const std::string& hpb_h, const std::vector<int>& path,
const absl::flat_hash_map<std::string, Annotation::Semantic>&
expected_annotations) {
absl::flat_hash_map<std::string, Annotation::Semantic>
expected_annotations_copy = expected_annotations;
std::vector<const GeneratedCodeInfo::Annotation*> annotations;
atu::FindAnnotationsOnPath(info, filename, path, &annotations);
EXPECT_TRUE(!annotations.empty());
for (const auto* annotation : annotations) {
auto substring = atu::GetAnnotationSubstring(hpb_h, *annotation);
ASSERT_TRUE(substring.has_value());
if (auto node = expected_annotations_copy.extract(*substring);
!node.empty()) {
EXPECT_EQ(node.mapped(), annotation->semantic())
<< "for substring " << *substring;
}
}
EXPECT_THAT(expected_annotations_copy, IsEmpty())
<< "substrings above were not found in the annotations.";
}
};
constexpr absl::string_view kSmallTestFile = R"schema(syntax = "proto2";
package foo;
message Message { }
)schema";
TEST_F(HpbMetadataTest, CapturesMessageNames) {
FileDescriptorProto file;
GeneratedCodeInfo info;
std::string hpb_h;
atu::AddFile("test.proto", kSmallTestFile);
EXPECT_TRUE(CaptureMetadata("test.proto", file, hpb_h, info));
EXPECT_EQ("Message", file.message_type(0).name());
std::vector<int> message_path;
message_path.push_back(FileDescriptorProto::kMessageTypeFieldNumber);
message_path.push_back(0);
const GeneratedCodeInfo::Annotation* message_annotation =
atu::FindAnnotationOnPath(info, "test.proto", message_path);
EXPECT_TRUE(nullptr != message_annotation);
EXPECT_TRUE(
atu::AnnotationMatchesSubstring(hpb_h, message_annotation, "Message"));
}
constexpr absl::string_view kStringFieldTestFile = R"schema(
syntax = "proto2";
package foo;
message Message {
optional string sfield = 1;
repeated string rsfield = 2;
}
)schema";
TEST_F(HpbMetadataTest, AnnotatesStringSemantics) {
FileDescriptorProto file;
GeneratedCodeInfo info;
std::string hpb_h;
atu::AddFile("test.proto", kStringFieldTestFile);
EXPECT_TRUE(CaptureMetadata("test.proto", file, hpb_h, info));
EXPECT_EQ("Message", file.message_type(0).name());
// Check annotations for `sfield`.
std::vector<int> field_path{
FileDescriptorProto::kMessageTypeFieldNumber,
0,
DescriptorProto::kFieldFieldNumber,
0,
};
ExpectAnnotationsForPathContain(info, "test.proto", hpb_h, field_path,
{
{"sfield", Annotation::NONE},
{"set_sfield", Annotation::SET},
{"has_sfield", Annotation::NONE},
{"clear_sfield", Annotation::NONE},
});
// Check annotations for `rsfield`.
field_path = {
FileDescriptorProto::kMessageTypeFieldNumber,
0,
DescriptorProto::kFieldFieldNumber,
1,
};
ExpectAnnotationsForPathContain(info, "test.proto", hpb_h, field_path,
{
{"rsfield", Annotation::NONE},
{"add_rsfield", Annotation::SET},
{"mutable_rsfield", Annotation::ALIAS},
{"rsfield_size", Annotation::NONE},
});
}
} // namespace
} // namespace hpb
} // namespace compiler
} // namespace protobuf
} // namespace google

View file

@ -7,7 +7,7 @@ load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
def upb_use_cpp_toolchain():
return use_cpp_toolchain()
def cc_library_func(ctx, name, hdrs, srcs, copts, dep_ccinfos, includes = [], alwayslink = False):
def cc_library_func(ctx, name, hdrs, srcs, copts, dep_ccinfos, includes = [], alwayslink = False, **kwargs):
"""Like cc_library(), but callable from rules.
Args:
@ -18,6 +18,8 @@ def cc_library_func(ctx, name, hdrs, srcs, copts, dep_ccinfos, includes = [], al
copts: Additional options for cc compilation.
dep_ccinfos: CcInfo providers of dependencies we should build/link against.
includes: Additional include paths.
alwayslink: Whether the library should be always linked.
**kwargs: Additional arguments passed to cc_common.compile.
Returns:
CcInfo provider for this compilation.
@ -43,6 +45,7 @@ def cc_library_func(ctx, name, hdrs, srcs, copts, dep_ccinfos, includes = [], al
public_hdrs = hdrs,
user_compile_flags = copts,
compilation_contexts = compilation_contexts,
**kwargs
)
# buildifier: disable=unused-variable