迁移指南

列出了对库版本进行的重大变更,以及如何更新代码以适应这些变更。

v30.0 中的变更

以下是对库版本进行的重大变更列表,以及如何更新代码以适应这些变更。

这涵盖了 v30.x 新闻公告v30.0 发布说明 中宣布的重大变更。

用Fetched Deps替换了CMake Submodules

此前,我们默认的 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 方法返回的字符串正在被用作

类型迁移

std::string

显式转换为 std::string 以保留现有行为。

或者,切换到性能更好的 absl::string_view

const std::string&

迁移到 absl::string_view

如果不可行(例如由于大量依赖项),复制到 std::string 可能更容易。

const std::string*

const char*

如果可空,迁移到 std::optional<absl::string_view>

否则,迁移到 absl::string_view

调用 data() 时要小心,因为 absl::string_view 不保证是 null 结尾的。

对于常用的容器和其他 API,您可能可以迁移到与 absl::string_view 兼容的变体。下面是一些常见示例。

类别迁移前迁移
插入到 std::vector<std::string>

push_back()

push_front()

push()

emplace_back()

emplace_front()

emplace()

Map 或 Set 的插入

set.insert(key)

map.insert({key, value})

map.insert({key,
{value_params...}})

set.emplace(key)

map.emplace(key, value)

map.try_emplace(key,
value_params...)

Map 或 Set 的查找

find()

count()

contains()

迁移到 Abseil 容器。

或者,定义一个透明比较器。

std::set<std::string, std::less<>>
std::map<std::string, T, std::less<>>

参见 https://abseil.io/tips/144

字符串连接

operator+

operator+=

absl::StrCat()

absl::StrAppend()

无论如何,都推荐出于性能原因使用这些方法。参见 https://abseil.io/tips/3

另请参见 https://abseil.io/tips/1 获取关于使用 absl::string_view 的一般技巧。

Poison 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",
    ],
)

用户还可以通过设置 opt-out 标志 --define=protobuf_allow_msvc=true 暂时忽略此错误,直到下一个重大版本发布。

另外,希望继续使用 MSVC 的用户可以切换到使用 CMake。这可以通过 Visual Studio 完成,或通过向 CMake 命令行提供 MSVC 生成器来完成。例如

cmake -G "Visual Studio 17 2022" -A Win64 .

从 FieldDescriptor Options 中移除了 ctype

我们停止暴露 FieldDescriptor options 中的 ctype。您可以使用在 v28 版本中添加的 FieldDescriptor::cpp_string_type() API 来代替它。

修改了 Debug API 以去除敏感字段

Protobuf C++ 调试 API(包括 Protobuf AbslStringify, proto2::ShortFormat, proto2::Utf8Format, Message::DebugString, Message::ShortDebugString, Message::Utf8DebugString)已更改为去除由 debug_redact 注解的敏感字段;这些 API 的输出包含一个每进程随机前缀,并且不再能被 Protobuf TextFormat Parsers 解析。对于大多数需要人类可读输出(例如日志记录)的情况,用户应采用新的去除敏感信息的调试格式,或者考虑切换到二进制格式进行序列化和反序列化。需要旧的、可反序列化格式的用户可以使用 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++ 支持矩阵,此版本将最低支持的 C++ 版本从 14 提高到 17。

用户应升级到 C++17。

在 Arena 上清除 Oneof 消息后引入了 ASAN Poisoning

此变更添加了一项强化检查,影响使用 Arenas 的 C++ protobuf。分配在 protobuf arena 上的 Oneof 消息现在在调试模式下会被清除,并在 ASAN 模式下会被 Poison。调用 clear 后,将来尝试使用该内存区域将在 ASAN 中作为 use-after-free 错误导致崩溃。

此实现需要 C++17。

停止了我们的 C++ CocoaPods 发布

我们停止了自 v4.x.x 以来就已损坏的 C++ CocoaPods 发布。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 的字段设置器现在在 edition 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: 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 中不支持此 API。

Python Map 字段的 setdefault 行为变更

setdefault 对于 ScalarMap 类似于 dict,但键和值都必须设置。setdefaultMessageMaps 无效。

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。

重构了 Unknown Field Handling 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。

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 property

