pw_protobuf: Codegen kConstantCase field enums
pw_protobuf generated code includes an "enum class Fields : uint32_t"
type in in message, with values equal to the IDs for each field in the
message.
Unfortunately, the existing generated code uses SNAKE_CASE for the
names, rather than kConstantCase. In addition to be against the
style guide, this makes collisions with legacy C macros much more
likely.
Indeed, I ran into this issue due to a naming collison on OUTPUT_TYPE
between stm32cube.h and descriptor.pwpb.h. Of course, ST deserves
significant blame here for using inadequately namespaced global
identifiers... it's not just pw_protobuf's fault for not using
kConstantCase.
This commit does not yet migrate pigweed code to use the kConstantCase
values, that will be in an immediate follow-on. However, this commit
DOES add a GN variable to control generation of the legacy
names (in addition to new names):
pw_protobuf_compiler_GENERATE_LEGACY_ENUM_SNAKE_CASE_NAMES
This currently defaults to true, but will change to default to false
after the follow-on commit which migrates upstream pigweed away from
SNAKE_CASE. That will have to be done by an upstream pigweed developer.
Bug: b/266298474
Change-Id: I460a7d203905d39d234d3bf1a7621b52b188c109
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/126804
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_protobuf/docs.rst b/pw_protobuf/docs.rst
index 7ebfb14..2bbcda3 100644
--- a/pw_protobuf/docs.rst
+++ b/pw_protobuf/docs.rst
@@ -373,15 +373,15 @@
while ((status = decoder.Next()).ok()) {
switch (decoder.Field().value()) {
- case Customer::Fields::AGE: {
+ case Customer::Fields::kAge: {
PW_TRY_ASSIGN(age, decoder.ReadAge());
break;
}
- case Customer::Fields::NAME: {
+ case Customer::Fields::kName: {
PW_TRY(decoder.ReadName(name));
break;
}
- case Customer::Fields::STATUS: {
+ case Customer::Fields::kStatus: {
PW_TRY_ASSIGN(status, decoder.ReadStatus());
break;
}
@@ -391,6 +391,15 @@
return status.IsOutOfRange() ? OkStatus() : status;
}
+.. warning:: ``Fields::SNAKE_CASE`` is deprecated. Use ``Fields::kCamelCase``.
+
+ Transitional support for ``Fields::SNAKE_CASE`` will soon only be available by
+ explicitly setting the following GN variable in your project:
+ ``pw_protobuf_compiler_GENERATE_LEGACY_ENUM_SNAKE_CASE_NAMES=true``
+
+ This support will be removed after downstream projects have been migrated.
+
+
Direct Writers and Readers
==========================
The lowest level API is provided by the core C++ implementation, and requires
@@ -432,12 +441,12 @@
#include "example_protos/customer.pwpb.h"
Status EncodeCustomer(pw::protobuf::StreamEncoder& encoder) {
- PW_TRY(encoder.WriteInt32(static_cast<uint32_t>(Customer::Fields::AGE),
+ PW_TRY(encoder.WriteInt32(static_cast<uint32_t>(Customer::Fields::kAge),
33));
- PW_TRY(encoder.WriteString(static_cast<uint32_t>(Customer::Fields::NAME),
+ PW_TRY(encoder.WriteString(static_cast<uint32_t>(Customer::Fields::kName),
"Joe Bloggs"sv));
PW_TRY(encoder.WriteUint32(
- static_cast<uint32_t>(Customer::Fields::STATUS),
+ static_cast<uint32_t>(Customer::Fields::kStatus),
static_cast<uint32_t>(Customer::Status::INACTIVE)));
}
@@ -475,15 +484,15 @@
while ((status = decoder.Next()).ok()) {
switch (decoder.FieldNumber().value()) {
- case static_cast<uint32_t>(Customer::Fields::AGE): {
+ case static_cast<uint32_t>(Customer::Fields::kAge): {
PW_TRY_ASSIGN(age, decoder.ReadInt32());
break;
}
- case static_cast<uint32_t>(Customer::Fields::NAME): {
+ case static_cast<uint32_t>(Customer::Fields::kName): {
PW_TRY(decoder.ReadString(name));
break;
}
- case static_cast<uint32_t>(Customer::Fields::STATUS): {
+ case static_cast<uint32_t>(Customer::Fields::kStatus): {
uint32_t status_value;
PW_TRY_ASSIGN(status_value, decoder.ReadUint32());
status = static_cast<Customer::Status>(status_value);
@@ -1318,7 +1327,7 @@
.. code:: c++
my_proto_encoder.WriteAge(42);
- my_proto_encoder.WriteInt32(static_cast<uint32_t>(MyProto::Fields::AGE), 42);
+ my_proto_encoder.WriteInt32(static_cast<uint32_t>(MyProto::Fields::kAge), 42);
Repeated Fields
---------------
@@ -1371,7 +1380,7 @@
my_proto_encoder.WriteNumbers(numbers);
my_proto_encoder.WritePackedInt32(
- static_cast<uint32_t>(MyProto::Fields::NUMBERS),
+ static_cast<uint32_t>(MyProto::Fields::kNumbers),
numbers);
Enumerations
@@ -1392,7 +1401,7 @@
my_proto_encoder.WriteAward(MyProto::Award::SILVER);
my_proto_encoder.WriteUint32(
- static_cast<uint32_t>(MyProto::Fields::AWARD),
+ static_cast<uint32_t>(MyProto::Fields::kAward),
static_cast<uint32_t>(MyProto::Award::SILVER));
Repeated Fields
@@ -1530,11 +1539,11 @@
// However, Field() is guaranteed to be valid after a call to Next()
// that returns OK, so the value can be used directly here.
switch (decoder.Field().value()) {
- case MyProto::Fields::AGE: {
+ case MyProto::Fields::kAge: {
PW_TRY_ASSIGN(age, decoder.ReadAge());
break;
}
- case MyProto::Fields::NAME:
+ case MyProto::Fields::kName:
// The string field is copied into the provided buffer. If the buffer
// is too small to fit the string, RESOURCE_EXHAUSTED is returned and
// the decoder is not advanced, allowing the field to be re-read.
@@ -1566,7 +1575,7 @@
Store::Message store{};
store.employees.SetDecoder([](Store::StreamDecoder& decoder) {
- PW_ASSERT(decoder.Field().value() == Store::Fields::EMPLOYEES);
+ PW_ASSERT(decoder.Field().value() == Store::Fields::kEmployees);
Employee::Message employee{};
// Set any callbacks on `employee`.
@@ -1604,7 +1613,7 @@
.. code:: c++
- case Owner::Fields::PET: {
+ case Owner::Fields::kPet: {
// Note that the parent decoder, owner_decoder, cannot be used until the
// nested decoder, pet_decoder, has been destroyed.
Animal::StreamDecoder pet_decoder = owner_decoder.GetPetDecoder();
@@ -1652,7 +1661,7 @@
.. code:: c++
PW_ASSERT(my_proto_decoder.FieldNumber().value() ==
- static_cast<uint32_t>(MyProto::Fields::AGE));
+ static_cast<uint32_t>(MyProto::Fields::kAge));
pw::Result<int32_t> my_proto_decoder.ReadInt32();
Repeated Fields
@@ -1718,7 +1727,7 @@
pw::Vector<int32_t, 8> numbers;
PW_ASSERT(my_proto_decoder.FieldNumber().value() ==
- static_cast<uint32_t>(MyProto::Fields::NUMBERS));
+ static_cast<uint32_t>(MyProto::Fields::kNumbers));
my_proto_decoder.ReadRepeatedInt32(numbers);
Enumerations
@@ -1758,7 +1767,7 @@
.. code-block:: c++
PW_ASSERT(my_proto_decoder.FieldNumber().value() ==
- static_cast<uint32_t>(MyProto::Fields::AWARD));
+ static_cast<uint32_t>(MyProto::Fields::kAward));
pw::Result<uint32_t> award_value = my_proto_decoder.ReadUint32();
if (award_value.ok()) {
MyProto::Award award = static_cast<MyProto::Award>(award_value);
diff --git a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
index 2a3346b..d5eadaa 100644
--- a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
+++ b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
@@ -256,7 +256,8 @@
Result<{ctype}> ReadFoo({params...}) {
Result<uint32_t> field_number = FieldNumber();
PW_ASSERT(field_number.ok());
- PW_ASSERT(field_number.value() == static_cast<uint32_t>(Fields::FOO));
+ PW_ASSERT(field_number.value() ==
+ static_cast<uint32_t>(Fields::kFoo));
return decoder_->Read{type}({params...});
}
@@ -2315,7 +2316,10 @@
def forward_declare(
- node: ProtoMessage, root: ProtoNode, output: OutputFile
+ node: ProtoMessage,
+ root: ProtoNode,
+ output: OutputFile,
+ exclude_legacy_snake_case_field_name_enums: bool,
) -> None:
"""Generates code forward-declaring entities in a message's namespace."""
namespace = node.cpp_namespace(root=root)
@@ -2327,6 +2331,14 @@
with output.indent():
for field in node.fields():
output.write_line(f'{field.enum_name()} = {field.number()},')
+
+ # Migration support from SNAKE_CASE to kConstantCase.
+ if not exclude_legacy_snake_case_field_name_enums:
+ for field in node.fields():
+ output.write_line(
+ f'{field.legacy_enum_name()} = {field.number()},'
+ )
+
output.write_line('};')
# Declare the message's message struct.
@@ -2551,6 +2563,7 @@
package: ProtoNode,
output: OutputFile,
suppress_legacy_namespace: bool,
+ exclude_legacy_snake_case_field_name_enums: bool,
) -> None:
"""Generates code for a single .pb.h file corresponding to a .proto file."""
@@ -2593,7 +2606,12 @@
for node in package:
if node.type() == ProtoNode.Type.MESSAGE:
- forward_declare(cast(ProtoMessage, node), package, output)
+ forward_declare(
+ cast(ProtoMessage, node),
+ package,
+ output,
+ exclude_legacy_snake_case_field_name_enums,
+ )
# Define all top-level enums.
for node in package.children():
@@ -2677,7 +2695,10 @@
def process_proto_file(
- proto_file, proto_options, suppress_legacy_namespace: bool
+ proto_file,
+ proto_options,
+ suppress_legacy_namespace: bool,
+ exclude_legacy_snake_case_field_name_enums: bool,
) -> Iterable[OutputFile]:
"""Generates code for a single .proto file."""
@@ -2690,7 +2711,11 @@
output_filename = _proto_filename_to_generated_header(proto_file.name)
output_file = OutputFile(output_filename)
generate_code_for_package(
- proto_file, package_root, output_file, suppress_legacy_namespace
+ proto_file,
+ package_root,
+ output_file,
+ suppress_legacy_namespace,
+ exclude_legacy_snake_case_field_name_enums,
)
return [output_file]
diff --git a/pw_protobuf/py/pw_protobuf/plugin.py b/pw_protobuf/py/pw_protobuf/plugin.py
index 9315b76..bc3c405 100755
--- a/pw_protobuf/py/pw_protobuf/plugin.py
+++ b/pw_protobuf/py/pw_protobuf/plugin.py
@@ -53,6 +53,12 @@
help='If set, suppresses `using namespace` declarations, which '
'disallows use of the legacy non-prefixed namespace',
)
+ parser.add_argument(
+ '--exclude-legacy-snake-case-field-name-enums',
+ dest='exclude_legacy_snake_case_field_name_enums',
+ action='store_true',
+ help='Do not generate legacy SNAKE_CASE names for field name enums.',
+ )
# protoc passes the custom arguments in shell quoted form, separated by
# commas. Use shlex to split them, correctly handling quoted sections, with
@@ -87,6 +93,9 @@
proto_file,
proto_options,
suppress_legacy_namespace=args.no_legacy_namespace,
+ exclude_legacy_snake_case_field_name_enums=(
+ args.exclude_legacy_snake_case_field_name_enums
+ ),
)
for output_file in output_files:
fd = res.file.add()
diff --git a/pw_protobuf/py/pw_protobuf/proto_tree.py b/pw_protobuf/py/pw_protobuf/proto_tree.py
index 44c8804..15bfb73 100644
--- a/pw_protobuf/py/pw_protobuf/proto_tree.py
+++ b/pw_protobuf/py/pw_protobuf/proto_tree.py
@@ -505,6 +505,9 @@
return self._field_name
def enum_name(self) -> str:
+ return 'k' + self.name()
+
+ def legacy_enum_name(self) -> str:
return self.upper_snake_case(
symbol_name_mapping.fix_cc_enum_value_name(self._field_name)
)
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
index 8a69770..8a62801 100644
--- a/pw_protobuf_compiler/proto.gni
+++ b/pw_protobuf_compiler/proto.gni
@@ -85,17 +85,21 @@
deps += invoker.other_deps
}
- args = [
- "--language",
- invoker.language,
- "--include-file",
- _includes[0],
- "--compile-dir",
- rebase_path(invoker.compile_dir, root_build_dir),
- "--out-dir",
- rebase_path(_out_dir, root_build_dir),
- "--sources",
- ] + rebase_path(invoker.sources, root_build_dir)
+ args = []
+ if (!pw_protobuf_compiler_GENERATE_LEGACY_ENUM_SNAKE_CASE_NAMES) {
+ args += [ "--exclude-pwpb-legacy-snake-case-field-name-enums" ]
+ }
+ args += [
+ "--language",
+ invoker.language,
+ "--include-file",
+ _includes[0],
+ "--compile-dir",
+ rebase_path(invoker.compile_dir, root_build_dir),
+ "--out-dir",
+ rebase_path(_out_dir, root_build_dir),
+ "--sources",
+ ] + rebase_path(invoker.sources, root_build_dir)
if (defined(invoker.plugin)) {
inputs = [ invoker.plugin ]
diff --git a/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py b/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
index a87dcda..95e2605 100644
--- a/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
+++ b/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
@@ -81,6 +81,15 @@
action='store_false',
help='Do not generate pyi files for python',
)
+ parser.add_argument(
+ '--exclude-pwpb-legacy-snake-case-field-name-enums',
+ dest='exclude_pwpb_legacy_snake_case_field_name_enums',
+ action='store_true',
+ help=(
+ 'If set, generates legacy SNAKE_CASE names for field name enums '
+ 'in PWPB.'
+ ),
+ )
return parser
@@ -95,15 +104,27 @@
def protoc_pwpb_args(
args: argparse.Namespace, include_paths: List[str]
) -> Tuple[str, ...]:
- return (
+ out_args = [
'--plugin',
f'protoc-gen-custom={args.plugin_path}',
f'--custom_opt=-I{args.compile_dir}',
*[f'--custom_opt=-I{include_path}' for include_path in include_paths],
- '--custom_out',
- args.out_dir,
+ ]
+
+ if args.exclude_pwpb_legacy_snake_case_field_name_enums:
+ out_args.append(
+ '--custom_opt=--exclude-legacy-snake-case-field-name-enums'
+ )
+
+ out_args.extend(
+ [
+ '--custom_out',
+ args.out_dir,
+ ]
)
+ return tuple(out_args)
+
def protoc_pwpb_rpc_args(
args: argparse.Namespace, _include_paths: List[str]
diff --git a/pw_protobuf_compiler/toolchain.gni b/pw_protobuf_compiler/toolchain.gni
index 606e69d..806a963 100644
--- a/pw_protobuf_compiler/toolchain.gni
+++ b/pw_protobuf_compiler/toolchain.gni
@@ -20,4 +20,10 @@
# in a single toolchain to avoid unnecessary duplication in the build.
pw_protobuf_compiler_TOOLCHAIN =
"$dir_pw_protobuf_compiler/toolchain:protocol_buffer"
+
+ # pwpb previously generated field enum names in SNAKE_CASE rather than
+ # kConstantCase. Set this variable to temporarily enable legacy SNAKE_CASE
+ # support while you migrate your codebase to kConstantCase.
+ # b/266298474
+ pw_protobuf_compiler_GENERATE_LEGACY_ENUM_SNAKE_CASE_NAMES = true
}