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

Update ruby_generator.cc to allow proto2 imports in proto3 #9003

Merged
merged 6 commits into from Sep 22, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion ruby/Rakefile
Expand Up @@ -61,7 +61,7 @@ unless ENV['IN_DOCKER'] == 'true'
output_file = proto_file.sub(/\.proto$/, "_pb.rb")
genproto_output << output_file
file output_file => proto_file do |file_task|
sh "#{protoc_command} -I../src -I. --ruby_out=. #{proto_file}"
sh "#{protoc_command} -I../src -I. -I./tests --ruby_out=. #{proto_file}"
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions ruby/tests/basic.rb
Expand Up @@ -168,6 +168,17 @@ def test_set_clear_defaults
assert_equal nil, m.singular_msg
end

def test_import_proto2
m = TestMessage.new
assert !m.has_optional_proto2_submessage?
m.optional_proto2_submessage = ::FooBar::Proto2::TestImportedMessage.new
assert m.has_optional_proto2_submessage?
assert TestMessage.descriptor.lookup('optional_proto2_submessage').has?(m)

m.clear_optional_proto2_submessage
assert !m.has_optional_proto2_submessage?
end

def test_clear_repeated_fields
m = TestMessage.new

Expand Down Expand Up @@ -487,6 +498,7 @@ def test_to_h
:optional_int64=>0,
:optional_msg=>nil,
:optional_msg2=>nil,
:optional_proto2_submessage=>nil,
:optional_string=>"foo",
:optional_uint32=>0,
:optional_uint64=>0,
Expand Down
2 changes: 2 additions & 0 deletions ruby/tests/basic_test.proto
Expand Up @@ -6,6 +6,7 @@ import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "test_import_proto2.proto";

