2023年4月20日宣布的变更
Ruby 生成器变更
此 GitHub PR(将在 23.x 版本中出现)更改了 Ruby 代码生成器,以便发出序列化的 proto 而非 DSL。
它从代码生成器中移除了 DSL,以便将来将 DSL 分拆到独立的包中。
给定一个 .proto 文件,例如
syntax = "proto3";
package pkg;
message TestMessage {
optional int32 i32 = 1;
optional TestMessage msg = 2;
}
变更前的生成代码
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: protoc_explorer/main.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_file("test.proto", :syntax => :proto3) do
add_message "pkg.TestMessage" do
proto3_optional :i32, :int32, 1
proto3_optional :msg, :message, 2, "pkg.TestMessage"
end
end
end
module Pkg
TestMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pkg.TestMessage").msgclass
end
变更后的生成代码
# frozen_string_literal: true
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test.proto
require 'google/protobuf'
descriptor_data = "\n\ntest.proto\x12\x03pkg\"S\n\x0bTestMessage\x12\x10\n\x03i32\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12\"\n\x03msg\x18\x02 \x01(\x0b\x32\x10.pkg.TestMessageH\x01\x88\x01\x01\x42\x06\n\x04_i32B\x06\n\x04_msgb\x06proto3"
begin
Google::Protobuf::DescriptorPool.generated_pool.add_serialized_file(descriptor_data)
rescue TypeError => e
# <compatibility code, see below>
end
module Pkg
TestMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pkg.TestMessage").msgclass
end
此变更修复了之前存在的几乎所有剩余的一致性问题。这是从 DSL(存在信息丢失)迁移到序列化描述符(保留所有信息)的副作用。
向后兼容性
此变更应与 2021 年 9 月发布的 Ruby Protobuf >= 3.18.0 完全兼容。此外,它还应与所有现有用户和部署兼容。
为了实现这种程度的向后兼容性,我们插入了**一些**特殊的兼容性代码,您应该知晓。如果没有兼容性代码,存在一个可能破坏向后兼容性的边缘情况。之前的代码在某种程度上比较宽松,而新代码将更加严格。
当使用完整的序列化描述符时,它包含了此文件导入的所有 `.proto` 文件列表(而 DSL 从未正确添加依赖项)。请参阅 [`descriptor.proto`](https://github.com/protocolbuffers/protobuf/blob/dfb71558a2226718dc3bcf5df27cbc11c1f72382/src/google/protobuf/descriptor.proto#L65-L66) 中的代码。
`add_serialized_file` 会验证描述符中列出的所有依赖项是否已先使用 `add_serialized_file` 添加。通常这没有问题,因为生成的代码会包含所有依赖项的 Ruby `require` 语句,并且如果依赖的类型未在 `DescriptorPool` 中预先定义,描述符也会加载失败。
但是,如果文件路径存在歧义,则可能出现问题。例如,考虑以下场景
// foo/bar.proto
syntax = "proto2";
message Bar {}
// foo/baz.proto
syntax = "proto2";
import "bar.proto";
message Baz {
optional Bar bar = 1;
}
如果您像这样调用 `protoc`,它将正常工作
$ protoc --ruby_out=. -Ifoo foo/bar.proto foo/baz.proto
$ RUBYLIB=. ruby baz_pb.rb
但是,如果您像这样调用 `protoc`,并且没有兼容性代码,它将加载失败
$ protoc --ruby_out=. -I. -Ifoo foo/baz.proto
$ protoc --ruby_out=. -I. -Ifoo foo/bar.proto
$ RUBYLIB=foo ruby foo/baz_pb.rb
foo/baz_pb.rb:10:in `add_serialized_file': Unable to build file to DescriptorPool: Depends on file 'bar.proto', but it has not been loaded (Google::Protobuf::TypeError)
from foo/baz_pb.rb:10:in `<main>'
问题在于 `bar.proto` 被两个不同的规范名称引用:`bar.proto` 和 `foo/bar.proto`。这是一个用户错误:每个导入都应始终使用一致的完整路径引用。希望这类用户错误很少见,但未经尝试难以知晓。
此变更中的代码会在此边缘情况发生时使用 `warn` 打印警告
$ RUBYLIB=foo ruby foo/baz_pb.rb
Warning: Protobuf detected an import path issue while loading generated file foo/baz_pb.rb
- foo/baz.proto imports bar.proto, but that import was loaded as foo/bar.proto
Each proto file must use a consistent fully-qualified name.
This will become an error in the next major version.
在这种情况下有两种可能的修复方法。一种是始终对导入使用名称 `bar.proto`(删除 `-I.`)。另一种是始终对导入使用名称 `foo/bar.proto`(将导入行更改为 `import "foo/bar.proto"` 并删除 `-Ifoo`)。
我们计划在下一个主要版本中移除此兼容性代码。