Allow code generators to specify whether or not they support editions.

Editions are still flag-guarded by the `--experimental_editions` flag for now, but once that's removed in a later release generators will need to explicitly specify that their support.  This will avoid cases where generators may happen to work for editions but produce incorrect code.

PiperOrigin-RevId: 547959326
pull/13192/head
Mike Kruskal 2 years ago committed by Copybara-Service
parent c87a767300
commit 2176a145ba
  1. 34
      src/google/protobuf/BUILD.bazel
  2. 2
      src/google/protobuf/compiler/code_generator.h
  3. 14
      src/google/protobuf/compiler/code_generator_unittest.cc
  4. 29
      src/google/protobuf/compiler/command_line_interface.cc
  5. 35
      src/google/protobuf/compiler/command_line_interface_unittest.cc
  6. 2
      src/google/protobuf/compiler/cpp/generator.h
  7. 9
      src/google/protobuf/compiler/fake_plugin.cc
  8. 14
      src/google/protobuf/compiler/mock_code_generator.cc
  9. 5
      src/google/protobuf/compiler/mock_code_generator.h
  10. 17
      src/google/protobuf/compiler/plugin.pb.cc
  11. 8
      src/google/protobuf/compiler/plugin.pb.h
  12. 1
      src/google/protobuf/compiler/plugin.proto
  13. 3
      src/google/protobuf/descriptor.cc

