Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hpp_options #54

Merged
merged 21 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ build
CMakeUserPresets.json
out
.vscode
.vs
.vs
.qodo
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ if(HPP_PROTO_TESTS)
endif()

if(HPP_PROTO_PROTOC_PLUGIN)
include(cmake/protobuf_generate.cmake)
include(cmake/protobuf_generate_hpp.cmake)

add_subdirectory(protoc-plugin)

Expand Down Expand Up @@ -150,7 +150,7 @@ if(HPP_PROTO_PROTOC_PLUGIN)
configure_file(hpp_proto-config.cmake.in lib/cmake/hpp_proto/hpp_proto-config.cmake @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/hpp_proto/hpp_proto-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/hpp_proto/hpp_proto-config-version.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/protobuf_generate.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/protobuf_generate_hpp.cmake"
DESTINATION "lib/cmake/hpp_proto")

install(
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/owning)
add_library(hpp_owning_benchmark_messages_lib INTERFACE ${proto_files})
protobuf_generate_hpp(TARGET hpp_owning_benchmark_messages_lib
PROTOC_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/owning
PLUGIN_OPTIONS "root_namespace=owning")
PLUGIN_OPTIONS "namespace_prefix=owning")

file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/non_owning)
add_library(hpp_non_owning_benchmark_messages_lib INTERFACE ${proto_files})
protobuf_generate_hpp(TARGET hpp_non_owning_benchmark_messages_lib
PROTOC_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/non_owning
PLUGIN_OPTIONS "non_owning,root_namespace=non_owning")
PLUGIN_OPTIONS "non_owning,namespace_prefix=non_owning")

add_library(google_benchmark_messages_lib OBJECT ${proto_files})
protobuf_generate(TARGET google_benchmark_messages_lib
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ function(protobuf_generate_hpp)
DEPENDENCIES hpp_proto::protoc-gen-hpp)

include(CMakeParseArguments)
cmake_parse_arguments(protobuf_generate_hpp "" "TARGET" "" "${ARGN}")
cmake_parse_arguments(protobuf_generate_hpp "" "TARGET;PLUGIN_OPTIONS;PROTOC_OUT_DIR" "" "${ARGN}")
if (protobuf_generate_hpp_TARGET)
get_target_property(_target_type ${protobuf_generate_hpp_TARGET} TYPE)

Expand All @@ -200,4 +200,12 @@ function(protobuf_generate_hpp)

target_link_libraries(${protobuf_generate_hpp_TARGET} ${_scope} hpp_proto::libhpp_proto)
endif()

