Skip to content
Open
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
6 changes: 6 additions & 0 deletions gradle-plugin/api/gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ public abstract class kotlinx/rpc/protoc/DefaultProtoTask : org/gradle/api/Defau
public final fun getProperties ()Lkotlinx/rpc/protoc/ProtoTask$Properties;
}

public abstract class kotlinx/rpc/protoc/ExtractDependencyProtoImports : org/gradle/api/tasks/Sync, kotlinx/rpc/protoc/ProtoTask {
public fun getProperties ()Lkotlinx/rpc/protoc/ProtoTask$Properties;
}

public final class kotlinx/rpc/protoc/PluginJarsKt {
public static final fun getGrpcKotlinMultiplatformProtocPluginJarPath (Lorg/gradle/api/Project;)Lorg/gradle/api/provider/Provider;
public static final fun getKotlinMultiplatformProtocPluginJarPath (Lorg/gradle/api/Project;)Lorg/gradle/api/provider/Provider;
Expand Down Expand Up @@ -190,7 +194,9 @@ public final class kotlinx/rpc/protoc/ProtoSourceSetKt {
public static final fun getProto (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)Lkotlinx/rpc/protoc/ProtoSourceSet;
public static final fun proto (Lorg/gradle/api/NamedDomainObjectProvider;Lorg/gradle/api/Action;)V
public static final fun proto (Lorg/gradle/api/tasks/SourceSet;Lorg/gradle/api/Action;)V
public static final fun proto (Lorg/jetbrains/kotlin/gradle/plugin/KotlinDependencyHandler;Ljava/lang/Object;)Lorg/gradle/api/artifacts/Dependency;
public static final fun proto (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;Lorg/gradle/api/Action;)V
public static final fun protoImport (Lorg/jetbrains/kotlin/gradle/plugin/KotlinDependencyHandler;Ljava/lang/Object;)Lorg/gradle/api/artifacts/Dependency;
public static final fun proto_kotlin (Lorg/gradle/api/NamedDomainObjectProvider;)Lorg/gradle/api/provider/Provider;
public static final fun proto_kotlin (Lorg/gradle/api/NamedDomainObjectProvider;Lorg/gradle/api/Action;)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package kotlinx.rpc.protoc

import kotlinx.rpc.buf.tasks.BufGenerateTask
import kotlinx.rpc.rpcExtension
import kotlinx.rpc.util.extendsFromLazy
import kotlinx.rpc.util.findOrCreate
import kotlinx.rpc.util.withLegacyAndroid
import kotlinx.rpc.util.withAndroidSourceSets
Expand All @@ -18,7 +19,9 @@ import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.NamedDomainObjectFactory
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
Expand All @@ -35,6 +38,7 @@ import java.io.File
import java.util.*
import java.util.function.Consumer
import javax.inject.Inject
import kotlin.contracts.ExperimentalContracts

@Suppress("UNCHECKED_CAST")
internal val Project.protoSourceSets: ProtoSourceSets
Expand All @@ -61,6 +65,7 @@ internal fun Project.findOrCreateProtoSourceSets(): NamedDomainObjectContainer<P
container
}

@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
internal open class DefaultProtoSourceSet(
internal val project: Project,
internal val sourceDirectorySet: SourceDirectorySet,
Expand Down Expand Up @@ -151,55 +156,112 @@ internal open class DefaultProtoSourceSet(
// only set for variant.name sourceSets
val androidProperties: Property<ProtoTask.AndroidProperties> = project.objects.property()

// Proto dependency configuration for this source set, created when protoc is activated.
// Allows users to declare proto dependencies via `dependencies { <name>Proto("...") }`.
// Resolved artifacts are extracted and included in code generation.
internal val protoConfiguration: Configuration

// Proto import dependency configuration for this source set, created when protoc is activated.
// Allows users to declare proto import dependencies via `dependencies { <name>ProtoImport("...") }`.
// Resolved artifacts are extracted and available as imports, but not for code generation.
private val protoImportConfigurationNew: Configuration

// Configurations attached lazily via importsFrom(Provider<...>) / importsAllFrom that cannot
// be wired through Configuration.extendsFrom: Gradle 8.8–9.3 has no Provider-based overload at
// all, and no Gradle version exposes a Provider<List<Configuration>> overload. Their files are
// merged into [protoImportFiles] at the consumer site, so resolution still sees them.
private val protoImportConfigurationLegacyList: ListProperty<Configuration> = project.objects.listProperty()

// Aggregated proto-import inputs for tasks: declared dependencies plus the transitive closure
// built up via Configuration.extendsFrom, plus everything stored in protoImportConfigOverflow.
internal val protoImportConfiguration: FileCollection by lazy {
project.files(protoImportConfigurationNew, protoImportConfigurationLegacyList)
}

init {
val protoConfigName = protoConfigurationName(name)
protoConfiguration = project.configurations.maybeCreate(protoConfigName).apply {
isCanBeResolved = true
isCanBeConsumed = false
description = "Proto file dependencies for source set '$name' (code generation)"
}

val protoImportConfigName = protoImportConfigurationName(name)
protoImportConfigurationNew = project.configurations.maybeCreate(protoImportConfigName).apply {
isCanBeResolved = true
isCanBeConsumed = false
description = "Proto file import dependencies for source set '$name' (imports only)"
}
}

override val imports: SetProperty<ProtoSourceSet> = project.objects.setProperty()
override val fileImports: ConfigurableFileCollection = project.objects.fileCollection()

override fun importsFrom(protoSourceSet: ProtoSourceSet) {
override fun importsFrom(rawProtoSourceSet: ProtoSourceSet) {
val protoSourceSet = rawProtoSourceSet.asDefault("extend")

imports.add(protoSourceSet.checkSelfImport())
imports.addAll(protoSourceSet.imports.checkSelfImport())

protoImportConfigurationNew.extendsFrom(protoSourceSet.protoImportConfigurationNew)
protoImportConfigurationLegacyList.addAll(protoSourceSet.protoImportConfigurationLegacyList)
}

override fun importsFrom(protoSourceSet: Provider<ProtoSourceSet>) {
override fun importsFrom(rawProtoSourceSet: Provider<ProtoSourceSet>) {
val protoSourceSet = rawProtoSourceSet.asDefault("extend")

imports.add(protoSourceSet.checkSelfImport())
imports.addAll(protoSourceSet.flatMap { it.imports.checkSelfImport() })

protoImportConfigurationNew.extendsFromLazy(
legacyList = protoImportConfigurationLegacyList,
provider = protoSourceSet.map { it.protoImportConfigurationNew },
)
protoImportConfigurationLegacyList.addAll(protoSourceSet.flatMap { it.protoImportConfigurationLegacyList })
}

override fun importsAllFrom(protoSourceSets: Provider<List<ProtoSourceSet>>) {
override fun importsAllFrom(rawProtoSourceSets: Provider<List<ProtoSourceSet>>) {
val protoSourceSets = rawProtoSourceSets.asDefault("extend")

imports.addAll(protoSourceSets.checkSelfImport())
imports.addAll(protoSourceSets.map { list -> list.flatMap { it.imports.checkSelfImport().get() } })
}

override fun importsFrom(protoSourceSet: NamedDomainObjectProvider<ProtoSourceSet>) {
imports.add(protoSourceSet.checkSelfImport())
imports.addAll(protoSourceSet.flatMap { it.imports.checkSelfImport() })
protoImportConfigurationLegacyList.addAll(
protoSourceSets.map { list -> list.map { it.protoImportConfigurationNew } },
)
protoImportConfigurationLegacyList.addAll(
protoSourceSets.map { list -> list.flatMap { it.protoImportConfigurationLegacyList.get() } },
)
}

private val extendsFrom: MutableSet<ProtoSourceSet> = mutableSetOf()

override fun extendsFrom(protoSourceSet: ProtoSourceSet) {
if (extendsFrom.contains(protoSourceSet)) {
override fun extendsFrom(rawProtoSourceSet: ProtoSourceSet) {
if (extendsFrom.contains(rawProtoSourceSet)) {
return
}

require(this != protoSourceSet) {
require(this != rawProtoSourceSet) {
"$name proto source set cannot extend from self"
}

require(protoSourceSet is DefaultProtoSourceSet) {
"$name proto source set can only extend from other default proto source sets." +
"${protoSourceSet.name} is not a ${DefaultProtoSourceSet::class.simpleName}"
}
val protoSourceSet = rawProtoSourceSet.asDefault("extend")

extendsFrom += protoSourceSet

source(protoSourceSet.sourceDirectorySet)
imports.addAll(protoSourceSet.imports.checkSelfImport())

plugins.addAll(protoSourceSet.plugins)

// Wire Gradle configuration inheritance for proto dependency configurations
protoConfiguration.extendsFrom(protoSourceSet.protoConfiguration)
protoImportConfigurationNew.extendsFrom(protoSourceSet.protoImportConfigurationNew)
protoImportConfigurationLegacyList.addAll(protoSourceSet.protoImportConfigurationLegacyList)
}

@JvmName("checkSelfImport_provider")
private fun Provider<ProtoSourceSet>.checkSelfImport() = map {
private fun Provider<DefaultProtoSourceSet>.checkSelfImport() = map {
it.checkSelfImport()
}

Expand All @@ -208,7 +270,7 @@ internal open class DefaultProtoSourceSet(
}

@JvmName("checkSelfImport_provider_list")
private fun Provider<List<ProtoSourceSet>>.checkSelfImport() = map { set ->
private fun Provider<List<DefaultProtoSourceSet>>.checkSelfImport() = map { set ->
set.onEach { it.checkSelfImport() }
}

Expand All @@ -220,6 +282,25 @@ internal open class DefaultProtoSourceSet(
return this
}

private fun Provider<ProtoSourceSet>.asDefault(action: String): Provider<DefaultProtoSourceSet> {
return map { it.asDefault(action) }
}

@JvmName("asDefault_provider_list")
private fun Provider<List<ProtoSourceSet>>.asDefault(action: String): Provider<List<DefaultProtoSourceSet>> {
return map { lists -> lists.map { it.asDefault(action) } }
}

@OptIn(ExperimentalContracts::class)
private fun ProtoSourceSet.asDefault(action: String): DefaultProtoSourceSet {
require(this is DefaultProtoSourceSet) {
"$name proto source set can only $action from other default proto source sets." +
"${this.name} is not a ${DefaultProtoSourceSet::class.simpleName}"
}

return this
}

// Java default methods

override fun forEach(action: Consumer<in File>?) {
Expand All @@ -240,7 +321,6 @@ internal fun Project.createProtoExtensions() {
val protoSourceSet = container.maybeCreate(languageSourceSetName) as DefaultProtoSourceSet

languageSourceSet?.let { protoSourceSet.languageSourceSets.add(it) }

return protoSourceSet
}

Expand Down Expand Up @@ -289,3 +369,13 @@ internal object PlatformOption {
const val COMMON = "common"
const val WASM = "wasm"
}

internal fun protoConfigurationName(sourceSetName: String): String {
if (sourceSetName == "main") return "proto"
return "${sourceSetName}Proto"
}

internal fun protoImportConfigurationName(sourceSetName: String): String {
if (sourceSetName == "main") return "protoImport"
return "${sourceSetName}ProtoImport"
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,43 @@ internal open class DefaultProtocExtension @Inject constructor(
dependsOn(processProtoTask)
}

val extractProtoTask = project.registerExtractDependencyProtoTask(
taskName = "extractProto${capitalName}",
destination = buildSourceSetsDir.resolve("protoExtracted"),
dependencyArchives = protoSourceSet.protoConfiguration,
properties = properties,
)

val extractProtoImportTask = project.registerExtractDependencyProtoTask(
taskName = "extractProtoImport${capitalName}",
destination = buildSourceSetsDir.resolve("importExtracted"),
dependencyArchives = protoSourceSet.protoImportConfiguration,
properties = properties,
)

// Wire extracted protos into the existing process tasks
processProtoTask.configure {
from(extractProtoTask.map { it.destinationDir })
dependsOn(extractProtoTask)
}

processImportProtoTask.configure {
from(extractProtoImportTask.map { it.destinationDir })
dependsOn(extractProtoImportTask)
}

val hasProtoImports = protoSourceSet.imports.map { it.isNotEmpty() || !protoSourceSet.fileImports.isEmpty }
val hasProtoImportConfig = project.provider {
!protoSourceSet.protoImportConfiguration.isEmpty
}
val withImport = hasProtoImports.zip(hasProtoImportConfig) { a, b -> a || b }

val generateBufYamlTask = project.registerGenerateBufYamlTask(
name = capitalName,
buildSourceSetsDir = buildSourceSetsDir,
buildSourceSetsProtoDir = buildSourceSetsProtoDir,
buildSourceSetsImportDir = buildSourceSetsImportDir,
withImport = protoSourceSet.imports.map { it.isNotEmpty() || !protoSourceSet.fileImports.isEmpty },
withImport = withImport,
properties = properties,
) {
dependsOn(processProtoTask)
Expand Down Expand Up @@ -295,6 +326,8 @@ internal open class DefaultProtocExtension @Inject constructor(
dependsOn(generateBufYamlTask)
dependsOn(processProtoTask)
dependsOn(processImportProtoTask)
dependsOn(extractProtoTask)
dependsOn(extractProtoImportTask)

val dependencies = project.provider {
protoSourceSet.getDependsOnTasksOf(project.protoSourceSets).mapNotNull { it.generateTask.orNull }
Expand Down Expand Up @@ -322,6 +355,8 @@ internal open class DefaultProtocExtension @Inject constructor(
generateBufGenYamlTask = generateBufGenYamlTask,
processProtoTask = processProtoTask,
processImportProtoTask = processImportProtoTask,
extractProtoTask = extractProtoTask,
extractProtoImportTask = extractProtoImportTask,
sourceSetsProtoDirFileTree = sourceSetsProtoDirFileTree,
properties = properties,
) {
Expand Down Expand Up @@ -450,6 +485,8 @@ internal open class DefaultProtocExtension @Inject constructor(
generateBufGenYamlTask: TaskProvider<GenerateBufGenYaml>,
processProtoTask: TaskProvider<ProcessProtoFiles>,
processImportProtoTask: TaskProvider<ProcessProtoFiles>,
extractProtoTask: TaskProvider<ExtractDependencyProtoImports>,
extractProtoImportTask: TaskProvider<ExtractDependencyProtoImports>,
sourceSetsProtoDirFileTree: ConfigurableFileTree,
properties: ProtoTask.Properties,
configure: BufExecTask.() -> Unit,
Expand All @@ -473,6 +510,8 @@ internal open class DefaultProtocExtension @Inject constructor(
dependsOn(generateBufGenYamlTask)
dependsOn(processProtoTask)
dependsOn(processImportProtoTask)
dependsOn(extractProtoTask)
dependsOn(extractProtoImportTask)

val dependencies = project.provider {
protoSourceSet.getDependsOnTasksOf(project.protoSourceSets).map { dependency ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
package kotlinx.rpc.protoc

import org.gradle.api.Project
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Internal
Expand Down Expand Up @@ -90,3 +92,52 @@ internal fun Project.registerProcessProtoFilesImportsTask(

return task
}

/**
* Extract proto files from dependency archives (JARs, ZIPs).
*
* Registered automatically when proto dependency configurations are used.
*/
public abstract class ExtractDependencyProtoImports @Inject internal constructor(
@get:Internal
override val properties: ProtoTask.Properties,
) : Sync(), ProtoTask {
@get:Inject
internal abstract val archiveOperations: ArchiveOperations

init {
group = PROTO_GROUP
}
}

internal fun Project.registerExtractDependencyProtoTask(
taskName: String,
destination: File,
dependencyArchives: FileCollection,
properties: ProtoTask.Properties,
configure: ExtractDependencyProtoImports.() -> Unit = {},
): TaskProvider<ExtractDependencyProtoImports> {
val task = tasks.register(
taskName,
ExtractDependencyProtoImports::class,
properties,
)

task.configure {
duplicatesStrategy = DuplicatesStrategy.WARN

from(dependencyArchives.elements.map { elements ->
elements.map { element ->
archiveOperations.zipTree(element.asFile).matching {
include("**/*.proto")
}
}
})

into(destination)

configure()
}

return task
}
Loading
Loading