替代方案: -[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: +[GPBEnumDescriptor allocDescriptorForName:valueNames:values:count:enumVerifier:extraTextFormatInfo:]

替代方案: 使用当前版本的库重新生成。

API: -[GPBExtensionDescriptor initWithExtensionDescription:]

替代方案: 使用当前版本的库重新生成。

Poison Pill 警告

我们更新了 poison pills,使其对在新的滚动升级策略下工作但会在下一个主要版本更新中破坏的旧 gencode + 新 runtime 组合发出警告。例如,Python 4.x.x gencode 应该可以在 5.x.x runtime 下工作,但会警告在 6.x.x runtime 下即将出现的破坏。

C# 和 Ruby 中 UTF-8 强制执行的变更

我们包含了一个修复程序,使 UTF-8 强制执行在不同语言中保持一致。在字符串字段中包含非 UTF-8 数据的用户可能会更早地看到 UTF-8 强制执行错误。

Ruby 和 PHP 在 JSON 解析中的错误

我们修复了根据 JSON 规范对数字字段中字符串进行 JSON 解析时存在的非一致性问题。

此修复没有伴随主要版本更新,但 Ruby 和 PHP 现在会对数字字段中的非数字字符串(例如 "", "12abc", "abc")引发错误。v29.x 对这些错误情况包含警告。

v22.0 中的编译器变更

JSON 字段名称冲突

变更来源: PR #11349, PR #10750

我们在处理与 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 构建其中任何一个,则必须迁移到 CMakeBazel。我们提供了一些 专门说明 关于使用 CMake 设置 protobuf 的信息。

Abseil 依赖

变更来源: PR #10416

在 v22.0 中,我们明确依赖于 Abseil。这使我们能够移除大部分 stubs,这些 stubs 是从后来成为 Abseil 的旧内部代码分支出来的。存在一些细微的行为变化,但大多数对用户应该是透明的。一些值得注意的变更包括

  • string_view - absl::string_view 在我们的许多 API 中取代了 const std::string&。这最常用于输入参数,用户应该不会注意到变化。在少数情况下(例如虚方法参数或返回类型),用户可能需要进行显式更改才能使用新的签名。

  • 表格 (tables) - 我们现在使用 Abseil 的 flat_hash_map, flat_hash_set, btree_map, 和 btree_set,而不是 STL 的 sets/maps。这些容器效率更高,并支持 异构查找。这对于用户来说大部分应该是不可见的,但也可能导致一些与表格排序相关的细微行为变化。

  • 日志记录 (logging) - Abseil 的 日志库 与我们旧的日志代码非常相似,只是拼写略有不同(例如,ABSL_CHECK 而不是 GOOGLE_CHECK)。最大的区别在于它不支持异常,并且当 FATAL 断言失败时将始终崩溃。(之前我们有一个 PROTOBUF_USE_EXCEPTIONS 标志可以切换到异常。)由于这些只在遇到严重问题时发生,我们认为无条件崩溃是合适的响应。

    日志变更来源: PR #11623

  • 构建依赖 (Build dependency) - 新的构建依赖项总是可能对下游用户造成破坏。我们要求 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。如果您现有的 proto 文件使用了这些关键字,则需要更新引用它们的 C++ 代码以添加相应的下划线。

Final 类

变更来源: PR #11604

作为强化 Protobuf 库中假设的一项更广泛工作的一部分,我们将一些从未打算被继承的类标记为 final。没有已知的使用案例需要继承这些类,并且这样做可能会导致问题。如果您的代码正在继承这些类,目前没有缓解措施,但如果您认为您有使用继承的合理原因,可以提交一个问题

容器静态断言

变更来源: PR #11550

作为强化 Protobuf 库中假设的一项更广泛工作的一部分,我们向 Map, RepeatedField, 和 RepeatedPtrField 容器添加了静态断言。这些断言确保您只使用预期类型与这些容器一起使用,如我们的文档所述。如果触发了这些静态断言,您应该迁移代码以使用 Abseil 或 STL 容器。std::vector 是 repeated field 容器的一个很好的直接替代品,而 std::unordered_mapabsl::flat_hash_map 可以用于 Map(前者提供类似的指针稳定性,而后者更高效)。

已清除元素弃用

变更来源: PR #11588, PR #11639

RepeatedPtrField 中关于“已清除字段”的 API 已被弃用,并将在未来的重大版本发布中完全移除。这最初是作为在元素被清除后重用内存的优化添加的,但最终效果不佳。如果您正在使用此 API,应考虑迁移到 arenas 以获得更好的内存重用。

UnsafeArena 弃用

变更来源: PR #10325

作为移除 arena-unsafe API 的一项更广泛工作的一部分,我们隐藏了 RepeatedField::UnsafeArenaSwap。这是我们目前移除的唯一一个,但在未来的版本中,我们将继续移除这些 API,并提供帮助函数来处理 arena 之间的有效借用模式。在单个 arena(或栈/堆)内,SwapUnsafeArenaSwap 一样高效。其好处是,如果您不小心在不同的 arena 之间调用它,它不会导致无效内存操作。

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 中,我们决定同时发布这两个变体,并将方法名从 AddErrorAddWarning 重命名为 RecordErrorRecordWarning。旧的签名已被标记为弃用,并且效率会略低(由于字符串复制),但仍可正常工作。您应将这些方法迁移到新版本,因为 Add* 方法将在未来的重大版本发布中移除。