迁移指南
v30.0 中的变更
以下是库版本中引入的重大变更列表,以及如何更新代码以适应这些变更。
本指南涵盖了在 v30.x 新闻公告和 v30.0 发行说明中宣布的重大变更。
使用 Fetched Deps 替换 CMake 子模块
以前,我们默认的 CMake 行为是使用 Git 子模块来获取固定的依赖项。指定 -Dprotobuf_ABSL_PROVIDER=package
会将我们的 CMake 配置切换为查找 Abseil 的本地安装(对于 jsoncpp 和 gtest 也有类似的选项)。这些选项已不复存在,现在的默认行为是首先查找所有依赖项的安装,如果需要,则回退到从 GitHub 获取固定版本。
要防止任何回退获取(类似于旧的 package
行为),您可以使用以下命令调用 CMake:
cmake . -Dprotobuf_LOCAL_DEPENDENCIES_ONLY=ON
要始终从固定版本获取依赖项(类似于旧的默认行为),您可以使用以下命令调用 CMake:
cmake . -Dprotobuf_FORCE_FETCH_DEPENDENCIES=ON
string_view 返回类型
以下描述符 API 的返回类型现在是 absl::string_view
,这可以节省内存:
MessageLite::GetTypeName
UnknownField::length_delimited
- 描述符 API 名称函数,例如
FieldDescriptor::full_name
我们预计未来的重大版本将继续将更多 API 迁移到 absl::string_view
。
在大多数情况下,您应该尝试在安全的情况下更新类型以使用 absl::string_view
,或者在需要时显式复制到原始类型。如果这是在函数中返回的,您可能还需要更新调用方。
如果受影响的 API 方法返回的字符串被用作:
类型 | 迁移 |
---|---|
| 显式转换为 或者,切换到性能更高的 |
| 迁移到 如果不可行(例如由于大量依赖关系),复制到 |
| 如果可为 null,则迁移到 否则,迁移到 调用 |
对于常见的容器和其他 API,您可以迁移到与 absl::string_view
兼容的变体。以下是一些常见的示例。
类别 | 迁移前 | 迁移 |
---|---|---|
插入到 std::vector<std::string> |
|
|
map 或 set 的插入 |
|
|
map 或 set 的查找 |
| 迁移到 Abseil 容器。 或者,定义一个透明比较器。
|
字符串连接 |
|
无论如何,出于性能原因,推荐使用这些。请参阅 https://abseil.io/tips/3。 |
另请参阅 https://abseil.io/tips/1 获取有关使用 absl::string_view
的一般提示。
停用 MSVC + Bazel
Windows 上的 Bazel 用户应切换到使用 clang-cl,方法是在其项目中添加以下内容,如此示例所示。
.bazelrc
common --enable_platform_specific_config build:windows
--extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl
--extra_execution_platforms=//:x64_windows-clang-cl
MODULE.bazel
bazel_dep(name = "platforms", version = "0.0.10")
bazel_dep(name = "rules_cc", version = "0.0.17")
# For clang-cl configuration
cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension")
use_repo(cc_configure, "local_config_cc")
WORKSPACE
load("//:protobuf_deps.bzl", "PROTOBUF_MAVEN_ARTIFACTS", "protobuf_deps")
protobuf_deps()
load("@rules_cc//cc:repositories.bzl", "rules_cc_dependencies", "rules_cc_toolchains")
rules_cc_dependencies()
rules_cc_toolchains()
BUILD
仅适用于需要与 Bazel 8 兼容的用户。
platform(
name = "x64_windows-clang-cl",
constraint_values = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
"@bazel_tools//tools/cpp:clang-cl",
],
)
适用于需要与 Bazel 7 和 8 兼容的用户。
platform(
name = "x64_windows-clang-cl",
constraint_values = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
# See https://github.com/bazelbuild/rules_cc/issues/330.
"@rules_cc//cc/private/toolchain:clang-cl",
],
)
用户还可以通过设置选择退出标志 --define=protobuf_allow_msvc=true
来暂时抑制错误,直到下一个重大版本。
或者,希望继续使用 MSVC 的用户可以切换到使用 CMake。这可以通过 Visual Studio 或通过向 CMake 命令行提供 MSVC 生成器来完成。例如:
cmake -G "Visual Studio 17 2022" -A Win64 .
ctype 从 FieldDescriptor 选项中移除
我们停止了从 FieldDescriptor
选项中公开 ctype
。您可以使用在 v28 版本中添加的 FieldDescriptor::cpp_string_type()
API 来替代它。
修改调试 API 以编辑敏感字段
Protobuf C++ 调试 API(包括 Protobuf AbslStringify、proto2::ShortFormat
、proto2::Utf8Format
、Message::DebugString
、Message::ShortDebugString
、Message::Utf8DebugString
)已更改为编辑由 debug_redact
注释的敏感字段;这些 API 的输出包含一个进程级随机前缀,并且不再可由 Protobuf TextFormat 解析器解析。用户应在大多数需要人类可读输出(例如日志记录)的情况下采用新的编辑后调试格式,或者考虑切换到二进制格式进行序列化和反序列化。需要旧的可反序列化格式的用户可以使用 TextFormat.printer().printToString(proto)
,但这不会编辑敏感字段,因此应谨慎使用。
请在 2024 年 12 月 4 日发布的新闻文章中阅读有关此内容的更多信息。
移除已弃用的 API
我们移除了以下公共运行时 API,这些 API 已被标记为弃用(例如 ABSL_DEPRECATED
)至少一个次要或主要版本,并且已过时或被替换。
API: Arena::CreateMessage
替代方案: Arena::Create
API: Arena::GetArena
替代方案: value->GetArena()
API: RepeatedPtrField::ClearedCount
替代方案: 迁移到 Arenas (迁移指南)。
API: JsonOptions
替代方案: JsonPrintOptions
放弃 C++14 支持
此版本放弃了 C++ 14 作为最低支持版本,并将其提高到 17,根据 基础 C++ 支持矩阵。
用户应升级到 C++17。
在 Arena 上清除 Oneof 消息后引入 ASAN 中毒
此更改添加了一项强化检查,影响使用 Arenas 的 C++ protobufs。分配在 protobuf arena 上的 oneof 消息现在在调试模式下被清除,在 ASAN 模式下被“毒化”。调用 clear 后,将来尝试使用该内存区域将在 ASAN 中导致崩溃,作为 use-after-free 错误。
此实现需要 C++17。
放弃我们的 C++ CocoaPods 版本
我们放弃了 C++ CocoaPods 版本,该版本自 v4.x.x 起已损坏。C++ 用户应直接使用我们的 GitHub release。
Python 中的变更
Python 的主版本从 5.29.x 提升到 6.30.x。
放弃 Python 3.8 支持
最低支持的 Python 版本是 3.9。用户应该升级。
移除 bazel/system_python.bzl 别名
我们移除了旧的 bazel/system_python.bzl
别名。
移除对 system_python.bzl
的直接引用,转而使用 protobuf_deps.bzl
。如果您需要直接引用,请使用它在 v5.27.0 中移动到的 python/dist/system_python.bzl
。
字段设置器验证变更
Python 和 upb 的字段设置器现在在 2023 版下验证封闭枚举。用无效值更新的封闭枚举字段会生成错误。
移除已弃用的 py_proto_library 宏
protobuf.bzl
中已弃用的内部 py_proto_library
Bazel 宏已被移除。它被官方的 py_proto_library
所取代,后者在 v29.x 中移至 protobuf 的 bazel/py_proto_library
中。此实现在 v29.x 之前可在 rules_python
中找到。
移除已弃用的 API
我们移除了以下公共运行时 API,这些 API 已被标记为弃用至少一个次要或主要版本。
反射方法
API: reflection.ParseMessage
、reflection.MakeClass
替代方案: message_factory.GetMessageClass()
RPC 服务接口
API: service.RpcException
、service.Service
、service.RpcController
和 service.RpcChannel
替代方案: 从 2.3.0 版本开始,RPC 实现不应尝试在此基础上构建,而应提供代码生成器插件,以生成特定于特定 RPC 实现的代码。
MessageFactory 和 SymbolDatabase 方法
API: MessageFactory.GetPrototype
、MessageFactory.CreatePrototype
、MessageFactory.GetMessages
、SymbolDatabase.GetPrototype
、SymbolDatabase.CreatePrototype
和 SymbolDatabase.GetMessages
替代方案: message_factory.GetMessageClass()
和 message_factory.GetMessageClassesForFiles()
。
GetDebugString
API: GetDebugString
替代方案
没有替代方案。它只存在于 Python C++ 中,而该版本已不再发布。纯 Python 或 UPB 不支持它。
Python map 字段的 setdefault 行为变更
对于 ScalarMap
,setdefault
类似于 dict
,但键和值都必须设置。对于 MessageMaps
,setdefault
会被拒绝。
Python 嵌套消息类的 __qualname__ 包含外部消息名称
Python 嵌套消息类的 __qualname__
现在包含外部消息名称。以前,对于嵌套消息,__qualname__
的结果与 __name__
相同,即不包含外部消息名称。
例如:
message Foo {
message Bar {
bool bool_field = 1;
}
}
nested = test_pb2.Foo.Bar()
self.assertEqual('Bar', nested.__class__.__name__)
self.assertEqual('Foo.Bar', nested.__class__.__qualname__) # It was 'Bar' before
Objective-C 中的变更
这是 Objective-C 的第一个重大版本发布.
Objective-C 的主版本从 3.x.x 提升到 4.30.x。
全面修订未知字段处理 API,弃用大部分现有 API
我们弃用了 GPBUnknownFieldSet
并用 GPBUnknownFields
替换了它。新类型保留了未知字段从原始输入或 API 调用中的顺序,以确保在将消息写回时保留任何语义上的顺序含义。
作为此项工作的一部分,GPBUnknownField
类型也进行了 API 更改,几乎所有现有 API 都被弃用并添加了新 API。
已弃用的属性 API
varintList
fixed32List
fixed64List
lengthDelimitedList
groupList
已弃用的修改 API
addVarint
addFixed32
addFixed64
addLengthDelimited
addGroup
已弃用的初始化器 initWithNumber:
。
新的属性 API
type
varint
fixed32
fixed64
lengthDelimited
group
此类型为其值建模单个字段编号,而不是将给定字段编号的所有值分组。创建新字段的 API 是 GPBUnknownFields
类上的 add*
API。
我们还弃用了 -[GPBMessage unknownFields]
。取而代之的是,有新的 API 来提取和更新消息的未知字段。
移除已弃用的 API
我们移除了以下公共运行时 API,这些 API 已被标记为弃用至少一个次要或主要版本。
GPBFileDescriptor
API: -[GPBFileDescriptor
syntax]
替代方案: 已过时。
GPBMessage mergeFrom:extensionRegistry
API: -[GPBMessage mergeFrom:extensionRegistry:
]
替代方案: -[GPBMessage mergeFrom:extensionRegistry:error:
]
GPBDuration timeIntervalSince1970
API: -[GPBDuration timeIntervalSince1970
]
替代方案: -[GPBDuration timeInterval
]
GPBTextFormatForUnknownFieldSet
API: GPBTextFormatForUnknownFieldSet()
替代方案: 已过时 - 使用 GPBTextFormatForMessage()
,它包含任何未知字段。
GPBUnknownFieldSet
API: GPBUnknownFieldSet
替代方案: GPBUnknownFields
GPBMessage unknownFields
API: GPBMessage unknownFields
属性
替代方案: -[GPBUnknownFields initFromMessage:
]、-[GPBMessage mergeUnknownFields:extensionRegistry:error:
] 和 -[GPBMessage clearUnknownFields
]
移除已弃用的旧 Gencode 运行时 API
此版本移除了支持 3.22.x 版本之前 Objective-C gencode 的已弃用运行时方法。当旧的生成代码启动时,该库还会在运行时发出一条日志消息。
API: +[GPBFileDescriptor allocDescriptorForClass:file:fields:fieldCount:storageSize:flags:]
替代方案: 使用当前版本的库重新生成。
API: +[GPBFileDescriptor allocDescriptorForClass:rootClass:file:fields:fieldCount:storageSize:flags:]
替代方案: 使用当前版本的库重新生成。
API: +[GPBEnumDescriptor allocDescriptorForName:valueNames:values:count:enumVerifier:]
替代方案: 使用当前版本的库重新生成。
替代方案: 使用当前版本的库重新生成。
API: -[GPBExtensionDescriptor initWithExtensionDescription:]
替代方案: 使用当前版本的库重新生成。
毒丸警告
我们更新了毒丸(poison pills),以便为旧的 gencode + 新的运行时组合发出警告,这些组合在新的滚动升级策略下可以工作,但在下一个主版本升级中会中断。例如,Python 4.x.x gencode 应该能与 5.x.x 运行时一起工作,但会警告即将与 6.x.x 运行时发生的中断。
C# 和 Ruby 中 UTF-8 强制执行的变更
我们修复了一个问题,以使 UTF-8 强制执行在各种语言中保持一致。在字符串字段中有错误的非 UTF8 数据的用户可能会更早地看到浮现的 UTF-8 强制执行错误。
Ruby 和 PHP 在 JSON 解析中的错误
我们根据 JSON 规范修复了在数字字段中解析字符串的不合规问题。
此修复没有伴随主版本升级,但 Ruby 和 PHP 现在会对数字字段中的非数字字符串(例如 ""
、"12abc"
、"abc"
)引发错误。v29.x 对这些错误情况包含了警告。
v22.0 中的编译器变更
JSON 字段名称冲突
我们对处理字段名称与 JSON 映射冲突的方式进行了一些细微的更改。在 proto3 中,我们部分放宽了限制,仅在字段名称产生大小写敏感的 JSON 映射(原始名称的驼峰式命名)时才给出错误。我们现在还检查 json_name
选项,并对大小写敏感的冲突给出错误。在 proto2 中,我们稍微收紧了限制,如果两个 json_name
规范冲突,将给出错误。如果隐式 JSON 映射(驼峰式命名)存在冲突,我们将在 proto2 中给出警告。
我们提供了一个临时的消息/枚举选项来恢复旧的行为。如果您无法立即重命名冲突的字段,可以在特定的消息/枚举上设置 deprecated_legacy_json_field_conflicts
选项。此选项将在未来的版本中移除,但能给您更多时间进行迁移。
v22.0 中的 C++ API 变更
4.22.0 对 C++ 运行时和 protoc 有重大变更,如八月公告所述。
Autotools 的弃用
变更来源: PR #10132
在 v22.0 中,我们从 protobuf 编译器和 C++ 运行时中移除了所有 Autotools 支持。如果您正在使用 Autotools 构建其中任何一个,您必须迁移到 CMake 或 Bazel。我们有一些关于使用 CMake 设置 protobuf 的专门说明。
Abseil 依赖
变更来源: PR #10416
从 v22.0 开始,我们明确依赖于 Abseil。这使我们能够移除大部分的stubs,这些 stubs 是从后来成为 Abseil 的旧内部代码分支出来的。存在一些细微的行为变化,但大多数对用户应该是透明的。一些值得注意的变化包括:
string_view -
absl::string_view
已经在我们的许多 API 中取代了const std::string&
。这最常用于输入参数,用户应该不会注意到明显的变化。在少数情况下(例如虚方法参数或返回类型),用户可能需要进行明确的更改以使用新的签名。表 - 我们现在使用 Abseil 的
flat_hash_map
、flat_hash_set
、btree_map
和btree_set
,而不是 STL 的 set/map。这些效率更高,并允许异构查找。这对用户来说基本上是不可见的,但可能会导致一些与表顺序相关的细微行为变化。日志记录 - Abseil 的日志库与我们旧的日志记录代码非常相似,只是拼写略有不同(例如,
ABSL_CHECK
而不是GOOGLE_CHECK
)。最大的区别是它不支持异常,并且现在在FATAL
断言失败时总是会崩溃。(以前我们有一个PROTOBUF_USE_EXCEPTIONS
标志来切换到异常。)由于这些只在遇到严重问题时发生,我们认为无条件崩溃是一个合适的回应。日志记录变更来源: PR #11623
构建依赖 - 新的构建依赖总是可能给下游用户带来破坏。我们需要 Abseil LTS 20230125 或更高版本才能构建。
对于 Bazel 构建,当从您的
WORKSPACE
运行protobuf_deps
时,Abseil 将在一个固定的 LTS 版本上自动下载和构建。这应该是透明的,但如果您依赖于旧版本的 Abseil,您将需要升级您的依赖。对于 CMake 构建,我们将首先查找由顶级 CMake 配置引入的现有 Abseil 安装(请参阅说明)。否则,如果
protobuf_ABSL_PROVIDER
设置为module
(其默认值),我们将尝试从我们的 git 子模块构建和链接 Abseil。如果protobuf_ABSL_PROVIDER
设置为package
,我们将查找一个预安装的系统版本的 Abseil。
GetCurrentTime 方法的变更
在 Windows 上,GetCurrentTime()
是系统提供的宏的名称。在 v22.x 之前,Protobuf 错误地移除了 GetCurrentTime()
的宏定义。这使得 Windows 开发者在包含 <protobuf/util/time_util.h>
后无法使用该宏。从 v22.x 开始,Protobuf 保留了宏定义。这可能会破坏依赖于先前行为的客户代码,例如,如果他们使用表达式 google::protobuf::util::TimeUtil::GetCurrentTime()
。
要将您的应用程序迁移到新行为,请更改您的代码以执行以下操作之一:
- 如果定义了
GetCurrent
宏,则显式取消定义GetCurrentTime
宏 - 通过使用
(google::protobuf::util::TimeUtil::GetCurrentTime)()
或类似表达式来防止宏展开
示例:取消定义宏
如果您不使用 Windows 中的宏,请使用此方法。
之前
#include <google/protobuf/util/time_util.h>
void F() {
auto time = google::protobuf::util::TimeUtil::GetCurrentTime();
}
之后
#include <google/protobuf/util/time_util.h>
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
void F() {
auto time = google::protobuf::util::TimeUtil::GetCurrentTime();
}
示例 2:防止宏展开
之前
#include <google/protobuf/util/time_util.h>
void F() {
auto time = google::protobuf::util::TimeUtil::GetCurrentTime();
}
之后
#include <google/protobuf/util/time_util.h>
void F() {
auto time = (google::protobuf::util::TimeUtil::GetCurrentTime)();
}
C++20 支持
变更来源: PR #10796
为了支持 C++20,我们在 C++ 生成的 protobuf 代码中保留了新的关键字。与其他保留关键字一样,如果您将它们用于任何字段、枚举或消息,我们将在它们后面添加一个下划线后缀以使其成为有效的 C++。例如,一个 concept
字段将生成一个 concept_()
getter。在您有使用这些关键字的现有 protos 的情况下,您需要更新引用它们以添加适当下划线的 C++ 代码。
Final 类
变更来源: PR #11604
作为加强 Protobuf 库中假设的更大努力的一部分,我们已将一些从未打算被继承的类标记为 final
。目前没有已知的继承这些类的用例,这样做可能会导致问题。如果您的代码正在继承这些类,没有缓解措施,但如果您认为您有继承的合理理由,可以提出一个问题。
容器静态断言
变更来源: PR #11550
作为加强 Protobuf 库中假设的更大努力的一部分,我们向 Map
、RepeatedField
和 RepeatedPtrField
容器添加了静态断言。这些确保您只使用这些容器与预期的类型,如我们的文档中所述。如果您遇到这些静态断言,您应该将您的代码迁移到使用 Abseil 或 STL 容器。std::vector
是 repeated field 容器的一个很好的直接替代品,而 std::unordered_map
或 absl::flat_hash_map
则是 Map
的替代品(前者提供类似的指针稳定性,而后者效率更高)。
已清除元素的弃用
关于“已清除字段”的 RepeatedPtrField
API 已被弃用,并将在未来的重大版本中完全移除。这最初是作为在元素被清除后重用元素的优化而添加的,但最终效果不佳。如果您正在使用此 API,您应该考虑迁移到 arenas 以实现更好的内存重用。
UnsafeArena 的弃用
变更来源: PR #10325
作为移除 arena 不安全 API 的更大努力的一部分,我们隐藏了 RepeatedField::UnsafeArenaSwap
。这是我们目前移除的唯一一个,但在后续版本中,我们将继续移除它们并提供帮助程序来处理 arena 之间的有效借用模式。在单个 arena(或堆栈/堆)内,Swap
与 UnsafeArenaSwap
一样高效。好处是,如果您不小心在不同的 arenas 之间调用它,它不会导致无效的内存操作。
Map Pair 升级
变更来源: PR #11625
对于 v22.0,我们已经开始清理 Map
API,使其与 Abseil 和 STL 更加一致。值得注意的是,我们已将 MapPair
类替换为 std::pair
的别名。这对大多数用户来说应该是透明的,但如果您直接使用该类,您可能需要更新您的代码。
新的 JSON 解析器
变更来源: PR #10729
我们在本版本中重写了 C++ JSON 解析器。这应该主要是一个隐藏的更改,但不可避免地,一些未记录的怪癖可能会发生变化;请相应地进行测试。解析不符合 RFC-8219 JSON 规范的文档(例如缺少引号或使用非标准布尔值)已被弃用,并将在未来的版本中移除。字段的序列化顺序现在保证与字段编号顺序匹配,而之前则不那么确定。
作为此次迁移的一部分,util/internal 下的所有文件都已被删除。这些文件用于旧的解析器,并且从未打算供外部使用。
Arena::Init
变更来源: PR #10623
Arena
中的 Init
方法是一段不做任何事情的代码,现已被移除。如果您正在调用此方法,您可能想直接使用一组 ArenaOptions
调用 Arena
构造函数。您应该删除该调用或迁移到该构造函数。
ErrorCollector 迁移
变更来源: PR #11555
作为我们 Abseil 迁移的一部分,我们正在从 const std::string&
迁移到 absl::string_view
。对于我们的三个错误收集器类,如果不破坏现有代码就无法做到这一点。对于 v22.0,我们决定发布两个变体,并将方法从 AddError
和 AddWarning
重命名为 RecordError
和 RecordWarning
。旧的签名已被标记为弃用,并且效率会稍低(由于字符串复制),但除此之外仍然有效。您应该将这些迁移到新版本,因为 Add*
方法将在后续的重大版本中移除。