@ -750,39 +750,7 @@ proto_library(
proto_library( proto_library(
name = "generic_test_protos", name = "generic_test_protos",
srcs = [ srcs = [":test_proto_srcs"],
"map_proto2_unittest.proto",
"map_proto3_unittest.proto",
"map_unittest.proto",
"unittest.proto",
"unittest_arena.proto",
"unittest_custom_options.proto",
"unittest_drop_unknown_fields.proto",
"unittest_embed_optimize_for.proto",
"unittest_empty.proto",
"unittest_enormous_descriptor.proto",
"unittest_import.proto",
"unittest_import_public.proto",
"unittest_lazy_dependencies.proto",
"unittest_lazy_dependencies_custom_option.proto",
"unittest_lazy_dependencies_enum.proto",
"unittest_lite_imports_nonlite.proto",
"unittest_mset.proto",
"unittest_mset_wire_format.proto",
"unittest_no_field_presence.proto",
"unittest_no_generic_services.proto",
"unittest_optimize_for.proto",
"unittest_preserve_unknown_enum.proto",
"unittest_preserve_unknown_enum2.proto",
"unittest_proto3.proto",
"unittest_proto3_arena.proto",
"unittest_proto3_arena_lite.proto",
"unittest_proto3_bad_macros.proto",
"unittest_proto3_lite.proto",
"unittest_proto3_optional.proto",
"unittest_retention.proto",
"unittest_well_known_types.proto",
],
strip_import_prefix = "/src", strip_import_prefix = "/src",
visibility = ["//:__subpackages__"], visibility = ["//:__subpackages__"],
deps = [ deps = [

@ -113,8 +113,10 @@ class PROTOC_EXPORT CodeGenerator {
// This must be kept in sync with plugin.proto. See that file for // This must be kept in sync with plugin.proto. See that file for
// documentation on each value. // documentation on each value.
// TODO(b/291092901) Use CodeGeneratorResponse.Feature here.
enum Feature { enum Feature {
FEATURE_PROTO3_OPTIONAL = 1, FEATURE_PROTO3_OPTIONAL = 1,
FEATURE_SUPPORTS_EDITIONS = 2,
}; };
// Implement this to indicate what features this code generator supports. // Implement this to indicate what features this code generator supports.

@ -224,6 +224,20 @@ TEST_F(CodeGeneratorTest, GetSourceFeaturesInherited) {
EXPECT_EQ(ext.string_source_feature(), "field"); EXPECT_EQ(ext.string_source_feature(), "field");
} }
TEST_F(CodeGeneratorTest, GetRuntimeProtoTrivial) {
auto file = BuildFile(R"schema(
edition = "2023";
package protobuf_unittest;
)schema");
ASSERT_THAT(file, NotNull());
FileDescriptorProto proto = TestGenerator::GetRuntimeProto(*file);
const FeatureSet& features = proto.options().features();
EXPECT_TRUE(features.has_raw_features());
EXPECT_THAT(features.raw_features(), EqualsProto(R"pb()pb"));
}
TEST_F(CodeGeneratorTest, GetRuntimeProtoRoot) { TEST_F(CodeGeneratorTest, GetRuntimeProtoRoot) {
auto file = BuildFile(R"schema( auto file = BuildFile(R"schema(
edition = "2023"; edition = "2023";

@ -302,6 +302,26 @@ std::string PluginName(absl::string_view plugin_prefix,
} }
bool EnforceEditionsSupport(
const std::string& codegen_name, uint64_t supported_features,
const std::vector<const FileDescriptor*>& parsed_files) {
if ((supported_features & CodeGenerator::FEATURE_SUPPORTS_EDITIONS) == 0) {
for (const auto fd : parsed_files) {
if (FileDescriptorLegacy(fd).syntax() ==
FileDescriptorLegacy::SYNTAX_EDITIONS) {
std::cerr << fd->name() << ": is an editions file, but code generator "
<< codegen_name
<< " hasn't been updated to support editions yet. Please ask "
"the owner of this code generator to add support or "
"switch back to proto2/proto3."
<< std::endl;
return false;
}
}
}
return true;
}
} // namespace } // namespace
void CommandLineInterface::GetTransitiveDependencies( void CommandLineInterface::GetTransitiveDependencies(
@ -2490,6 +2510,12 @@ bool CommandLineInterface::GenerateOutput(
return false; return false;
} }
if (!EnforceEditionsSupport(
output_directive.name,
output_directive.generator->GetSupportedFeatures(), parsed_files)) {
return false;
}
if (!output_directive.generator->GenerateAll(parsed_files, parameters, if (!output_directive.generator->GenerateAll(parsed_files, parameters,
generator_context, &error)) { generator_context, &error)) {
// Generator returned an error. // Generator returned an error.
@ -2686,6 +2712,9 @@ bool CommandLineInterface::GeneratePluginOutput(
} else if (!EnforceProto3OptionalSupport( } else if (!EnforceProto3OptionalSupport(
plugin_name, response.supported_features(), parsed_files)) { plugin_name, response.supported_features(), parsed_files)) {
return false; return false;
} else if (!EnforceEditionsSupport(plugin_name, response.supported_features(),
parsed_files)) {
return false;
} }
return true; return true;

@ -1643,6 +1643,41 @@ TEST_F(CommandLineInterfaceTest, Plugin_SourceFeatures) {
} }
} }
TEST_F(CommandLineInterfaceTest, GeneratorNoEditionsSupport) {
CreateTempFile("foo.proto", R"schema(
edition = "2023";
message Foo {
int32 i = 1;
}
)schema");
CreateGeneratorWithMissingFeatures("--no_editions_out",
"Doesn't support editions",
CodeGenerator::FEATURE_SUPPORTS_EDITIONS);
Run("protocol_compiler --experimental_editions "
"--proto_path=$tmpdir foo.proto --no_editions_out=$tmpdir");
ExpectErrorSubstring(
"code generator --no_editions_out hasn't been updated to support "
"editions");
}
TEST_F(CommandLineInterfaceTest, PluginNoEditionsSupport) {
CreateTempFile("foo.proto", R"schema(
edition = "2023";
message Foo {
int32 i = 1;
}
)schema");
Run("protocol_compiler --experimental_editions "
"--proto_path=$tmpdir foo.proto --plug_out=no_editions:$tmpdir");
ExpectErrorSubstring(
"code generator prefix-gen-plug hasn't been updated to support editions");
}
#endif // PROTOBUF_FUTURE_EDITIONS #endif // PROTOBUF_FUTURE_EDITIONS

@ -89,7 +89,7 @@ class PROTOC_EXPORT CppGenerator : public CodeGenerator {
std::string* error) const override; std::string* error) const override;
uint64_t GetSupportedFeatures() const override { uint64_t GetSupportedFeatures() const override {
return FEATURE_PROTO3_OPTIONAL; return FEATURE_PROTO3_OPTIONAL | FEATURE_SUPPORTS_EDITIONS;
} }
private: private:

@ -39,6 +39,9 @@
#include "google/protobuf/compiler/plugin.pb.h" #include "google/protobuf/compiler/plugin.pb.h"
#include "google/protobuf/io/io_win32.h" #include "google/protobuf/io/io_win32.h"
using google::protobuf::compiler::CodeGeneratorRequest;
using google::protobuf::compiler::CodeGeneratorResponse;
// This fake protoc plugin does nothing but write out the CodeGeneratorRequest // This fake protoc plugin does nothing but write out the CodeGeneratorRequest
// in base64. This is not very useful except that it gives us a way to make // in base64. This is not very useful except that it gives us a way to make
// assertions in tests about the contents of requests that protoc sends to // assertions in tests about the contents of requests that protoc sends to
@ -50,10 +53,12 @@ int main(int argc, char* argv[]) {
google::protobuf::io::win32::setmode(STDOUT_FILENO, _O_BINARY); google::protobuf::io::win32::setmode(STDOUT_FILENO, _O_BINARY);
#endif #endif
google::protobuf::compiler::CodeGeneratorRequest request; CodeGeneratorRequest request;
ABSL_CHECK(request.ParseFromFileDescriptor(STDIN_FILENO)); ABSL_CHECK(request.ParseFromFileDescriptor(STDIN_FILENO));
ABSL_CHECK(!request.file_to_generate().empty()); ABSL_CHECK(!request.file_to_generate().empty());
google::protobuf::compiler::CodeGeneratorResponse response; CodeGeneratorResponse response;
response.set_supported_features(
CodeGeneratorResponse::FEATURE_SUPPORTS_EDITIONS);
response.add_file()->set_name( response.add_file()->set_name(
absl::StrCat(request.file_to_generate(0), ".request")); absl::StrCat(request.file_to_generate(0), ".request"));
response.mutable_file(0)->set_content( response.mutable_file(0)->set_content(

@ -39,6 +39,7 @@
#include <memory> #include <memory>
#include <ostream> #include <ostream>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "google/protobuf/testing/file.h" #include "google/protobuf/testing/file.h"
@ -96,7 +97,8 @@ MockCodeGenerator::MockCodeGenerator(absl::string_view name) : name_(name) {}
MockCodeGenerator::~MockCodeGenerator() = default; MockCodeGenerator::~MockCodeGenerator() = default;
uint64_t MockCodeGenerator::GetSupportedFeatures() const { uint64_t MockCodeGenerator::GetSupportedFeatures() const {
uint64_t all_features = CodeGenerator::FEATURE_PROTO3_OPTIONAL; uint64_t all_features = CodeGenerator::FEATURE_PROTO3_OPTIONAL |
CodeGenerator::FEATURE_SUPPORTS_EDITIONS;
return all_features & ~suppressed_features_; return all_features & ~suppressed_features_;
} }
@ -212,6 +214,16 @@ bool MockCodeGenerator::Generate(const FileDescriptor* file,
const std::string& parameter, const std::string& parameter,
GeneratorContext* context, GeneratorContext* context,
std::string* error) const { std::string* error) const {
std::vector<std::pair<std::string, std::string>> options;
ParseGeneratorParameter(parameter, &options);
for (const auto& option : options) {
const auto& key = option.first;
if (key == "no_editions") {
suppressed_features_ |= CodeGenerator::FEATURE_SUPPORTS_EDITIONS;
}
}
bool annotate = false; bool annotate = false;
for (int i = 0; i < file->message_type_count(); i++) { for (int i = 0; i < file->message_type_count(); i++) {
if (absl::StartsWith(file->message_type(i)->name(), "MockCodeGenerator_")) { if (absl::StartsWith(file->message_type(i)->name(), "MockCodeGenerator_")) {

@ -118,7 +118,10 @@ class MockCodeGenerator : public CodeGenerator {
private: private:
std::string name_; std::string name_;
uint64_t suppressed_features_ = 0;
// Mark this mutable so that our test plugin can modify it during the Generate
// call via generator flags.
mutable uint64_t suppressed_features_ = 0;
static std::string GetOutputFileContent(absl::string_view generator_name, static std::string GetOutputFileContent(absl::string_view generator_name,
absl::string_view parameter, absl::string_view parameter,

@ -221,18 +221,19 @@ const char descriptor_table_protodef_google_2fprotobuf_2fcompiler_2fplugin_2epro
"FileDescriptorProto\022E\n\027source_file_descr" "FileDescriptorProto\022E\n\027source_file_descr"
"iptors\030\021 \003(\0132$.google.protobuf.FileDescr" "iptors\030\021 \003(\0132$.google.protobuf.FileDescr"
"iptorProto\022;\n\020compiler_version\030\003 \001(\0132!.g" "iptorProto\022;\n\020compiler_version\030\003 \001(\0132!.g"
"oogle.protobuf.compiler.Version\"\301\002\n\025Code" "oogle.protobuf.compiler.Version\"\340\002\n\025Code"
"GeneratorResponse\022\r\n\005error\030\001 \001(\t\022\032\n\022supp" "GeneratorResponse\022\r\n\005error\030\001 \001(\t\022\032\n\022supp"
"orted_features\030\002 \001(\004\022B\n\004file\030\017 \003(\01324.goo" "orted_features\030\002 \001(\004\022B\n\004file\030\017 \003(\01324.goo"
"gle.protobuf.compiler.CodeGeneratorRespo" "gle.protobuf.compiler.CodeGeneratorRespo"
"nse.File\032\177\n\004File\022\014\n\004name\030\001 \001(\t\022\027\n\017insert" "nse.File\032\177\n\004File\022\014\n\004name\030\001 \001(\t\022\027\n\017insert"
"ion_point\030\002 \001(\t\022\017\n\007content\030\017 \001(\t\022\?\n\023gene" "ion_point\030\002 \001(\t\022\017\n\007content\030\017 \001(\t\022\?\n\023gene"
"rated_code_info\030\020 \001(\0132\".google.protobuf." "rated_code_info\030\020 \001(\0132\".google.protobuf."
"GeneratedCodeInfo\"8\n\007Feature\022\020\n\014FEATURE_" "GeneratedCodeInfo\"W\n\007Feature\022\020\n\014FEATURE_"
"NONE\020\000\022\033\n\027FEATURE_PROTO3_OPTIONAL\020\001Br\n\034c" "NONE\020\000\022\033\n\027FEATURE_PROTO3_OPTIONAL\020\001\022\035\n\031F"
"om.google.protobuf.compilerB\014PluginProto" "EATURE_SUPPORTS_EDITIONS\020\002Br\n\034com.google"
"sZ)google.golang.org/protobuf/types/plug" ".protobuf.compilerB\014PluginProtosZ)google"
"inpb\252\002\030Google.Protobuf.Compiler" ".golang.org/protobuf/types/pluginpb\252\002\030Go"
"ogle.Protobuf.Compiler"
}; };
static const ::_pbi::DescriptorTable* const descriptor_table_google_2fprotobuf_2fcompiler_2fplugin_2eproto_deps[1] = static const ::_pbi::DescriptorTable* const descriptor_table_google_2fprotobuf_2fcompiler_2fplugin_2eproto_deps[1] =
{ {
@ -242,7 +243,7 @@ static ::absl::once_flag descriptor_table_google_2fprotobuf_2fcompiler_2fplugin_
const ::_pbi::DescriptorTable descriptor_table_google_2fprotobuf_2fcompiler_2fplugin_2eproto = { const ::_pbi::DescriptorTable descriptor_table_google_2fprotobuf_2fcompiler_2fplugin_2eproto = {
false, false,
false, false,
871, 902,
descriptor_table_protodef_google_2fprotobuf_2fcompiler_2fplugin_2eproto, descriptor_table_protodef_google_2fprotobuf_2fcompiler_2fplugin_2eproto,
"google/protobuf/compiler/plugin.proto", "google/protobuf/compiler/plugin.proto",
&descriptor_table_google_2fprotobuf_2fcompiler_2fplugin_2eproto_once, &descriptor_table_google_2fprotobuf_2fcompiler_2fplugin_2eproto_once,
@ -285,6 +286,7 @@ bool CodeGeneratorResponse_Feature_IsValid(int value) {
switch (value) { switch (value) {
case 0: case 0:
case 1: case 1:
case 2:
return true; return true;
default: default:
return false; return false;
@ -295,6 +297,7 @@ bool CodeGeneratorResponse_Feature_IsValid(int value) {
constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::FEATURE_NONE; constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::FEATURE_NONE;
constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::FEATURE_PROTO3_OPTIONAL; constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::FEATURE_PROTO3_OPTIONAL;
constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::FEATURE_SUPPORTS_EDITIONS;
constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::Feature_MIN; constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::Feature_MIN;
constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::Feature_MAX; constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse::Feature_MAX;
constexpr int CodeGeneratorResponse::Feature_ARRAYSIZE; constexpr int CodeGeneratorResponse::Feature_ARRAYSIZE;

@ -94,12 +94,13 @@ namespace compiler {
enum CodeGeneratorResponse_Feature : int { enum CodeGeneratorResponse_Feature : int {
CodeGeneratorResponse_Feature_FEATURE_NONE = 0, CodeGeneratorResponse_Feature_FEATURE_NONE = 0,
CodeGeneratorResponse_Feature_FEATURE_PROTO3_OPTIONAL = 1, CodeGeneratorResponse_Feature_FEATURE_PROTO3_OPTIONAL = 1,
CodeGeneratorResponse_Feature_FEATURE_SUPPORTS_EDITIONS = 2,
}; };
PROTOC_EXPORT bool CodeGeneratorResponse_Feature_IsValid(int value); PROTOC_EXPORT bool CodeGeneratorResponse_Feature_IsValid(int value);
constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse_Feature_Feature_MIN = static_cast<CodeGeneratorResponse_Feature>(0); constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse_Feature_Feature_MIN = static_cast<CodeGeneratorResponse_Feature>(0);
constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse_Feature_Feature_MAX = static_cast<CodeGeneratorResponse_Feature>(1); constexpr CodeGeneratorResponse_Feature CodeGeneratorResponse_Feature_Feature_MAX = static_cast<CodeGeneratorResponse_Feature>(2);
constexpr int CodeGeneratorResponse_Feature_Feature_ARRAYSIZE = 1 + 1; constexpr int CodeGeneratorResponse_Feature_Feature_ARRAYSIZE = 2 + 1;
PROTOC_EXPORT const ::google::protobuf::EnumDescriptor* PROTOC_EXPORT const ::google::protobuf::EnumDescriptor*
CodeGeneratorResponse_Feature_descriptor(); CodeGeneratorResponse_Feature_descriptor();
template <typename T> template <typename T>
@ -112,7 +113,7 @@ const std::string& CodeGeneratorResponse_Feature_Name(T value) {
template <> template <>
inline const std::string& CodeGeneratorResponse_Feature_Name(CodeGeneratorResponse_Feature value) { inline const std::string& CodeGeneratorResponse_Feature_Name(CodeGeneratorResponse_Feature value) {
return ::google::protobuf::internal::NameOfDenseEnum<CodeGeneratorResponse_Feature_descriptor, return ::google::protobuf::internal::NameOfDenseEnum<CodeGeneratorResponse_Feature_descriptor,
0, 1>( 0, 2>(
static_cast<int>(value)); static_cast<int>(value));
} }
inline bool CodeGeneratorResponse_Feature_Parse(absl::string_view name, CodeGeneratorResponse_Feature* value) { inline bool CodeGeneratorResponse_Feature_Parse(absl::string_view name, CodeGeneratorResponse_Feature* value) {
@ -943,6 +944,7 @@ class PROTOC_EXPORT CodeGeneratorResponse final :
using Feature = CodeGeneratorResponse_Feature; using Feature = CodeGeneratorResponse_Feature;
static constexpr Feature FEATURE_NONE = CodeGeneratorResponse_Feature_FEATURE_NONE; static constexpr Feature FEATURE_NONE = CodeGeneratorResponse_Feature_FEATURE_NONE;
static constexpr Feature FEATURE_PROTO3_OPTIONAL = CodeGeneratorResponse_Feature_FEATURE_PROTO3_OPTIONAL; static constexpr Feature FEATURE_PROTO3_OPTIONAL = CodeGeneratorResponse_Feature_FEATURE_PROTO3_OPTIONAL;
static constexpr Feature FEATURE_SUPPORTS_EDITIONS = CodeGeneratorResponse_Feature_FEATURE_SUPPORTS_EDITIONS;
static inline bool Feature_IsValid(int value) { static inline bool Feature_IsValid(int value) {
return CodeGeneratorResponse_Feature_IsValid(value); return CodeGeneratorResponse_Feature_IsValid(value);
} }

@ -122,6 +122,7 @@ message CodeGeneratorResponse {
enum Feature { enum Feature {
FEATURE_NONE = 0; FEATURE_NONE = 0;
FEATURE_PROTO3_OPTIONAL = 1; FEATURE_PROTO3_OPTIONAL = 1;
FEATURE_SUPPORTS_EDITIONS = 2;
} }
// Represents a single generated file. // Represents a single generated file.

@ -2773,9 +2773,6 @@ FileDescriptorProto InternalFeatureHelper::GetGeneratorProto(
if (&features != &FeatureSet::default_instance() && if (&features != &FeatureSet::default_instance() &&
!IsLegacyFeatureSet(features)) { !IsLegacyFeatureSet(features)) {
*proto.mutable_options()->mutable_features() = features; *proto.mutable_options()->mutable_features() = features;
}
const auto& raw_features = GetRawFeatures(desc);
if (&raw_features != &FeatureSet::default_instance()) {
*proto.mutable_options()->mutable_features()->mutable_raw_features() = *proto.mutable_options()->mutable_features()->mutable_raw_features() =
GetRawFeatures(desc); GetRawFeatures(desc);
} }

Loading…
Cancel
Save