if (protobuf_generate_hpp_PLUGIN_OPTIONS MATCHES "directory_prefix=([^,]+)")
set(_dir_prefix "${CMAKE_MATCH_1}")
get_filename_component(_abs_output_dir "${protobuf_generate_hpp_PROTOC_OUT_DIR}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
if (NOT "${_abs_output_dir}" MATCHES "${_dir_prefix}$")
message(FATAL_ERROR "the value of PROTOC_OUT_DIR, i.e. ${_abs_output_dir}, does not ends with the directory_prefix parameter, i.e. ${_dir_prefix}")
endif()
endif()
endfunction()
51 changes: 32 additions & 19 deletions docs/Code_Generation_Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ protobuf_generate_hpp(
[PLUGIN_OPTIONS <plugin_options>])
```

- IMPORT_DIRS: Specifies common parent directories for schema files.
- PROTOC_OUT_DIR: Output directory for generated source files. Defaults to CMAKE_CURRENT_BINARY_DIR.
- PROTOS: List of proto schema files. If omitted, then every source file ending in proto of TARGET will be used.
- PLUGIN_OPTIONS: A comma-separated string forwarded to the protoc-gen-hpp plugin to customize code generation. Options include:
* `root_namespace=`: Prepends a root namespace to the generated code, in addition to the package namespace.
* `top_directory=`: Prepends a directory to all import dependencies.
* `proto2_explicit_presence=`: For Proto2 only, makes optional scalar fields implicitly present except for specified scopes. This option can be specified multiple times. For example: the option `proto2_explicit_presence=.pkg1.msg1.field1,proto2_explicit_presence=.pkg1.msg2` instructs the code generator that explicit presence is only applicable for the `field1` of `pkg1.msg1` and all fields of `pkg1.msg2`.
* `non_owning`: Generates non-owning messages.
- IMPORT_DIRS: Specifies common parent directories for schema files.
- PROTOC_OUT_DIR: Output directory for generated source files. Defaults to CMAKE_CURRENT_BINARY_DIR.
- PROTOS: List of proto schema files. If omitted, then every source file ending in proto of TARGET will be used.
- <a id="plugin-options">PLUGIN_OPTIONS</a>: A comma-separated string forwarded to the protoc-gen-hpp plugin to customize code generation. Options include:
* `namespace_prefix=`: Specifies a namespace prefix to be added to the generated C++ code in addition to the package namespace. If the specified prefix contains multiple components, they should be separated by a __dot (.)__ instead of double colons (::).
* `directory_prefix=`: Prepends a directory to included non-system dependencies.
* `proto2_explicit_presence=`: For Proto2 only, makes optional scalar fields implicitly present except for specified scopes. This option can be specified multiple times. For example: the option `proto2_explicit_presence=.pkg1.msg1.field1,proto2_explicit_presence=.pkg1.msg2` instructs the code generator that explicit presence is only applicable for the `field1` of `pkg1.msg1` and all fields of `pkg1.msg2`.
* `non_owning`: Generates non-owning messages.
* `string_keyed_map`: Uses an alternative type template, such as `std.unordered_map`, for generated string-keyed maps. Note: The namespace should be separated by a __dot (.)__ instead of double colons (::) due to the protoc option parsing mechanism. This option applies only to __regular mode__ map fields.
* `numeric_keyed_map`: Uses an alternative type template, such as 'std.map', for generated numeric keyed map. Note: The namespace should be separated by a __dot(.)__ instead of double colons (::) due to protoc option parsing mechanism. This option applies only to __regular mode__ map fields.

The compiler generates several header files for each .proto file, transforming the filename and extension as follows:

Expand All @@ -37,9 +39,9 @@ target_include_directories(non_owning_unittest_proto3_proto_lib INTERFACE ${CMAK
protobuf_generate_hpp(
TARGET non_owning_unittest_proto3_proto_lib
IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
PROTOC_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/non_owning
PROTOC_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}
PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/google/protobuf/unittest_proto3.proto
PLUGIN_OPTIONS non_owning,root_namespace=non_owning,top_directory=non_owning)
PLUGIN_OPTIONS non_owning,namespace_prefix=non_owning,directory_prefix=non_owning)
```

### Compiler Invocation from command line
Expand All @@ -58,13 +60,17 @@ package foo.bar;
```
All declarations in the file will reside in the `foo::bar` namespace.

If you need to use both `hpp-proto` and the official Google Protobuf library in the same program, you can use the root_namespace option to customize the top-level namespace in the generated files. For example:
If you need to use both `hpp-proto` and the official Google Protobuf library in the same program, you can use the namespace_prefix option to customize the top-level namespace in the generated files. For example:

```
protoc --proto_path=src --hpp_out build/gen --hpp_opt=root_namespace=baz src/foo.proto
protoc --proto_path=src --hpp_out build/gen --hpp_opt=namespace_prefix=baz src/foo.proto
```
This command will place all declarations in the `baz::foo::bar` namespace.

## Non-owning mode only for specific files, messages or fields.

In addition to using the [protoc plugin option](#plugin-options) to enable __non-owning mode__ globally, the `hpp_options` extension can be used to apply __non-owning mode__ selectively to specific files, messages, or fields. The file [hpp_options_test.proto](hpp_options_test.proto) provides an example of how to mix __regular__ and __non-owning mode__ code generation within a single proto file.

## Messages

Given a simple message declaration like:
Expand Down Expand Up @@ -190,7 +196,6 @@ message TestOneof {
int32 foo_int = 1;
string foo_string = 2;
NestedMessage foo_message = 3;
NestedMessage foo_lazy_message = 4 [lazy = true];
}
message NestedMessage {
double required_double = 1;
Expand All @@ -210,11 +215,10 @@ struct TestOneof {
enum foo_oneof_case : int {
foo_int = 1,
foo_string = 2,
foo_message = 3,
foo_lazy_message = 4
foo_message = 3
};

std::variant<std::monostate, int32_t, std::string, NestedMessage, NestedMessage> foo;
std::variant<std::monostate, int32_t, std::string, NestedMessage> foo;

bool operator == (const TestOneof&) const = default;
};
Expand All @@ -224,20 +228,27 @@ struct TestOneof {
### Map fields
For `map` fields:
```proto
import "hpp_proto/hpp_options.proto";

message TestMap {
map<int32, int32> map1 = 1;
map<string, int32> map2 = 2 [(hpp.proto.hpp_field_opts).string_keyed_map = 'std::unordered_map'];
map<int32, int32> map3 = 3 [(hpp.proto.hpp_field_opts).numeric_keyed_map = 'std::map'];;
}
```
The compiler generates `hpp::proto::flat_map<key_type, mapped_type>` for regular mode and with `hpp::proto::equality_comparable_span<std::pair<key_type, mapped_type>>` for non-owning mode. In the non-owning mode,
the library does not handle key deduplication during serialization or deserialization.
Given a deserialized message with duplicate map keys, users are responsible to make sure that only the last key seen should be treated as valid.

By default, in __regular mode__, the compiler generates `hpp::proto::flat_map<key_type, mapped_type>` for map fields. Alternatively, a template class conforming to the `std::map interface` can be used by specifying either `string_keyed_map` or `numeric_keyed_map` in the [protoc plugin options](#plugin-options) globally, or by annotating a specific file, message, or field with the hpp_options extension.

In __non-owning mode__, the compiler always generates `hpp::proto::equality_comparable_span<std::pair<key_type, mapped_type>>` for map fields. Additionally, the library does not handle key deduplication during serialization or deserialization. If a deserialized message contains duplicate map keys, users must ensure that only the last occurrence of each key is considered valid.

<details open><summary> Regular Mode </summary>
<p>

```cpp
struct TestMap {
hpp::proto::flat_map<int32_t,int32_t> map1;
std::unordered_map<std::string, int32_t> map2;
std::map<int32_t, int32_t> map3;
bool operator == (const TestMap&) const = default;
};
```
Expand All @@ -249,6 +260,8 @@ struct TestMap {
```cpp
struct TestMap {
hpp::proto::equality_comparable_span<std::pair<int32_t,int32_t>> map1;
hpp::proto::equality_comparable_span<std::pair<std::string,int32_t>> map2;
hpp::proto::equality_comparable_span<std::pair<int32_t,int32_t>> map3;
bool operator == (const TestMap&) const = default;
};
```
Expand Down
1 change: 1 addition & 0 deletions docs/hpp_options_test.proto
2 changes: 1 addition & 1 deletion hpp_proto-config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ if (HPP_PROTO_PROTOC STREQUAL "find")
endif()

include("${CMAKE_CURRENT_LIST_DIR}/hpp_proto-targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/protobuf_generate.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/protobuf_generate_hpp.cmake")
2 changes: 2 additions & 0 deletions include/google/protobuf/compiler/plugin.msg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

#include <hpp_proto/field_types.hpp>
#include "google/protobuf/descriptor.msg.hpp"
// @@protoc_insertion_point(includes)


namespace google::protobuf::compiler {
//NOLINTBEGIN(performance-enum-size)
Expand Down
2 changes: 1 addition & 1 deletion include/google/protobuf/descriptor.desc.hpp

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions include/google/protobuf/descriptor.msg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#pragma once

#include <hpp_proto/field_types.hpp>
// @@protoc_insertion_point(includes)


namespace google::protobuf {
//NOLINTBEGIN(performance-enum-size)
Expand Down Expand Up @@ -130,6 +132,28 @@ struct SourceCodeInfo {

std::vector<Location> location;

struct extension_t {
using pb_extension = SourceCodeInfo;
hpp::proto::flat_map<uint32_t, std::vector<std::byte>> fields;
bool operator==(const extension_t &other) const = default;
} extensions;

[[nodiscard]] auto get_extension(auto meta) const {
return meta.read(extensions);
}
template<typename Meta>
[[nodiscard]] auto set_extension(Meta meta, typename Meta::set_value_type &&value) {
return meta.write(extensions, std::move(value));
}
template<typename Meta>
requires Meta::is_repeated
[[nodiscard]] auto set_extension(Meta meta, std::initializer_list<typename Meta::element_type> value) {
return meta.write(extensions, std::span<const typename Meta::element_type>{value.begin(), value.end()});
}
[[nodiscard]] bool has_extension(auto meta) const {
return meta.element_of(extensions);
}

bool operator == (const SourceCodeInfo&) const = default;
};

Expand Down Expand Up @@ -698,6 +722,28 @@ struct FileDescriptorProto {
struct FileDescriptorSet {
std::vector<FileDescriptorProto> file;

struct extension_t {
using pb_extension = FileDescriptorSet;
hpp::proto::flat_map<uint32_t, std::vector<std::byte>> fields;
bool operator==(const extension_t &other) const = default;
} extensions;

[[nodiscard]] auto get_extension(auto meta) const {
return meta.read(extensions);
}
template<typename Meta>
[[nodiscard]] auto set_extension(Meta meta, typename Meta::set_value_type &&value) {
return meta.write(extensions, std::move(value));
}
template<typename Meta>
requires Meta::is_repeated
[[nodiscard]] auto set_extension(Meta meta, std::initializer_list<typename Meta::element_type> value) {
return meta.write(extensions, std::span<const typename Meta::element_type>{value.begin(), value.end()});
}
[[nodiscard]] bool has_extension(auto meta) const {
return meta.element_of(extensions);
}

bool operator == (const FileDescriptorSet&) const = default;
};

Expand Down
18 changes: 10 additions & 8 deletions include/google/protobuf/descriptor.pb.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
namespace google::protobuf {

auto pb_meta(const FileDescriptorSet &) -> std::tuple<
hpp::proto::field_meta<1, &FileDescriptorSet::file, hpp::proto::field_option::none>>;
hpp::proto::field_meta<1, &FileDescriptorSet::file, hpp::proto::field_option::none>,
hpp::proto::field_meta<UINT32_MAX, &FileDescriptorSet::extensions>>;

auto pb_meta(const FileDescriptorProto &) -> std::tuple<
hpp::proto::field_meta<1, &FileDescriptorProto::name, hpp::proto::field_option::none>,
Expand Down Expand Up @@ -57,7 +58,7 @@ auto pb_meta(const ExtensionRangeOptions &) -> std::tuple<
hpp::proto::field_meta<999, &ExtensionRangeOptions::uninterpreted_option, hpp::proto::field_option::none>,
hpp::proto::field_meta<2, &ExtensionRangeOptions::declaration, hpp::proto::field_option::none>,
hpp::proto::field_meta<50, &ExtensionRangeOptions::features, hpp::proto::field_option::explicit_presence>,
hpp::proto::field_meta<3, &ExtensionRangeOptions::verification, hpp::proto::field_option::none, void, ::google::protobuf::ExtensionRangeOptions::VerificationState::UNVERIFIED>,
hpp::proto::field_meta<3, &ExtensionRangeOptions::verification, hpp::proto::field_option::closed_enum, void, ::google::protobuf::ExtensionRangeOptions::VerificationState::UNVERIFIED>,
hpp::proto::field_meta<UINT32_MAX, &ExtensionRangeOptions::extensions>>;

auto pb_meta(const ExtensionRangeOptions::Declaration &) -> std::tuple<
Expand Down Expand Up @@ -119,7 +120,7 @@ auto pb_meta(const FileOptions &) -> std::tuple<
hpp::proto::field_meta<10, &FileOptions::java_multiple_files, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<20, &FileOptions::java_generate_equals_and_hash, hpp::proto::field_option::none, bool>,
hpp::proto::field_meta<27, &FileOptions::java_string_check_utf8, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<9, &FileOptions::optimize_for, hpp::proto::field_option::none, void, ::google::protobuf::FileOptions::OptimizeMode::SPEED>,
hpp::proto::field_meta<9, &FileOptions::optimize_for, hpp::proto::field_option::closed_enum, void, ::google::protobuf::FileOptions::OptimizeMode::SPEED>,
hpp::proto::field_meta<11, &FileOptions::go_package, hpp::proto::field_option::none>,
hpp::proto::field_meta<16, &FileOptions::cc_generic_services, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<17, &FileOptions::java_generic_services, hpp::proto::field_option::none, bool, false>,
Expand Down Expand Up @@ -148,16 +149,16 @@ auto pb_meta(const MessageOptions &) -> std::tuple<
hpp::proto::field_meta<UINT32_MAX, &MessageOptions::extensions>>;

auto pb_meta(const FieldOptions &) -> std::tuple<
hpp::proto::field_meta<1, &FieldOptions::ctype, hpp::proto::field_option::none, void, ::google::protobuf::FieldOptions::CType::STRING>,
hpp::proto::field_meta<1, &FieldOptions::ctype, hpp::proto::field_option::closed_enum, void, ::google::protobuf::FieldOptions::CType::STRING>,
hpp::proto::field_meta<2, &FieldOptions::packed, hpp::proto::field_option::explicit_presence, bool>,
hpp::proto::field_meta<6, &FieldOptions::jstype, hpp::proto::field_option::none, void, ::google::protobuf::FieldOptions::JSType::JS_NORMAL>,
hpp::proto::field_meta<6, &FieldOptions::jstype, hpp::proto::field_option::closed_enum, void, ::google::protobuf::FieldOptions::JSType::JS_NORMAL>,
hpp::proto::field_meta<5, &FieldOptions::lazy, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<15, &FieldOptions::unverified_lazy, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<3, &FieldOptions::deprecated, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<10, &FieldOptions::weak, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<16, &FieldOptions::debug_redact, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<17, &FieldOptions::retention, hpp::proto::field_option::closed_enum, void, ::google::protobuf::FieldOptions::OptionRetention::RETENTION_UNKNOWN>,
hpp::proto::field_meta<19, &FieldOptions::targets, hpp::proto::field_option::none>,
hpp::proto::field_meta<19, &FieldOptions::targets, hpp::proto::field_option::closed_enum>,
hpp::proto::field_meta<20, &FieldOptions::edition_defaults, hpp::proto::field_option::none>,
hpp::proto::field_meta<21, &FieldOptions::features, hpp::proto::field_option::explicit_presence>,
hpp::proto::field_meta<22, &FieldOptions::feature_support, hpp::proto::field_option::explicit_presence>,
Expand Down Expand Up @@ -203,7 +204,7 @@ auto pb_meta(const ServiceOptions &) -> std::tuple<

auto pb_meta(const MethodOptions &) -> std::tuple<
hpp::proto::field_meta<33, &MethodOptions::deprecated, hpp::proto::field_option::none, bool, false>,
hpp::proto::field_meta<34, &MethodOptions::idempotency_level, hpp::proto::field_option::none, void, ::google::protobuf::MethodOptions::IdempotencyLevel::IDEMPOTENCY_UNKNOWN>,
hpp::proto::field_meta<34, &MethodOptions::idempotency_level, hpp::proto::field_option::closed_enum, void, ::google::protobuf::MethodOptions::IdempotencyLevel::IDEMPOTENCY_UNKNOWN>,
hpp::proto::field_meta<35, &MethodOptions::features, hpp::proto::field_option::explicit_presence>,
hpp::proto::field_meta<999, &MethodOptions::uninterpreted_option, hpp::proto::field_option::none>,
hpp::proto::field_meta<UINT32_MAX, &MethodOptions::extensions>>;
Expand Down Expand Up @@ -241,7 +242,8 @@ auto pb_meta(const FeatureSetDefaults::FeatureSetEditionDefault &) -> std::tuple
hpp::proto::field_meta<5, &FeatureSetDefaults::FeatureSetEditionDefault::fixed_features, hpp::proto::field_option::explicit_presence>>;

auto pb_meta(const SourceCodeInfo &) -> std::tuple<
hpp::proto::field_meta<1, &SourceCodeInfo::location, hpp::proto::field_option::none>>;
hpp::proto::field_meta<1, &SourceCodeInfo::location, hpp::proto::field_option::none>,
hpp::proto::field_meta<UINT32_MAX, &SourceCodeInfo::extensions>>;

auto pb_meta(const SourceCodeInfo::Location &) -> std::tuple<
hpp::proto::field_meta<1, &SourceCodeInfo::Location::path, hpp::proto::field_option::is_packed, hpp::proto::vint64_t>,
Expand Down
Loading