Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/wire_compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Getting Started
---------------

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

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

Expand Down
15 changes: 8 additions & 7 deletions wire-gradle-plugin/api/wire-gradle-plugin.api
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
public class com/squareup/wire/gradle/CustomOutput : com/squareup/wire/gradle/WireOutput {
public fun <init> ()V
public final fun getExcludes ()Ljava/util/List;
public final fun getExclusive ()Z
public final fun getIncludes ()Ljava/util/List;
public final fun getOptions ()Ljava/util/Map;
public final fun getSchemaHandlerFactory ()Lcom/squareup/wire/schema/SchemaHandler$Factory;
public final fun getSchemaHandlerFactoryClass ()Ljava/lang/String;
public final fun getExcludes ()Lorg/gradle/api/provider/ListProperty;
public final fun getExclusive ()Lorg/gradle/api/provider/Property;
public final fun getIncludes ()Lorg/gradle/api/provider/ListProperty;
public final fun getOptions ()Lorg/gradle/api/provider/MapProperty;
public final fun getSchemaHandlerFactory ()Lorg/gradle/api/provider/Property;
public final fun getSchemaHandlerFactoryClass ()Lorg/gradle/api/provider/Property;
public final fun setExcludes (Ljava/util/List;)V
public final fun setExclusive (Z)V
public final fun setIncludes (Ljava/util/List;)V
Expand Down Expand Up @@ -160,7 +160,8 @@ public final class com/squareup/wire/gradle/WireExtension$ProtoRootSet {

public abstract class com/squareup/wire/gradle/WireOutput {
public fun <init> ()V
public final fun getOut ()Ljava/lang/String;
protected fun getObjectFactory ()Lorg/gradle/api/model/ObjectFactory;
public final fun getOut ()Lorg/gradle/api/provider/Property;
public final fun setOut (Ljava/lang/String;)V
public abstract fun toTarget (Ljava/lang/String;)Lcom/squareup/wire/schema/Target;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,39 @@ import com.squareup.wire.schema.ProtoTarget
import com.squareup.wire.schema.SchemaHandler
import com.squareup.wire.schema.Target
import com.squareup.wire.schema.newSchemaHandler
import java.io.File
import javax.inject.Inject
import kotlin.LazyThreadSafetyMode.NONE
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider

/**
* Specifies Wire's outputs (expressed as a list of [Target] objects) using Gradle's DSL (expressed
* as destination directories and configuration options). This includes registering output
* directories with the project so they can be compiled after they are generated.
*/
abstract class WireOutput {
// Gradle decorates this getter for instances created with ObjectFactory.newInstance().
@get:Inject
protected open val objectFactory: ObjectFactory
get() = throw UnsupportedOperationException("Injected by Gradle")

val out: Property<String> by lazy(NONE) {
objectFactory.property(String::class.java)
}

/** Set this to override the default output directory for this [WireOutput]. */
var out: String? = null
fun setOut(value: String?) {
out.set(value)
}

internal fun outputDirectory(
projectDir: File,
defaultOutputDirectory: Provider<String>,
): Provider<String> = out.map { relativizeOutputDirectory(it, projectDir) }.orElse(defaultOutputDirectory)

/**
* Transforms this [WireOutput] into a [Target] for which Wire will generate code. The [Target]
Expand All @@ -43,6 +66,16 @@ abstract class WireOutput {
abstract fun toTarget(outputDirectory: String): Target
}

internal fun relativizeOutputDirectory(
outputDirectory: String,
projectDir: File,
): String {
val file = File(outputDirectory)
if (!file.isAbsolute) return outputDirectory
return runCatching { file.relativeTo(projectDir).path }
.getOrElse { outputDirectory }
}

open class JavaOutput @Inject constructor() : WireOutput() {
/** See [com.squareup.wire.schema.Target.includes] */
var includes: List<String>? = null
Expand Down Expand Up @@ -228,40 +261,78 @@ open class ProtoOutput @Inject constructor() : WireOutput() {
}

open class CustomOutput @Inject constructor() : WireOutput() {
val includes: ListProperty<String> by lazy(NONE) {
objectFactory.listProperty(String::class.java)
}

val excludes: ListProperty<String> by lazy(NONE) {
objectFactory.listProperty(String::class.java)
}

val exclusive: Property<Boolean> by lazy(NONE) {
objectFactory.property(Boolean::class.java).convention(true)
}

val options: MapProperty<String, String> by lazy(NONE) {
objectFactory.mapProperty(String::class.java, String::class.java)
}

val schemaHandlerFactory: Property<SchemaHandler.Factory> by lazy(NONE) {
objectFactory.property(SchemaHandler.Factory::class.java)
}

val schemaHandlerFactoryClass: Property<String> by lazy(NONE) {
objectFactory.property(String::class.java)
}

/** See [com.squareup.wire.schema.Target.includes] */
var includes: List<String>? = null
fun setIncludes(value: List<String>?) {
includes.set(value)
}

/** See [com.squareup.wire.schema.Target.excludes] */
var excludes: List<String>? = null
fun setExcludes(value: List<String>?) {
excludes.set(value)
}

/** See [com.squareup.wire.schema.Target.exclusive] */
var exclusive: Boolean = true
fun setExclusive(value: Boolean) {
exclusive.set(value)
}

/**
* Black boxed payload which a caller can set for the custom [SchemaHandler.Factory] to receive.
*/
var options: Map<String, String>? = null
/** Black boxed payload which a caller can set for the custom [SchemaHandler.Factory] to receive. */
fun setOptions(value: Map<String, String>?) {
options.set(value)
}

/** Assign the schema handler factory instance. */
var schemaHandlerFactory: SchemaHandler.Factory? = null
fun setSchemaHandlerFactory(value: SchemaHandler.Factory?) {
schemaHandlerFactory.set(value)
}

/**
* Assign the schema handler factory by name. If you use a class name, that class must have a
* no-arguments constructor.
*/
var schemaHandlerFactoryClass: String? = null
fun setSchemaHandlerFactoryClass(value: String?) {
schemaHandlerFactoryClass.set(value)
}

override fun toTarget(outputDirectory: String): CustomTarget {
check((schemaHandlerFactory != null) || (schemaHandlerFactoryClass != null)) {
val configuredSchemaHandlerFactory = schemaHandlerFactory.orNull
val configuredSchemaHandlerFactoryClass = schemaHandlerFactoryClass.orNull

check(configuredSchemaHandlerFactory != null || configuredSchemaHandlerFactoryClass != null) {
"schemaHandlerFactory or schemaHandlerFactoryClass required"
}

return CustomTarget(
includes = includes ?: listOf("*"),
excludes = excludes ?: listOf(),
exclusive = exclusive,
includes = includes.orNull ?: listOf("*"),
excludes = excludes.orNull ?: listOf(),
exclusive = exclusive.orElse(true).get(),
outDirectory = outputDirectory,
options = options ?: mapOf(),
schemaHandlerFactory = schemaHandlerFactory ?: newSchemaHandler(schemaHandlerFactoryClass!!),
options = options.orNull ?: mapOf(),
schemaHandlerFactory = configuredSchemaHandlerFactory ?: newSchemaHandler(configuredSchemaHandlerFactoryClass!!),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import com.squareup.wire.gradle.internal.libraryProtoOutputPath
import com.squareup.wire.gradle.internal.protoProjectDependenciesJvmConfiguration
import com.squareup.wire.gradle.kotlin.WireSource
import com.squareup.wire.gradle.kotlin.forEachWireSource
import com.squareup.wire.schema.ProtoTarget
import com.squareup.wire.schema.newEventListenerFactory
import com.squareup.wire.wireVersion
import java.io.File
Expand Down Expand Up @@ -85,12 +84,12 @@ class WirePlugin : Plugin<Project> {
val existingProtoOutput = extension.outputs.get().filterIsInstance<ProtoOutput>().singleOrNull()
if (existingProtoOutput != null) {
// There exists a `proto {}` target already, we only set the output path if need be.
if (existingProtoOutput.out == null) {
existingProtoOutput.out = File(project.libraryProtoOutputPath()).path
if (!existingProtoOutput.out.isPresent) {
existingProtoOutput.out.set(File(project.libraryProtoOutputPath()).path)
}
} else {
extension.proto { protoOutput ->
protoOutput.out = File(project.libraryProtoOutputPath()).path
protoOutput.out.set(File(project.libraryProtoOutputPath()).path)
}
}
}
Expand Down Expand Up @@ -134,6 +133,11 @@ class WirePlugin : Plugin<Project> {
source: WireSource,
) {
val outputs = extension.outputs.get()
val nonProtoOutputs = outputs.filterNot { it is ProtoOutput }
val projectDir = project.projectDir
val projectDirectory = project.layout.projectDirectory
val defaultOutputPath = relativizeOutputDirectory(source.outputDir(project).path, projectDir)
val defaultOutputDirectory = project.providers.provider { defaultOutputPath }

val protoSourceProtoRootSets = extension.protoSourceProtoRootSets.toMutableList()
val protoPathProtoRootSets = extension.protoPathProtoRootSets.toMutableList()
Expand All @@ -149,11 +153,13 @@ class WirePlugin : Plugin<Project> {
}
}

val targets = outputs.map {
it.toTarget(project.relativePath(it.out ?: source.outputDir(project)))
val targets = project.provider {
outputs.map { output ->
output.toTarget(output.outputDirectory(projectDir, defaultOutputDirectory).get())
}
}

val protoTarget = targets.filterIsInstance<ProtoTarget>().singleOrNull()
val protoOutput = outputs.filterIsInstance<ProtoOutput>().singleOrNull()

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

targets
// Emitted `.proto` files have a special treatment. Their root should be a resource, not
// a source. We exclude the `ProtoTarget` and we'll add its output to the resources
// below.
.filterNot { it is ProtoTarget }.forEach { target ->
val dir = project.objects.directoryProperty()
dir.set(
project.tasks.named(taskName).map {
project.layout.projectDirectory.dir(target.outDirectory)
},
)
task.outputDirectoriesList.add(dir)
}
nonProtoOutputs.forEach { output ->
val dir = project.objects.directoryProperty()
dir.set(
project.tasks.named(taskName).map {
projectDirectory.dir(output.outputDirectory(projectDir, defaultOutputDirectory).get())
},
)
task.outputDirectoriesList.add(dir)
}
task.protoSourceConfiguration.setFrom(project.configurations.getByName("protoSource"))
task.protoPathConfiguration.setFrom(project.configurations.getByName("protoPath"))
task.projectDependenciesJvmConfiguration.setFrom(project.configurations.getByName("protoProjectDependenciesJvm"))
if (protoTarget != null) {
task.protoLibraryOutput.set(project.file(protoTarget.outDirectory))
if (protoOutput != null) {
task.protoLibraryOutput.set(
protoOutput.outputDirectory(projectDir, defaultOutputDirectory)
.map { projectDirectory.dir(it) },
)
}
task.sourceInput.set(project.provider { protoSourceProtoRootSets.inputLocations })
task.protoInput.set(project.provider { protoPathProtoRootSets.inputLocations })
Expand All @@ -219,10 +224,10 @@ class WirePlugin : Plugin<Project> {
task.eventListenerFactories.set(factories)
}

source.registerGeneratedSources(project, task, targets)
source.registerGeneratedSources(project, task, nonProtoOutputs)

val protoOutputDirectory = task.map { it.protoLibraryOutput }
if (protoTarget != null) {
if (protoOutput != null) {
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
// Note that there are no source sets for some platforms such as native.
// TODO(Benoit) Probably should be checking for other names than `main`. As well, source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ package com.squareup.wire.gradle.kotlin

import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.Variant
import com.squareup.wire.gradle.CustomOutput
import com.squareup.wire.gradle.JavaOutput
import com.squareup.wire.gradle.KotlinOutput
import com.squareup.wire.gradle.WireOutput
import com.squareup.wire.gradle.WireTask
import com.squareup.wire.gradle.internal.targetDefaultOutputPath
import com.squareup.wire.schema.CustomTarget
import com.squareup.wire.schema.JavaTarget
import com.squareup.wire.schema.KotlinTarget
import com.squareup.wire.schema.ProtoTarget
import com.squareup.wire.schema.Target
import java.io.File
import org.gradle.api.Project
import org.gradle.api.file.Directory
Expand Down Expand Up @@ -106,7 +105,7 @@ internal abstract class WireSource(
abstract fun registerGeneratedSources(
project: Project,
wireTask: TaskProvider<WireTask>,
targets: List<Target>,
outputs: List<WireOutput>,
)
}

Expand Down Expand Up @@ -134,28 +133,25 @@ private class JvmOrKmpSource(
override fun registerGeneratedSources(
project: Project,
wireTask: TaskProvider<WireTask>,
targets: List<Target>,
outputs: List<WireOutput>,
) {
targets.forEachIndexed { index, target ->
outputs.forEachIndexed { index, output ->
val outputDirectory = wireTask.flatMap { it.outputDirectoriesList[index] }
when (target) {
is JavaTarget -> {
when (output) {
is JavaOutput -> {
javaSourceDirectorySet?.srcDir(outputDirectory)
}
is KotlinTarget -> {
is KotlinOutput -> {
registerKotlinGeneratedSources(kotlinSourceSet, outputDirectory)
}
is CustomTarget -> {
is CustomOutput -> {
// Custom targets are wildcards, so we add all output directories.
javaSourceDirectorySet?.srcDir(outputDirectory)
registerKotlinGeneratedSources(kotlinSourceSet, outputDirectory)
}
is ProtoTarget -> {
// Do nothing
}
else -> {
throw IllegalArgumentException(
"Wire target ${target::class.simpleName} is not supported in project ${project.path}",
"Wire output ${output::class.simpleName} is not supported in project ${project.path}",
)
}
}
Expand All @@ -173,27 +169,24 @@ private class AndroidSource(
override fun registerGeneratedSources(
project: Project,
wireTask: TaskProvider<WireTask>,
targets: List<Target>,
outputs: List<WireOutput>,
) {
targets.forEachIndexed { index, target ->
when (target) {
is JavaTarget -> {
outputs.forEachIndexed { index, output ->
when (output) {
is JavaOutput -> {
variant.sources.java?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
}
is KotlinTarget -> {
is KotlinOutput -> {
variant.sources.kotlin?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
}
is CustomTarget -> {
is CustomOutput -> {
// Custom targets are wildcards, so we add all output directories.
variant.sources.java?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
variant.sources.kotlin?.addGeneratedSourceDirectory(wireTask) { it.outputDirectoriesList[index] }
}
is ProtoTarget -> {
// Do nothing
}
else -> {
throw IllegalArgumentException(
"Wire target ${target::class.simpleName} is not supported in Android project ${project.path}",
"Wire output ${output::class.simpleName} is not supported in Android project ${project.path}",
)
}
}
Expand Down
Loading
Loading