于 2023 年 4 月 20 日宣布的变更

于 2023 年 4 月 20 日宣布的 Protocol Buffers 相关变更。

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 版本 100% 兼容。此外,它也应该与所有现有的用户和部署兼容。

为了实现这种程度的向后兼容性,我们插入了一些特殊的兼容性代码,您应该了解这一点。如果没有这些兼容性代码,有一个边缘情况可能会破坏向后兼容性。旧代码在某种程度上比较宽松,而新代码会更加严格。

当使用完整的序列化描述符时,它包含一个由该文件导入的所有 .proto 文件的列表(而 DSL 从未正确添加依赖项)。请参阅 descriptor.proto 中的代码。

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.protofoo/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)。

我们计划在下一个主要版本中移除此兼容性代码。