Merge pull request #17019 from protocolbuffers/cp-27
Cherrypick editions fixes to 27.x
diff --git a/.github/workflows/test_ruby.yml b/.github/workflows/test_ruby.yml
index 38cb5ef..bbd76f4 100644
--- a/.github/workflows/test_ruby.yml
+++ b/.github/workflows/test_ruby.yml
@@ -40,7 +40,7 @@
- name: Run tests
uses: protocolbuffers/protobuf-ci/bazel-docker@v3
with:
- image: ${{ matrix.image || format('us-docker.pkg.dev/protobuf-build/containers/test/linux/ruby:{0}-6.3.0-a6940b1421a71325ef4c7828ec72d404f56bbf30', matrix.ruby) }}
+ image: ${{ matrix.image || format('us-docker.pkg.dev/protobuf-build/containers/test/linux/ruby:{0}-6.3.0-9848710ff1370795ee7517570a20b81e140112ec', matrix.ruby) }}
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
bazel-cache: ruby_linux/${{ matrix.ruby }}_${{ matrix.bazel }}
bazel: test //ruby/... //ruby/tests:ruby_version --test_env=KOKORO_RUBY_VERSION --test_env=BAZEL=true ${{ matrix.ffi == 'FFI' && '--//ruby:ffi=enabled --test_env=PROTOCOL_BUFFERS_RUBY_IMPLEMENTATION=FFI' || '' }}
@@ -172,7 +172,7 @@
- name: Run tests
uses: protocolbuffers/protobuf-ci/bazel-docker@v3
with:
- image: us-docker.pkg.dev/protobuf-build/containers/test/linux/ruby:${{ matrix.ruby }}-6.3.0-a6940b1421a71325ef4c7828ec72d404f56bbf30
+ image: us-docker.pkg.dev/protobuf-build/containers/test/linux/ruby:${{ matrix.ruby }}-6.3.0-9848710ff1370795ee7517570a20b81e140112ec
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
bazel-cache: ruby_install/${{ matrix.ruby }}_${{ matrix.bazel }}
bash: >
diff --git a/BUILD.bazel b/BUILD.bazel
index d9e2c44..301a046 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -418,6 +418,7 @@
name = "cc_toolchain",
blacklisted_protos = [
"//:compiler_plugin_proto",
+ "//:cpp_features_proto",
"//:descriptor_proto",
],
command_line = "--cpp_out=$(OUT)",
diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc
index 82cce7b..690018f 100644
--- a/src/google/protobuf/compiler/command_line_interface.cc
+++ b/src/google/protobuf/compiler/command_line_interface.cc
@@ -2854,23 +2854,24 @@
}
// Check for errors.
- if (!response.error().empty()) {
- // Generator returned an error.
- *error = response.error();
- return false;
- }
+ bool success = true;
if (!EnforceProto3OptionalSupport(plugin_name, response.supported_features(),
parsed_files)) {
- return false;
+ success = false;
}
if (!EnforceEditionsSupport(plugin_name, response.supported_features(),
static_cast<Edition>(response.minimum_edition()),
static_cast<Edition>(response.maximum_edition()),
parsed_files)) {
- return false;
+ success = false;
+ }
+ if (!response.error().empty()) {
+ // Generator returned an error.
+ *error = response.error();
+ success = false;
}
- return true;
+ return success;
}
bool CommandLineInterface::EncodeOrDecode(const DescriptorPool* pool) {
diff --git a/src/google/protobuf/compiler/command_line_interface_unittest.cc b/src/google/protobuf/compiler/command_line_interface_unittest.cc
index 6a2db03..8870dab 100644
--- a/src/google/protobuf/compiler/command_line_interface_unittest.cc
+++ b/src/google/protobuf/compiler/command_line_interface_unittest.cc
@@ -1854,6 +1854,22 @@
"code generator prefix-gen-plug hasn't been updated to support editions");
}
+TEST_F(CommandLineInterfaceTest, PluginErrorAndNoEditionsSupport) {
+ CreateTempFile("foo.proto", R"schema(
+ edition = "2023";
+ message MockCodeGenerator_Error { }
+ )schema");
+
+ SetMockGeneratorTestCase("no_editions");
+ Run("protocol_compiler "
+ "--proto_path=$tmpdir foo.proto --plug_out=$tmpdir");
+
+ ExpectErrorSubstring(
+ "code generator prefix-gen-plug hasn't been updated to support editions");
+ ExpectErrorSubstring(
+ "--plug_out: foo.proto: Saw message type MockCodeGenerator_Error.");
+}
+
TEST_F(CommandLineInterfaceTest, EditionDefaults) {
CreateTempFile("google/protobuf/descriptor.proto",
google::protobuf::DescriptorProto::descriptor()->file()->DebugString());
diff --git a/src/google/protobuf/feature_resolver.cc b/src/google/protobuf/feature_resolver.cc
index bf24e18..7accef3 100644
--- a/src/google/protobuf/feature_resolver.cc
+++ b/src/google/protobuf/feature_resolver.cc
@@ -189,17 +189,38 @@
return absl::OkStatus();
}
+void MaybeInsertEdition(Edition edition, Edition maximum_edition,
+ absl::btree_set<Edition>& editions) {
+ if (edition <= maximum_edition) {
+ editions.insert(edition);
+ }
+}
+
+// This collects all of the editions that are relevant to any features defined
+// in a message descriptor. We only need to consider editions where something
+// has changed.
void CollectEditions(const Descriptor& descriptor, Edition maximum_edition,
absl::btree_set<Edition>& editions) {
for (int i = 0; i < descriptor.field_count(); ++i) {
- for (const auto& def : descriptor.field(i)->options().edition_defaults()) {
- if (maximum_edition < def.edition()) continue;
+ const FieldOptions& options = descriptor.field(i)->options();
+ // Editions where a new feature is introduced should be captured.
+ MaybeInsertEdition(options.feature_support().edition_introduced(),
+ maximum_edition, editions);
+
+ // Editions where a feature is removed should be captured.
+ if (options.feature_support().has_edition_removed()) {
+ MaybeInsertEdition(options.feature_support().edition_removed(),
+ maximum_edition, editions);
+ }
+
+ // Any edition where a default value changes should be captured.
+ for (const auto& def : options.edition_defaults()) {
// TODO Remove this once all features use EDITION_LEGACY.
if (def.edition() == Edition::EDITION_LEGACY) {
editions.insert(Edition::EDITION_PROTO2);
continue;
}
- editions.insert(def.edition());
+ MaybeInsertEdition(def.edition(), maximum_edition, editions);
}
}
}
diff --git a/src/google/protobuf/feature_resolver_test.cc b/src/google/protobuf/feature_resolver_test.cc
index e5aaf05..bbd4750 100644
--- a/src/google/protobuf/feature_resolver_test.cc
+++ b/src/google/protobuf/feature_resolver_test.cc
@@ -1311,6 +1311,81 @@
HasError(HasSubstr("edition 1_TEST_ONLY is earlier than the oldest")));
}
+TEST_F(FeatureResolverPoolTest, CompileDefaultsRemovedOnly) {
+ const FileDescriptor* file = ParseSchema(R"schema(
+ syntax = "proto2";
+ package test;
+ import "google/protobuf/descriptor.proto";
+
+ extend google.protobuf.FeatureSet {
+ optional Foo bar = 9999;
+ }
+ enum Bar {
+ TEST_ENUM_FEATURE_UNKNOWN = 0;
+ VALUE1 = 1;
+ VALUE2 = 2;
+ }
+ message Foo {
+ optional Bar file_feature = 1 [
+ targets = TARGET_TYPE_FIELD,
+ feature_support.edition_introduced = EDITION_2023,
+ feature_support.edition_removed = EDITION_99998_TEST_ONLY,
+ edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
+ ];
+ }
+ )schema");
+ ASSERT_NE(file, nullptr);
+
+ const FieldDescriptor* ext = file->extension(0);
+ auto compiled_defaults = FeatureResolver::CompileDefaults(
+ feature_set_, {ext}, EDITION_99997_TEST_ONLY, EDITION_99999_TEST_ONLY);
+ ASSERT_OK(compiled_defaults);
+ const auto& defaults = *compiled_defaults->defaults().rbegin();
+ EXPECT_THAT(defaults.edition(), EDITION_99998_TEST_ONLY);
+ EXPECT_THAT(defaults.fixed_features().GetExtension(pb::test).file_feature(),
+ pb::VALUE1);
+ EXPECT_FALSE(defaults.overridable_features()
+ .GetExtension(pb::test)
+ .has_file_feature());
+}
+
+TEST_F(FeatureResolverPoolTest, CompileDefaultsIntroducedOnly) {
+ const FileDescriptor* file = ParseSchema(R"schema(
+ syntax = "proto2";
+ package test;
+ import "google/protobuf/descriptor.proto";
+
+ extend google.protobuf.FeatureSet {
+ optional Foo bar = 9999;
+ }
+ enum Bar {
+ TEST_ENUM_FEATURE_UNKNOWN = 0;
+ VALUE1 = 1;
+ VALUE2 = 2;
+ }
+ message Foo {
+ optional Bar file_feature = 1 [
+ targets = TARGET_TYPE_FIELD,
+ feature_support.edition_introduced = EDITION_99998_TEST_ONLY,
+ edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
+ ];
+ }
+ )schema");
+ ASSERT_NE(file, nullptr);
+
+ const FieldDescriptor* ext = file->extension(0);
+ auto compiled_defaults = FeatureResolver::CompileDefaults(
+ feature_set_, {ext}, EDITION_99997_TEST_ONLY, EDITION_99999_TEST_ONLY);
+ ASSERT_OK(compiled_defaults);
+ const auto& defaults = *compiled_defaults->defaults().rbegin();
+ EXPECT_THAT(defaults.edition(), EDITION_99998_TEST_ONLY);
+ EXPECT_THAT(
+ defaults.overridable_features().GetExtension(pb::test).file_feature(),
+ pb::VALUE1);
+ EXPECT_FALSE(
+ defaults.fixed_features().GetExtension(pb::test).has_file_feature());
+}
+
TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
diff --git a/src/google/protobuf/unittest_features.proto b/src/google/protobuf/unittest_features.proto
index d32f91c..ddf510c 100644
--- a/src/google/protobuf/unittest_features.proto
+++ b/src/google/protobuf/unittest_features.proto
@@ -193,7 +193,7 @@
targets = TARGET_TYPE_FILE,
targets = TARGET_TYPE_FIELD,
feature_support = {
- edition_introduced: EDITION_PROTO2
+ edition_introduced: EDITION_PROTO3
edition_removed: EDITION_2023
},
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" },