1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
|
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Generate extra functionality for protobuf messages, including:
# - Serialization to base::Value.
# - Stream operator support for C++ printing.
# - equality operator support
# - (future) gtest matchers.
# This does not directly generate the protobuf bindings for any language, so
# callers must include the build target for C++ bindings in the deps. It
# is intended to be used in conjunction with proto_library, not on its own.
#
# Conversion to `base::Value` support:
# - The main generated method is `Serialize(const ProtoMessage& message)`, which
# returns a `base::Value` (as a dictionary), and resides in the namespace of
# the proto message.
# - There is a helper method to facilitate the serialization of optional
# messages, called `MaybeSerialize(const std::optional<ProtoMessage>&
# message, std::string_view name, base::DictValue& output_dictionary)`.
# This will serialize the proto to a `base::DictValue` and set it on the
# passed in `output_dictionary` with the given `name`.
# - Include via <name>.to_value.h.
# - Disable via `omit_to_value_serialization` (default is false)
# - Caveats:
# - Integer types in the proto that are not compatible with base::Value are
# serialized as strings (e.g. uint64_t).
# - Map field scalar keys (numbers) are always converted to a string, as
# `base::Value` does not support other types as keys for dictionaries.
#
# Stream operator (<<) support:
# - The generated operator resides in the namespace of the proto message.
# - Include via <name>.ostream.h.
# - Disable via `omit_stream_operators` (default is false).
#
# Equality operator (==) support:
# - The generated operator resides in the namespace of the proto message.
# - Include via <name>.equal.h.
# - Disable via `omit_equality` (default is false).
#
# The proto_extras template reads the following other properties:
# - sources (required): The .proto files to generate from.
# - proto_in_dir: The directory to find the proto files in. Defaults to "//".
# - deps: These should be proto_library targets.
# - extras_deps: These are the proto_extras targets for any proto dependencies.
# - defines: This is forwarded to all generated targets.
# - visibility: This is forwarded to all generated targets.
#
# The functionality is split up per-file (instead of everything in a 'utils' or
# 'extras' file) as per go/no-utils guidance. This:
# - Helps prevent scope creep of lots of functionality in one file, and
# provides the infrastructure to easily make more functionality in a dedicated file.
# - Reduces header includes and build size.
# - Minimizes the build graph if users only need one piece of functionality.
#
# For cases where the message uses the full google::protobuf::Message type,
# the protobuf_full_support option can be used to ensure the generated code
# with the full protobuf library. Due to android build complications, this also
# requires the `use_fuzzing_engine_with_lpm` build flag to be set.
# This option is relevant for base::Value serialization and equality.
#
# Example:
# proto_extras("foo_extras") {
# sources = [
# "foo.proto",
# ]
# deps = [
# ":foo", # The target generating foo.pb.h
# ":foo_dependency", # The target generating foo_dependency.pb.h
# ]
# extras_deps = [
# ":foo_dependency_extras", # The proto_extras target for foo_dependency.
# ]
# omit_to_value_serialization = true # default is false
# omit_stream_operators = true # default is false
# protobuf_full_support = true # default is false
# # Note: This target fails as there is no functionality being generated!
# }
#
# Consumers would then depend on ":foo_extras", and include foo.to_value.h,
# foo.ostream.h, etc.
#
# Other Notes:
# - base::DictValue is the preferred type to base::Value::Dict, as it is
# forward-declarable, allowing for less header includes.
import("//testing/libfuzzer/fuzzer_test.gni")
import("//third_party/protobuf/proto_library.gni")
template("proto_extras") {
_generate_to_value = true
if (defined(invoker.omit_to_value_serialization) &&
invoker.omit_to_value_serialization) {
_generate_to_value = false
}
_generate_ostream = true
if (defined(invoker.omit_stream_operators) &&
!invoker.omit_stream_operators) {
_generate_ostream = false
}
_generate_equality = true
if (defined(invoker.omit_equality) && invoker.omit_equality) {
_generate_equality = false
}
_protobuf_full_support = false
if (defined(invoker.protobuf_full_support) && invoker.protobuf_full_support &&
use_fuzzing_engine_with_lpm) {
_protobuf_full_support = true
}
_proto_in_dir = "//"
if (defined(invoker.proto_in_dir)) {
_proto_in_dir = invoker.proto_in_dir
}
assert(
!_generate_ostream || _generate_to_value,
"Stream operator generation currently depends on ToValue " +
"serialization. Cannot set omit_to_value_serialization = true if " +
"omit_stream_operators = false. Target: ${target_name}")
assert(_generate_to_value || _generate_ostream || _generate_equality,
"There must be one generation type enabled. Target: ${target_name}")
_extras_deps = []
if (defined(invoker.extras_deps)) {
_extras_deps += invoker.extras_deps
}
_all_targets = []
_to_value_target_name = "${target_name}_to_value"
if (_generate_to_value) {
_all_targets += [ _to_value_target_name ]
proto_library(_to_value_target_name) {
proto_in_dir = _proto_in_dir
forward_variables_from(invoker,
[
"deps",
"defines",
"sources",
"visibility",
])
link_deps = [ "//components/proto_extras:proto_extras_lib" ]
if (_protobuf_full_support) {
link_deps += [ "//components/proto_extras:protobuf_full_support" ]
}
# Link the *_to_value targets for all extras_deps.
_extras_deps_to_value = []
foreach(_dep, _extras_deps) {
_extras_deps_to_value += [ "${_dep}_to_value" ]
}
link_deps += _extras_deps_to_value
generator_plugin_label = "//components/proto_extras:proto_extras_plugin"
generator_plugin_suffix = ".to_value"
generate_cc = false
generate_python = false
link_public_deps = [ "//base" ]
_to_value_options_list = [ "generate_to_value_serialization" ]
if (_protobuf_full_support) {
_to_value_options_list += [ "protobuf_full_support" ]
}
generator_plugin_options = string_join(",", _to_value_options_list)
}
}
if (_generate_ostream) {
_ostream_target_name = "${target_name}_ostream"
_all_targets += [ _ostream_target_name ]
proto_library(_ostream_target_name) {
proto_in_dir = _proto_in_dir
forward_variables_from(invoker,
[
"defines",
"sources",
"visibility",
])
deps = [ ":$_to_value_target_name" ]
if (defined(invoker.deps)) {
deps += invoker.deps
}
link_deps = [ "//base" ]
generator_plugin_label = "//components/proto_extras:proto_extras_plugin"
generator_plugin_suffix = ".ostream"
generate_cc = false
generate_python = false
generator_plugin_options = "generate_stream_operator"
}
}
if (_generate_equality) {
_equality_target_name = "${target_name}_equal"
_all_targets += [ _equality_target_name ]
proto_library(_equality_target_name) {
proto_in_dir = _proto_in_dir
forward_variables_from(invoker,
[
"deps",
"defines",
"sources",
"visibility",
])
# Link the *_to_value targets for all extras_deps.
_extras_deps_equality = []
foreach(_dep, _extras_deps) {
_extras_deps_equality += [ "${_dep}_equal" ]
}
link_deps = _extras_deps_equality
_equality_options_list = [ "generate_equality" ]
if (_protobuf_full_support) {
_equality_options_list += [ "protobuf_full_support" ]
link_deps += [ "//components/proto_extras:protobuf_full_support" ]
}
generator_plugin_label = "//components/proto_extras:proto_extras_plugin"
generator_plugin_suffix = ".equal"
generate_cc = false
generate_python = false
generator_plugin_options = string_join(",", _equality_options_list)
}
}
# Group all generated targets into one group for the target_name.
# Users can techncally depend on the generated target names for a more
# optimized build graph, but it is uncommon in the codebase to not depend on a
# target that is specifically named, so this ensures that the normal behavior
# works.
group(target_name) {
forward_variables_from(invoker,
[
"defines",
"visibility",
])
public_deps = []
foreach(_target, _all_targets) {
public_deps += [ ":$_target" ]
}
}
}
# Generate extra test functionality for protobuf messages. Similar to
# proto_extras, this does not generate protobuf bindings, but instead generates
# gtest matchers for the generated protobuf bindings.
#
# Features:
# - Gmock matchers for all messages, for verbose equality checking in tests
# - PrintTo implementations for gtest output.
#
# The functionality is output to a file named <name>.test.h.
#
# The proto_test_extras template supports the following other properties:
# - test_extras_deps: These are the proto_test_extras targets for any proto
# dependencies.
# - extras_deps: These are the proto_extras targets for any proto dependencies.
# - sources: The .proto files to generate from.
# - deps: These should be proto_library targets.
# - defines: This is forwarded to all generated targets.
# - visibility: This is forwarded to all generated targets.
#
# The reason this target needs the proto_extras target is that the generated
# code uses the generated `Serialize` <name>.to_value.h serialization.
#
# Example usage:
# proto_test_extras("foo_test_extras") {
# sources = [
# "foo.proto",
# ]
# deps = [
# ":foo",
# ":foo_dependency", # The target generating foo_dependency.pb.h
# ]
# test_extras_deps = [
# ":foo_dependency_test_extras",
# ]
# extras_deps = [
# ":foo_dependency_extras",
# ]
# }
template("proto_test_extras") {
testonly = true
_test_extras_deps = []
if (defined(invoker.test_extras_deps)) {
_test_extras_deps += invoker.test_extras_deps
}
_extras_deps = []
if (defined(invoker.extras_deps)) {
_extras_deps += invoker.extras_deps
}
_proto_in_dir = "//"
if (defined(invoker.proto_in_dir)) {
_proto_in_dir = invoker.proto_in_dir
}
if (defined(invoker.deps) && invoker.deps != [] && _extras_deps == []) {
assert(
false,
"The `deps` field is set, but the `extras_deps` field is not set. " +
"Protobuf depependencies need to have a corresponding proto_extras " +
" target so the generated matchers can use the `Serialize()` call.")
}
proto_library("${target_name}") {
testonly = true
proto_in_dir = _proto_in_dir
forward_variables_from(invoker,
[
"defines",
"visibility",
"deps",
"sources",
])
generator_plugin_label =
"//components/proto_extras:proto_test_extras_plugin"
generator_plugin_suffix = ".test"
generate_cc = false
generate_python = false
link_deps = []
foreach(_dep, _extras_deps) {
link_deps += [ "${_dep}_to_value" ]
}
# Due to gmock/gtest being an entirely header-based library, all
# dependencies need to be publicly linked as there is no private
# implementation.
link_public_deps = [
"//base",
"//components/proto_extras:proto_matchers",
"//testing/gmock",
]
link_public_deps += _test_extras_deps
}
}
|