Wire 编译器与 Gradle 插件¶
Wire 有两个关键组件:一个在构建时生成源代码的编译器,以及一个在程序执行时支持生成代码的运行时库。编译器是高度可配置的;本指南将解释其特性和用法。
入门¶
配置和执行 Wire 编译器的最佳方法是通过我们的 Gradle 插件。它需要 Gradle 5.5 或更高版本。
一个典型的项目在其标准目录 src/main/proto
中包含 .proto
文件。
src/
main/
proto/
com/
example/
pizza/
pizza_delivery.proto
pizza.proto
sales/
price.proto
store.proto
geo/
address.proto
country.proto
kotlin/
com/
example/
pizza/
PizzaApp.kt
test/
kotlin/
com/
example/
pizza/
PizzaAppTest.kt
build.gradle
由于此项目使用默认配置,插件设置很简单。只需在 build.gradle
中添加一个简单的块即可,
使用插件应用
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.squareup.wire:wire-gradle-plugin:<version>'
}
}
apply plugin: 'com.squareup.wire'
wire {
kotlin {}
}
使用 plugins DSL
plugins {
id 'application'
id 'org.jetbrains.kotlin.jvm'
id 'com.squareup.wire'
}
wire {
kotlin {}
}
该插件将读取 .proto
模式定义,验证它们,链接它们,并生成 Kotlin 代码。它内部使用 KotlinPoet 生成紧凑易读的源文件。
如果未设置 sourcePath
,Wire 默认会在 src/main/proto
中查找 .proto
文件。
Wire 会在 build/generated/source/wire
中生成文件。它还会将此目录注册为项目的源目录,以便生成的源文件由 Kotlin 编译器编译。
将 kotlin
替换为 java
以生成 Java 源文件。
输入和输出¶
Wire 可以从本地目录、本地 .jar
文件或使用 Maven 坐标指定外部构件中拉取源 .proto
模式定义。
wire {
sourcePath {
srcDir 'src/main/protos'
}
sourcePath {
srcJar 'lib/pizza-protos.jar'
}
sourcePath {
srcJar 'com.example.pizza:pizza-protos:1.0.0'
}
...
}
如果您的资源或目录包含比您需要编译的文件更多的 .proto
文件,您可以将输入限制为特定文件
wire {
sourcePath {
srcDir 'src/main/protos'
include 'com/example/pizza/pizza_delivery.proto'
include 'com/example/pizza/pizza.proto'
}
}
也支持通配符(带有两个星号)
wire {
sourcePath {
srcDir 'src/main/protos'
include 'com/example/pizza/**'
}
}
您也可以反过来排除一些文件
wire {
sourcePath {
srcDir 'src/main/protos'
exclude 'com/example/juice/**'
exclude 'com/example/pizza/toppings.proto'
}
}
Wire 可以在同一构建中生成多种语言的代码。使用 includes
指定针对目标语言生成哪些类型;随后的语言将生成剩余的部分。
wire {
...
kotlin {
// Kotlin emits the matched types only.
includes = ['com.example.pizza.*']
}
java {
// Java gets everything else!
}
}
请注意,源指定为文件系统路径(使用斜杠分隔),目标指定为 Proto 类型名称(使用点分隔)。
您也可以使用 excludes 指定目标。如果同时存在 includes 和 excludes,则 excludes 优先。
wire {
...
kotlin {
// Kotlin emits everything but sales and geo packages.
excludes = ['com.example.sales.*', 'com.example.geo.*']
}
java {
// Java gets those because they're left over.
}
}
如果 includes 或 excludes 中的任何名称未使用,Wire 将打印警告。
配置 Wire 时,必须使用 .proto
文件中 package
声明的包名。option java_package
名称不用于 Wire 配置。
库的 Proto 路径¶
大型项目可能跨越多个模块。为了支持这一点,Wire 引入了‘proto 路径’。此路径上的 .proto
模式文件用于链接和验证,但不会生成输出文件。
proto 路径支持与源路径相同的输入:目录、.jar
文件和 Maven 坐标。类似地,proto 路径可以使用 include
进行过滤。
wire {
protoPath {
srcDir 'src/main/address-protos'
}
protoPath {
srcJar 'lib/price.jar'
}
protoPath {
srcJar 'com.example:countries:1.0.0'
include 'com/example/geo/country.proto'
}
protoPath {
srcDir 'src/main/extra-protos'
exclude 'com/example/**'
}
...
}
源路径和 proto 路径是相互关联的,但只生成源路径上的类型。
Gradle 模块间的依赖关系¶
Wire 支持定义同一项目内模块之间的依赖关系。
模块可以将其 .proto
文件包含到输出资源中。当您的 .jar
文件可用作其他 proto 或 Wire 项目的库时,请使用此选项。请注意,只会包含库中使用的 .proto
文件。
wire {
protoLibrary = true
}
Wire 还创建了两个配置 protoPath
和 protoSource
,您可以使用它们来定义对其他 proto 或 Wire 项目的依赖。
dependencies {
// The task `:common-protos:jar` will be added into the dependency
// graph of this module for the Wire generating tasks.
protoPath(project(':common-protos'))
implementation(project(':common-protos'))
}
wire {
kotlin {
}
}
请注意,protoPath
和 protoSource
依赖默认不是传递的。如果需要,您可以手动更改。
configurations.protoPath {
transitive = true
}
修剪¶
对于移动项目,很容易生成大量不必要的代码。例如,一个 .proto
模式可能支持已过时功能的类型。
像 R8 和 ProGuard 这样的通用代码压缩工具难以压缩 Wire 生成的源代码,因为 encode()
和 decode()
函数仍在使用它们。相反,Wire 提供了自己的代码压缩器,以便及早消除不需要的声明。
使用 prune
可以精确地消除不需要的类型或成员。它们将连同所有对它们的引用一起被消除。
wire {
...
prune 'com.example.store.Store'
prune 'com.example.geo.Country'
...
}
或者,您可以选择指定保留哪些内容,修剪所有不可达的内容。
wire {
...
root 'com.example.pizza.PizzaDelivery'
...
}
默认情况下,对于未知的 root
和 prune
参数,此功能是严格的。您可能希望其更为宽松
wire {
rejectUnusedRootsOrPrunes = false
...
}
版本匹配¶
修剪过时字段的另一种方法是为其分配版本,然后根据版本范围或唯一版本生成代码。超出版本范围的字段将被修剪。
成员可以使用 wire.since
和 wire.until
选项声明;枚举常量可以使用 wire.constant_since
和 wire.constant_until
。例如,这些选项声明了一个字段 age
,它在版本 “5.0” 中被 birth_date
替换。
import "wire/extensions.proto";
message Singer {
optional string name = 1;
optional int32 age = 2 [(wire.until) = "5.0"];
optional Date birth_date = 3 [(wire.since) = "5.0"];
}
客户端代码通常应针对单个版本。在此示例中,客户端将只包含 name
和 birth_date
字段。
wire {
onlyVersion "5.0"
}
支持多个客户端的服务代码应支持所有受支持客户端的版本联合。此类代码将包含 name
,以及 age
和 birth_date
字段。
wire {
sinceVersion "3.0"
untilVersion "6.0"
}
Proto 库¶
默认情况下,生成的 .jar
构件不包含 .proto
输入文件。使用 protoLibrary
选项可以包含它们
wire {
protoLibrary = true
}
这在为其他 wire
任务构建 .jar
文件作为依赖项时最有用。请注意,只包含真正的源文件——被修剪或未使用的 proto 消息不包含在输出构件中。
空运行¶
使用 dry run 选项,编译器只会将本应生成到标准输出的源文件名输出。
wire {
dryRun = true
}
自定义输出¶
Java¶
这是一个详尽的 Java 配置。布尔值显示其默认行为。
wire {
java {
// Proto types to include generated sources for. Types listed here will be
// generated for this target and not for subsequent targets in the task.
//
// This list should contain package names (suffixed with `.*`) and type
// names only. It should not contain member names.
includes = ['com.example.pizza.*']
// Proto types to excluded generated sources for. Types listed here will
// not be generated for this target.
//
// This list should contain package names (suffixed with `.*`) and type
// names only. It should not contain member names.
excludes = ['com.example.sales.*']
// True if types emitted for this target should not also be emitted for
// other targets. Use this to cause multiple outputs to be emitted for the
// same input type.
exclusive = true
// Directory to emit to.
out "${buildDir}/custom"
// True for emitted types to implement android.os.Parcelable.
android = false
// True to enable the androidx.annotation. Nullable annotation
// where applicable.
androidAnnotations = false
// True to emit code that uses reflection for reading, writing, and toString
// methods which are normally implemented with generated code.
compact = false
// True to turn visibility of all generated types' constructors
// to non-public.
buildersOnly = false
// True to emit types for options declared on messages, fields, etc.
emitDeclaredOptions = true
// True to emit annotations for options applied on messages, fields, etc.
emitAppliedOptions = true
}
}
Kotlin¶
这是一个详尽的 Kotlin 配置。布尔值和枚举显示其默认行为。
wire {
kotlin {
// Proto types to include generated sources for. Types listed here will be
// generated for this target and not for subsequent targets in the task.
//
// This list should contain package names (suffixed with `.*`) and type
// names only. It should not contain member names.
includes = ['com.example.pizza.*']
// Proto types to excluded generated sources for. Types listed here will not
// be generated for this target.
//
// This list should contain package names (suffixed with `.*`) and type
// names only. It should not contain member names.
excludes = ['com.example.sales.*']
// True if types emitted for this target should not also be emitted for
// other targets. Use this to cause multiple outputs to be emitted for the
// same input type.
exclusive = true
// Directory to emit to.
out "${buildDir}/custom"
// True for emitted types to implement android.os.Parcelable.
android = false
// True for emitted types to implement APIs for easier migration from the
// Java target.
javaInterop = false
// True to turn visibility of all generated types' constructors
// to non-public.
buildersOnly = false
// True to emit types for options declared on messages, fields, etc.
emitDeclaredOptions = true
// True to emit annotations for options applied on messages, fields, etc.
emitAppliedOptions = true
// `suspending` to generate coroutines APIs that require a Kotlin
// coroutines context.
// `blocking` to generate blocking APIs callable by Java and Kotlin.
rpcCallStyle = 'blocking'
// `client` to generate interfaces best suited to sending outbound calls.
// `server` to generate interfaces best suited to receiving inbound calls.
// `none` to not generate services.
rpcRole = 'server'
// If set, the value will be appended to generated service type names. If
// null, their rpcRole will be used as a suffix instead.
nameSuffix = "Suffix"
// True for emitted services to implement one interface per RPC.
singleMethodServices = false
// Set how many oneof choices are necessary for generated message classes to use the
// `OneOf<Key<T>, T>` form rather than the default, where options are flattened into the
// enclosing type.
boxOneOfsMinSize = 5000
// True to escape Kotlin keywords like `value` and `data` with backticks. Otherwise an
// underscore underscore is added as a suffix, like `value_` and `data_`.
escapeKotlinKeywords = false
// Defines how an protobuf enum type is to be generated. See `com.squareup.wire.kotlin.EnumMode`
enumMode = "enum_class"
// True to emit a adapters that include a decode() function that accepts a `ProtoReader32`.
// Use this optimization when targeting Kotlin/JS, where `Long` cursors are inefficient.
emitProtoReader32 = false
}
}
Proto¶
这是一个详尽的 Proto 配置。您可以使用此目标生成修剪后的 proto 模式,或对其进行漂亮打印。请注意,即使设置了多个输出,proto 目标也将始终输出模式的所有类型、所有服务和所有扩展。
wire {
proto {
// Directory to emit to.
out "${buildDir}/custom"
}
}
自定义处理程序¶
通过自定义模式处理程序,您可以以您想要的任何方式处理 proto 模式,包括代码生成或其他副作用,如验证、日志记录等。
您需要首先扩展 SchemaHandler 类,然后扩展 SchemaHandler.Factory 接口,该接口用于返回前者的实例。请参阅我们的 recipes 以了解不同用例的实现。
将其构建为一个 jar
构件,并将其作为构建脚本依赖项添加到您的 Gradle 项目中。
buildscript {
dependencies {
classpath "com.example.my-custom-handler:my-custom-handler:1.0.0"
}
}
接下来配置 Wire 插件以调用您的自定义处理程序。这是一个详尽的自定义配置。布尔值和枚举显示其默认行为。
wire {
custom {
// The name of a Java class to generate code with. This class must:
// * be in the buildscript dependencies for this Gradle project
// * be a public class
// * have a public no-arguments constructor
// * implement the com.squareup.wire.schema.SchemaHandler.Factory interface
schemaHandlerFactoryClass = "com.example.MyCustomHandlerFactory"
// These options work the same as the java and kotlin targets above.
includes = ['com.example.pizza.*']
excludes = ['com.example.sales.*']
exclusive = true
out "${buildDir}/custom"
// Custom payload which can be passed to the `SchemaHandler.Factory`.
options = [a: "one", b: "two", c: "three"]
}
}
Swift 支持¶
最简单的入门方法是使用 CocoaPods。
# Add the Wire compiler so that it is downloaded and available.
# CocoaPods will download the source and build the compiler directly,
# so you'll need Java installed.
pod 'WireCompiler'
# Add the Wire runtime to do the serializing/deserializing
pod 'Wire'
然后运行 pod install 以获取依赖项并构建 Wire 编译器。
也支持 Swift Package Manager 用于链接 Wire 运行时。
构建您的 Protos¶
Wire 编译器使用 SwiftPoet 生成 Swift 代码。生成的对象自动遵循 Equatable
、Codable
和 Sendable
协议。
假设您已使用 CocoaPods 下载了 Wire 编译器,要将您的 protos 编译成 Swift 文件
java -jar ./Pods/WireCompiler/compiler.jar \
"--proto_path=<directory containing .proto files>" \
"--swift_out=<directory where the generated .swift files go>" \
"--experimental-module-manifest=<path to manifest yaml file>"
Swift Manifest¶
Swift 引入了一个 Kotlin 和 Java 中不存在的新挑战:模块。Kotlin 和 Java 都使用完全限定的包名,但 Swift 模块由其编译单元定义,因此命名空间不在类型或文件级别声明。这意味着我们需要为 Swift 构建一个新的打包系统,以处理 Swift 模块命名空间和导入。
我们认为,调用方定义模块的最简单方法是让 Wire 直接处理这些定义。一个 manifest 文件定义了模块、它们的名称、它们的依赖关系以及上面提到的内容根和修剪规则。
在此示例 manifest 中,DarkSide 和 LightSide 模块将依赖并导入 CommonProtos 模块
CommonProtos:
roots:
- jedi.Lightsaber
- jedi.MindTrick
- jedi.TheForce
- jedi.Anakin
DarkSideProtos:
dependencies:
- CommonProtos
roots:
- darkside.*
- jedi.Lightning
prunes:
- jedi.Mercy
LightSideProtos:
dependencies:
- CommonProtos
roots:
- lightside.*
# Import the rest of the Jedi powers not already in CommonProtos
- jedi.*
prunes:
# Remove unused lightsaber colors
- jedi.LightsaberColor#red
# Remove deprecated field. Use green_lightsaber instead.
- lightside.Luke#blue_lightsaber
# Remove dark-side-only types
- jedi.Lightning