diff --git a/wire-gradle-plugin/api/wire-gradle-plugin.api b/wire-gradle-plugin/api/wire-gradle-plugin.api index c947120db2..c88f2f736e 100644 --- a/wire-gradle-plugin/api/wire-gradle-plugin.api +++ b/wire-gradle-plugin/api/wire-gradle-plugin.api @@ -1,11 +1,14 @@ -public class com/squareup/wire/gradle/CustomOutput : com/squareup/wire/gradle/WireOutput { +public abstract class com/squareup/wire/gradle/CustomOutput : com/squareup/wire/gradle/WireOutput { public fun ()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 exclusive (Z)V + 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 options (Ljava/util/Map;)V + public final fun schemaHandlerFactoryClass (Ljava/lang/String;)V public final fun setExcludes (Ljava/util/List;)V public final fun setExclusive (Z)V public final fun setIncludes (Ljava/util/List;)V @@ -16,17 +19,17 @@ public class com/squareup/wire/gradle/CustomOutput : com/squareup/wire/gradle/Wi public synthetic fun toTarget (Ljava/lang/String;)Lcom/squareup/wire/schema/Target; } -public class com/squareup/wire/gradle/JavaOutput : com/squareup/wire/gradle/WireOutput { +public abstract class com/squareup/wire/gradle/JavaOutput : com/squareup/wire/gradle/WireOutput { public fun ()V - public final fun getAndroid ()Z - public final fun getAndroidAnnotations ()Z - public final fun getBuildersOnly ()Z - public final fun getCompact ()Z - public final fun getEmitAppliedOptions ()Z - public final fun getEmitDeclaredOptions ()Z - public final fun getExcludes ()Ljava/util/List; - public final fun getExclusive ()Z - public final fun getIncludes ()Ljava/util/List; + public final fun getAndroid ()Lorg/gradle/api/provider/Property; + public final fun getAndroidAnnotations ()Lorg/gradle/api/provider/Property; + public final fun getBuildersOnly ()Lorg/gradle/api/provider/Property; + public final fun getCompact ()Lorg/gradle/api/provider/Property; + public final fun getEmitAppliedOptions ()Lorg/gradle/api/provider/Property; + public final fun getEmitDeclaredOptions ()Lorg/gradle/api/provider/Property; + 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 setAndroid (Z)V public final fun setAndroidAnnotations (Z)V public final fun setBuildersOnly (Z)V @@ -40,28 +43,28 @@ public class com/squareup/wire/gradle/JavaOutput : com/squareup/wire/gradle/Wire public synthetic fun toTarget (Ljava/lang/String;)Lcom/squareup/wire/schema/Target; } -public class com/squareup/wire/gradle/KotlinOutput : com/squareup/wire/gradle/WireOutput { +public abstract class com/squareup/wire/gradle/KotlinOutput : com/squareup/wire/gradle/WireOutput { public fun ()V - public final fun getAndroid ()Z - public final fun getBoxOneOfsMinSize ()I - public final fun getBuildersOnly ()Z - public final fun getEmitAppliedOptions ()Z - public final fun getEmitDeclaredOptions ()Z - public final fun getEmitProtoReader32 ()Z - public final fun getEnumMode ()Ljava/lang/String; - public final fun getEscapeKotlinKeywords ()Z - public final fun getExcludes ()Ljava/util/List; - public final fun getExclusive ()Z - public final fun getExplicitStreamingCalls ()Z - public final fun getGrpcServerCompatible ()Z - public final fun getIncludes ()Ljava/util/List; - public final fun getJavaInterop ()Z - public final fun getMakeImmutableCopies ()Z - public final fun getMutableTypes ()Z - public final fun getNameSuffix ()Ljava/lang/String; - public final fun getRpcCallStyle ()Ljava/lang/String; - public final fun getRpcRole ()Ljava/lang/String; - public final fun getSingleMethodServices ()Z + public final fun getAndroid ()Lorg/gradle/api/provider/Property; + public final fun getBoxOneOfsMinSize ()Lorg/gradle/api/provider/Property; + public final fun getBuildersOnly ()Lorg/gradle/api/provider/Property; + public final fun getEmitAppliedOptions ()Lorg/gradle/api/provider/Property; + public final fun getEmitDeclaredOptions ()Lorg/gradle/api/provider/Property; + public final fun getEmitProtoReader32 ()Lorg/gradle/api/provider/Property; + public final fun getEnumMode ()Lorg/gradle/api/provider/Property; + public final fun getEscapeKotlinKeywords ()Lorg/gradle/api/provider/Property; + public final fun getExcludes ()Lorg/gradle/api/provider/ListProperty; + public final fun getExclusive ()Lorg/gradle/api/provider/Property; + public final fun getExplicitStreamingCalls ()Lorg/gradle/api/provider/Property; + public final fun getGrpcServerCompatible ()Lorg/gradle/api/provider/Property; + public final fun getIncludes ()Lorg/gradle/api/provider/ListProperty; + public final fun getJavaInterop ()Lorg/gradle/api/provider/Property; + public final fun getMakeImmutableCopies ()Lorg/gradle/api/provider/Property; + public final fun getMutableTypes ()Lorg/gradle/api/provider/Property; + public final fun getNameSuffix ()Lorg/gradle/api/provider/Property; + public final fun getRpcCallStyle ()Lorg/gradle/api/provider/Property; + public final fun getRpcRole ()Lorg/gradle/api/provider/Property; + public final fun getSingleMethodServices ()Lorg/gradle/api/provider/Property; public final fun setAndroid (Z)V public final fun setBoxOneOfsMinSize (I)V public final fun setBuildersOnly (Z)V @@ -95,7 +98,7 @@ public class com/squareup/wire/gradle/Move : java/io/Serializable { public final fun toTypeMoverMove ()Lcom/squareup/wire/schema/internal/TypeMover$Move; } -public class com/squareup/wire/gradle/ProtoOutput : com/squareup/wire/gradle/WireOutput { +public abstract class com/squareup/wire/gradle/ProtoOutput : com/squareup/wire/gradle/WireOutput { public fun ()V public fun toTarget (Ljava/lang/String;)Lcom/squareup/wire/schema/ProtoTarget; public synthetic fun toTarget (Ljava/lang/String;)Lcom/squareup/wire/schema/Target; @@ -160,7 +163,8 @@ public final class com/squareup/wire/gradle/WireExtension$ProtoRootSet { public abstract class com/squareup/wire/gradle/WireOutput { public fun ()V - public final fun getOut ()Ljava/lang/String; + public final fun getOut ()Lorg/gradle/api/provider/Property; + public final fun out (Ljava/lang/String;)V public final fun setOut (Ljava/lang/String;)V public abstract fun toTarget (Ljava/lang/String;)Lcom/squareup/wire/schema/Target; } @@ -200,4 +204,3 @@ public abstract class com/squareup/wire/gradle/WireTask : org/gradle/api/tasks/S public abstract fun getTargets ()Lorg/gradle/api/provider/ListProperty; public abstract fun getUntilVersion ()Lorg/gradle/api/provider/Property; } - diff --git a/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WireOutput.kt b/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WireOutput.kt index 216f70efa3..79079ea2f5 100644 --- a/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WireOutput.kt +++ b/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WireOutput.kt @@ -25,7 +25,14 @@ 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 @@ -33,8 +40,26 @@ import javax.inject.Inject * directories with the project so they can be compiled after they are generated. */ abstract class WireOutput { + @get:Inject + protected abstract val objectFactory: ObjectFactory + + val out: Property 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) + } + + fun out(value: String) { + out.set(value) + } + + internal fun outputDirectory( + projectDir: File, + defaultOutputDirectory: Provider, + ): Provider = out.map { relativizeOutputDirectory(it, projectDir) }.orElse(defaultOutputDirectory) /** * Transforms this [WireOutput] into a [Target] for which Wire will generate code. The [Target] @@ -43,225 +68,456 @@ abstract class WireOutput { abstract fun toTarget(outputDirectory: String): Target } -open class JavaOutput @Inject constructor() : WireOutput() { +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 } +} + +abstract class JavaOutput @Inject constructor() : WireOutput() { + val includes: ListProperty by lazy(NONE) { + objectFactory.listProperty(String::class.java) + } + + val excludes: ListProperty by lazy(NONE) { + objectFactory.listProperty(String::class.java) + } + + val exclusive: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(true) + } + + val android: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val androidAnnotations: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val compact: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val emitDeclaredOptions: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(true) + } + + val emitAppliedOptions: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(true) + } + + val buildersOnly: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + /** See [com.squareup.wire.schema.Target.includes] */ - var includes: List? = null + fun setIncludes(values: List?) { + includes.set(values) + } /** See [com.squareup.wire.schema.Target.excludes] */ - var excludes: List? = null + fun setExcludes(values: List?) { + excludes.set(values) + } /** See [com.squareup.wire.schema.Target.exclusive] */ - var exclusive: Boolean = true + fun setExclusive(value: Boolean) { + exclusive.set(value) + } /** True for emitted types to implement `android.os.Parcelable`. */ - var android: Boolean = false + fun setAndroid(value: Boolean) { + android.set(value) + } /** True to enable the `androidx.annotation.Nullable` annotation where applicable. */ - var androidAnnotations: Boolean = false + fun setAndroidAnnotations(value: Boolean) { + androidAnnotations.set(value) + } /** * True to emit code that uses reflection for reading, writing, and toString methods which are * normally implemented with generated code. */ - var compact: Boolean = false + fun setCompact(value: Boolean) { + compact.set(value) + } /** True to emit types for options declared on messages, fields, etc. */ - var emitDeclaredOptions: Boolean = true + fun setEmitDeclaredOptions(value: Boolean) { + emitDeclaredOptions.set(value) + } /** True to emit annotations for options applied on messages, fields, etc. */ - var emitAppliedOptions: Boolean = true + fun setEmitAppliedOptions(value: Boolean) { + emitAppliedOptions.set(value) + } /** If true, the constructor of all generated types will be non-public. */ - var buildersOnly: Boolean = false + fun setBuildersOnly(value: Boolean) { + buildersOnly.set(value) + } override fun toTarget(outputDirectory: String): JavaTarget = JavaTarget( - includes = includes ?: listOf("*"), - excludes = excludes ?: listOf(), - exclusive = exclusive, + includes = includes.orNull ?: listOf("*"), + excludes = excludes.orNull ?: listOf(), + exclusive = exclusive.get(), outDirectory = outputDirectory, - android = android, - androidAnnotations = androidAnnotations, - compact = compact, - emitDeclaredOptions = emitDeclaredOptions, - emitAppliedOptions = emitAppliedOptions, - buildersOnly = buildersOnly, + android = android.get(), + androidAnnotations = androidAnnotations.get(), + compact = compact.get(), + emitDeclaredOptions = emitDeclaredOptions.get(), + emitAppliedOptions = emitAppliedOptions.get(), + buildersOnly = buildersOnly.get(), ) } -open class KotlinOutput @Inject constructor() : WireOutput() { +abstract class KotlinOutput @Inject constructor() : WireOutput() { + val includes: ListProperty by lazy(NONE) { + objectFactory.listProperty(String::class.java) + } + + val excludes: ListProperty by lazy(NONE) { + objectFactory.listProperty(String::class.java) + } + + val exclusive: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(true) + } + + val android: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val javaInterop: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val emitDeclaredOptions: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(true) + } + + val emitAppliedOptions: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(true) + } + + val rpcCallStyle: Property by lazy(NONE) { + objectFactory.property(String::class.java).convention("suspending") + } + + val rpcRole: Property by lazy(NONE) { + objectFactory.property(String::class.java).convention("client") + } + + val singleMethodServices: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val boxOneOfsMinSize: Property by lazy(NONE) { + objectFactory.property(Int::class.javaObjectType).convention(5_000) + } + + @Deprecated("See https://square.github.io/wire/wire_grpc/#wire-grpc-server") + val grpcServerCompatible: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val nameSuffix: Property by lazy(NONE) { + objectFactory.property(String::class.java) + } + + val buildersOnly: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val escapeKotlinKeywords: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val enumMode: Property by lazy(NONE) { + objectFactory.property(String::class.java).convention("enum_class") + } + + val emitProtoReader32: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val mutableTypes: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val explicitStreamingCalls: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(false) + } + + val makeImmutableCopies: Property by lazy(NONE) { + objectFactory.property(Boolean::class.javaObjectType).convention(true) + } + /** See [com.squareup.wire.schema.Target.includes] */ - var includes: List? = null + fun setIncludes(values: List?) { + includes.set(values) + } /** See [com.squareup.wire.schema.Target.excludes] */ - var excludes: List? = null + fun setExcludes(values: List?) { + excludes.set(values) + } /** See [com.squareup.wire.schema.Target.exclusive] */ - var exclusive: Boolean = true + fun setExclusive(value: Boolean) { + exclusive.set(value) + } /** True for emitted types to implement `android.os.Parcelable`. */ - var android: Boolean = false + fun setAndroid(value: Boolean) { + android.set(value) + } /** True for emitted types to implement APIs for easier migration from the Java target. */ - var javaInterop: Boolean = false + fun setJavaInterop(value: Boolean) { + javaInterop.set(value) + } /** True to emit types for options declared on messages, fields, etc. */ - var emitDeclaredOptions: Boolean = true + fun setEmitDeclaredOptions(value: Boolean) { + emitDeclaredOptions.set(value) + } /** True to emit annotations for options applied on messages, fields, etc. */ - var emitAppliedOptions: Boolean = true + fun setEmitAppliedOptions(value: Boolean) { + emitAppliedOptions.set(value) + } /** Blocking or suspending. */ - var rpcCallStyle: String = "suspending" + fun setRpcCallStyle(value: String) { + rpcCallStyle.set(value) + } /** Client, server, or none. */ - var rpcRole: String = "client" + fun setRpcRole(value: String) { + rpcRole.set(value) + } /** True for emitted services to implement one interface per RPC. */ - var singleMethodServices: Boolean = false + fun setSingleMethodServices(value: Boolean) { + singleMethodServices.set(value) + } /** * If a oneof has more than or [boxOneOfsMinSize] fields, it will be generated using boxed oneofs * as defined in [OneOf][com.squareup.wire.OneOf]. */ - var boxOneOfsMinSize: Int = 5_000 + fun setBoxOneOfsMinSize(value: Int) { + boxOneOfsMinSize.set(value) + } @Deprecated("See https://square.github.io/wire/wire_grpc/#wire-grpc-server") - var grpcServerCompatible: Boolean = false + fun setGrpcServerCompatible(value: Boolean) { + grpcServerCompatible.set(value) + } /** * If present, generated services classes will use this as a suffix instead of inferring one * from the [rpcRole]. */ - var nameSuffix: String? = null + fun setNameSuffix(value: String?) { + nameSuffix.set(value) + } /** * If true, the constructor of all generated types will be non-public, and they will be * instantiable via their builders, regardless of the value of [javaInterop]. */ - var buildersOnly: Boolean = false + fun setBuildersOnly(value: Boolean) { + buildersOnly.set(value) + } /** If true, Kotlin keywords are escaped with backticks. If false, an underscore is added as a suffix. */ - var escapeKotlinKeywords: Boolean = false + fun setEscapeKotlinKeywords(value: Boolean) { + escapeKotlinKeywords.set(value) + } /** enum_class or sealed_class. See [EnumMode][com.squareup.wire.kotlin.EnumMode]. */ - var enumMode: String = "enum_class" + fun setEnumMode(value: String) { + enumMode.set(value) + } /** * If true, adapters will generate decode functions for `ProtoReader32`. Use this optimization * when targeting Kotlin/JS, where `Long` cursors are inefficient. */ - var emitProtoReader32: Boolean = false + fun setEmitProtoReader32(value: Boolean) { + emitProtoReader32.set(value) + } - /** - * If true, the generated classes will be mutable.. - */ - var mutableTypes: Boolean = false + /** If true, the generated classes will be mutable. */ + fun setMutableTypes(value: Boolean) { + mutableTypes.set(value) + } /** * If true, the generated gRPC client will use explicit classes for client, server, * and bidirectional streaming calls. */ - var explicitStreamingCalls: Boolean = false + fun setExplicitStreamingCalls(value: Boolean) { + explicitStreamingCalls.set(value) + } /** * If false, repeated and map fields will not have immutable copies made when constructing - * a [com.squareup.wire.Message]. It is up to the developer to avoid mutating any Maps or Lists - * after they are used to create a [com.squareup.wire.Message] as the [com.squareup.wire.Message] - * will reference those collections rather than creating a copy. In exchange, the developer can - * avoid the overhead of creating a list which can be excessive for long lists or for performance - * critical usages. + * a [com.squareup.wire.Message]. */ - var makeImmutableCopies: Boolean = true + fun setMakeImmutableCopies(value: Boolean) { + makeImmutableCopies.set(value) + } override fun toTarget(outputDirectory: String): KotlinTarget { - if (grpcServerCompatible) { + if (grpcServerCompatible.get()) { throw IllegalArgumentException( "grpcServerCompatible is no longer valid.\n" + "Please migrate by following the steps defined in https://square.github.io/wire/wire_grpc/#wire-grpc-server", ) } - val rpcCallStyle = RpcCallStyle.values() - .singleOrNull { it.toString().equals(rpcCallStyle, ignoreCase = true) } + val rpcCallStyleValue = RpcCallStyle.values() + .singleOrNull { it.toString().equals(rpcCallStyle.get(), ignoreCase = true) } ?: throw IllegalArgumentException( - "Unknown rpcCallStyle $rpcCallStyle. Valid values: ${RpcCallStyle.values().contentToString()}", + "Unknown rpcCallStyle ${rpcCallStyle.get()}. Valid values: ${RpcCallStyle.values().contentToString()}", ) - val rpcRole = RpcRole.values() - .singleOrNull { it.toString().equals(rpcRole, ignoreCase = true) } + val rpcRoleValue = RpcRole.values() + .singleOrNull { it.toString().equals(rpcRole.get(), ignoreCase = true) } ?: throw IllegalArgumentException( - "Unknown rpcRole $rpcRole. Valid values: ${RpcRole.values().contentToString()}", + "Unknown rpcRole ${rpcRole.get()}. Valid values: ${RpcRole.values().contentToString()}", ) - val enumMode = EnumMode.values() - .singleOrNull { it.toString().equals(enumMode, ignoreCase = true) } + val enumModeValue = EnumMode.values() + .singleOrNull { it.toString().equals(enumMode.get(), ignoreCase = true) } ?: throw IllegalArgumentException( - "Unknown enumMode $enumMode. Valid values: ${EnumMode.values().contentToString()}", + "Unknown enumMode ${enumMode.get()}. Valid values: ${EnumMode.values().contentToString()}", ) return KotlinTarget( - includes = includes ?: listOf("*"), - excludes = excludes ?: listOf(), - exclusive = exclusive, + includes = includes.orNull ?: listOf("*"), + excludes = excludes.orNull ?: listOf(), + exclusive = exclusive.get(), outDirectory = outputDirectory, - android = android, - javaInterop = javaInterop, - emitDeclaredOptions = emitDeclaredOptions, - emitAppliedOptions = emitAppliedOptions, - rpcCallStyle = rpcCallStyle, - rpcRole = rpcRole, - singleMethodServices = singleMethodServices, - boxOneOfsMinSize = boxOneOfsMinSize, - nameSuffix = nameSuffix, - buildersOnly = buildersOnly, - escapeKotlinKeywords = escapeKotlinKeywords, - enumMode = enumMode, - emitProtoReader32 = emitProtoReader32, - mutableTypes = mutableTypes, - explicitStreamingCalls = explicitStreamingCalls, - makeImmutableCopies = makeImmutableCopies, + android = android.get(), + javaInterop = javaInterop.get(), + emitDeclaredOptions = emitDeclaredOptions.get(), + emitAppliedOptions = emitAppliedOptions.get(), + rpcCallStyle = rpcCallStyleValue, + rpcRole = rpcRoleValue, + singleMethodServices = singleMethodServices.get(), + boxOneOfsMinSize = boxOneOfsMinSize.get(), + nameSuffix = nameSuffix.orNull, + buildersOnly = buildersOnly.get(), + escapeKotlinKeywords = escapeKotlinKeywords.get(), + enumMode = enumModeValue, + emitProtoReader32 = emitProtoReader32.get(), + mutableTypes = mutableTypes.get(), + explicitStreamingCalls = explicitStreamingCalls.get(), + makeImmutableCopies = makeImmutableCopies.get(), ) } } -open class ProtoOutput @Inject constructor() : WireOutput() { +abstract class ProtoOutput @Inject constructor() : WireOutput() { override fun toTarget(outputDirectory: String): ProtoTarget = ProtoTarget(outDirectory = outputDirectory) } -open class CustomOutput @Inject constructor() : WireOutput() { +abstract class CustomOutput @Inject constructor() : WireOutput() { + val includes: ListProperty by lazy(NONE) { + objectFactory.listProperty(String::class.java) + } + + val excludes: ListProperty by lazy(NONE) { + objectFactory.listProperty(String::class.java) + } + + val exclusive: Property by lazy(NONE) { + objectFactory.property(Boolean::class.java).convention(true) + } + + val options: MapProperty by lazy(NONE) { + objectFactory.mapProperty(String::class.java, String::class.java) + } + + val schemaHandlerFactory: Property by lazy(NONE) { + objectFactory.property(SchemaHandler.Factory::class.java) + } + + val schemaHandlerFactoryClass: Property by lazy(NONE) { + objectFactory.property(String::class.java) + } + /** See [com.squareup.wire.schema.Target.includes] */ - var includes: List? = null + fun setIncludes(values: List?) { + includes.set(values) + } /** See [com.squareup.wire.schema.Target.excludes] */ - var excludes: List? = null + fun setExcludes(values: List?) { + excludes.set(values) + } /** See [com.squareup.wire.schema.Target.exclusive] */ - var exclusive: Boolean = true + fun setExclusive(value: Boolean) { + exclusive.set(value) + } + + fun exclusive(value: Boolean) { + exclusive.set(value) + } /** * Black boxed payload which a caller can set for the custom [SchemaHandler.Factory] to receive. */ - var options: Map? = null + fun options(value: Map) { + options.set(value) + } + + fun setOptions(value: Map?) { + 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) + } + + fun schemaHandlerFactoryClass(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!!), ) } } diff --git a/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WirePlugin.kt b/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WirePlugin.kt index 0c15594121..fbff3d7eba 100644 --- a/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WirePlugin.kt +++ b/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/WirePlugin.kt @@ -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 @@ -85,12 +84,12 @@ class WirePlugin : Plugin { val existingProtoOutput = extension.outputs.get().filterIsInstance().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) } } } @@ -134,6 +133,11 @@ class WirePlugin : Plugin { 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() @@ -149,11 +153,13 @@ class WirePlugin : Plugin { } } - 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().singleOrNull() + val protoOutput = outputs.filterIsInstance().singleOrNull() val taskName = "generate${source.name.replaceFirstChar { it.uppercase() }}Protos" val task = project.tasks.register(taskName, WireTask::class.java) { task: WireTask -> @@ -177,24 +183,23 @@ class WirePlugin : Plugin { } } - 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 }) @@ -219,10 +224,10 @@ class WirePlugin : Plugin { 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 diff --git a/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/kotlin/SourceRoots.kt b/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/kotlin/SourceRoots.kt index c57f7dc400..1bf2a84d84 100644 --- a/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/kotlin/SourceRoots.kt +++ b/wire-gradle-plugin/src/main/kotlin/com/squareup/wire/gradle/kotlin/SourceRoots.kt @@ -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 @@ -106,7 +105,7 @@ internal abstract class WireSource( abstract fun registerGeneratedSources( project: Project, wireTask: TaskProvider, - targets: List, + outputs: List, ) } @@ -134,28 +133,25 @@ private class JvmOrKmpSource( override fun registerGeneratedSources( project: Project, wireTask: TaskProvider, - targets: List, + outputs: List, ) { - 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}", ) } } @@ -173,27 +169,24 @@ private class AndroidSource( override fun registerGeneratedSources( project: Project, wireTask: TaskProvider, - targets: List, + outputs: List, ) { - 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}", ) } } diff --git a/wire-gradle-plugin/src/test/kotlin/com/squareup/wire/gradle/WireOutputProviderTest.kt b/wire-gradle-plugin/src/test/kotlin/com/squareup/wire/gradle/WireOutputProviderTest.kt new file mode 100644 index 0000000000..73abf8a78c --- /dev/null +++ b/wire-gradle-plugin/src/test/kotlin/com/squareup/wire/gradle/WireOutputProviderTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2025 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.wire.gradle + +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import com.squareup.wire.schema.CustomTarget +import com.squareup.wire.schema.Extend +import com.squareup.wire.schema.Field +import com.squareup.wire.schema.SchemaHandler +import com.squareup.wire.schema.Service +import com.squareup.wire.schema.Type +import okio.Path +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Test + +class WireOutputProviderTest { + @Test + fun customOutputResolvesProviderBackedProperties() { + val project = ProjectBuilder.builder().build() + val output = project.objects.newInstance(CustomOutput::class.java) + + output.out.set(project.providers.provider { "build/generated/custom-wire" }) + output.includes.set(project.providers.provider { listOf("squareup.dinosaurs.Dinosaur") }) + output.excludes.set(project.providers.provider { listOf("squareup.geology.Period") }) + output.exclusive.set(project.providers.provider { false }) + output.options.set(project.providers.provider { mapOf("a" to "one", "b" to "two") }) + output.schemaHandlerFactory.set(TestSchemaHandlerFactory()) + + assertThat(output.out.orNull).isEqualTo("build/generated/custom-wire") + + val target = output.toTarget(output.out.get()) as CustomTarget + + assertThat(target.outDirectory).isEqualTo("build/generated/custom-wire") + assertThat(target.includes).containsExactly("squareup.dinosaurs.Dinosaur") + assertThat(target.excludes).containsExactly("squareup.geology.Period") + assertThat(target.exclusive).isFalse() + assertThat(target.options).isEqualTo(mapOf("a" to "one", "b" to "two")) + assertThat(target.newHandler()::class.java).isEqualTo(TestSchemaHandler::class.java) + } + + @Test + fun customOutputExclusiveFallsBackToConventionWhenProviderIsAbsent() { + val project = ProjectBuilder.builder().build() + val output = project.objects.newInstance(CustomOutput::class.java) + + output.exclusive.set(false) + output.exclusive.set(project.providers.gradleProperty("missing-exclusive").map(String::toBoolean)) + output.schemaHandlerFactory.set(TestSchemaHandlerFactory()) + + val target = output.toTarget("build/generated/custom-wire") as CustomTarget + + assertThat(target.exclusive).isEqualTo(true) + } +} + +private class TestSchemaHandlerFactory : SchemaHandler.Factory { + override fun create( + includes: List, + excludes: List, + exclusive: Boolean, + outDirectory: String, + options: Map, + ): SchemaHandler = TestSchemaHandler() +} + +private class TestSchemaHandler : SchemaHandler() { + override fun handle(type: Type, context: Context): Path? = null + + override fun handle(service: Service, context: Context): List = listOf() + + override fun handle(extend: Extend, field: Field, context: Context): Path? = null +} diff --git a/wire-gradle-plugin/src/test/kotlin/com/squareup/wire/gradle/WirePluginTest.kt b/wire-gradle-plugin/src/test/kotlin/com/squareup/wire/gradle/WirePluginTest.kt index dec28cbf65..ff4fc49597 100644 --- a/wire-gradle-plugin/src/test/kotlin/com/squareup/wire/gradle/WirePluginTest.kt +++ b/wire-gradle-plugin/src/test/kotlin/com/squareup/wire/gradle/WirePluginTest.kt @@ -953,6 +953,15 @@ class WirePluginTest { .contains("Couldn't find SchemaHandlerClass 'NoSuchClass'") } + @Test + fun customOutputProviderBackedDsl() { + val fixtureRoot = File("src/test/projects/custom-output-provider-kotlin-dsl") + + val result = fixtureGradleRunner(fixtureRoot).buildAndFail() + assertThat(result.output) + .contains("Couldn't find SchemaHandlerClass 'NoSuchClass'") + } + @Test fun sinceUntil() { val fixtureRoot = File("src/test/projects/since-until") diff --git a/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/build.gradle.kts b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/build.gradle.kts new file mode 100644 index 0000000000..29b1fb1d77 --- /dev/null +++ b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/build.gradle.kts @@ -0,0 +1,40 @@ +import com.squareup.wire.gradle.WireExtension + +buildscript { + val wireVersion = gradle.startParameter.projectProperties.getValue("wireVersion") + + dependencies { + classpath("com.squareup.wire:wire-gradle-plugin:$wireVersion") + } + + repositories { + maven { + url = uri(File(rootDir, "../../../../../build/localMaven")) + } + mavenCentral() + google() + } +} + +apply(plugin = "application") +apply(plugin = "com.squareup.wire") + +configure { + sourcePath("src/main/proto") + + custom { + out.set(providers.gradleProperty("wireOut")) + includes.set(providers.provider { listOf(providers.gradleProperty("includeType").get()) }) + excludes.set(providers.provider { listOf(providers.gradleProperty("excludeType").get()) }) + exclusive.set(providers.provider { providers.gradleProperty("exclusive").get().toBoolean() }) + options.set( + providers.provider { + mapOf( + "a" to providers.gradleProperty("optionA").get(), + "b" to providers.gradleProperty("optionB").get(), + ) + }, + ) + schemaHandlerFactoryClass.set(providers.gradleProperty("handlerClass")) + } +} diff --git a/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/gradle.properties b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/gradle.properties new file mode 100644 index 0000000000..ae036ce6ae --- /dev/null +++ b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/gradle.properties @@ -0,0 +1,7 @@ +handlerClass=NoSuchClass +wireOut=build/generated/provider-custom-output +includeType=squareup.dinosaurs.Dinosaur +excludeType=squareup.geology.Period +exclusive=true +optionA=one +optionB=two diff --git a/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/settings.gradle.kts b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/settings.gradle.kts new file mode 100644 index 0000000000..19d12e5ed3 --- /dev/null +++ b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "custom-output-provider-kotlin-dsl" diff --git a/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/src/main/proto/squareup/geology/period.proto b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/src/main/proto/squareup/geology/period.proto new file mode 100644 index 0000000000..c95691315e --- /dev/null +++ b/wire-gradle-plugin/src/test/projects/custom-output-provider-kotlin-dsl/src/main/proto/squareup/geology/period.proto @@ -0,0 +1,16 @@ +syntax = "proto2"; + +package squareup.geology; + +option java_package = "com.squareup.geology"; + +enum Period { + /** 145.5 million years ago -- 66.0 million years ago. */ + CRETACEOUS = 1; + + /** 201.3 million years ago -- 145.0 million years ago. */ + JURASSIC = 2; + + /** 252.17 million years ago -- 201.3 million years ago. */ + TRIASSIC = 3; +}