Skip to content

Commit 7bd55c8

Browse files
authored
Merge pull request #3582 from square/sedwards/gradle-provider-target-dsl
Use Gradle properties in nested Wire output DSL
2 parents a52f52c + 7457067 commit 7bd55c8

11 files changed

Lines changed: 340 additions & 74 deletions

File tree

docs/wire_compiler.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Getting Started
99
---------------
1010

1111
The best way to configure and execute the Wire compiler is via our [Gradle][gradle] plugin. It
12-
requires Gradle 5.5 or newer.
12+
requires Gradle 8.2 or newer.
1313

1414
A typical project has `.proto` files in the standard `src/main/proto` directory.
1515

wire-gradle-plugin/api/wire-gradle-plugin.api

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
public class com/squareup/wire/gradle/CustomOutput : com/squareup/wire/gradle/WireOutput {
22
public fun <init> ()V
3-
public final fun getExcludes ()Ljava/util/List;
4-
public final fun getExclusive ()Z
5-
public final fun getIncludes ()Ljava/util/List;
6-
public final fun getOptions ()Ljava/util/Map;
7-
public final fun getSchemaHandlerFactory ()Lcom/squareup/wire/schema/SchemaHandler$Factory;
8-
public final fun getSchemaHandlerFactoryClass ()Ljava/lang/String;
3+
public final fun getExcludes ()Lorg/gradle/api/provider/ListProperty;
4+
public final fun getExclusive ()Lorg/gradle/api/provider/Property;
5+
public final fun getIncludes ()Lorg/gradle/api/provider/ListProperty;
6+
public final fun getOptions ()Lorg/gradle/api/provider/MapProperty;
7+
public final fun getSchemaHandlerFactory ()Lorg/gradle/api/provider/Property;
8+
public final fun getSchemaHandlerFactoryClass ()Lorg/gradle/api/provider/Property;
99
public final fun setExcludes (Ljava/util/List;)V
1010
public final fun setExclusive (Z)V
1111
public final fun setIncludes (Ljava/util/List;)V
@@ -162,7 +162,8 @@ public final class com/squareup/wire/gradle/WireExtension$ProtoRootSet {
162162

163163
public abstract class com/squareup/wire/gradle/WireOutput {
164164
public fun <init> ()V
165-
public final fun getOut ()Ljava/lang/String;
165+
protected fun getObjectFactory ()Lorg/gradle/api/model/ObjectFactory;
166+
public final fun getOut ()Lorg/gradle/api/provider/Property;
166167
public final fun setOut (Ljava/lang/String;)V
167168
public abstract fun toTarget (Ljava/lang/String;)Lcom/squareup/wire/schema/Target;
168169
}

wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WireOutput.kt

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,39 @@ import com.squareup.wire.schema.ProtoTarget
2626
import com.squareup.wire.schema.SchemaHandler
2727
import com.squareup.wire.schema.Target
2828
import com.squareup.wire.schema.newSchemaHandler
29+
import java.io.File
2930
import javax.inject.Inject
31+
import kotlin.LazyThreadSafetyMode.NONE
32+
import org.gradle.api.model.ObjectFactory
33+
import org.gradle.api.provider.ListProperty
34+
import org.gradle.api.provider.MapProperty
35+
import org.gradle.api.provider.Property
36+
import org.gradle.api.provider.Provider
3037

3138
/**
3239
* Specifies Wire's outputs (expressed as a list of [Target] objects) using Gradle's DSL (expressed
3340
* as destination directories and configuration options). This includes registering output
3441
* directories with the project so they can be compiled after they are generated.
3542
*/
3643
abstract class WireOutput {
44+
// Gradle decorates this getter for instances created with ObjectFactory.newInstance().
45+
@get:Inject
46+
protected open val objectFactory: ObjectFactory
47+
get() = throw UnsupportedOperationException("Injected by Gradle")
48+
49+
val out: Property<String> by lazy(NONE) {
50+
objectFactory.property(String::class.java)
51+
}
52+
3753
/** Set this to override the default output directory for this [WireOutput]. */
38-
var out: String? = null
54+
fun setOut(value: String?) {
55+
out.set(value)
56+
}
57+
58+
internal fun outputDirectory(
59+
projectDir: File,
60+
defaultOutputDirectory: Provider<String>,
61+
): Provider<String> = out.map { relativizeOutputDirectory(it, projectDir) }.orElse(defaultOutputDirectory)
3962

4063
/**
4164
* Transforms this [WireOutput] into a [Target] for which Wire will generate code. The [Target]
@@ -44,6 +67,16 @@ abstract class WireOutput {
4467
abstract fun toTarget(outputDirectory: String): Target
4568
}
4669

70+
internal fun relativizeOutputDirectory(
71+
outputDirectory: String,
72+
projectDir: File,
73+
): String {
74+
val file = File(outputDirectory)
75+
if (!file.isAbsolute) return outputDirectory
76+
return runCatching { file.relativeTo(projectDir).path }
77+
.getOrElse { outputDirectory }
78+
}
79+
4780
open class JavaOutput @Inject constructor() : WireOutput() {
4881
/** See [com.squareup.wire.schema.Target.includes] */
4982
var includes: List<String>? = null
@@ -239,40 +272,78 @@ open class ProtoOutput @Inject constructor() : WireOutput() {
239272
}
240273

241274
open class CustomOutput @Inject constructor() : WireOutput() {
275+
val includes: ListProperty<String> by lazy(NONE) {
276+
objectFactory.listProperty(String::class.java)
277+
}
278+
279+
val excludes: ListProperty<String> by lazy(NONE) {
280+
objectFactory.listProperty(String::class.java)
281+
}
282+
283+
val exclusive: Property<Boolean> by lazy(NONE) {
284+
objectFactory.property(Boolean::class.java).convention(true)
285+
}
286+
287+
val options: MapProperty<String, String> by lazy(NONE) {
288+
objectFactory.mapProperty(String::class.java, String::class.java)
289+
}
290+
291+
val schemaHandlerFactory: Property<SchemaHandler.Factory> by lazy(NONE) {
292+
objectFactory.property(SchemaHandler.Factory::class.java)
293+
}
294+
295+
val schemaHandlerFactoryClass: Property<String> by lazy(NONE) {
296+
objectFactory.property(String::class.java)
297+
}
298+
242299
/** See [com.squareup.wire.schema.Target.includes] */
243-
var includes: List<String>? = null
300+
fun setIncludes(value: List<String>?) {
301+
includes.set(value)
302+
}
244303

245304
/** See [com.squareup.wire.schema.Target.excludes] */
246-
var excludes: List<String>? = null
305+
fun setExcludes(value: List<String>?) {
306+
excludes.set(value)
307+
}
247308

248309
/** See [com.squareup.wire.schema.Target.exclusive] */
249-
var exclusive: Boolean = true
310+
fun setExclusive(value: Boolean) {
311+
exclusive.set(value)
312+
}
250313

251-
/**
252-
* Black boxed payload which a caller can set for the custom [SchemaHandler.Factory] to receive.
253-
*/
254-
var options: Map<String, String>? = null
314+
/** Black boxed payload which a caller can set for the custom [SchemaHandler.Factory] to receive. */
315+
fun setOptions(value: Map<String, String>?) {
316+
options.set(value)
317+
}
255318

256319
/** Assign the schema handler factory instance. */
257-
var schemaHandlerFactory: SchemaHandler.Factory? = null
320+
fun setSchemaHandlerFactory(value: SchemaHandler.Factory?) {
321+
schemaHandlerFactory.set(value)
322+
}
258323

259324
/**
260325
* Assign the schema handler factory by name. If you use a class name, that class must have a
261326
* no-arguments constructor.
262327
*/
263-
var schemaHandlerFactoryClass: String? = null
328+
fun setSchemaHandlerFactoryClass(value: String?) {
329+
schemaHandlerFactoryClass.set(value)
330+
}
264331

265332
override fun toTarget(outputDirectory: String): CustomTarget {
266-
check((schemaHandlerFactory != null) || (schemaHandlerFactoryClass != null)) {
333+
val configuredSchemaHandlerFactory = schemaHandlerFactory.orNull
334+
val configuredSchemaHandlerFactoryClass = schemaHandlerFactoryClass.orNull
335+
336+
check(configuredSchemaHandlerFactory != null || configuredSchemaHandlerFactoryClass != null) {
267337
"schemaHandlerFactory or schemaHandlerFactoryClass required"
268338
}
339+
269340
return CustomTarget(
270-
includes = includes ?: listOf("*"),
271-
excludes = excludes ?: listOf(),
272-
exclusive = exclusive,
341+
includes = includes.orNull ?: listOf("*"),
342+
excludes = excludes.orNull ?: listOf(),
343+
exclusive = exclusive.orElse(true).get(),
273344
outDirectory = outputDirectory,
274-
options = options ?: mapOf(),
275-
schemaHandlerFactory = schemaHandlerFactory ?: newSchemaHandler(schemaHandlerFactoryClass!!),
345+
options = options.orNull ?: mapOf(),
346+
schemaHandlerFactory = configuredSchemaHandlerFactory ?: newSchemaHandler(configuredSchemaHandlerFactoryClass!!),
276347
)
277348
}
278349
}

wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WirePlugin.kt

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import com.squareup.wire.gradle.internal.libraryProtoOutputPath
1919
import com.squareup.wire.gradle.internal.protoProjectDependenciesJvmConfiguration
2020
import com.squareup.wire.gradle.kotlin.WireSource
2121
import com.squareup.wire.gradle.kotlin.forEachWireSource
22-
import com.squareup.wire.schema.ProtoTarget
2322
import com.squareup.wire.schema.newEventListenerFactory
2423
import com.squareup.wire.wireVersion
2524
import java.io.File
@@ -85,12 +84,12 @@ class WirePlugin : Plugin<Project> {
8584
val existingProtoOutput = extension.outputs.get().filterIsInstance<ProtoOutput>().singleOrNull()
8685
if (existingProtoOutput != null) {
8786
// There exists a `proto {}` target already, we only set the output path if need be.
88-
if (existingProtoOutput.out == null) {
89-
existingProtoOutput.out = File(project.libraryProtoOutputPath()).path
87+
if (!existingProtoOutput.out.isPresent) {
88+
existingProtoOutput.out.set(File(project.libraryProtoOutputPath()).path)
9089
}
9190
} else {
9291
extension.proto { protoOutput ->
93-
protoOutput.out = File(project.libraryProtoOutputPath()).path
92+
protoOutput.out.set(File(project.libraryProtoOutputPath()).path)
9493
}
9594
}
9695
}
@@ -134,6 +133,11 @@ class WirePlugin : Plugin<Project> {
134133
source: WireSource,
135134
) {
136135
val outputs = extension.outputs.get()
136+
val nonProtoOutputs = outputs.filterNot { it is ProtoOutput }
137+
val projectDir = project.projectDir
138+
val projectDirectory = project.layout.projectDirectory
139+
val defaultOutputPath = relativizeOutputDirectory(source.outputDir(project).path, projectDir)
140+
val defaultOutputDirectory = project.providers.provider { defaultOutputPath }
137141

138142
val protoSourceProtoRootSets = extension.protoSourceProtoRootSets.toMutableList()
139143
val protoPathProtoRootSets = extension.protoPathProtoRootSets.toMutableList()
@@ -149,11 +153,13 @@ class WirePlugin : Plugin<Project> {
149153
}
150154
}
151155

152-
val targets = outputs.map {
153-
it.toTarget(project.relativePath(it.out ?: source.outputDir(project)))
156+
val targets = project.provider {
157+
outputs.map { output ->
158+
output.toTarget(output.outputDirectory(projectDir, defaultOutputDirectory).get())
159+
}
154160
}
155161

156-
val protoTarget = targets.filterIsInstance<ProtoTarget>().singleOrNull()
162+
val protoOutput = outputs.filterIsInstance<ProtoOutput>().singleOrNull()
157163

158164
val taskName = "generate${source.name.replaceFirstChar { it.uppercase() }}Protos"
159165
val task = project.tasks.register(taskName, WireTask::class.java) { task: WireTask ->
@@ -177,24 +183,23 @@ class WirePlugin : Plugin<Project> {
177183
}
178184
}
179185

180-
targets
181-
// Emitted `.proto` files have a special treatment. Their root should be a resource, not
182-
// a source. We exclude the `ProtoTarget` and we'll add its output to the resources
183-
// below.
184-
.filterNot { it is ProtoTarget }.forEach { target ->
185-
val dir = project.objects.directoryProperty()
186-
dir.set(
187-
project.tasks.named(taskName).map {
188-
project.layout.projectDirectory.dir(target.outDirectory)
189-
},
190-
)
191-
task.outputDirectoriesList.add(dir)
192-
}
186+
nonProtoOutputs.forEach { output ->
187+
val dir = project.objects.directoryProperty()
188+
dir.set(
189+
project.tasks.named(taskName).map {
190+
projectDirectory.dir(output.outputDirectory(projectDir, defaultOutputDirectory).get())
191+
},
192+
)
193+
task.outputDirectoriesList.add(dir)
194+
}
193195
task.protoSourceConfiguration.setFrom(project.configurations.getByName("protoSource"))
194196
task.protoPathConfiguration.setFrom(project.configurations.getByName("protoPath"))
195197
task.projectDependenciesJvmConfiguration.setFrom(project.configurations.getByName("protoProjectDependenciesJvm"))
196-
if (protoTarget != null) {
197-
task.protoLibraryOutput.set(project.file(protoTarget.outDirectory))
198+
if (protoOutput != null) {
199+
task.protoLibraryOutput.set(
200+
protoOutput.outputDirectory(projectDir, defaultOutputDirectory)
201+
.map { projectDirectory.dir(it) },
202+
)
198203
}
199204
task.sourceInput.set(project.provider { protoSourceProtoRootSets.inputLocations })
200205
task.protoInput.set(project.provider { protoPathProtoRootSets.inputLocations })
@@ -219,10 +224,10 @@ class WirePlugin : Plugin<Project> {
219224
task.eventListenerFactories.set(factories)
220225
}
221226

222-
source.registerGeneratedSources(project, task, targets)
227+
source.registerGeneratedSources(project, task, nonProtoOutputs)
223228

224229
val protoOutputDirectory = task.map { it.protoLibraryOutput }
225-
if (protoTarget != null) {
230+
if (protoOutput != null) {
226231
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
227232
// Note that there are no source sets for some platforms such as native.
228233
// TODO(Benoit) Probably should be checking for other names than `main`. As well, source

wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/kotlin/SourceRoots.kt

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ package com.squareup.wire.gradle.kotlin
1717

1818
import com.android.build.api.variant.AndroidComponentsExtension
1919
import com.android.build.api.variant.Variant
20+
import com.squareup.wire.gradle.CustomOutput
21+
import com.squareup.wire.gradle.JavaOutput
22+
import com.squareup.wire.gradle.KotlinOutput
23+
import com.squareup.wire.gradle.WireOutput
2024
import com.squareup.wire.gradle.WireTask
2125
import com.squareup.wire.gradle.internal.targetDefaultOutputPath
22-
import com.squareup.wire.schema.CustomTarget
23-
import com.squareup.wire.schema.JavaTarget
24-
import com.squareup.wire.schema.KotlinTarget
25-
import com.squareup.wire.schema.ProtoTarget
26-
import com.squareup.wire.schema.Target
2726
import java.io.File
2827
import org.gradle.api.Project
2928
import org.gradle.api.file.Directory
@@ -106,7 +105,7 @@ internal abstract class WireSource(
106105
abstract fun registerGeneratedSources(
107106
project: Project,
108107
wireTask: TaskProvider<WireTask>,
109-
targets: List<Target>,
108+
outputs: List<WireOutput>,
110109
)
111110
}
112111

@@ -134,28 +133,25 @@ private class JvmOrKmpSource(
134133
override fun registerGeneratedSources(
135134
project: Project,
136135
wireTask: TaskProvider<WireTask>,
137-
targets: List<Target>,
136+
outputs: List<WireOutput>,
138137
) {
139-
targets.forEachIndexed { index, target ->
138+
outputs.forEachIndexed { index, output ->
140139
val outputDirectory = wireTask.flatMap { it.outputDirectoriesList[index] }
141-
when (target) {
142-
is JavaTarget -> {
140+
when (output) {
141+
is JavaOutput -> {
143142
javaSourceDirectorySet?.srcDir(outputDirectory)
144143
}
145-
is KotlinTarget -> {
144+
is KotlinOutput -> {
146145
registerKotlinGeneratedSources(kotlinSourceSet, outputDirectory)
147146
}
148-
is CustomTarget -> {
147+
is CustomOutput -> {
149148
// Custom targets are wildcards, so we add all output directories.
150149
javaSourceDirectorySet?.srcDir(outputDirectory)
151150
registerKotlinGeneratedSources(kotlinSourceSet, outputDirectory)
152151
}
153-
is ProtoTarget -> {
154-
// Do nothing
155-
}
156152
else -> {
157153
throw IllegalArgumentException(
158-
"Wire target ${target::class.simpleName} is not supported in project ${project.path}",
154+
"Wire output ${output::class.simpleName} is not supported in project ${project.path}",
159155
)
160156
}
161157
}
@@ -173,27 +169,24 @@ private class AndroidSource(
173169
override fun registerGeneratedSources(
174170
project: Project,
175171
wireTask: TaskProvider<WireTask>,
176-
targets: List<Target>,
172+
outputs: List<WireOutput>,
177173
) {
178-
targets.forEachIndexed { index, target ->
179-
when (target) {
180-
is JavaTarget -> {
174+
outputs.forEachIndexed { index, output ->
175+
when (output) {
176+
is JavaOutput -> {
181177
variant.sources.java?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
182178
}
183-
is KotlinTarget -> {
179+
is KotlinOutput -> {
184180
variant.sources.kotlin?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
185181
}
186-
is CustomTarget -> {
182+
is CustomOutput -> {
187183
// Custom targets are wildcards, so we add all output directories.
188184
variant.sources.java?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
189185
variant.sources.kotlin?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
190186
}
191-
is ProtoTarget -> {
192-
// Do nothing
193-
}
194187
else -> {
195188
throw IllegalArgumentException(
196-
"Wire target ${target::class.simpleName} is not supported in Android project ${project.path}",
189+
"Wire output ${output::class.simpleName} is not supported in Android project ${project.path}",
197190
)
198191
}
199192
}

0 commit comments

Comments
 (0)