message Foo {
Bar bar = 1;
Expand All @@ -32,6 +33,7 @@ message TestMessage {
optional bytes optional_bytes = 9;
optional TestMessage2 optional_msg = 10;
optional TestEnum optional_enum = 11;
optional foo_bar.proto2.TestImportedMessage optional_proto2_submessage = 24;

repeated int32 repeated_int32 = 12;
repeated int64 repeated_int64 = 13;
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.am
Expand Up @@ -546,6 +546,7 @@ EXTRA_DIST = \
google/protobuf/compiler/package_info.h \
google/protobuf/compiler/ruby/ruby_generated_code.proto \
google/protobuf/compiler/ruby/ruby_generated_code_pb.rb \
google/protobuf/compiler/ruby/ruby_generated_code_proto2_import.proto \
google/protobuf/compiler/ruby/ruby_generated_code_proto2.proto \
google/protobuf/compiler/ruby/ruby_generated_code_proto2_pb.rb \
google/protobuf/compiler/ruby/ruby_generated_pkg_explicit.proto \
Expand Down
3 changes: 3 additions & 0 deletions src/google/protobuf/compiler/ruby/ruby_generated_code.proto
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package A.B.C;

import "ruby_generated_code_proto2_import.proto";

message TestMessage {
int32 optional_int32 = 1;
int64 optional_int64 = 2;
Expand All @@ -14,6 +16,7 @@ message TestMessage {
bytes optional_bytes = 9;
TestEnum optional_enum = 10;
TestMessage optional_msg = 11;
TestImportedMessage optional_proto2_submessage = 12;

repeated int32 repeated_int32 = 21;
repeated int64 repeated_int64 = 22;
Expand Down
2 changes: 2 additions & 0 deletions src/google/protobuf/compiler/ruby/ruby_generated_code_pb.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -2,6 +2,8 @@ syntax = "proto2";

package A.B.C;

import "ruby_generated_code_proto2_import.proto";

message TestMessage {
optional int32 optional_int32 = 1 [default = 1];
optional int64 optional_int64 = 2 [default = 2];
Expand All @@ -14,6 +16,7 @@ message TestMessage {
optional bytes optional_bytes = 9 [default = "\0\1\2\100fubar"];
optional TestEnum optional_enum = 10 [default = A];
optional TestMessage optional_msg = 11;
optional TestImportedMessage optional_proto2_submessage = 12;

repeated int32 repeated_int32 = 21;
repeated int64 repeated_int64 = 22;
Expand Down
@@ -0,0 +1,5 @@
syntax = "proto2";

package A.B.C;

message TestImportedMessage {}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 1 addition & 40 deletions src/google/protobuf/compiler/ruby/ruby_generator.cc
Expand Up @@ -490,43 +490,6 @@ bool UsesTypeFromFile(const Descriptor* message, const FileDescriptor* file,
return false;
}

// Ruby doesn't currently support proto2. This causes a failure even for proto3
// files that import proto2. But in some cases, the proto2 file is only being
// imported to extend another proto2 message. The prime example is declaring
// custom options by extending FileOptions/FieldOptions/etc.
//
// If the proto3 messages don't have any proto2 submessages, it is safe to omit
// the dependency completely. Users won't be able to use any proto2 extensions,
// but they already couldn't because proto2 messages aren't supported.
//
// If/when we add proto2 support, we should remove this.
bool MaybeEmitDependency(const FileDescriptor* import,
const FileDescriptor* from,
io::Printer* printer,
std::string* error) {
if (from->syntax() == FileDescriptor::SYNTAX_PROTO3 &&
import->syntax() == FileDescriptor::SYNTAX_PROTO2) {
for (int i = 0; i < from->message_type_count(); i++) {
if (UsesTypeFromFile(from->message_type(i), import, error)) {
// Error text was already set by UsesTypeFromFile().
return false;
}
}

// Ok to omit this proto2 dependency -- so we won't print anything.
GOOGLE_LOG(WARNING) << "Omitting proto2 dependency '" << import->name()
<< "' from proto3 output file '"
<< GetOutputFilename(from->name())
<< "' because we don't support proto2 and no proto2 "
"types from that file are being used.";
return true;
} else {
printer->Print(
"require '$name$'\n", "name", GetRequireName(import->name()));
return true;
}
}

bool GenerateDslDescriptor(const FileDescriptor* file, io::Printer* printer,
std::string* error) {
printer->Print(
Expand Down Expand Up @@ -572,9 +535,7 @@ bool GenerateFile(const FileDescriptor* file, io::Printer* printer,
"filename", file->name());

for (int i = 0; i < file->dependency_count(); i++) {
if (!MaybeEmitDependency(file->dependency(i), file, printer, error)) {
return false;
}
printer->Print("require '$name$'\n", "name", GetRequireName(file->dependency(i)->name()));
}

// TODO: Remove this when ruby supports extensions for proto2 syntax.
Expand Down
20 changes: 17 additions & 3 deletions src/google/protobuf/compiler/ruby/ruby_generator_unittest.cc
Expand Up @@ -57,7 +57,7 @@ std::string FindRubyTestDir() {
// Some day, we may integrate build systems between protoc and the language
// extensions to the point where we can do this test in a more automated way.

void RubyTest(string proto_file) {
void RubyTest(string proto_file, string import_proto_file = "") {
std::string ruby_tests = FindRubyTestDir();

google::protobuf::compiler::CommandLineInterface cli;
Expand All @@ -77,9 +77,23 @@ void RubyTest(string proto_file) {
test_input,
true));

// Copy generated_code_import.proto to the temporary test directory.
std::string test_import;
if (!import_proto_file.empty()) {
GOOGLE_CHECK_OK(File::GetContents(
ruby_tests + import_proto_file + ".proto",
&test_import,
true));
GOOGLE_CHECK_OK(File::SetContents(
TestTempDir() + import_proto_file + ".proto",
test_import,
true));
}

// Invoke the proto compiler (we will be inside TestTempDir() at this point).
std::string ruby_out = "--ruby_out=" + TestTempDir();
std::string proto_path = "--proto_path=" + TestTempDir();

std::string proto_target = TestTempDir() + proto_file + ".proto";
const char* argv[] = {
"protoc",
Expand All @@ -105,11 +119,11 @@ void RubyTest(string proto_file) {
}

TEST(RubyGeneratorTest, Proto3GeneratorTest) {
RubyTest("/ruby_generated_code");
RubyTest("/ruby_generated_code", "/ruby_generated_code_proto2_import");
}

TEST(RubyGeneratorTest, Proto2GeneratorTest) {
RubyTest("/ruby_generated_code_proto2");
RubyTest("/ruby_generated_code_proto2", "/ruby_generated_code_proto2_import");
}

TEST(RubyGeneratorTest, Proto3ImplicitPackageTest) {
Expand Down