From 7b59c4d298806d12c0907d792cc15f33d08c76c4 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 19 Nov 2025 15:36:32 +0100 Subject: [PATCH 1/9] Added full KMP support with proto source sets hierarchy and better plugin management --- .../main/kotlin/conventions-jvm.gradle.kts | 5 +- .../main/kotlin/util/other/generateSource.kt | 30 +- .../src/main/kotlin/kotlinx/rpc/Extensions.kt | 51 +++- .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 9 +- .../kotlinx/rpc/buf/tasks/BufExecTask.kt | 2 +- .../kotlinx/rpc/buf/tasks/BufGenerateTask.kt | 2 +- .../rpc/buf/tasks/GenerateBufGenYaml.kt | 8 +- .../kotlinx/rpc/buf/tasks/GenerateBufYaml.kt | 4 +- ...gureLocalProtocGenDevelopmentDependency.kt | 5 +- .../rpc/protoc/DefaultProtoSourceSet.kt | 147 +++++---- .../rpc/protoc/DefaultProtocExtension.kt | 245 +++++++++++---- .../kotlinx/rpc/protoc/ProcessProtoFiles.kt | 48 ++- .../kotlinx/rpc/protoc/ProtoSourceSet.kt | 81 +++-- .../kotlinx/rpc/protoc/ProtocExtension.kt | 13 +- .../kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt | 19 +- .../main/kotlin/kotlinx/rpc/protoc/consts.kt | 5 + .../src/main/kotlin/kotlinx/rpc/util/kgp.kt | 13 +- .../kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt | 281 ++++++++++++------ .../kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt | 46 +-- .../kotlin/kotlinx/rpc/base/GrpcBaseTest.kt | 78 ++--- .../build.gradle.kts | 13 +- .../build.gradle.kts | 8 +- .../build.gradle.kts | 3 +- .../src/main/proto/exclude.proto | 0 .../src/main/proto/exclude/no1.proto | 0 .../src/main/proto/exclude/no2.proto | 0 .../src/main/proto/ok/ok.proto | 0 .../src/main/proto/some.proto | 0 .../src/test/proto/include.proto | 0 .../src/test/proto/include/yes1.proto | 0 .../src/test/proto/include/yes2.proto | 0 .../src/test/proto/other.proto | 0 .../proto/some/package/hello/world/file.proto | 0 .../build.gradle.kts | 52 ++++ .../src/main/proto/no.proto | 0 .../src/main/proto/some.proto | 5 + .../src/test/proto/no2.proto | 0 .../src/test/proto/other.proto | 5 + .../java_source_sets/build.gradle.kts | 29 ++ .../java_source_sets/src/main/proto/no.proto | 0 .../src/main/proto/some.proto | 5 + .../java_source_sets/src/test/proto/no2.proto | 0 .../src/test/proto/other.proto | 5 + .../no_grpc/build.gradle.kts | 6 - .../build.gradle.kts | 9 +- protobuf/protobuf-core/build.gradle.kts | 19 +- tests/protobuf-conformance/build.gradle.kts | 6 +- 47 files changed, 867 insertions(+), 390 deletions(-) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/build.gradle.kts (91%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/main/proto/exclude.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/main/proto/exclude/no1.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/main/proto/exclude/no2.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/main/proto/ok/ok.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/main/proto/some.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/test/proto/include.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/test/proto/include/yes1.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/test/proto/include/yes2.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/test/proto/other.proto (100%) rename gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/{exclude_and_include_in_protosourcesets => exclude_and_include_in_proto_sourcesets}/src/test/proto/some/package/hello/world/file.proto (100%) create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/build.gradle.kts create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/main/proto/no.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/main/proto/some.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/test/proto/no2.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/test/proto/other.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/build.gradle.kts create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/main/proto/no.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/main/proto/some.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/test/proto/no2.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/test/proto/other.proto diff --git a/gradle-conventions/src/main/kotlin/conventions-jvm.gradle.kts b/gradle-conventions/src/main/kotlin/conventions-jvm.gradle.kts index 89c0c1b2e..48b394dcc 100644 --- a/gradle-conventions/src/main/kotlin/conventions-jvm.gradle.kts +++ b/gradle-conventions/src/main/kotlin/conventions-jvm.gradle.kts @@ -20,7 +20,10 @@ java { withSourcesJar() } -configureJavaCompatibility(8) +if (project.name != "gradle-plugin") { + configureJavaCompatibility(8) +} + configureKotlinCompatibility("2.0.0") kotlin { diff --git a/gradle-conventions/src/main/kotlin/util/other/generateSource.kt b/gradle-conventions/src/main/kotlin/util/other/generateSource.kt index 4fa33c3f5..3cd91c096 100644 --- a/gradle-conventions/src/main/kotlin/util/other/generateSource.kt +++ b/gradle-conventions/src/main/kotlin/util/other/generateSource.kt @@ -40,20 +40,28 @@ fun Project.generateSource( text: String, chooseSourceSet: NamedDomainObjectContainer.() -> NamedDomainObjectProvider, ) { - val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/kotlin") - - val generatePluginVersionTask = - tasks.register("generateSources_$name", name, text, sourcesDir) - withKotlinJvmExtension { - chooseSourceSet(sourceSets).configure { - kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir }) - } + generateSource(sourceSets, name, text, chooseSourceSet) } withKotlinKmpExtension { - chooseSourceSet(sourceSets).configure { - kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir }) - } + generateSource(sourceSets, name, text, chooseSourceSet) + } +} + +private fun Project.generateSource( + sourceSets: NamedDomainObjectContainer, + name: String, + text: String, + chooseSourceSet: NamedDomainObjectContainer.() -> NamedDomainObjectProvider, +) { + val sourceSet = chooseSourceSet(sourceSets) + val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/kotlin/${sourceSet.name}") + + val generatePluginVersionTask = + tasks.register("generateSources_$name", name, text, sourcesDir) + + sourceSet.configure { + kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir }) } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt index e0a595383..f8c5e2606 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt @@ -24,7 +24,7 @@ internal fun Project.rpcExtensionOrNull(): RpcExtension? = extensions.findByType internal fun Project.rpcExtension(): RpcExtension = rpcExtensionOrNull() ?: error("Rpc extension not found. Please apply the plugin to the project") -public open class RpcExtension @Inject constructor(objects: ObjectFactory, private val project: Project) { +public open class RpcExtension @Inject constructor(objects: ObjectFactory, project: Project) { /** * Controls `@Rpc` [annotation type-safety](https://github.com/Kotlin/kotlinx-rpc/pull/240) compile-time checkers. * @@ -48,25 +48,60 @@ public open class RpcExtension @Inject constructor(objects: ObjectFactory, priva configure.execute(strict) } - internal val protocApplied = AtomicBoolean(false) /** * Protoc settings. + * + * Can't be called in a lazy context if not initialized. */ - public val protoc: ProtocExtension by lazy { - if (protocApplied.get()) { - error("Illegal access to protoc extension during DefaultProtocExtension.init") + public val protoc: Provider = project.provider { + if (!protocApplied.get()) { + error(""" + Protoc extension was not initialized. + Please, apply the plugin by using the following declaration: + + rpc { + protoc() + } + + If the error persists, check if you are using this property lazily, + i.e. by calling 'protoc.map { }' and not 'protoc.get()', + otherwise the order of initialization may be wrong. + """.trimIndent()) } - protocApplied.set(true) - objects.newInstance() + protocInternal } /** * Protoc settings. */ public fun protoc(configure: Action = Action {}) { - configure.execute(protoc) + configure.execute(protocInternal) + } + + internal val protocApplied = AtomicBoolean(false) + + internal val protocInternal by lazy { + if (protocApplied.get()) { + error("Illegal access to protoc extension during DefaultProtocExtension.init") + } + + protocApplied.set(true) + objects.newInstance().apply { + callbacks.forEach { it.execute(this) } + } + } + + private val callbacks = mutableListOf>() + + internal fun whenProtocApplied(action: Action) { + if (protocApplied.get()) { + action.execute(protoc.get()) + return + } + + callbacks.add(action) } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt index 449ae074b..b6cf264da 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -24,7 +24,7 @@ import kotlin.time.Duration * * @see buf commands */ -public open class BufExtension @Inject constructor(objects: ObjectFactory) { +public open class BufExtension @Inject internal constructor(objects: ObjectFactory) { /** * `--config` argument value. * @@ -101,7 +101,7 @@ public open class BufExtension @Inject constructor(objects: ObjectFactory) { /** * Allows registering custom Buf tasks that can operate on the generated workspace. */ -public open class BufTasksExtension @Inject constructor(internal val project: Project) { +public open class BufTasksExtension @Inject internal constructor(internal val project: Project) { /** * Registers a custom Buf task that operates on the generated workspace. @@ -109,9 +109,9 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr * Name conventions: * `lint` input for [name] will result in tasks * named 'bufLintMain' and 'bufLintTest' for Kotlin/JVM projects - * and 'bufLintCommonMain' and 'bufLintCommonTest' for Kotlin/Multiplatform projects. + * and 'bufLintCommonMain', 'bufLintCommonTest', 'bufLintNativeMain', etc., for Kotlin/Multiplatform projects. * - * Note the by default 'test' task doesn't depend on 'main' task. + * Note the by default 'test' task doesn't depend on the 'main' task. */ public fun registerWorkspaceTask( kClass: KClass, @@ -129,7 +129,6 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr return provider } - /** * Registers a custom Buf task that operates on the generated workspace. * diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt index e2a147401..5110a568b 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt @@ -117,7 +117,7 @@ internal fun Project.registerBufExecTask( bufExecutable.set(executableConfiguration.singleFile) this.workingDir.set(workingDir) - val buf = provider { rpcExtension().protoc.buf } + val buf = provider { rpcExtension().protoc.get().buf } configFile.set(buf.flatMap { it.configFile }) logFormat.set(buf.flatMap { it.logFormat }) bufTimeoutInWholeSeconds.set(buf.flatMap { it.timeout.map { duration -> duration.inWholeSeconds } }) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt index dc6d62077..94db55c8e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt @@ -131,7 +131,7 @@ internal fun Project.registerBufGenerateTask( group = PROTO_GROUP description = "Generates code from .proto files using 'buf generate'" - val generate = provider { rpcExtension().protoc.buf.generate } + val generate = provider { rpcExtension().protoc.get().buf.generate } includeImports.set(generate.flatMap { it.includeImports }) includeWkt.set(generate.flatMap { it.includeWkt }) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt index c281706b8..a01a002b0 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt @@ -137,19 +137,13 @@ public abstract class GenerateBufGenYaml : DefaultTask() { internal fun Project.registerGenerateBufGenYamlTask( name: String, buildSourceSetsDir: File, - protocPlugins: Provider>, + protocPlugins: Provider>, configure: GenerateBufGenYaml.() -> Unit = {}, ): TaskProvider { val capitalizeName = name.replaceFirstChar { it.uppercase() } return project.tasks.register("${GenerateBufGenYaml.NAME_PREFIX}$capitalizeName") { val pluginsProvider = project.provider { protocPlugins.get().map { plugin -> - if (!plugin.artifact.isPresent) { - throw GradleException( - "Artifact is not specified for protoc plugin ${plugin.name}. " + - "Use `local {}` or `remote {}` to specify it.") - } - val artifact = plugin.artifact.get() val locator = when (artifact) { is ProtocPlugin.Artifact.Local -> artifact.executor.get() diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt index 3b2d8b63e..112025541 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt @@ -5,11 +5,13 @@ package kotlinx.rpc.buf.tasks import kotlinx.rpc.buf.BUF_YAML +import kotlinx.rpc.protoc.DefaultProtoSourceSet import kotlinx.rpc.protoc.PROTO_GROUP import kotlinx.rpc.util.ensureRegularFileExists import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction @@ -88,7 +90,7 @@ internal fun Project.registerGenerateBufYamlTask( buildSourceSetsDir: File, buildSourceSetsProtoDir: File, buildSourceSetsImportDir: File, - withImport: Boolean, + withImport: Provider, configure: GenerateBufYaml.() -> Unit = {}, ): TaskProvider { val capitalizeName = name.replaceFirstChar { it.uppercase() } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt index c742ec391..ce9a1a154 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt @@ -7,7 +7,6 @@ package kotlinx.rpc.internal import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.protoc.grpcKotlinMultiplatform import kotlinx.rpc.protoc.kotlinMultiplatform -import kotlinx.rpc.protoc.protoSourceSets import kotlinx.rpc.rpcExtension import org.gradle.api.Project import org.gradle.internal.extensions.core.extra @@ -21,9 +20,7 @@ public fun Project.configureLocalProtocGenDevelopmentDependency( val globalRootDir: String by extra // init - rpcExtension().protoc() - - protoSourceSets.all { + rpcExtension().protoc { plugins { kotlinMultiplatform { local { diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtoSourceSet.kt index e2cce027e..2f7bf07ce 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtoSourceSet.kt @@ -4,31 +4,32 @@ package kotlinx.rpc.protoc -import kotlinx.rpc.buf.BufCommentsExtension -import kotlinx.rpc.buf.BufGenerateExtension import kotlinx.rpc.buf.tasks.BufGenerateTask -import kotlinx.rpc.protoc.ProtocPlugin.Companion.GRPC_KOTLIN_MULTIPLATFORM -import kotlinx.rpc.protoc.ProtocPlugin.Companion.KOTLIN_MULTIPLATFORM import kotlinx.rpc.rpcExtension -import kotlinx.rpc.rpcExtensionOrNull import kotlinx.rpc.util.findOrCreate -import kotlinx.rpc.util.withKotlinJvmExtension -import kotlinx.rpc.util.withKotlinKmpExtension -import org.gradle.api.* +import kotlinx.rpc.util.withLazyKotlinJvmExtension +import kotlinx.rpc.util.withLazyKotlinKmpExtension +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.NamedDomainObjectFactory +import org.gradle.api.NamedDomainObjectProvider +import org.gradle.api.Project import org.gradle.api.file.SourceDirectorySet import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSetContainer import org.gradle.kotlin.dsl.add import org.gradle.kotlin.dsl.listProperty -import org.gradle.kotlin.dsl.newInstance import org.gradle.kotlin.dsl.property +import org.gradle.kotlin.dsl.setProperty import org.gradle.kotlin.dsl.the import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension -import org.jetbrains.kotlin.gradle.targets.js.npm.includedRange +import java.io.File +import java.util.* +import java.util.function.Consumer import javax.inject.Inject @Suppress("UNCHECKED_CAST") @@ -42,75 +43,77 @@ internal class ProtoSourceSetFactory(private val project: Project) : NamedDomain } } -internal open class DefaultProtoSourceSet @Inject constructor( +internal open class DefaultProtoSourceSet( internal val project: Project, - override val name: String, -) : ProtoSourceSet { - override val plugins: NamedDomainObjectContainer = - project.objects.domainObjectContainer(ProtocPlugin::class.java) { name -> - ProtocPlugin(name, project) - } - - override fun plugins(action: Action>) { - action.execute(plugins) + private val sourceDirectorySet: SourceDirectorySet, +) : ProtoSourceSet, SourceDirectorySet by sourceDirectorySet { + + @Inject + constructor(project: Project, protoName: String) : this( + project = project, + sourceDirectorySet = project.objects.sourceDirectorySet(protoName, "Proto sources for $protoName").apply { + srcDirs("src/${protoName}/proto") + }, + ) + + private val explicitApiModeEnabled = project.provider { + project.the().explicitApi != ExplicitApiMode.Disabled } - init { - val explicitApiModeEnabled = project.provider { - project.the().explicitApi != ExplicitApiMode.Disabled - } - - plugins.create(KOTLIN_MULTIPLATFORM) { - local { - javaJar(project.kotlinMultiplatformProtocPluginJarPath) - } + val plugins = project.objects.setProperty() - defaultOptions(explicitApiModeEnabled) - } + override fun plugin(plugin: ProtocPlugin, configure: Action?) { + plugins.add(plugin.copy().also { initPlugin(it, configure) }) + } - plugins.create(GRPC_KOTLIN_MULTIPLATFORM) { - local { - javaJar(project.grpcKotlinMultiplatformProtocPluginJarPath) - } + override fun plugin(provider: NamedDomainObjectProvider, configure: Action?) { + plugins.add(provider.map { plugin -> plugin.copy().also { initPlugin(it, configure) } }) + } - defaultOptions(explicitApiModeEnabled) - } + override fun plugin(provider: Provider, configure: Action?) { + plugins.add(provider.map { plugin -> plugin.copy().also { initPlugin(it, configure) } }) } - private fun ProtocPlugin.defaultOptions(explicitApiModeEnabled: Provider) { - options.put("debugOutput", "protoc-gen-$name.log") + override fun plugin( + configure: Action?, + select: NamedDomainObjectContainer.() -> ProtocPlugin, + ) { + plugins.add(project.rpcExtension().protoc.map { protoc -> + protoc.plugins.select().copy().also { initPlugin(it, configure) } + }) + } + private fun initPlugin(copy: ProtocPlugin, configure: Action?) { if (this@DefaultProtoSourceSet.name.lowercase().endsWith("main")) { - options.put("explicitApiModeEnabled", explicitApiModeEnabled) + copy.options.put("explicitApiModeEnabled", explicitApiModeEnabled) } - val comments: Provider = project.provider { - project.rpcExtensionOrNull()?.run { protoc.buf.generate } - ?: project.objects.newInstance() - } + configure?.execute(copy) + } - options.put("generateComments", comments.flatMap { it.comments.copyComments }) - options.put("generateFileLevelComments", comments.flatMap { it.comments.includeFileLevelComments }) - options.put("indentSize", comments.flatMap { it.indentSize }) + init { + project.rpcExtension().whenProtocApplied { + plugin(plugins.kotlinMultiplatform) + plugin(plugins.grpcKotlinMultiplatform) + } } val languageSourceSets: ListProperty = project.objects.listProperty() val generateTask: Property = project.objects.property() - override val proto: SourceDirectorySet = project.objects.sourceDirectorySet( - PROTO_SOURCE_DIRECTORY_NAME, - "Proto sources", - ).apply { - srcDirs("src/${this@DefaultProtoSourceSet.name}/proto") + // Java default methods + + override fun forEach(action: Consumer?) { + sourceDirectorySet.forEach(action) } - override fun proto(action: Action) { - action.execute(proto) + override fun spliterator(): Spliterator { + return sourceDirectorySet.spliterator() } } internal fun Project.createProtoExtensions() { - fun findOrCreateAndConfigure(languageSourceSetName: String, languageSourceSet: Any?) { + fun findOrCreateAndConfigure(languageSourceSetName: String, languageSourceSet: Any?): ProtoSourceSet { val container = project.findOrCreate(PROTO_SOURCE_SETS) { val container = objects.domainObjectContainer( ProtoSourceSet::class.java, @@ -125,35 +128,29 @@ internal fun Project.createProtoExtensions() { val protoSourceSet = container.maybeCreate(languageSourceSetName) as DefaultProtoSourceSet languageSourceSet?.let { protoSourceSet.languageSourceSets.add(it) } - } - project.withKotlinJvmExtension { - findOrCreateAndConfigure("main", null) - findOrCreateAndConfigure("test", null) + return protoSourceSet + } - sourceSets.configureEach { - if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { - findOrCreateAndConfigure(name, this) - } + project.withLazyKotlinJvmExtension { + sourceSets.all { + findOrCreateAndConfigure(name, this) } project.extensions.configure("sourceSets") { - configureEach { - if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { - findOrCreateAndConfigure(name, this) + all { + val protoSourceSet = findOrCreateAndConfigure(name, this) + + findOrCreate(PROTO_SOURCE_SET_EXTENSION_NAME) { + extensions.add(PROTO_SOURCE_SET_EXTENSION_NAME, protoSourceSet) } } } } - project.withKotlinKmpExtension { - findOrCreateAndConfigure("commonMain", null) - findOrCreateAndConfigure("commonTest", null) - - sourceSets.configureEach { - if (name == "commonMain" || name == "commonTest") { - findOrCreateAndConfigure(name, this) - } + project.withLazyKotlinKmpExtension { + sourceSets.all { + findOrCreateAndConfigure(name, this) } } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt index a491d32ba..e2855d699 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt @@ -13,14 +13,22 @@ import kotlinx.rpc.buf.tasks.registerBufExecTask import kotlinx.rpc.buf.tasks.registerBufGenerateTask import kotlinx.rpc.buf.tasks.registerGenerateBufGenYamlTask import kotlinx.rpc.buf.tasks.registerGenerateBufYamlTask +import kotlinx.rpc.protoc.ProtocPlugin.Companion.GRPC_KOTLIN_MULTIPLATFORM +import kotlinx.rpc.protoc.ProtocPlugin.Companion.KOTLIN_MULTIPLATFORM import kotlinx.rpc.util.ensureDirectoryExists +import kotlinx.rpc.util.kotlinJvmExtensionOrNull +import kotlinx.rpc.util.kotlinKmpExtensionOrNull import org.gradle.api.Action import org.gradle.api.GradleException +import org.gradle.api.Named +import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.SourceDirectorySet import org.gradle.api.model.ObjectFactory import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.Provider +import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.compile.JavaCompile import org.gradle.kotlin.dsl.findByType @@ -30,6 +38,9 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask import java.io.File import javax.inject.Inject +import kotlin.collections.filterIsInstance +import kotlin.collections.filterNotNull +import kotlin.collections.plus internal open class DefaultProtocExtension @Inject constructor( objects: ObjectFactory, @@ -40,6 +51,15 @@ internal open class DefaultProtocExtension @Inject constructor( action.execute(buf) } + override val plugins: NamedDomainObjectContainer = + project.objects.domainObjectContainer(ProtocPlugin::class.java) { name -> + ProtocPlugin(name, project) + } + + override fun plugins(action: Action>) { + action.execute(plugins) + } + init { project.configureBufExecutable() project.configureKotlinMultiplatformPluginJarConfiguration() @@ -50,6 +70,22 @@ internal open class DefaultProtocExtension @Inject constructor( project.normalization.runtimeClasspath.ignore("**/protoc-gen-grpc-kotlin-multiplatform.log") project.normalization.runtimeClasspath.ignore("**/.keep") + plugins.create(KOTLIN_MULTIPLATFORM) { + local { + javaJar(project.kotlinMultiplatformProtocPluginJarPath) + } + + defaultOptions() + } + + plugins.create(GRPC_KOTLIN_MULTIPLATFORM) { + local { + javaJar(project.grpcKotlinMultiplatformProtocPluginJarPath) + } + + defaultOptions() + } + project.protoSourceSets.all { if (this !is DefaultProtoSourceSet) { return@all @@ -59,6 +95,14 @@ internal open class DefaultProtocExtension @Inject constructor( } } + private fun ProtocPlugin.defaultOptions() { + options.put("debugOutput", "protoc-gen-$name.log") + + options.put("generateComments", buf.generate.comments.copyComments) + options.put("generateFileLevelComments", buf.generate.comments.includeFileLevelComments) + options.put("indentSize", buf.generate.indentSize) + } + @Suppress("detekt.LongMethod", "detekt.CyclomaticComplexMethod", "detekt.ThrowsCount") private fun Project.configureTasks(protoSourceSet: DefaultProtoSourceSet) { val baseName = protoSourceSet.name @@ -72,13 +116,22 @@ internal open class DefaultProtocExtension @Inject constructor( val buildSourceSetsImportDir = buildSourceSetsDir.resolve(PROTO_FILES_IMPORT_DIR) .ensureDirectoryExists() - val pairSourceSet = protoSourceSet.correspondingMainSourceSetOrNull() + // only resolve in task's 'execute' due to the deferred nature of dependsOn + val importsProvider = protoSourceSet.getImports(protoSourceSets) val includedProtocPlugins = provider { - protoSourceSet.plugins.distinct() + protoSourceSet.plugins.get().also { list -> + list.forEach { plugin -> + if (!plugin.artifact.isPresent) { + throw GradleException( + "Artifact is not specified for protoc plugin ${plugin.name}. " + + "Use `local {}` or `remote {}` to specify it.") + } + } + } } - val protoFiles = protoSourceSet.proto + val protoFiles = protoSourceSet as SourceDirectorySet val processProtoTask = registerProcessProtoFilesTask( name = baseName, @@ -86,18 +139,12 @@ internal open class DefaultProtocExtension @Inject constructor( protoFiles = protoFiles, ) - val processImportProtoTask = if (pairSourceSet != null) { - val importProtoFiles = pairSourceSet.proto - - registerProcessProtoFilesTask( - name = "${baseName}Import", - destination = buildSourceSetsImportDir, - protoFiles = importProtoFiles, - ) { - dependsOn(processProtoTask) - } - } else { - null + val processImportProtoTask = registerProcessProtoFilesImportsTask( + name = baseName, + destination = buildSourceSetsImportDir, + importsProvider = importsProvider, + ) { + dependsOn(processProtoTask) } val generateBufYamlTask = registerGenerateBufYamlTask( @@ -105,12 +152,9 @@ internal open class DefaultProtocExtension @Inject constructor( buildSourceSetsDir = buildSourceSetsDir, buildSourceSetsProtoDir = buildSourceSetsProtoDir, buildSourceSetsImportDir = buildSourceSetsImportDir, - withImport = pairSourceSet != null, + withImport = importsProvider.map { it.isNotEmpty() }, ) { dependsOn(processProtoTask) - if (processImportProtoTask != null) { - dependsOn(processImportProtoTask) - } } val generateBufGenYamlTask = registerGenerateBufGenYamlTask( @@ -147,14 +191,14 @@ internal open class DefaultProtocExtension @Inject constructor( dependsOn(generateBufGenYamlTask) dependsOn(generateBufYamlTask) dependsOn(processProtoTask) - if (processImportProtoTask != null) { - dependsOn(processImportProtoTask) - } + dependsOn(processImportProtoTask) - if (pairSourceSet != null) { - dependsOn(pairSourceSet.generateTask) + val dependencies = project.provider { + protoSourceSet.getDependsOn(protoSourceSets).map { it.generateTask.get() } } + dependsOn(dependencies) + onlyIf { !sourceSetsProtoDirFileTree.filter { it.extension == "proto" }.isEmpty } } @@ -194,53 +238,69 @@ internal open class DefaultProtocExtension @Inject constructor( } } - configureAfterEvaluate( + configureSourceDirectories( baseName = baseName, - protoSourceSet = protoSourceSet, - buildSourceSetsDir = buildSourceSetsDir, includedProtocPlugins = includedProtocPlugins, - generateBufYamlTask = generateBufYamlTask, - generateBufGenYamlTask = generateBufGenYamlTask, - processProtoTask = processProtoTask, - processImportProtoTask = processImportProtoTask, bufGenerateTask = bufGenerateTask, - sourceSetsProtoDirFileTree = sourceSetsProtoDirFileTree, ) + +// configureCustomTasks( +// baseName = baseName, +// buildSourceSetsDir = buildSourceSetsDir, +// generateBufYamlTask = generateBufYamlTask, +// generateBufGenYamlTask = generateBufGenYamlTask, +// processProtoTask = processProtoTask, +// processImportProtoTask = processImportProtoTask, +// sourceSetsProtoDirFileTree = sourceSetsProtoDirFileTree, +// ) } - private fun Project.configureAfterEvaluate( + private fun Project.configureSourceDirectories( + baseName: String, + includedProtocPlugins: Provider>, + bufGenerateTask: TaskProvider, + ) { + // locates correctly jvmMain, main jvmTest, test + extensions.findByType()?.sourceSets?.all { + val javaSourceSet = this + + if (javaSourceSet.name == baseName) { + val javaSourcesProvider = includedProtocPlugins.map { plugins -> + val out = bufGenerateTask.get().outputDirectory.get() + plugins.filter { it.isJava.get() }.map { out.resolve(it.name) } + } + + javaSourceSet.java.srcDirs(javaSourcesProvider) + } + } + + val kotlinSourcesProvider = includedProtocPlugins.map { plugins -> + val out = bufGenerateTask.get().outputDirectory.get() + plugins.filter { !it.isJava.get() }.map { out.resolve(it.name) } + } + + project.kotlinJvmExtensionOrNull?.sourceSets?.all { + if (name == baseName) { + kotlin.srcDirs(kotlinSourcesProvider) + } + } + + project.kotlinKmpExtensionOrNull?.sourceSets?.all { + if (name == baseName) { + kotlin.srcDirs(kotlinSourcesProvider) + } + } + } + + private fun Project.configureCustomTasks( baseName: String, - protoSourceSet: DefaultProtoSourceSet, buildSourceSetsDir: File, - includedProtocPlugins: Provider>, generateBufYamlTask: TaskProvider, generateBufGenYamlTask: TaskProvider, processProtoTask: TaskProvider, processImportProtoTask: TaskProvider?, - bufGenerateTask: TaskProvider, sourceSetsProtoDirFileTree: ConfigurableFileTree, ) = afterEvaluate { - val out = bufGenerateTask.get().outputDirectory.get() - val plugins = includedProtocPlugins.get() - - plugins.forEach { plugin -> - // locates correctly jvmMain, main jvmTest, test - val javaSourceSet = extensions.findByType() - ?.sourceSets?.findByName(baseName)?.java - - if (plugin.isJava.get() && javaSourceSet != null) { - javaSourceSet.srcDirs(out.resolve(plugin.name)) - } else { - protoSourceSet.languageSourceSets.get().find { it is KotlinSourceSet }?.let { - (it as KotlinSourceSet) - .kotlin.srcDirs(out.resolve(plugin.name)) - } ?: error( - "Unable to find fitting source directory set " + - "for plugin '${plugin.name}' in '$protoSourceSet' proto source set" - ) - } - } - val baseCapital = baseName.replaceFirstChar { it.uppercase() } buf.tasks.customTasks.get().forEach { definition -> val capital = definition.name.replaceFirstChar { it.uppercase() } @@ -277,14 +337,20 @@ internal open class DefaultProtocExtension @Inject constructor( } } - private fun DefaultProtoSourceSet.correspondingMainSourceSetOrNull(): DefaultProtoSourceSet? { + private fun DefaultProtoSourceSet.getImports( + protoSourceSets: ProtoSourceSets, + ): Provider> { return when { name.lowercase().endsWith("main") -> { - null + getImportsForTestOrMain(protoSourceSets) } name.lowercase().endsWith("test") -> { - project.protoSourceSets.findByName(correspondingMainName()) as? DefaultProtoSourceSet + val main = getImportsForTestOrMain(protoSourceSets) + val test = (project.protoSourceSets.findByName(correspondingMainName()) as? DefaultProtoSourceSet) + ?.getImportsForTestOrMain(protoSourceSets) + + if (test == null) main else main.zip(test) { a, b -> a + b } } else -> { @@ -293,7 +359,64 @@ internal open class DefaultProtocExtension @Inject constructor( } } - private fun ProtoSourceSet.correspondingMainName(): String { + private fun DefaultProtoSourceSet.getImportsForTestOrMain( + protoSourceSets: ProtoSourceSets, + ): Provider> { + return languageSourceSets.map { list -> + list.filterIsInstance().flatMap { + it.dependsOn.mapNotNull { dependency -> + (protoSourceSets.getByName(dependency.name) as? DefaultProtoSourceSet) + }.flatMap { proto -> + // can't use plus because DefaultProtoSourceSet is Iterable + proto.getImportsForTestOrMain(protoSourceSets).get().toMutableList().apply { + add(proto) + } + } + } + list.filterIsInstance().mapNotNull { + if (it.name.endsWith("Test")) { + project.protoSourceSets.findByName(correspondingMainName()) as? DefaultProtoSourceSet + } else { + null + } + } + list.filterIsInstance().mapNotNull { + if (it.name == SourceSet.TEST_SOURCE_SET_NAME) { + project.protoSourceSets.findByName(correspondingMainName()) as? DefaultProtoSourceSet + } else { + null + } + } + } + } + + private fun DefaultProtoSourceSet.getDependsOn(protoSourceSets: ProtoSourceSets): List { + val sourceSets = languageSourceSets.get() + + val kmpDependsOn = sourceSets + .filterIsInstance() + .flatMap { + it.dependsOn.map { dependency -> dependency.name } + } + .distinct() + .mapNotNull { + protoSourceSets.getByName(it) as? DefaultProtoSourceSet + } + + val kmp = if (name.endsWith("Test")) { + (protoSourceSets.getByName(correspondingMainName()) as? DefaultProtoSourceSet) + } else { + null + } + + val jvm = if (name == SourceSet.TEST_SOURCE_SET_NAME) { + (protoSourceSets.getByName(correspondingMainName()) as? DefaultProtoSourceSet) + } else { + null + } + + return (kmpDependsOn + kmp + jvm).filterNotNull() + } + + private fun Named.correspondingMainName(): String { return when { name == "test" -> "main" name.endsWith("Test") -> name.removeSuffix("Test") + "Main" diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt index 7bc197c2b..fc9e56697 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt @@ -6,7 +6,9 @@ package kotlinx.rpc.protoc import kotlinx.rpc.util.ensureRegularFileExists import org.gradle.api.Project +import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Copy import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.register @@ -32,10 +34,52 @@ internal fun Project.registerProcessProtoFilesTask( return tasks.register("process${capitalName}ProtoFiles") { // this task deletes the destination directory if it results in NO-SOURCE, // which breaks the configuration cache for bufGenerate - // so we prevent No-SOURCE by creating a .keep file in the destination directory + // so we prevent NO-SOURCE by creating a .keep file in the destination directory val keep = protoBuildDirSourceSetsKeep.ensureRegularFileExists() from(keep) - from(protoFiles) + + from(files(protoFiles.sourceDirectories)) { + include(protoFiles.includes) + exclude(protoFiles.excludes) + } + + into(destination) + + doFirst { + destination.deleteRecursively() + } + + configure() + } +} + +internal fun Project.registerProcessProtoFilesImportsTask( + name: String, + destination: File, + importsProvider: Provider>, + configure: ProcessProtoFiles.() -> Unit = {}, +): TaskProvider { + val capitalName = name.replaceFirstChar { it.uppercase() } + + return tasks.register("process${capitalName}ProtoFilesImports") { + // this task deletes the destination directory if it results in NO-SOURCE, + // which breaks the configuration cache for bufGenerate + // so we prevent NO-SOURCE by creating a .keep file in the destination directory + val keep = protoBuildDirSourceSetsKeep.ensureRegularFileExists() + from(keep) + + duplicatesStrategy = DuplicatesStrategy.FAIL + + val allImports = importsProvider.map { list -> + list.map { import -> + files(import.sourceDirectories) { + include(import.includes) + exclude(import.excludes) + } + } + } + + from(allImports) into(destination) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt index 1087d3ddf..95ac93cbd 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt @@ -8,37 +8,66 @@ import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.SourceSet +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet public typealias ProtoSourceSets = NamedDomainObjectContainer /** * Represents a source set for proto files. */ -public interface ProtoSourceSet { - /** - * Name of the source set. - */ - public val name: String - - /** - * Container of protoc plugins to be applied to the source set. - */ - public val plugins: NamedDomainObjectContainer - - /** - * Configures the protoc plugins. - */ - public fun plugins(action: Action>) - - /** - * Default [SourceDirectorySet] for proto files. - */ - public val proto: SourceDirectorySet - - /** - * Configures [proto] source directory set. - */ - public fun proto(action: Action) { - action.execute(proto) +public sealed interface ProtoSourceSet : SourceDirectorySet { + public fun plugin(plugin: ProtocPlugin, configure: Action? = null) + + public fun plugin(provider: NamedDomainObjectProvider, configure: Action? = null) + + public fun plugin(provider: Provider, configure: Action? = null) + + public fun plugin( + configure: Action? = null, + select: NamedDomainObjectContainer.() -> ProtocPlugin, + ) +} + +public val KotlinSourceSet.proto: ProtoSourceSet + get(): ProtoSourceSet { + return project.protoSourceSets.getByName(name) + } + +public fun KotlinSourceSet.proto(action: Action) { + proto.apply(action::execute) +} + +@get:JvmName("proto_kotlin") +public val NamedDomainObjectProvider.proto: Provider + get() { + return map { it.proto } + } + +@JvmName("proto_kotlin") +public fun NamedDomainObjectProvider.proto(action: Action) { + configure { + proto(action) + } +} + +public val SourceSet.proto: ProtoSourceSet + get(): ProtoSourceSet { + return extensions.getByType(ProtoSourceSet::class.java) + } + +public fun SourceSet.proto(action: Action) { + extensions.configure(ProtoSourceSet::class.java, action::execute) +} + +public val NamedDomainObjectProvider.proto: Provider + get() { + return map { it.proto } + } + +public fun NamedDomainObjectProvider.proto(action: Action) { + configure { + proto(action) } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocExtension.kt index b1ed62e3e..023e55e78 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocExtension.kt @@ -6,6 +6,7 @@ package kotlinx.rpc.protoc import kotlinx.rpc.buf.BufExtension import org.gradle.api.Action +import org.gradle.api.NamedDomainObjectContainer /** * Configuration for the Protoc capabilities. @@ -17,7 +18,7 @@ import org.gradle.api.Action * } * ``` */ -public interface ProtocExtension { +public sealed interface ProtocExtension { /** * Configuration for the Buf build tool. */ @@ -27,4 +28,14 @@ public interface ProtocExtension { * Configures the Buf build tool. */ public fun buf(action: Action) + + /** + * Container of protoc plugins. + */ + public val plugins: NamedDomainObjectContainer + + /** + * Configures the protoc plugins. + */ + public fun plugins(action: Action>) } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt index c5f0c7774..75036b673 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt @@ -50,9 +50,9 @@ public fun NamedDomainObjectContainer.grpcKotlinMultiplatform(acti /** * Access to a specific protoc plugin. */ -public open class ProtocPlugin( +public open class ProtocPlugin internal constructor( public val name: String, - private val project: Project, + internal val project: Project, ) { /** * Whether the plugin generates Java code. @@ -275,4 +275,19 @@ public open class ProtocPlugin( public val locator: Property = project.objects.property() } } + + // todo check if coping works + internal fun copy(): ProtocPlugin { + return ProtocPlugin(name, project) + .also { + it.isJava.set(isJava) + it.options.set(options) + it.artifact.set(artifact) + it.strategy.set(strategy) + it.includeImports.set(includeImports) + it.includeWkt.set(includeWkt) + it.types.set(types) + it.excludeTypes.set(excludeTypes) + } + } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/consts.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/consts.kt index 5448e8156..35d8435a3 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/consts.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/consts.kt @@ -16,6 +16,11 @@ public const val PROTO_GROUP: String = "proto" */ public const val PROTO_SOURCE_SETS: String = "protoSourceSets" +/** + * Name of the extension that is created for [org.gradle.api.tasks.SourceSet] instances. + */ +public const val PROTO_SOURCE_SET_EXTENSION_NAME: String = "proto" + /** * Name of the default source directory set for proto files in [PROTO_SOURCE_SETS]. */ diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt index dce7f0305..b6377254a 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt @@ -6,6 +6,7 @@ package kotlinx.rpc.util import org.gradle.api.Action import org.gradle.api.Project +import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.the import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -13,14 +14,22 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension private const val KOTLIN_MULTIPLATFORM_PLUGIN_ID = "org.jetbrains.kotlin.multiplatform" private const val KOTLIN_JVM_PLUGIN_ID = "org.jetbrains.kotlin.jvm" -internal fun Project.withKotlinJvmExtension(action: Action) { +internal fun Project.withLazyKotlinJvmExtension(action: Action) { plugins.withId(KOTLIN_JVM_PLUGIN_ID) { the().apply { action.execute(this) } } } -internal fun Project.withKotlinKmpExtension(action: Action) { +internal fun Project.withLazyKotlinKmpExtension(action: Action) { plugins.withId(KOTLIN_MULTIPLATFORM_PLUGIN_ID) { the().apply { action.execute(this) } } } + +internal val Project.kotlinJvmExtensionOrNull: KotlinJvmProjectExtension? get() { + return extensions.findByType() +} + +internal val Project.kotlinKmpExtensionOrNull: KotlinMultiplatformExtension? get() { + return extensions.findByType() +} diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt index 4103cfda5..c58340b97 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt @@ -17,7 +17,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { @Test fun `Minimal gRPC Configuration`() = runGrpcTest { - val result = runGradle(bufGenerateMain) + val result = runGradle(bufGenerateCommonMain) result.assertMainTaskExecuted( protoFiles = listOf( @@ -32,20 +32,20 @@ class GrpcJvmProjectTest : GrpcBaseTest() { @Test fun `No gRPC`() = runGrpcTest { - runNonExistentTask(bufGenerateMain) - runNonExistentTask(bufGenerateTest) - runNonExistentTask(processMainProtoFiles) - runNonExistentTask(processTestProtoFiles) - runNonExistentTask(processTestImportProtoFiles) - runNonExistentTask(generateBufYamlMain) - runNonExistentTask(generateBufYamlTest) - runNonExistentTask(generateBufGenYamlMain) - runNonExistentTask(generateBufGenYamlTest) + runNonExistentTask(bufGenerateCommonMain) + runNonExistentTask(bufGenerateCommonTest) + runNonExistentTask(processCommonMainProtoFiles) + runNonExistentTask(processCommonTestProtoFiles) + runNonExistentTask(processCommonTestProtoFilesImports) + runNonExistentTask(generateBufYamlCommonMain) + runNonExistentTask(generateBufYamlCommonTest) + runNonExistentTask(generateBufGenYamlCommonMain) + runNonExistentTask(generateBufGenYamlCommonTest) } @Test fun `Test-Only Sources`() = runGrpcTest { - val result = runGradle(bufGenerateTest) + val result = runGradle(bufGenerateCommonTest) result.assertTestTaskExecuted( protoFiles = listOf( @@ -62,7 +62,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { @Test fun `Main and Test Mixed Sources`() = runGrpcTest { - val mainRun = runGradle(bufGenerateMain) + val mainRun = runGradle(bufGenerateCommonMain) mainRun.assertMainTaskExecuted( protoFiles = listOf( @@ -76,7 +76,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { cleanProtoBuildDir() - val testRun = runGradle(bufGenerateTest) + val testRun = runGradle(bufGenerateCommonTest) testRun.assertTestTaskExecuted( protoFiles = listOf( @@ -97,8 +97,106 @@ class GrpcJvmProjectTest : GrpcBaseTest() { } @Test - fun `Exclude and Include in protoSourceSets`() = runGrpcTest { - runGradle(processMainProtoFiles) + fun `Java Source Sets`() = runGrpcTest { + val mainRun = runGradle(bufGenerateCommonMain) + + mainRun.assertMainTaskExecuted( + protoFiles = listOf( + Path("some.proto") + ), + generatedFiles = listOf( + Path("Some.kt"), + Path(RPC_INTERNAL, "Some.kt"), + ) + ) + + cleanProtoBuildDir() + + val testRun = runGradle(bufGenerateCommonTest) + + testRun.assertTestTaskExecuted( + protoFiles = listOf( + Path("other.proto") + ), + importProtoFiles = listOf( + Path("some.proto") + ), + generatedFiles = listOf( + Path("Other.kt"), + Path(RPC_INTERNAL, "Other.kt"), + ), + importGeneratedFiles = listOf( + Path("Some.kt"), + Path(RPC_INTERNAL, "Some.kt"), + ) + ) + } + + @Test + fun `Java and Kotlin Source Sets`() = runGrpcTest { + runGradle(processCommonMainProtoFiles) + runGradle(generateBufGenYamlCommonMain) + + assertWorkspaceProtoFilesCopied( + mainSourceSet, + Path("some.proto"), + ) + + assertBufGenYaml( + sourceSet = mainSourceSet, + content = """ +version: v2 +clean: true +plugins: + - local: some + out: myPlugin + opt: + - explicitApiModeEnabled=true + - local: some2 + out: myPlugin2 + opt: + - explicitApiModeEnabled=true + - local: [protoc-gen-kotlin-multiplatform] + out: kotlin-multiplatform + opt: + - debugOutput=protoc-gen-kotlin-multiplatform.log + - generateComments=true + - generateFileLevelComments=true + - indentSize=4 + - explicitApiModeEnabled=true + - local: [protoc-gen-grpc-kotlin-multiplatform] + out: grpc-kotlin-multiplatform + opt: + - debugOutput=protoc-gen-grpc-kotlin-multiplatform.log + - generateComments=true + - generateFileLevelComments=true + - indentSize=4 + - explicitApiModeEnabled=true +inputs: + - directory: proto + """.trimIndent() + ) + + cleanProtoBuildDir() + + runGradle(processCommonTestProtoFiles) + runGradle(processCommonTestProtoFilesImports) + runGradle(generateBufGenYamlCommonTest) + + assertWorkspaceProtoFilesCopied( + testSourceSet, + Path("other.proto"), + ) + + assertWorkspaceImportProtoFilesCopied( + testSourceSet, + Path("some.proto"), + ) + } + + @Test + fun `Exclude and Include in proto sourceSets`() = runGrpcTest { + runGradle(processCommonMainProtoFiles) assertWorkspaceProtoFilesCopied( mainSourceSet, @@ -106,7 +204,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { Path("ok", "ok.proto"), ) - runGradle(processTestProtoFiles) + runGradle(processCommonTestProtoFiles) assertWorkspaceProtoFilesCopied( testSourceSet, @@ -116,7 +214,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { Path("include", "yes2.proto"), ) - runGradle(processTestImportProtoFiles) + runGradle(processCommonTestProtoFilesImports) assertWorkspaceImportProtoFilesCopied( testSourceSet, @@ -127,7 +225,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { @Test fun `Can Add Custom Protoc Plugins`() = runGrpcTest { - runGradle(generateBufGenYamlMain) + runGradle(generateBufGenYamlCommonMain) assertBufGenYaml( sourceSet = mainSourceSet, @@ -135,21 +233,28 @@ class GrpcJvmProjectTest : GrpcBaseTest() { version: v2 clean: true plugins: - - local: [protoc-gen-grpc-kotlin-multiplatform] - out: grpc-kotlin-multiplatform - opt: - - debugOutput=protoc-gen-grpc-kotlin-multiplatform.log - - explicitApiModeEnabled=true - local: [protoc-gen-kotlin-multiplatform] out: kotlin-multiplatform opt: - debugOutput=protoc-gen-kotlin-multiplatform.log + - generateComments=true + - generateFileLevelComments=true + - indentSize=4 + - explicitApiModeEnabled=true + - local: [protoc-gen-grpc-kotlin-multiplatform] + out: grpc-kotlin-multiplatform + opt: + - debugOutput=protoc-gen-grpc-kotlin-multiplatform.log + - generateComments=true + - generateFileLevelComments=true + - indentSize=4 - explicitApiModeEnabled=true - local: [path, to, protoc-gen-myplugin.exe] out: myPlugin opt: - hello=world - foo=bar + - explicitApiModeEnabled=true strategy: all include_imports: true include_wkt: false @@ -159,6 +264,8 @@ plugins: - my.type.Nope - remote: my.remote.plugin out: myRemotePlugin + opt: + - explicitApiModeEnabled=true inputs: - directory: proto """.trimIndent() @@ -167,7 +274,7 @@ inputs: @Test fun `Custom Protoc Plugins Must Declare an Artifact`() = runGrpcTest { - val result = runGradleToFail(generateBufGenYamlMain) + val result = runGradleToFail(generateBufGenYamlCommonMain) assert(result.output.contains("Artifact is not specified for protoc plugin myPlugin")) } @@ -177,7 +284,7 @@ inputs: @Test fun `Custom Buf CLI Flags`() = runGrpcTest { - val result = runGradleToFail(bufGenerateMain) + val result = runGradleToFail(bufGenerateCommonMain) assert(result.output.contains("could not read file: open some.buf.yaml: no such file or directory")) { "Expected failure due to missing some.buf.yaml file" } @@ -190,105 +297,105 @@ inputs: @Test fun `Skip Generation When No Proto Files`() = runGrpcTest { - val result = runGradle(bufGenerateMain) + val result = runGradle(bufGenerateCommonMain) - assertEquals(TaskOutcome.SKIPPED, result.protoTaskOutcome(bufGenerateMain)) + assertEquals(TaskOutcome.SKIPPED, result.protoTaskOutcome(bufGenerateCommonMain)) } @Test fun `Proto Tasks Are Cached Properly`() = runGrpcTest { - val firstRunMain = runGradle(bufGenerateMain) + val firstRunMain = runGradle(bufGenerateCommonMain) - assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, firstRunMain.protoTaskOutcome(processCommonMainProtoFiles)) - val secondRunMain = runGradle(bufGenerateMain) + val secondRunMain = runGradle(bufGenerateCommonMain) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunMain.protoTaskOutcome(processCommonMainProtoFiles)) cleanProtoBuildDir() - val thirdRunMain = runGradle(bufGenerateMain) + val thirdRunMain = runGradle(bufGenerateCommonMain) - assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, thirdRunMain.protoTaskOutcome(processCommonMainProtoFiles)) mainProtoFileSources .resolve("some.proto") .replace("content = 1", "content = 2") - val fourthRunMain = runGradle(bufGenerateMain) + val fourthRunMain = runGradle(bufGenerateCommonMain) - assertEquals(TaskOutcome.SUCCESS, fourthRunMain.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.UP_TO_DATE, fourthRunMain.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, fourthRunMain.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.SUCCESS, fourthRunMain.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, fourthRunMain.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, fourthRunMain.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, fourthRunMain.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, fourthRunMain.protoTaskOutcome(processCommonMainProtoFiles)) - val firstRunTest = runGradle(bufGenerateTest) + val firstRunTest = runGradle(bufGenerateCommonTest) - assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(bufGenerateTest)) - assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(generateBufYamlTest)) - assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(generateBufGenYamlTest)) - assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(processTestProtoFiles)) - assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(processTestImportProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(bufGenerateCommonTest)) + assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(generateBufYamlCommonTest)) + assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(generateBufGenYamlCommonTest)) + assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(processCommonTestProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, firstRunTest.protoTaskOutcome(processCommonTestProtoFilesImports)) - assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, firstRunTest.protoTaskOutcome(processCommonMainProtoFiles)) - val secondRunTest = runGradle(bufGenerateTest) + val secondRunTest = runGradle(bufGenerateCommonTest) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(bufGenerateTest)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufYamlTest)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufGenYamlTest)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(processTestProtoFiles)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(processTestImportProtoFiles)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(bufGenerateCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufYamlCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufGenYamlCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(processCommonTestProtoFiles)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(processCommonTestProtoFilesImports)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, secondRunTest.protoTaskOutcome(processCommonMainProtoFiles)) testProtoFileSources .resolve("other.proto") .replace("content = 1", "content = 2") - val thirdRunTest = runGradle(bufGenerateTest) + val thirdRunTest = runGradle(bufGenerateCommonTest) - assertEquals(TaskOutcome.SUCCESS, thirdRunTest.protoTaskOutcome(bufGenerateTest)) - assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufYamlTest)) - assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufGenYamlTest)) - assertEquals(TaskOutcome.SUCCESS, thirdRunTest.protoTaskOutcome(processTestProtoFiles)) - assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(processTestImportProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, thirdRunTest.protoTaskOutcome(bufGenerateCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufYamlCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufGenYamlCommonTest)) + assertEquals(TaskOutcome.SUCCESS, thirdRunTest.protoTaskOutcome(processCommonTestProtoFiles)) + assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(processCommonTestProtoFilesImports)) - assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, thirdRunTest.protoTaskOutcome(processCommonMainProtoFiles)) mainProtoFileSources .resolve("some.proto") .replace("content = 2", "content = 3") - val fourthRunTest = runGradle(bufGenerateTest) + val fourthRunTest = runGradle(bufGenerateCommonTest) - assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(bufGenerateTest)) - assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufYamlTest)) - assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufGenYamlTest)) - assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(processTestProtoFiles)) - assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(processTestImportProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(bufGenerateCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufYamlCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufGenYamlCommonTest)) + assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(processCommonTestProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(processCommonTestProtoFilesImports)) - assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufGenYamlMain)) - assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(processMainProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufGenYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(processCommonMainProtoFiles)) } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt index bd0dafedd..865d20576 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt @@ -15,7 +15,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { @Test fun `Minimal gRPC Configuration`() = runGrpcTest { - val result = runGradle(bufGenerateMain) + val result = runGradle(bufGenerateCommonMain) result.assertMainTaskExecuted( protoFiles = listOf( @@ -30,20 +30,20 @@ class GrpcKmpProjectTest : GrpcBaseTest() { @Test fun `No gRPC`() = runGrpcTest { - runNonExistentTask(bufGenerateMain) - runNonExistentTask(bufGenerateTest) - runNonExistentTask(processMainProtoFiles) - runNonExistentTask(processTestProtoFiles) - runNonExistentTask(processTestImportProtoFiles) - runNonExistentTask(generateBufYamlMain) - runNonExistentTask(generateBufYamlTest) - runNonExistentTask(generateBufGenYamlMain) - runNonExistentTask(generateBufGenYamlTest) + runNonExistentTask(bufGenerateCommonMain) + runNonExistentTask(bufGenerateCommonTest) + runNonExistentTask(processCommonMainProtoFiles) + runNonExistentTask(processCommonTestProtoFiles) + runNonExistentTask(processCommonTestProtoFilesImports) + runNonExistentTask(generateBufYamlCommonMain) + runNonExistentTask(generateBufYamlCommonTest) + runNonExistentTask(generateBufGenYamlCommonMain) + runNonExistentTask(generateBufGenYamlCommonTest) } @Test fun `Test-Only Sources`() = runGrpcTest { - val result = runGradle(bufGenerateTest) + val result = runGradle(bufGenerateCommonTest) result.assertTestTaskExecuted( protoFiles = listOf( @@ -60,7 +60,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { @Test fun `Main and Test Mixed Sources`() = runGrpcTest { - val mainRun = runGradle(bufGenerateMain) + val mainRun = runGradle(bufGenerateCommonMain) mainRun.assertMainTaskExecuted( protoFiles = listOf( @@ -74,7 +74,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { cleanProtoBuildDir() - val testRun = runGradle(bufGenerateTest) + val testRun = runGradle(bufGenerateCommonTest) testRun.assertTestTaskExecuted( protoFiles = listOf( @@ -96,20 +96,20 @@ class GrpcKmpProjectTest : GrpcBaseTest() { @Test fun `No JVM Targets`() = runGrpcTest { - runNonExistentTask(bufGenerateMain) - runNonExistentTask(bufGenerateTest) - runNonExistentTask(processMainProtoFiles) - runNonExistentTask(processTestProtoFiles) - runNonExistentTask(processTestImportProtoFiles) - runNonExistentTask(generateBufYamlMain) - runNonExistentTask(generateBufYamlTest) - runNonExistentTask(generateBufGenYamlMain) - runNonExistentTask(generateBufGenYamlTest) + runNonExistentTask(bufGenerateCommonMain) + runNonExistentTask(bufGenerateCommonTest) + runNonExistentTask(processCommonMainProtoFiles) + runNonExistentTask(processCommonTestProtoFiles) + runNonExistentTask(processCommonTestProtoFilesImports) + runNonExistentTask(generateBufYamlCommonMain) + runNonExistentTask(generateBufYamlCommonTest) + runNonExistentTask(generateBufGenYamlCommonMain) + runNonExistentTask(generateBufGenYamlCommonTest) } @Test fun `With Other Targets`() = runGrpcTest { - val result = runGradle(bufGenerateMain) + val result = runGradle(bufGenerateCommonMain) result.assertMainTaskExecuted( protoFiles = listOf( diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt index 89ca6c01a..176bf46f4 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt @@ -44,7 +44,7 @@ abstract class GrpcBaseTest : BaseTest() { files.forEach { file -> assert(dir.resolve(file).exists()) { - "File 'file://${file.absolutePathString()}' in '$dir' does not exist" + "File '${file}' in '$dir' does not exist" } } } @@ -54,7 +54,7 @@ abstract class GrpcBaseTest : BaseTest() { files.forEach { file -> assert(!dir.resolve(file).exists()) { - "File 'file://${file.absolutePathString()}' in '$dir' should not exist" + "File '${file}' in '$dir' should not exist" } } } @@ -65,7 +65,7 @@ abstract class GrpcBaseTest : BaseTest() { val included = files.map { file -> val resolved = protoSources.resolve(file) assert(resolved.exists()) { - "File 'file://${file.absolutePathString()}' in '$protoSources' does not exist" + "File '${file}' in '$protoSources' does not exist" } resolved.relativeTo(protoSources).pathString }.toSet() @@ -73,7 +73,7 @@ abstract class GrpcBaseTest : BaseTest() { protoSources.walk().forEach { val pathString = it.relativeTo(protoSources).pathString if (it.isRegularFile() && it.extension == "proto" && pathString !in included) { - fail("File 'file://${it.absolutePathString()}' in '$protoSources' is not expected") + fail("File '${it}' in '$protoSources' is not expected") } } } @@ -123,16 +123,16 @@ abstract class GrpcBaseTest : BaseTest() { protoFiles: List, generatedFiles: List, ) { - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processMainProtoFiles)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufGenYamlMain)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processCommonMainProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufGenYamlCommonMain)) - assertProtoTaskNotExecuted(bufGenerateTest) - assertProtoTaskNotExecuted(processTestProtoFiles) - assertProtoTaskNotExecuted(processTestImportProtoFiles) - assertProtoTaskNotExecuted(generateBufYamlTest) - assertProtoTaskNotExecuted(generateBufGenYamlTest) + assertProtoTaskNotExecuted(bufGenerateCommonTest) + assertProtoTaskNotExecuted(processCommonTestProtoFiles) + assertProtoTaskNotExecuted(processCommonTestProtoFilesImports) + assertProtoTaskNotExecuted(generateBufYamlCommonTest) + assertProtoTaskNotExecuted(generateBufGenYamlCommonTest) assertSourceCodeGenerated(mainSourceSet, *generatedFiles.toTypedArray()) assertSourceCodeNotGenerated(testSourceSet, *generatedFiles.toTypedArray()) @@ -150,11 +150,11 @@ abstract class GrpcBaseTest : BaseTest() { generatedFiles: List, importGeneratedFiles: List, ) { - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(bufGenerateTest)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processTestProtoFiles)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processTestImportProtoFiles)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufYamlTest)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufGenYamlTest)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(bufGenerateCommonTest)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processCommonTestProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processCommonTestProtoFilesImports)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufYamlCommonTest)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufGenYamlCommonTest)) val mainGenerateOutcome = if (importProtoFiles.isEmpty()) { TaskOutcome.SKIPPED @@ -162,10 +162,10 @@ abstract class GrpcBaseTest : BaseTest() { TaskOutcome.SUCCESS } - assertEquals(mainGenerateOutcome, protoTaskOutcome(bufGenerateMain)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processMainProtoFiles)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufYamlMain)) - assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufGenYamlMain)) + assertEquals(mainGenerateOutcome, protoTaskOutcome(bufGenerateCommonMain)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processCommonMainProtoFiles)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufYamlCommonMain)) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufGenYamlCommonMain)) assertSourceCodeGenerated(testSourceSet, *generatedFiles.toTypedArray()) assertSourceCodeNotGenerated(mainSourceSet, *generatedFiles.toTypedArray()) @@ -182,20 +182,20 @@ abstract class GrpcBaseTest : BaseTest() { val mainSourceSet = if (isKmp) "${KMP_SOURCE_SET}Main" else "main" val testSourceSet = if (isKmp) "${KMP_SOURCE_SET}Test" else "test" - val bufGenerateMain = if (isKmp) "bufGenerate${KMP_SOURCE_SET_CAPITAL}Main" else "bufGenerateMain" - val bufGenerateTest = if (isKmp) "bufGenerate${KMP_SOURCE_SET_CAPITAL}Test" else "bufGenerateTest" - val processMainProtoFiles = - if (isKmp) "process${KMP_SOURCE_SET_CAPITAL}MainProtoFiles" else "processMainProtoFiles" - val processTestProtoFiles = - if (isKmp) "process${KMP_SOURCE_SET_CAPITAL}TestProtoFiles" else "processTestProtoFiles" - val processTestImportProtoFiles = - if (isKmp) "process${KMP_SOURCE_SET_CAPITAL}TestImportProtoFiles" else "processTestImportProtoFiles" - val generateBufYamlMain = if (isKmp) "generateBufYaml${KMP_SOURCE_SET_CAPITAL}Main" else "generateBufYamlMain" - val generateBufYamlTest = if (isKmp) "generateBufYaml${KMP_SOURCE_SET_CAPITAL}Test" else "generateBufYamlTest" - val generateBufGenYamlMain = - if (isKmp) "generateBufGenYaml${KMP_SOURCE_SET_CAPITAL}Main" else "generateBufGenYamlMain" - val generateBufGenYamlTest = - if (isKmp) "generateBufGenYaml${KMP_SOURCE_SET_CAPITAL}Test" else "generateBufGenYamlTest" + val bufGenerateCommonMain = if (isKmp) "bufGenerate${KMP_COMMON_SOURCE_SET_CAPITAL}Main" else "bufGenerateMain" + val bufGenerateCommonTest = if (isKmp) "bufGenerate${KMP_COMMON_SOURCE_SET_CAPITAL}Test" else "bufGenerateTest" + val processCommonMainProtoFiles = + if (isKmp) "process${KMP_COMMON_SOURCE_SET_CAPITAL}MainProtoFiles" else "processMainProtoFiles" + val processCommonTestProtoFiles = + if (isKmp) "process${KMP_COMMON_SOURCE_SET_CAPITAL}TestProtoFiles" else "processTestProtoFiles" + val processCommonTestProtoFilesImports = + if (isKmp) "process${KMP_COMMON_SOURCE_SET_CAPITAL}TestProtoFilesImports" else "processTestProtoFilesImports" + val generateBufYamlCommonMain = if (isKmp) "generateBufYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Main" else "generateBufYamlMain" + val generateBufYamlCommonTest = if (isKmp) "generateBufYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Test" else "generateBufYamlTest" + val generateBufGenYamlCommonMain = + if (isKmp) "generateBufGenYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Main" else "generateBufGenYamlMain" + val generateBufGenYamlCommonTest = + if (isKmp) "generateBufGenYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Test" else "generateBufGenYamlTest" val protoBuildDir: Path by lazy { projectDir @@ -213,12 +213,12 @@ abstract class GrpcBaseTest : BaseTest() { .resolve("generated") } - val mainProtoFileSources = projectDir + val mainProtoFileSources: Path = projectDir .resolve("src") .resolve(mainSourceSet) .resolve("proto") - val testProtoFileSources = projectDir + val testProtoFileSources: Path = projectDir .resolve("src") .resolve(testSourceSet) .resolve("proto") @@ -226,7 +226,7 @@ abstract class GrpcBaseTest : BaseTest() { companion object { private const val KMP_SOURCE_SET = "common" - private val KMP_SOURCE_SET_CAPITAL = KMP_SOURCE_SET.replaceFirstChar(Char::uppercaseChar) + private val KMP_COMMON_SOURCE_SET_CAPITAL = KMP_SOURCE_SET.replaceFirstChar(Char::uppercaseChar) private const val KOTLIN_MULTIPLATFORM_DIR = "kotlin-multiplatform" const val RPC_INTERNAL = "_rpc_internal" } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts index e6e1a3eac..3e354eeca 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts @@ -11,11 +11,7 @@ plugins { } rpc { - protoc() -} - -protoSourceSets { - main { + protoc { plugins { create("myPlugin") { local { @@ -39,3 +35,10 @@ protoSourceSets { } } } + +kotlin.sourceSets { + main.proto { + plugin { getByName("myPlugin") } + plugin { getByName("myRemotePlugin") } + } +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts index 44c4b5591..7cd17a0a8 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts @@ -10,12 +10,12 @@ plugins { id("org.jetbrains.kotlinx.rpc.plugin") } -rpc { - protoc() +kotlin.sourceSets.main.proto { + plugin { getByName("myPlugin") } } -protoSourceSets { - main { +rpc { + protoc { plugins { create("myPlugin") {} } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/build.gradle.kts similarity index 91% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/build.gradle.kts rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/build.gradle.kts index 60d6d646d..9cb883293 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/build.gradle.kts @@ -3,13 +3,14 @@ */ import org.gradle.kotlin.dsl.version +import kotlinx.rpc.protoc.* plugins { kotlin("jvm") version "" id("org.jetbrains.kotlinx.rpc.plugin") } -protoSourceSets { +kotlin.sourceSets.apply { main { proto { exclude("exclude/**") diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/exclude.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/exclude.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/exclude.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/exclude.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/exclude/no1.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/exclude/no1.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/exclude/no1.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/exclude/no1.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/exclude/no2.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/exclude/no2.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/exclude/no2.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/exclude/no2.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/ok/ok.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/ok/ok.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/ok/ok.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/ok/ok.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/some.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/some.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/main/proto/some.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/main/proto/some.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/include.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/include.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/include.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/include.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/include/yes1.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/include/yes1.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/include/yes1.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/include/yes1.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/include/yes2.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/include/yes2.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/include/yes2.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/include/yes2.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/other.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/other.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/other.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/other.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/some/package/hello/world/file.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/some/package/hello/world/file.proto similarity index 100% rename from gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/src/test/proto/some/package/hello/world/file.proto rename to gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_proto_sourcesets/src/test/proto/some/package/hello/world/file.proto diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/build.gradle.kts new file mode 100644 index 000000000..a0d0a518b --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/build.gradle.kts @@ -0,0 +1,52 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.kotlin.dsl.version +import kotlinx.rpc.protoc.* + +plugins { + kotlin("jvm") version "" + id("org.jetbrains.kotlinx.rpc.plugin") +} + +sourceSets { + main { + proto { + plugin { getByName("myPlugin") } + exclude("no.proto") + } + } + + test { + proto { + exclude("no2.proto") + } + } +} + +kotlin { + sourceSets.main { + proto { + plugin { getByName("myPlugin2") } + } + } +} + +rpc { + protoc { + plugins { + create("myPlugin") { + local { + executor("some") + } + } + + create("myPlugin2") { + local { + executor("some2") + } + } + } + } +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/main/proto/no.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/main/proto/no.proto new file mode 100644 index 000000000..e69de29bb diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/main/proto/some.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/main/proto/some.proto new file mode 100644 index 000000000..9ce4a4156 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/main/proto/some.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Some { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/test/proto/no2.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/test/proto/no2.proto new file mode 100644 index 000000000..e69de29bb diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/test/proto/other.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/test/proto/other.proto new file mode 100644 index 000000000..7ad86c779 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_and_kotlin_source_sets/src/test/proto/other.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Other { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/build.gradle.kts new file mode 100644 index 000000000..8b007f1fb --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.kotlin.dsl.version +import kotlinx.rpc.protoc.proto + +plugins { + kotlin("jvm") version "" + id("org.jetbrains.kotlinx.rpc.plugin") +} + +sourceSets { + main { + proto { + exclude("no.proto") + } + } + + test { + proto { + exclude("no2.proto") + } + } +} + +rpc { + protoc() +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/main/proto/no.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/main/proto/no.proto new file mode 100644 index 000000000..e69de29bb diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/main/proto/some.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/main/proto/some.proto new file mode 100644 index 000000000..9ce4a4156 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/main/proto/some.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Some { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/test/proto/no2.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/test/proto/no2.proto new file mode 100644 index 000000000..e69de29bb diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/test/proto/other.proto b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/test/proto/other.proto new file mode 100644 index 000000000..7ad86c779 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/java_source_sets/src/test/proto/other.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Other { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/no_grpc/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/no_grpc/build.gradle.kts index 85727fd2f..cc6194819 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/no_grpc/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/no_grpc/build.gradle.kts @@ -8,9 +8,3 @@ plugins { kotlin("jvm") version "" id("org.jetbrains.kotlinx.rpc.plugin") } - -// should nonetheless be available -protoSourceSets { - main {} - test {} -} diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts index 9d75c4634..06f29653b 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts @@ -4,17 +4,16 @@ import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.include import org.gradle.kotlin.dsl.version +import kotlinx.rpc.protoc.* plugins { kotlin("jvm") version "" id("org.jetbrains.kotlinx.rpc.plugin") } -protoSourceSets { - main { - proto { - exclude("some.proto") - } +kotlin.sourceSets.main { + proto { + exclude("some.proto") } } diff --git a/protobuf/protobuf-core/build.gradle.kts b/protobuf/protobuf-core/build.gradle.kts index 500d37acb..8623c62f8 100644 --- a/protobuf/protobuf-core/build.gradle.kts +++ b/protobuf/protobuf-core/build.gradle.kts @@ -7,6 +7,7 @@ import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.internal.InternalRpcApi import kotlinx.rpc.internal.configureLocalProtocGenDevelopmentDependency +import kotlinx.rpc.protoc.proto import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import util.configureCLibCInterop @@ -31,6 +32,10 @@ kotlin { api(libs.kotlinx.io.core) } + + proto { + exclude("exclude/**") + } } commonTest { @@ -64,17 +69,11 @@ kotlin { } } -protoSourceSets { - commonTest { - proto { - exclude("exclude/**") - } - } -} - rpc { - protoc.buf.generate.comments { - includeFileLevelComments = false + protoc { + buf.generate.comments { + includeFileLevelComments = false + } } } diff --git a/tests/protobuf-conformance/build.gradle.kts b/tests/protobuf-conformance/build.gradle.kts index 265437f26..c8e4c6f80 100644 --- a/tests/protobuf-conformance/build.gradle.kts +++ b/tests/protobuf-conformance/build.gradle.kts @@ -37,8 +37,10 @@ setupProtobufConformanceResources() configureLocalProtocGenDevelopmentDependency("Main") rpc { - protoc.buf.generate.comments { - includeFileLevelComments = false + protoc { + buf.generate.comments { + includeFileLevelComments = false + } } } From 0c1f294269ed8c1e9fb65c92b5ce10c82c2737c2 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 19 Nov 2025 21:23:47 +0100 Subject: [PATCH 2/9] Added testing with multiple Gradle versions --- gradle-plugin/build.gradle.kts | 6 +- .../kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt | 26 +++---- .../kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt | 14 ++-- .../test/kotlin/kotlinx/rpc/base/BaseTest.kt | 70 ++++++++++++++----- .../kotlin/kotlinx/rpc/base/GrpcBaseTest.kt | 28 +++++--- .../build.gradle.kts | 6 +- .../build.gradle.kts | 1 - .../no_jvm_targets/build.gradle.kts | 6 -- .../with_other_targets/build.gradle.kts | 9 +-- 9 files changed, 97 insertions(+), 69 deletions(-) diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 011f8c3ae..edce9f75d 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -5,10 +5,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import util.other.generateSource -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - plugins { `kotlin-dsl` alias(libs.plugins.conventions.jvm) @@ -22,7 +18,7 @@ version = rootProject.libs.versions.kotlinx.rpc.get() kotlin { explicitApi() - jvmToolchain(11) + jvmToolchain(17) } tasks.withType().configureEach { diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt index c58340b97..d381eb67c 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt @@ -6,16 +6,16 @@ package kotlinx.rpc import kotlinx.rpc.base.GrpcBaseTest import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.TestInstance import kotlin.io.path.Path -import kotlin.test.Test import kotlin.test.assertEquals @TestInstance(TestInstance.Lifecycle.PER_METHOD) class GrpcJvmProjectTest : GrpcBaseTest() { override val isKmp: Boolean = false - @Test + @TestFactory fun `Minimal gRPC Configuration`() = runGrpcTest { val result = runGradle(bufGenerateCommonMain) @@ -30,7 +30,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { ) } - @Test + @TestFactory fun `No gRPC`() = runGrpcTest { runNonExistentTask(bufGenerateCommonMain) runNonExistentTask(bufGenerateCommonTest) @@ -43,7 +43,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { runNonExistentTask(generateBufGenYamlCommonTest) } - @Test + @TestFactory fun `Test-Only Sources`() = runGrpcTest { val result = runGradle(bufGenerateCommonTest) @@ -60,7 +60,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { ) } - @Test + @TestFactory fun `Main and Test Mixed Sources`() = runGrpcTest { val mainRun = runGradle(bufGenerateCommonMain) @@ -96,7 +96,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { ) } - @Test + @TestFactory fun `Java Source Sets`() = runGrpcTest { val mainRun = runGradle(bufGenerateCommonMain) @@ -132,7 +132,7 @@ class GrpcJvmProjectTest : GrpcBaseTest() { ) } - @Test + @TestFactory fun `Java and Kotlin Source Sets`() = runGrpcTest { runGradle(processCommonMainProtoFiles) runGradle(generateBufGenYamlCommonMain) @@ -194,7 +194,7 @@ inputs: ) } - @Test + @TestFactory fun `Exclude and Include in proto sourceSets`() = runGrpcTest { runGradle(processCommonMainProtoFiles) @@ -223,7 +223,7 @@ inputs: ) } - @Test + @TestFactory fun `Can Add Custom Protoc Plugins`() = runGrpcTest { runGradle(generateBufGenYamlCommonMain) @@ -272,7 +272,7 @@ inputs: ) } - @Test + @TestFactory fun `Custom Protoc Plugins Must Declare an Artifact`() = runGrpcTest { val result = runGradleToFail(generateBufGenYamlCommonMain) assert(result.output.contains("Artifact is not specified for protoc plugin myPlugin")) @@ -282,7 +282,7 @@ inputs: private val cliFlagsRegex = "- Buf Arguments: \\[.*?, generate, --output, .*?, --include-imports, --include-wkt, --error-format, json, --config, some\\.buf\\.yaml, --log-format, json, --timeout, 60s]".toRegex() - @Test + @TestFactory fun `Custom Buf CLI Flags`() = runGrpcTest { val result = runGradleToFail(bufGenerateCommonMain) assert(result.output.contains("could not read file: open some.buf.yaml: no such file or directory")) { @@ -295,14 +295,14 @@ inputs: } } - @Test + @TestFactory fun `Skip Generation When No Proto Files`() = runGrpcTest { val result = runGradle(bufGenerateCommonMain) assertEquals(TaskOutcome.SKIPPED, result.protoTaskOutcome(bufGenerateCommonMain)) } - @Test + @TestFactory fun `Proto Tasks Are Cached Properly`() = runGrpcTest { val firstRunMain = runGradle(bufGenerateCommonMain) diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt index 865d20576..b5a477ec1 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt @@ -5,15 +5,15 @@ package kotlinx.rpc import kotlinx.rpc.base.GrpcBaseTest +import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.TestInstance import kotlin.io.path.Path -import kotlin.test.Test @TestInstance(TestInstance.Lifecycle.PER_METHOD) class GrpcKmpProjectTest : GrpcBaseTest() { override val isKmp: Boolean = true - @Test + @TestFactory fun `Minimal gRPC Configuration`() = runGrpcTest { val result = runGradle(bufGenerateCommonMain) @@ -28,7 +28,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { ) } - @Test + @TestFactory fun `No gRPC`() = runGrpcTest { runNonExistentTask(bufGenerateCommonMain) runNonExistentTask(bufGenerateCommonTest) @@ -41,7 +41,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { runNonExistentTask(generateBufGenYamlCommonTest) } - @Test + @TestFactory fun `Test-Only Sources`() = runGrpcTest { val result = runGradle(bufGenerateCommonTest) @@ -58,7 +58,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { ) } - @Test + @TestFactory fun `Main and Test Mixed Sources`() = runGrpcTest { val mainRun = runGradle(bufGenerateCommonMain) @@ -94,7 +94,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { ) } - @Test + @TestFactory fun `No JVM Targets`() = runGrpcTest { runNonExistentTask(bufGenerateCommonMain) runNonExistentTask(bufGenerateCommonTest) @@ -107,7 +107,7 @@ class GrpcKmpProjectTest : GrpcBaseTest() { runNonExistentTask(generateBufGenYamlCommonTest) } - @Test + @TestFactory fun `With Other Targets`() = runGrpcTest { val result = runGradle(bufGenerateCommonMain) diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/BaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/BaseTest.kt index febb3e71b..f006bb3f4 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/BaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/BaseTest.kt @@ -16,35 +16,64 @@ import kotlin.io.path.copyTo import kotlin.io.path.copyToRecursively import kotlin.io.path.readText import kotlin.io.path.writeText -import kotlinx.rpc.KOTLIN_VERSION import kotlinx.rpc.BUILD_REPO +import org.junit.jupiter.api.DynamicTest +import java.util.stream.Stream import kotlin.io.path.absolute import kotlin.io.path.createDirectories import kotlin.io.path.deleteRecursively +class VersionsEnv( + val gradle: String, + val kotlin: String, +) + +private val GradleVersions = listOf( + VersionsEnv("9.2.1", "2.2.21"), + VersionsEnv("8.14.1", "2.2.0"), + VersionsEnv("8.8", "2.0.0"), +) + +internal fun BaseTest.runWithAllGradleVersions(body: (VersionsEnv) -> Unit): Stream { + return GradleVersions.stream().map { + setupTest(it) + + DynamicTest.dynamicTest("Gradle ${it.gradle}, Kotlin ${it.kotlin}") { + body(it) + } + } +} + @OptIn(ExperimentalPathApi::class) @TestInstance(TestInstance.Lifecycle.PER_METHOD) abstract class BaseTest { + private lateinit var testClassName: String + private lateinit var testMethodName: String + private lateinit var baseDir: Path private lateinit var projectDir: Path @BeforeEach protected fun setup(testInfo: TestInfo) { TEST_KIT_PATH.createDirectories() - val testClassName = testInfo.testClass.get().simpleName - val testMethodName = testInfo.testMethod.get().name + testClassName = testInfo.testClass.get().simpleName + testMethodName = testInfo.testMethod.get().name .replace(nameRegex, "_") .lowercase() - val baseDir = TEST_PROJECTS_PATH + baseDir = TEST_PROJECTS_PATH .resolve(testClassName) .resolve(testMethodName) baseDir.deleteRecursively() baseDir.createDirectories() + } + + fun setupTest(versions: VersionsEnv) { + val versioned = baseDir.resolve(versions.gradle.replace(".", "_")) - projectDir = baseDir.resolve(PROJECT_DIR) - val buildCacheDir = baseDir.resolve(BUILD_CACHE_DIR) + projectDir = versioned.resolve(PROJECT_DIR) + val buildCacheDir = versioned.resolve(BUILD_CACHE_DIR) projectDir.createDirectories() buildCacheDir.createDirectories() @@ -72,7 +101,8 @@ abstract class BaseTest { testTemplateDirectory.copyToRecursively(projectDir, followLinks = false, overwrite = true) val buildScriptFile = projectDir.resolve("build.gradle.kts") - buildScriptFile.replace("", KOTLIN_VERSION) + buildScriptFile + .replace("", versions.kotlin) println(""" Setup project '$projectName' @@ -83,12 +113,14 @@ abstract class BaseTest { private fun runGradleInternal( task: String, + versions: VersionsEnv, vararg args: String, body: GradleRunner.() -> BuildResult, ): BuildResult { val gradleRunner = GradleRunner.create() .withProjectDir(projectDir.absolute().toFile()) .withTestKitDir(TEST_KIT_PATH.absolute().toFile()) + .withGradleVersion(versions.gradle) .withPluginClasspath() .withArguments( listOfNotNull( @@ -108,10 +140,6 @@ abstract class BaseTest { return gradleRunner.body() } - protected fun runBaseTest(body: TestEnv.() -> Unit) { - runTest(TestEnv(), body) - } - protected fun runTest(testEnv: T, body: T.() -> Unit) { try { testEnv.body() @@ -127,19 +155,27 @@ abstract class BaseTest { } } - open inner class TestEnv { + open inner class TestEnv( + val versions: VersionsEnv, + ) { val projectDir: Path get() = this@BaseTest.projectDir var latestBuild: BuildResult? = null private set - fun runGradle(task: String, vararg args: String): BuildResult { - return runGradleInternal(task, *args) { + fun runGradle( + task: String, + vararg args: String, + ): BuildResult { + return runGradleInternal(task, versions, *args) { build().also { latestBuild = it } } } - fun runGradleToFail(task: String, vararg args: String): BuildResult { - return runGradleInternal(task, *args) { + fun runGradleToFail( + task: String, + vararg args: String, + ): BuildResult { + return runGradleInternal(task, versions, *args) { buildAndFail().also { latestBuild = it } } } @@ -151,7 +187,7 @@ abstract class BaseTest { } fun BuildResult.assertNoTask(name: String) { - assert(output.contains("Task '$name' not found")) { + assert(tasks.none { it.path.endsWith(":$name") }) { "Task '$name' should not be present in the project" } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt index 176bf46f4..b05a8de2e 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt @@ -7,8 +7,10 @@ package kotlinx.rpc.base import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.TaskOutcome import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.TestInstance import java.nio.file.Path +import java.util.stream.Stream import kotlin.io.path.* import kotlin.test.assertEquals import kotlin.test.fail @@ -17,11 +19,11 @@ import kotlin.test.fail abstract class GrpcBaseTest : BaseTest() { abstract val isKmp: Boolean - protected fun runGrpcTest(test: GrpcTestEnv.() -> Unit) { - runTest(GrpcTestEnv(), test) + protected fun runGrpcTest(test: GrpcTestEnv.() -> Unit): Stream = runWithAllGradleVersions { + runTest(GrpcTestEnv(it), test) } - inner class GrpcTestEnv : TestEnv() { + inner class GrpcTestEnv(versions: VersionsEnv) : TestEnv(versions) { fun BuildResult.protoTaskOutcome(name: String): TaskOutcome { return tasks.find { it.path == ":$name" }?.outcome ?: fail("Task ':$name' was not present in the build result") @@ -213,15 +215,19 @@ abstract class GrpcBaseTest : BaseTest() { .resolve("generated") } - val mainProtoFileSources: Path = projectDir - .resolve("src") - .resolve(mainSourceSet) - .resolve("proto") + val mainProtoFileSources: Path by lazy { + projectDir + .resolve("src") + .resolve(mainSourceSet) + .resolve("proto") + } - val testProtoFileSources: Path = projectDir - .resolve("src") - .resolve(testSourceSet) - .resolve("proto") + val testProtoFileSources: Path by lazy { + projectDir + .resolve("src") + .resolve(testSourceSet) + .resolve("proto") + } } companion object { diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts index 3e354eeca..e7c9e3fa6 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts @@ -21,9 +21,9 @@ rpc { options.put("hello", "world") options.put("foo", "bar") - strategy = ProtocPlugin.Strategy.All - includeImports = true - includeWkt = false + strategy.set(ProtocPlugin.Strategy.All) + includeImports.set(true) + includeWkt.set(false) types = listOf("my.type.Yay") excludeTypes = listOf("my.type.Nope") } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts index 06f29653b..810cdd8a7 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts @@ -2,7 +2,6 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.include import org.gradle.kotlin.dsl.version import kotlinx.rpc.protoc.* diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts index 0e712e9ca..7c8d7c9c6 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/no_jvm_targets/build.gradle.kts @@ -13,9 +13,3 @@ kotlin { js() macosArm64() } - -// should nonetheless be available -protoSourceSets { - commonMain {} - commonTest {} -} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts index 1a8ba4661..20bdf1041 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts @@ -11,15 +11,12 @@ plugins { kotlin { jvm() - js() + js { + nodejs() + } macosArm64() } -protoSourceSets { - commonMain {} - commonTest {} -} - rpc { protoc() } From 5be747ed99140b17e7bd4fbc367edbc324644f4b Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Thu, 20 Nov 2025 15:14:46 +0100 Subject: [PATCH 3/9] Added execution task order test for KMP --- .../kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt | 92 ++++++++++ .../kotlin/kotlinx/rpc/base/GrpcBaseTest.kt | 161 ++++++++++++++---- .../kmp_hierarchy/build.gradle.kts | 22 +++ .../src/appleMain/proto/appleMain.proto | 5 + .../src/appleTest/proto/appleTest.proto | 5 + .../src/commonMain/proto/commonMain.proto | 5 + .../src/commonTest/proto/commonTest.proto | 5 + .../src/jsMain/proto/jsMain.proto | 5 + .../src/jsTest/proto/jsTest.proto | 5 + .../src/jvmMain/proto/jvmMain.proto | 5 + .../src/jvmTest/proto/jvmTest.proto | 5 + .../macosArm64Main/proto/macosArm64Main.proto | 5 + .../macosArm64Test/proto/macosArm64Test.proto | 5 + .../src/macosMain/proto/macosMain.proto | 5 + .../src/macosTest/proto/macosTest.proto | 5 + .../src/nativeMain/proto/nativeMain.proto | 5 + .../src/nativeTest/proto/nativeTest.proto | 5 + 17 files changed, 315 insertions(+), 30 deletions(-) create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/build.gradle.kts create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleMain/proto/appleMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleTest/proto/appleTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonMain/proto/commonMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonTest/proto/commonTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsMain/proto/jsMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsTest/proto/jsTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmMain/proto/jvmMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmTest/proto/jvmTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Main/proto/macosArm64Main.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Test/proto/macosArm64Test.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosMain/proto/macosMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosTest/proto/macosTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeMain/proto/nativeMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeTest/proto/nativeTest.proto diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt index b5a477ec1..b8f86d5dc 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt @@ -121,4 +121,96 @@ class GrpcKmpProjectTest : GrpcBaseTest() { ) ) } + + @TestFactory + fun `KMP Hierarchy`() = runGrpcTest { + runKmp( + SSets.commonMain, + ) + + runKmp( + SSets.commonTest, + SSets.commonMain, + ) + + runKmp( + SSets.nativeMain, + SSets.commonMain, + ) + + runKmp( + SSets.nativeTest, + SSets.commonMain, SSets.nativeMain, + SSets.commonTest, + ) + + runKmp( + SSets.jvmMain, + SSets.commonMain, + ) + + runKmp( + SSets.jvmTest, + SSets.commonMain, SSets.jvmMain, + SSets.commonTest, + ) + + runKmp( + SSets.jsMain, + SSets.commonMain, + ) + + runKmp( + SSets.jsTest, + SSets.commonMain, SSets.jsMain, + SSets.commonTest, + ) + + runKmp( + SSets.appleMain, + SSets.commonMain, SSets.nativeMain, + ) + + runKmp( + SSets.appleTest, + SSets.commonMain, SSets.nativeMain, SSets.appleMain, + SSets.commonTest, SSets.nativeTest + ) + + runKmp( + SSets.macosMain, + SSets.commonMain, SSets.nativeMain, SSets.appleMain, + ) + + runKmp( + SSets.macosTest, + SSets.commonMain, SSets.nativeMain, SSets.appleMain, SSets.macosMain, + SSets.commonTest, SSets.nativeTest, SSets.appleTest + ) + + runKmp( + SSets.macosArm64Main, + SSets.commonMain, SSets.nativeMain, SSets.appleMain, SSets.macosMain, + ) + + runKmp( + SSets.macosArm64Test, + SSets.commonMain, SSets.nativeMain, SSets.appleMain, SSets.macosMain, SSets.macosArm64Main, + SSets.commonTest, SSets.nativeTest, SSets.appleTest, SSets.macosTest, + clean = false, + ) + } + + private fun GrpcTestEnv.runKmp( + sourceSet: SSets, + vararg imports: SSets, + clean: Boolean = true, + ) { + runGradle(bufGenerate(sourceSet)) + .assertKmpSourceSet(sourceSet, *imports) + + if (clean) { + cleanProtoBuildDir() + } + } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt index b05a8de2e..fc2893d85 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt @@ -36,8 +36,8 @@ abstract class GrpcBaseTest : BaseTest() { } } - fun assertSourceCodeGenerated(sourceSet: String, vararg files: Path) { - val dir = protoBuildDirGenerated.resolve(sourceSet).resolve(KOTLIN_MULTIPLATFORM_DIR) + fun assertSourceCodeGenerated(sourceSet: SSets, vararg files: Path) { + val dir = protoBuildDirGenerated.resolve(sourceSet.name).resolve(KOTLIN_MULTIPLATFORM_DIR) if (files.isNotEmpty()) { assert(dir.exists()) { "Directory '$dir' with generated sources does not exist" @@ -51,8 +51,8 @@ abstract class GrpcBaseTest : BaseTest() { } } - fun assertSourceCodeNotGenerated(sourceSet: String, vararg files: Path) { - val dir = protoBuildDirGenerated.resolve(sourceSet).resolve(KOTLIN_MULTIPLATFORM_DIR) + fun assertSourceCodeNotGenerated(sourceSet: SSets, vararg files: Path) { + val dir = protoBuildDirGenerated.resolve(sourceSet.name).resolve(KOTLIN_MULTIPLATFORM_DIR) files.forEach { file -> assert(!dir.resolve(file).exists()) { @@ -61,9 +61,37 @@ abstract class GrpcBaseTest : BaseTest() { } } + fun assertSourceCodeNotGeneratedExcept(sourceSet: SSets, vararg files: Path) { + val dir = protoBuildDirGenerated.resolve(sourceSet.name).resolve(KOTLIN_MULTIPLATFORM_DIR) + + fun Path.doAssert() { + listDirectoryEntries().forEach { entry -> + when { + entry.isDirectory() -> { + entry.doAssert() + } + + entry.isRegularFile() && entry.relativeTo(dir) in files -> { + // fine + } + + entry.name == ".keep" -> { + // fine + } + + else -> { + fail("File '${entry}' in '$this' should not exist") + } + } + } + } + + dir.doAssert() + } + @OptIn(ExperimentalPathApi::class) - private fun assertWorkspaceProtoFilesCopiedInternal(vararg files: Path, sourceSet: String, dir: String) { - val protoSources = protoBuildDirSourceSets.resolve(sourceSet).resolve(dir) + private fun assertWorkspaceProtoFilesCopiedInternal(vararg files: Path, sourceSet: SSets, dir: String) { + val protoSources = protoBuildDirSourceSets.resolve(sourceSet.name).resolve(dir) val included = files.map { file -> val resolved = protoSources.resolve(file) assert(resolved.exists()) { @@ -80,11 +108,11 @@ abstract class GrpcBaseTest : BaseTest() { } } - fun assertWorkspaceProtoFilesCopied(sourceSet: String, vararg files: Path) { + fun assertWorkspaceProtoFilesCopied(sourceSet: SSets, vararg files: Path) { assertWorkspaceProtoFilesCopiedInternal(*files, sourceSet = sourceSet, dir = "proto") } - fun assertWorkspaceImportProtoFilesCopied(sourceSet: String, vararg files: Path) { + fun assertWorkspaceImportProtoFilesCopied(sourceSet: SSets, vararg files: Path) { assertWorkspaceProtoFilesCopiedInternal(*files, sourceSet = sourceSet, dir = "import") } @@ -93,9 +121,9 @@ abstract class GrpcBaseTest : BaseTest() { protoBuildDir.deleteRecursively() } - fun assertBufGenYaml(sourceSet: String, @Language("Yaml") content: String) { + fun assertBufGenYaml(sourceSet: SSets, @Language("Yaml") content: String) { val file = protoBuildDirSourceSets - .resolve(sourceSet) + .resolve(sourceSet.name) .resolve("buf.gen.yaml") assert(file.exists()) { @@ -107,9 +135,11 @@ abstract class GrpcBaseTest : BaseTest() { it.contains("protoc-gen-kotlin-multiplatform") -> { it.replace(localPluginExecRegex, "[protoc-gen-kotlin-multiplatform]") } + it.contains("protoc-gen-grpc-kotlin-multiplatform") -> { it.replace(localPluginExecRegex, "[protoc-gen-grpc-kotlin-multiplatform]") } + else -> { it } @@ -182,22 +212,72 @@ abstract class GrpcBaseTest : BaseTest() { assertWorkspaceImportProtoFilesCopied(mainSourceSet) } - val mainSourceSet = if (isKmp) "${KMP_SOURCE_SET}Main" else "main" - val testSourceSet = if (isKmp) "${KMP_SOURCE_SET}Test" else "test" - val bufGenerateCommonMain = if (isKmp) "bufGenerate${KMP_COMMON_SOURCE_SET_CAPITAL}Main" else "bufGenerateMain" - val bufGenerateCommonTest = if (isKmp) "bufGenerate${KMP_COMMON_SOURCE_SET_CAPITAL}Test" else "bufGenerateTest" - val processCommonMainProtoFiles = - if (isKmp) "process${KMP_COMMON_SOURCE_SET_CAPITAL}MainProtoFiles" else "processMainProtoFiles" - val processCommonTestProtoFiles = - if (isKmp) "process${KMP_COMMON_SOURCE_SET_CAPITAL}TestProtoFiles" else "processTestProtoFiles" - val processCommonTestProtoFilesImports = - if (isKmp) "process${KMP_COMMON_SOURCE_SET_CAPITAL}TestProtoFilesImports" else "processTestProtoFilesImports" - val generateBufYamlCommonMain = if (isKmp) "generateBufYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Main" else "generateBufYamlMain" - val generateBufYamlCommonTest = if (isKmp) "generateBufYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Test" else "generateBufYamlTest" - val generateBufGenYamlCommonMain = - if (isKmp) "generateBufGenYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Main" else "generateBufGenYamlMain" - val generateBufGenYamlCommonTest = - if (isKmp) "generateBufGenYaml${KMP_COMMON_SOURCE_SET_CAPITAL}Test" else "generateBufGenYamlTest" + fun BuildResult.assertTaskExecuted( + sourceSet: SSets, + protoFiles: List, + importProtoFiles: List, + generatedFiles: List, + notExecuted: List, + ) { + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(bufGenerate(sourceSet))) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processProtoFiles(sourceSet))) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(processProtoFilesImports(sourceSet))) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufYaml(sourceSet))) + assertEquals(TaskOutcome.SUCCESS, protoTaskOutcome(generateBufGenYaml(sourceSet))) + + assertSourceCodeGenerated(sourceSet, *generatedFiles.toTypedArray()) + assertSourceCodeNotGeneratedExcept(sourceSet, *generatedFiles.toTypedArray()) + assertWorkspaceProtoFilesCopied(sourceSet, *protoFiles.toTypedArray()) + assertWorkspaceImportProtoFilesCopied(sourceSet, *importProtoFiles.toTypedArray()) + + notExecuted.forEach { + assertProtoTaskNotExecuted(bufGenerate(it)) + assertProtoTaskNotExecuted(processProtoFiles(it)) + assertProtoTaskNotExecuted(processProtoFilesImports(it)) + assertProtoTaskNotExecuted(generateBufYaml(it)) + assertProtoTaskNotExecuted(generateBufGenYaml(it)) + } + } + + fun BuildResult.assertKmpSourceSet( + sourceSet: SSets, + vararg imports: SSets, + ) { + val ktFile = "${sourceSet.capital}.kt" + val importsSet = imports.toSet() + + assertTaskExecuted( + sourceSet = sourceSet, + protoFiles = listOf(Path("${sourceSet.name}.proto")), + importProtoFiles = imports.map { + Path("${it.name}.proto") + }, + generatedFiles = listOf( + Path(ktFile), + Path(RPC_INTERNAL, ktFile), + ), + notExecuted = SSets.entries.filter { it != sourceSet && it !in importsSet }, + ) + } + + fun bufGenerate(sourceSet: SSets) = "bufGenerate${sourceSet.capital}" + fun processProtoFiles(sourceSet: SSets) = "process${sourceSet.capital}ProtoFiles" + fun processProtoFilesImports(sourceSet: SSets) = "process${sourceSet.capital}ProtoFilesImports" + fun generateBufYaml(sourceSet: SSets) = "generateBufYaml${sourceSet.capital}" + fun generateBufGenYaml(sourceSet: SSets) = "generateBufGenYaml${sourceSet.capital}" + + val mainSourceSet = if (isKmp) SSets.commonMain else SSets.main + val testSourceSet = if (isKmp) SSets.commonTest else SSets.test + + val bufGenerateCommonMain = bufGenerate(mainSourceSet) + val bufGenerateCommonTest = bufGenerate(testSourceSet) + val processCommonMainProtoFiles = processProtoFiles(mainSourceSet) + val processCommonTestProtoFiles = processProtoFiles(testSourceSet) + val processCommonTestProtoFilesImports = processProtoFilesImports(testSourceSet) + val generateBufYamlCommonMain = generateBufYaml(mainSourceSet) + val generateBufYamlCommonTest = generateBufYaml(testSourceSet) + val generateBufGenYamlCommonMain = generateBufGenYaml(mainSourceSet) + val generateBufGenYamlCommonTest = generateBufGenYaml(testSourceSet) val protoBuildDir: Path by lazy { projectDir @@ -218,21 +298,42 @@ abstract class GrpcBaseTest : BaseTest() { val mainProtoFileSources: Path by lazy { projectDir .resolve("src") - .resolve(mainSourceSet) + .resolve(mainSourceSet.name) .resolve("proto") } val testProtoFileSources: Path by lazy { projectDir .resolve("src") - .resolve(testSourceSet) + .resolve(testSourceSet.name) + .resolve("proto") + } + + fun SSets.sourceDir(): Path { + return projectDir + .resolve("src") + .resolve(name) .resolve("proto") } } + @Suppress("EnumEntryName") + enum class SSets { + main, test, + + commonMain, commonTest, + jvmMain, jvmTest, + androidMain, androidTest, + jsMain, jsTest, + nativeMain, nativeTest, + appleMain, appleTest, + macosMain, macosTest, + macosArm64Main, macosArm64Test, + } + + private val SSets.capital get() = name.replaceFirstChar { it.titlecase() } + companion object { - private const val KMP_SOURCE_SET = "common" - private val KMP_COMMON_SOURCE_SET_CAPITAL = KMP_SOURCE_SET.replaceFirstChar(Char::uppercaseChar) private const val KOTLIN_MULTIPLATFORM_DIR = "kotlin-multiplatform" const val RPC_INTERNAL = "_rpc_internal" } diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/build.gradle.kts new file mode 100644 index 000000000..20bdf1041 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.kotlin.dsl.version + +plugins { + kotlin("multiplatform") version "" + id("org.jetbrains.kotlinx.rpc.plugin") +} + +kotlin { + jvm() + js { + nodejs() + } + macosArm64() +} + +rpc { + protoc() +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleMain/proto/appleMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleMain/proto/appleMain.proto new file mode 100644 index 000000000..dd73f50fb --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleMain/proto/appleMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message AppleMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleTest/proto/appleTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleTest/proto/appleTest.proto new file mode 100644 index 000000000..b3c6b034f --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/appleTest/proto/appleTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message AppleTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonMain/proto/commonMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonMain/proto/commonMain.proto new file mode 100644 index 000000000..ef46aa298 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonMain/proto/commonMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message CommonMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonTest/proto/commonTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonTest/proto/commonTest.proto new file mode 100644 index 000000000..f2aa03a69 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/commonTest/proto/commonTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message CommonTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsMain/proto/jsMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsMain/proto/jsMain.proto new file mode 100644 index 000000000..94cedbed5 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsMain/proto/jsMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JsMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsTest/proto/jsTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsTest/proto/jsTest.proto new file mode 100644 index 000000000..9ea0eb27a --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jsTest/proto/jsTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JsTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmMain/proto/jvmMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmMain/proto/jvmMain.proto new file mode 100644 index 000000000..936ce0d1d --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmMain/proto/jvmMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JvmMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmTest/proto/jvmTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmTest/proto/jvmTest.proto new file mode 100644 index 000000000..8786bedaa --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/jvmTest/proto/jvmTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JvmTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Main/proto/macosArm64Main.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Main/proto/macosArm64Main.proto new file mode 100644 index 000000000..41c097c16 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Main/proto/macosArm64Main.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosArm64Main { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Test/proto/macosArm64Test.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Test/proto/macosArm64Test.proto new file mode 100644 index 000000000..a33f00ee7 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosArm64Test/proto/macosArm64Test.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosArm64Test { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosMain/proto/macosMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosMain/proto/macosMain.proto new file mode 100644 index 000000000..c65489499 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosMain/proto/macosMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosTest/proto/macosTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosTest/proto/macosTest.proto new file mode 100644 index 000000000..b65cd541c --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/macosTest/proto/macosTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeMain/proto/nativeMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeMain/proto/nativeMain.proto new file mode 100644 index 000000000..bbd13541d --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeMain/proto/nativeMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message NativeMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeTest/proto/nativeTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeTest/proto/nativeTest.proto new file mode 100644 index 000000000..df96a67d0 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/nativeTest/proto/nativeTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message NativeTest { + string content = 1; +} From 5e727979ab73967300a7444d15f304777f2c5e69 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Sat, 22 Nov 2025 00:52:40 +0100 Subject: [PATCH 4/9] Added caching task test for KMP --- .../kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt | 555 +++++++++++++++++- .../kotlin/kotlinx/rpc/base/GrpcBaseTest.kt | 4 + .../build.gradle.kts | 22 + .../src/appleMain/proto/appleMain.proto | 5 + .../src/appleTest/proto/appleTest.proto | 5 + .../src/commonMain/proto/commonMain.proto | 5 + .../src/commonTest/proto/commonTest.proto | 5 + .../src/jsMain/proto/jsMain.proto | 5 + .../src/jsTest/proto/jsTest.proto | 5 + .../src/jvmMain/proto/jvmMain.proto | 5 + .../src/jvmTest/proto/jvmTest.proto | 5 + .../macosArm64Main/proto/macosArm64Main.proto | 5 + .../macosArm64Test/proto/macosArm64Test.proto | 5 + .../src/macosMain/proto/macosMain.proto | 5 + .../src/macosTest/proto/macosTest.proto | 5 + .../src/nativeMain/proto/nativeMain.proto | 5 + .../src/nativeTest/proto/nativeTest.proto | 5 + 17 files changed, 634 insertions(+), 17 deletions(-) create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/build.gradle.kts create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleMain/proto/appleMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleTest/proto/appleTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonMain/proto/commonMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonTest/proto/commonTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsMain/proto/jsMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsTest/proto/jsTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmMain/proto/jvmMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmTest/proto/jvmTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Main/proto/macosArm64Main.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Test/proto/macosArm64Test.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosMain/proto/macosMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosTest/proto/macosTest.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeMain/proto/nativeMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeTest/proto/nativeTest.proto diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt index b8f86d5dc..728b0365a 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt @@ -5,9 +5,12 @@ package kotlinx.rpc import kotlinx.rpc.base.GrpcBaseTest +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.TestInstance import kotlin.io.path.Path +import kotlin.test.assertEquals @TestInstance(TestInstance.Lifecycle.PER_METHOD) class GrpcKmpProjectTest : GrpcBaseTest() { @@ -124,76 +127,76 @@ class GrpcKmpProjectTest : GrpcBaseTest() { @TestFactory fun `KMP Hierarchy`() = runGrpcTest { - runKmp( + runKmpAndCheckFiles( SSets.commonMain, ) - runKmp( + runKmpAndCheckFiles( SSets.commonTest, SSets.commonMain, ) - runKmp( + runKmpAndCheckFiles( SSets.nativeMain, SSets.commonMain, ) - runKmp( + runKmpAndCheckFiles( SSets.nativeTest, SSets.commonMain, SSets.nativeMain, SSets.commonTest, ) - runKmp( + runKmpAndCheckFiles( SSets.jvmMain, SSets.commonMain, ) - runKmp( + runKmpAndCheckFiles( SSets.jvmTest, SSets.commonMain, SSets.jvmMain, SSets.commonTest, ) - runKmp( + runKmpAndCheckFiles( SSets.jsMain, SSets.commonMain, ) - runKmp( + runKmpAndCheckFiles( SSets.jsTest, SSets.commonMain, SSets.jsMain, SSets.commonTest, ) - runKmp( + runKmpAndCheckFiles( SSets.appleMain, SSets.commonMain, SSets.nativeMain, ) - runKmp( + runKmpAndCheckFiles( SSets.appleTest, SSets.commonMain, SSets.nativeMain, SSets.appleMain, SSets.commonTest, SSets.nativeTest ) - runKmp( + runKmpAndCheckFiles( SSets.macosMain, SSets.commonMain, SSets.nativeMain, SSets.appleMain, ) - runKmp( + runKmpAndCheckFiles( SSets.macosTest, SSets.commonMain, SSets.nativeMain, SSets.appleMain, SSets.macosMain, SSets.commonTest, SSets.nativeTest, SSets.appleTest ) - runKmp( + runKmpAndCheckFiles( SSets.macosArm64Main, SSets.commonMain, SSets.nativeMain, SSets.appleMain, SSets.macosMain, ) - runKmp( + runKmpAndCheckFiles( SSets.macosArm64Test, SSets.commonMain, SSets.nativeMain, SSets.appleMain, SSets.macosMain, SSets.macosArm64Main, SSets.commonTest, SSets.nativeTest, SSets.appleTest, SSets.macosTest, @@ -201,16 +204,534 @@ class GrpcKmpProjectTest : GrpcBaseTest() { ) } - private fun GrpcTestEnv.runKmp( + @TestFactory + fun `Proto Tasks Are Cached Properly`() = runGrpcTest { + val firstRunCommonMain = runKmp(SSets.commonMain) + + assertOutcomes( + result = firstRunCommonMain, + sourceSet = SSets.commonMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + // didn't run + assertOutcomes(firstRunCommonMain, SSets.commonTest) + assertOutcomes(firstRunCommonMain, SSets.nativeMain) + assertOutcomes(firstRunCommonMain, SSets.nativeTest) + assertOutcomes(firstRunCommonMain, SSets.jvmMain) + assertOutcomes(firstRunCommonMain, SSets.jsTest) + assertOutcomes(firstRunCommonMain, SSets.jsMain) + assertOutcomes(firstRunCommonMain, SSets.jsTest) + assertOutcomes(firstRunCommonMain, SSets.appleMain) + assertOutcomes(firstRunCommonMain, SSets.appleTest) + assertOutcomes(firstRunCommonMain, SSets.macosMain) + assertOutcomes(firstRunCommonMain, SSets.macosTest) + assertOutcomes(firstRunCommonMain, SSets.macosArm64Main) + assertOutcomes(firstRunCommonMain, SSets.macosArm64Test) + + val secondRunCommonMain = runKmp(SSets.commonMain) + + assertOutcomes( + result = secondRunCommonMain, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + cleanProtoBuildDir() + + val thirdRunCommonMain = runKmp(SSets.commonMain) + + assertOutcomes( + result = thirdRunCommonMain, + sourceSet = SSets.commonMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + SSets.commonMain.sourceDir() + .resolve("commonMain.proto") + .replace("content = 1", "content = 2") + + val fourthRunCommonMain = runKmp(SSets.commonMain) + + assertOutcomes( + result = fourthRunCommonMain, + sourceSet = SSets.commonMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + val firstRunMacosArm64Main = runKmp(SSets.macosArm64Main) + + assertOutcomes( + result = firstRunMacosArm64Main, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = firstRunMacosArm64Main, + sourceSet = SSets.nativeMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + assertOutcomes( + result = firstRunMacosArm64Main, + sourceSet = SSets.appleMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + assertOutcomes( + result = firstRunMacosArm64Main, + sourceSet = SSets.macosMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + assertOutcomes( + result = firstRunMacosArm64Main, + sourceSet = SSets.macosArm64Main, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + // didn't run + assertOutcomes(firstRunMacosArm64Main, SSets.nativeTest) + assertOutcomes(firstRunMacosArm64Main, SSets.jvmMain) + assertOutcomes(firstRunMacosArm64Main, SSets.jsTest) + assertOutcomes(firstRunMacosArm64Main, SSets.jsMain) + assertOutcomes(firstRunMacosArm64Main, SSets.jsTest) + assertOutcomes(firstRunMacosArm64Main, SSets.appleTest) + assertOutcomes(firstRunMacosArm64Main, SSets.macosTest) + assertOutcomes(firstRunMacosArm64Main, SSets.macosArm64Test) + + val firstRunMacosArm64Test = runKmp(SSets.macosArm64Test) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.nativeMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.appleMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.macosMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.macosArm64Main, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.nativeTest, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.appleTest, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.macosTest, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + assertOutcomes( + result = firstRunMacosArm64Test, + sourceSet = SSets.macosArm64Test, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + // didn't run + assertOutcomes(firstRunMacosArm64Test, SSets.jvmMain) + assertOutcomes(firstRunMacosArm64Test, SSets.jsTest) + assertOutcomes(firstRunMacosArm64Test, SSets.jsMain) + assertOutcomes(firstRunMacosArm64Test, SSets.jsTest) + + SSets.macosMain.sourceDir() + .resolve("macosMain.proto") + .replace("content = 1", "content = 2") + + val fifthRunCommonMain = runKmp(SSets.commonMain) + + assertOutcomes( + result = fifthRunCommonMain, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + // didn't run + assertOutcomes(fifthRunCommonMain, SSets.commonTest) + assertOutcomes(fifthRunCommonMain, SSets.nativeMain) + assertOutcomes(fifthRunCommonMain, SSets.nativeTest) + assertOutcomes(fifthRunCommonMain, SSets.jvmMain) + assertOutcomes(fifthRunCommonMain, SSets.jsTest) + assertOutcomes(fifthRunCommonMain, SSets.jsMain) + assertOutcomes(fifthRunCommonMain, SSets.jsTest) + assertOutcomes(fifthRunCommonMain, SSets.appleMain) + assertOutcomes(fifthRunCommonMain, SSets.appleTest) + assertOutcomes(fifthRunCommonMain, SSets.macosMain) + assertOutcomes(fifthRunCommonMain, SSets.macosTest) + assertOutcomes(fifthRunCommonMain, SSets.macosArm64Main) + assertOutcomes(fifthRunCommonMain, SSets.macosArm64Test) + + val secondRunMacosArm64Main = runKmp(SSets.macosArm64Main) + + assertOutcomes( + result = secondRunMacosArm64Main, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Main, + sourceSet = SSets.nativeMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Main, + sourceSet = SSets.appleMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Main, + sourceSet = SSets.macosMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Main, + sourceSet = SSets.macosArm64Main, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + // didn't run + assertOutcomes(secondRunMacosArm64Main, SSets.nativeTest) + assertOutcomes(secondRunMacosArm64Main, SSets.jvmMain) + assertOutcomes(secondRunMacosArm64Main, SSets.jsTest) + assertOutcomes(secondRunMacosArm64Main, SSets.jsMain) + assertOutcomes(secondRunMacosArm64Main, SSets.jsTest) + assertOutcomes(secondRunMacosArm64Main, SSets.appleTest) + assertOutcomes(secondRunMacosArm64Main, SSets.macosTest) + assertOutcomes(secondRunMacosArm64Main, SSets.macosArm64Test) + + val secondRunMacosArm64Test = runKmp(SSets.macosArm64Test) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.nativeMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.appleMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.macosMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.macosArm64Main, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.nativeTest, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.appleTest, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.macosTest, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + assertOutcomes( + result = secondRunMacosArm64Test, + sourceSet = SSets.macosArm64Test, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + // didn't run + assertOutcomes(secondRunMacosArm64Test, SSets.jvmMain) + assertOutcomes(secondRunMacosArm64Test, SSets.jsTest) + assertOutcomes(secondRunMacosArm64Test, SSets.jsMain) + assertOutcomes(secondRunMacosArm64Test, SSets.jsTest) + + val firstRunJvmMain = runKmp(SSets.jvmMain) + + assertOutcomes( + result = firstRunJvmMain, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = firstRunJvmMain, + sourceSet = SSets.jvmMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.SUCCESS, + bufGenYaml = TaskOutcome.SUCCESS, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.SUCCESS, + ) + + // didn't run + assertOutcomes(firstRunJvmMain, SSets.commonTest) + assertOutcomes(firstRunJvmMain, SSets.nativeMain) + assertOutcomes(firstRunJvmMain, SSets.nativeTest) + assertOutcomes(firstRunJvmMain, SSets.jsTest) + assertOutcomes(firstRunJvmMain, SSets.jsMain) + assertOutcomes(firstRunJvmMain, SSets.jsTest) + assertOutcomes(firstRunJvmMain, SSets.appleMain) + assertOutcomes(firstRunJvmMain, SSets.appleTest) + assertOutcomes(firstRunJvmMain, SSets.macosMain) + assertOutcomes(firstRunJvmMain, SSets.macosTest) + assertOutcomes(firstRunJvmMain, SSets.macosArm64Main) + assertOutcomes(firstRunJvmMain, SSets.macosArm64Test) + + SSets.jvmMain.sourceDir() + .resolve("jvmMain.proto") + .replace("content = 1", "content = 2") + + val secondRunJvmMain = runKmp(SSets.jvmMain) + + assertOutcomes( + result = secondRunJvmMain, + sourceSet = SSets.commonMain, + generate = TaskOutcome.UP_TO_DATE, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.UP_TO_DATE, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + assertOutcomes( + result = secondRunJvmMain, + sourceSet = SSets.jvmMain, + generate = TaskOutcome.SUCCESS, + bufYaml = TaskOutcome.UP_TO_DATE, + bufGenYaml = TaskOutcome.UP_TO_DATE, + protoFiles = TaskOutcome.SUCCESS, + protoFilesImports = TaskOutcome.UP_TO_DATE, + ) + + // didn't run + assertOutcomes(secondRunJvmMain, SSets.commonTest) + assertOutcomes(secondRunJvmMain, SSets.nativeMain) + assertOutcomes(secondRunJvmMain, SSets.nativeTest) + assertOutcomes(secondRunJvmMain, SSets.jsTest) + assertOutcomes(secondRunJvmMain, SSets.jsMain) + assertOutcomes(secondRunJvmMain, SSets.jsTest) + assertOutcomes(secondRunJvmMain, SSets.appleMain) + assertOutcomes(secondRunJvmMain, SSets.appleTest) + assertOutcomes(secondRunJvmMain, SSets.macosMain) + assertOutcomes(secondRunJvmMain, SSets.macosTest) + assertOutcomes(secondRunJvmMain, SSets.macosArm64Main) + assertOutcomes(secondRunJvmMain, SSets.macosArm64Test) + } + + private fun GrpcTestEnv.runKmpAndCheckFiles( sourceSet: SSets, vararg imports: SSets, clean: Boolean = true, ) { - runGradle(bufGenerate(sourceSet)) - .assertKmpSourceSet(sourceSet, *imports) + runKmp(sourceSet).assertKmpSourceSet(sourceSet, *imports) if (clean) { cleanProtoBuildDir() } } + + private fun GrpcTestEnv.runKmp(sourceSet: SSets): BuildResult { + return runGradle(bufGenerate(sourceSet)) + } + + private fun GrpcTestEnv.assertOutcomes( + result: BuildResult, + sourceSet: SSets, + generate: TaskOutcome? = null, + bufYaml: TaskOutcome? = null, + bufGenYaml: TaskOutcome? = null, + protoFiles: TaskOutcome? = null, + protoFilesImports: TaskOutcome? = null, + ) { + assertEquals(generate, result.protoTaskOutcomeOrNull(bufGenerate(sourceSet))) + assertEquals(bufYaml, result.protoTaskOutcomeOrNull(generateBufYaml(sourceSet))) + assertEquals(bufGenYaml, result.protoTaskOutcomeOrNull(generateBufGenYaml(sourceSet))) + assertEquals(protoFiles, result.protoTaskOutcomeOrNull(processProtoFiles(sourceSet))) + assertEquals(protoFilesImports, result.protoTaskOutcomeOrNull(processProtoFilesImports(sourceSet))) + } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt index fc2893d85..2483aba44 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt @@ -29,6 +29,10 @@ abstract class GrpcBaseTest : BaseTest() { ?: fail("Task ':$name' was not present in the build result") } + fun BuildResult.protoTaskOutcomeOrNull(name: String): TaskOutcome? { + return tasks.find { it.path == ":$name" }?.outcome + } + fun BuildResult.assertProtoTaskNotExecuted(name: String) { val task = tasks.find { it.path == ":$name" } if (task != null) { diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/build.gradle.kts new file mode 100644 index 000000000..20bdf1041 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.kotlin.dsl.version + +plugins { + kotlin("multiplatform") version "" + id("org.jetbrains.kotlinx.rpc.plugin") +} + +kotlin { + jvm() + js { + nodejs() + } + macosArm64() +} + +rpc { + protoc() +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleMain/proto/appleMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleMain/proto/appleMain.proto new file mode 100644 index 000000000..dd73f50fb --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleMain/proto/appleMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message AppleMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleTest/proto/appleTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleTest/proto/appleTest.proto new file mode 100644 index 000000000..b3c6b034f --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/appleTest/proto/appleTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message AppleTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonMain/proto/commonMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonMain/proto/commonMain.proto new file mode 100644 index 000000000..ef46aa298 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonMain/proto/commonMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message CommonMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonTest/proto/commonTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonTest/proto/commonTest.proto new file mode 100644 index 000000000..f2aa03a69 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/commonTest/proto/commonTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message CommonTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsMain/proto/jsMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsMain/proto/jsMain.proto new file mode 100644 index 000000000..94cedbed5 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsMain/proto/jsMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JsMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsTest/proto/jsTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsTest/proto/jsTest.proto new file mode 100644 index 000000000..9ea0eb27a --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jsTest/proto/jsTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JsTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmMain/proto/jvmMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmMain/proto/jvmMain.proto new file mode 100644 index 000000000..936ce0d1d --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmMain/proto/jvmMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JvmMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmTest/proto/jvmTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmTest/proto/jvmTest.proto new file mode 100644 index 000000000..8786bedaa --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/jvmTest/proto/jvmTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message JvmTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Main/proto/macosArm64Main.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Main/proto/macosArm64Main.proto new file mode 100644 index 000000000..41c097c16 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Main/proto/macosArm64Main.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosArm64Main { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Test/proto/macosArm64Test.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Test/proto/macosArm64Test.proto new file mode 100644 index 000000000..a33f00ee7 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosArm64Test/proto/macosArm64Test.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosArm64Test { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosMain/proto/macosMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosMain/proto/macosMain.proto new file mode 100644 index 000000000..c65489499 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosMain/proto/macosMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosTest/proto/macosTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosTest/proto/macosTest.proto new file mode 100644 index 000000000..b65cd541c --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/macosTest/proto/macosTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message MacosTest { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeMain/proto/nativeMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeMain/proto/nativeMain.proto new file mode 100644 index 000000000..bbd13541d --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeMain/proto/nativeMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message NativeMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeTest/proto/nativeTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeTest/proto/nativeTest.proto new file mode 100644 index 000000000..df96a67d0 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/proto_tasks_are_cached_properly/src/nativeTest/proto/nativeTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message NativeTest { + string content = 1; +} From 85f851b2cb5643c2a5572d59e35c1d26fd6c68d5 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 24 Nov 2025 20:58:19 +0100 Subject: [PATCH 5/9] Added custom tasks back and BufTasks container with tests --- .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 69 ++--- .../kotlinx/rpc/buf/tasks/BufExecTask.kt | 55 +++- .../kotlinx/rpc/buf/tasks/BufGenerateTask.kt | 14 +- .../kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt | 184 ++++++++++++ ...gureLocalProtocGenDevelopmentDependency.kt | 16 +- .../rpc/protoc/DefaultProtocExtension.kt | 170 ++++++----- .../kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt | 5 + .../kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt | 43 ++- .../kotlin/kotlinx/rpc/base/GrpcBaseTest.kt | 1 + .../buf_tasks/build.gradle.kts | 70 +++++ .../buf_tasks/build.gradle.kts | 272 ++++++++++++++++++ .../src/webMain/proto/webMain.proto | 5 + .../src/webTest/proto/webTest.proto | 5 + protobuf/protobuf-core/build.gradle.kts | 30 +- tests/protobuf-conformance/build.gradle.kts | 21 +- 15 files changed, 778 insertions(+), 182 deletions(-) create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt create mode 100644 gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/buf_tasks/build.gradle.kts create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/buf_tasks/build.gradle.kts create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webMain/proto/webMain.proto create mode 100644 gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webTest/proto/webTest.proto diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt index b6cf264da..363a8223a 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -4,15 +4,17 @@ package kotlinx.rpc.buf +import kotlinx.rpc.buf.tasks.BufAllTasks +import kotlinx.rpc.buf.tasks.BufAllTasksImpl import kotlinx.rpc.buf.tasks.BufExecTask +import kotlinx.rpc.buf.tasks.BufGenerateTask +import kotlinx.rpc.buf.tasks.BufTasks +import kotlinx.rpc.buf.tasks.BufTasksImpl import kotlinx.rpc.protoc.ProtocPlugin import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.kotlin.dsl.listProperty import org.gradle.kotlin.dsl.property import java.io.File import javax.inject.Inject @@ -102,6 +104,12 @@ public open class BufExtension @Inject internal constructor(objects: ObjectFacto * Allows registering custom Buf tasks that can operate on the generated workspace. */ public open class BufTasksExtension @Inject internal constructor(internal val project: Project) { + /** + * Returns a collection of all `buf` tasks registered in the project. + */ + public fun all(): BufAllTasks { + return BufAllTasksImpl(project, project.tasks.withType(BufExecTask::class.java)) + } /** * Registers a custom Buf task that operates on the generated workspace. @@ -109,24 +117,17 @@ public open class BufTasksExtension @Inject internal constructor(internal val pr * Name conventions: * `lint` input for [name] will result in tasks * named 'bufLintMain' and 'bufLintTest' for Kotlin/JVM projects - * and 'bufLintCommonMain', 'bufLintCommonTest', 'bufLintNativeMain', etc., for Kotlin/Multiplatform projects. - * - * Note the by default 'test' task doesn't depend on the 'main' task. + * and 'bufLintCommonMain', 'bufLintCommonTest', 'bufLintNativeMain', etc. for Kotlin/Multiplatform projects. */ public fun registerWorkspaceTask( kClass: KClass, name: String, - configure: Action, - ): TaskProvider { - val mainProperty = project.objects.property(kClass) - val testProperty = project.objects.property(kClass) - - val provider = TaskProperty(mainProperty, testProperty) - + configure: Action = Action {}, + ): BufTasks { @Suppress("UNCHECKED_CAST") - customTasks.add(Definition(name, kClass, configure, provider as TaskProperty)) + customTasks.add(Definition(name, kClass, configure)) - return provider + return all().matchingType(kClass) } /** @@ -135,45 +136,22 @@ public open class BufTasksExtension @Inject internal constructor(internal val pr * Name conventions: * `lint` input for [name] will result in tasks * named 'bufLintMain' and 'bufLintTest' for Kotlin/JVM projects - * and 'bufLintCommonMain' and 'bufLintCommonTest' for Kotlin/Multiplatform projects. - * - * Note the by default 'test' task doesn't depend on 'main' task. + * and 'bufLintCommonMain', 'bufLintCommonTest', 'bufLintNativeMain', etc. for Kotlin/Multiplatform projects. */ public inline fun registerWorkspaceTask( name: String, - configure: Action, - ): TaskProvider { + configure: Action = Action {}, + ): BufTasks { return registerWorkspaceTask(T::class, name, configure) } - internal val customTasks: ListProperty> = project.objects.listProperty() + internal val customTasks = project.objects.domainObjectContainer(Definition::class.java) internal class Definition( val name: String, val kClass: KClass, val configure: Action, - val property: TaskProperty, ) - - /** - * Container for the main and test Buf tasks created by [BufTasksExtension.registerWorkspaceTask]. - */ - public sealed interface TaskProvider { - /** - * Task created via [BufTasksExtension.registerWorkspaceTask] and associated with the main source set. - */ - public val mainTask: Provider - - /** - * Task created via [BufTasksExtension.registerWorkspaceTask] and associated with the test source set. - */ - public val testTask: Provider - } - - internal class TaskProperty( - override val mainTask: Property, - override val testTask: Property, - ) : TaskProvider } /** @@ -183,6 +161,13 @@ public open class BufTasksExtension @Inject internal constructor(internal val pr * @see [BUF_GEN_YAML] */ public open class BufGenerateExtension @Inject constructor(internal val project: Project) { + /** + * Returns a collection of all `buf generate` tasks registered in the project. + */ + public fun allTasks(): BufTasks { + return BufTasksImpl(project, project.tasks.withType(BufGenerateTask::class.java), BufGenerateTask::class) + } + /** * `--include-imports` option. * diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt index 5110a568b..6d0eba355 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt @@ -27,22 +27,45 @@ import kotlinx.rpc.buf.BUF_GEN_YAML import kotlinx.rpc.buf.BUF_YAML import kotlinx.rpc.buf.BufTasksExtension import org.gradle.api.logging.LogLevel +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Internal +import javax.inject.Inject /** * Abstract base class for `buf` tasks. */ -public abstract class BufExecTask : DefaultTask() { +public abstract class BufExecTask @Inject constructor( + @Internal + public val properties: Properties +) : DefaultTask() { init { group = PROTO_GROUP } + // list of buf task dependencies of the same type + @get:Internal + internal abstract val bufTaskDependencies: SetProperty + @get:InputFile internal abstract val bufExecutable: Property @get:Input internal abstract val debug: Property + public open class Properties( + public val isTest: Boolean, + public val sourceSetName: String, + ) + + public class AndroidProperties( + isTest: Boolean, + sourceSetName: String, + public val flavour: String, + public val buildType: String, + public val variant: String, + ) : Properties(isTest, sourceSetName) + /** * The `buf` command to execute. * @@ -103,25 +126,29 @@ public abstract class BufExecTask : DefaultTask() { public inline fun Project.registerBufExecTask( name: String, workingDir: Provider, + properties: BufExecTask.Properties, noinline configuration: T.() -> Unit, -): TaskProvider = registerBufExecTask(T::class, name, workingDir, configuration) +): TaskProvider = registerBufExecTask(T::class, name, workingDir, properties, configuration) @PublishedApi internal fun Project.registerBufExecTask( clazz: KClass, name: String, workingDir: Provider, + properties: BufExecTask.Properties, configuration: T.() -> Unit = {}, -): TaskProvider = tasks.register(name, clazz) { - val executableConfiguration = configurations.getByName(BUF_EXECUTABLE_CONFIGURATION) - bufExecutable.set(executableConfiguration.singleFile) - this.workingDir.set(workingDir) - - val buf = provider { rpcExtension().protoc.get().buf } - configFile.set(buf.flatMap { it.configFile }) - logFormat.set(buf.flatMap { it.logFormat }) - bufTimeoutInWholeSeconds.set(buf.flatMap { it.timeout.map { duration -> duration.inWholeSeconds } }) - debug.set(gradle.startParameter.logLevel == LogLevel.DEBUG) - - configuration() +): TaskProvider = tasks.register(name, clazz, properties).apply { + configure { + val executableConfiguration = configurations.getByName(BUF_EXECUTABLE_CONFIGURATION) + bufExecutable.set(executableConfiguration.singleFile) + this.workingDir.set(workingDir) + + val buf = provider { rpcExtension().protoc.get().buf } + configFile.set(buf.flatMap { it.configFile }) + logFormat.set(buf.flatMap { it.logFormat }) + bufTimeoutInWholeSeconds.set(buf.flatMap { it.timeout.map { duration -> duration.inWholeSeconds } }) + debug.set(gradle.startParameter.logLevel == LogLevel.DEBUG) + + configuration() + } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt index 94db55c8e..1ac7988df 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt @@ -18,13 +18,14 @@ import java.io.File import kotlinx.rpc.buf.BufGenerateExtension import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFiles +import javax.inject.Inject /** * Buf `generate` command. * * @see buf generate */ -public abstract class BufGenerateTask : BufExecTask() { +public abstract class BufGenerateTask @Inject internal constructor(properties: Properties) : BufExecTask(properties) { // unsued, but required for Gradle to properly recognise inputs @get:InputDirectory internal abstract val protoFilesDir: Property @@ -117,17 +118,22 @@ public abstract class BufGenerateTask : BufExecTask() { } internal fun Project.registerBufGenerateTask( - name: String, + sourceSetName: String, workingDir: File, outputDirectory: File, protoFilesDir: File, importFilesDir: File, configure: BufGenerateTask.() -> Unit = {}, ): TaskProvider { - val capitalName = name.replaceFirstChar { it.uppercase() } + val capitalName = sourceSetName.replaceFirstChar { it.uppercase() } val bufGenerateTaskName = "${BufGenerateTask.NAME_PREFIX}$capitalName" - return registerBufExecTask(bufGenerateTaskName, provider { workingDir }) { + val properties = BufExecTask.Properties( + isTest = sourceSetName.lowercase().endsWith("test"), + sourceSetName = sourceSetName, + ) + + return registerBufExecTask(bufGenerateTaskName, provider { workingDir }, properties) { group = PROTO_GROUP description = "Generates code from .proto files using 'buf generate'" diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt new file mode 100644 index 000000000..2f24a95a8 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt @@ -0,0 +1,184 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.buf.tasks + +import org.gradle.api.NamedDomainObjectProvider +import org.gradle.api.Project +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.TaskCollection +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import java.util.function.IntFunction +import kotlin.reflect.KClass + +public sealed interface BufTasks : TaskCollection { + public fun matchingSourceSet(sourceSetName: String): BufTasks + + public fun matchingKotlinSourceSet(sourceSet: KotlinSourceSet): BufTasks + + public fun matchingKotlinSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks + + public fun matchingSourceSet(sourceSet: SourceSet): BufTasks + + public fun matchingSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks + + public fun executedForSourceSet(sourceSetName: String): BufTasks + + public fun executedForSourceSet(sourceSet: KotlinSourceSet): BufTasks + + public fun executedForSourceSet(sourceSet: SourceSet): BufTasks + + public fun testTasks(): BufTasks + + public fun nonTestTasks(): BufTasks + + // android + + public fun matchingFlavor(flavor: String): BufTasks + + public fun matchingBuildType(buildType: String): BufTasks + + public fun matchingVariant(variant: String): BufTasks +} + +public sealed interface BufAllTasks : BufTasks { + public fun matchingType(kClass: KClass): BufTasks +} + +public inline fun BufAllTasks.matchingType(): BufTasks { + return matchingType(BufTask::class) +} + +public inline fun BufTask.bufDependsOn(): BufTasks { + return bufDependsOn(BufTask::class) +} + +@PublishedApi +internal fun BufTask.bufDependsOn(kClass: KClass): BufTasks { + return BufTasksImpl(project, project.tasks.withType(kClass).matching { it.name in bufTaskDependencies.get() }, kClass) +} + +internal open class BufTasksImpl internal constructor( + private val project: Project, + private val collection: TaskCollection, + private val kClass: KClass, +) : BufTasks, TaskCollection by collection { + override fun matchingSourceSet(sourceSetName: String): BufTasks { + return BufTasksImpl( + project, + collection.matching { it.properties.sourceSetName == sourceSetName }, + kClass + ) + } + + override fun matchingKotlinSourceSet(sourceSet: KotlinSourceSet): BufTasks { + return matchingSourceSet(sourceSet.name) + } + + override fun matchingKotlinSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks { + return matchingSourceSet(sourceSet.name) + } + + override fun matchingSourceSet(sourceSet: SourceSet): BufTasks { + return matchingSourceSet(sourceSet.name) + } + + override fun matchingSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks { + return matchingSourceSet(sourceSet.name) + } + + override fun executedForSourceSet(sourceSetName: String): BufTasks { + val allExecuted = project.tasks.withType(kClass.java).matching { + it.properties.sourceSetName == sourceSetName + }.singleOrNull()?.bufDependsOn(kClass) ?: return empty() + + val allExecutedLazySet = lazy { allExecuted.map { it.name }.toSet() } + + return BufTasksImpl( + project = project, + collection = collection.matching { dependency -> + dependency.properties.sourceSetName == sourceSetName || dependency.name in allExecutedLazySet.value + }, + kClass = kClass, + ) + } + + override fun executedForSourceSet(sourceSet: KotlinSourceSet): BufTasks { + return executedForSourceSet(sourceSet.name) + } + + override fun executedForSourceSet(sourceSet: SourceSet): BufTasks { + return executedForSourceSet(sourceSet.name) + } + + override fun testTasks(): BufTasks { + return BufTasksImpl(project, collection.matching { it.properties.isTest }, kClass) + } + + override fun nonTestTasks(): BufTasks { + return BufTasksImpl(project, collection.matching { !it.properties.isTest }, kClass) + } + + override fun matchingFlavor(flavor: String): BufTasks { + return BufTasksImpl( + project = project, + collection = collection.matching { + (it.properties as? BufExecTask.AndroidProperties)?.flavour == flavor + }, + kClass = kClass, + ) + } + + override fun matchingBuildType(buildType: String): BufTasks { + return BufTasksImpl( + project = project, + collection = collection.matching { + (it.properties as? BufExecTask.AndroidProperties)?.buildType == buildType + }, + kClass = kClass, + ) + } + + override fun matchingVariant(variant: String): BufTasks { + return BufTasksImpl( + project = project, + collection = collection.matching { + (it.properties as? BufExecTask.AndroidProperties)?.variant == variant + }, + kClass = kClass, + ) + } + + // Java default method override + @Deprecated("Deprecated in Java") + final override fun toArray(generator: IntFunction?>): Array? { + @Suppress("DEPRECATION") + return super.toArray(generator) + } + + fun empty() = BufTasksImpl(project, matching { false }, kClass) +} + +internal open class BufAllTasksImpl internal constructor( + private val project: Project, + private val collection: BufTasksImpl, +) : BufAllTasks, BufTasks by collection { + constructor(project: Project, collection: TaskCollection) : this( + project, + BufTasksImpl(project, collection, BufExecTask::class) + ) + + override fun matchingType(kClass: KClass): BufTasks { + @Suppress("UNCHECKED_CAST") + return BufTasksImpl(project, collection.matching { kClass.isInstance(it) } as TaskCollection, kClass) + } + + // Java default method override + @Deprecated("Deprecated in Java") + final override fun toArray(generator: IntFunction?>): Array? { + @Suppress("DEPRECATION") + return super.toArray(generator) + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt index ce9a1a154..e58bf4fdd 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt @@ -4,14 +4,12 @@ package kotlinx.rpc.internal -import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.protoc.grpcKotlinMultiplatform import kotlinx.rpc.protoc.kotlinMultiplatform import kotlinx.rpc.rpcExtension import org.gradle.api.Project import org.gradle.internal.extensions.core.extra import org.gradle.kotlin.dsl.provideDelegate -import org.gradle.kotlin.dsl.withType @InternalRpcApi public fun Project.configureLocalProtocGenDevelopmentDependency( @@ -34,13 +32,13 @@ public fun Project.configureLocalProtocGenDevelopmentDependency( } } } - } - tasks.withType().configureEach { - if (sourceSetSuffix.any { name.endsWith(it) }) { - val includedBuild = gradle.includedBuild("protoc-gen") - dependsOn(includedBuild.task(":grpc:jar")) - dependsOn(includedBuild.task(":protobuf:jar")) - } + buf.generate.allTasks() + .matching { sourceSetSuffix.any { name.endsWith(it) } } + .configureEach { + val includedBuild = gradle.includedBuild("protoc-gen") + dependsOn(includedBuild.task(":grpc:jar")) + dependsOn(includedBuild.task(":protobuf:jar")) + } } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt index e2855d699..8dc9443af 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt @@ -6,6 +6,7 @@ package kotlinx.rpc.protoc import kotlinx.rpc.buf.BufExtension import kotlinx.rpc.buf.configureBufExecutable +import kotlinx.rpc.buf.tasks.BufExecTask import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.buf.tasks.GenerateBufGenYaml import kotlinx.rpc.buf.tasks.GenerateBufYaml @@ -125,7 +126,8 @@ internal open class DefaultProtocExtension @Inject constructor( if (!plugin.artifact.isPresent) { throw GradleException( "Artifact is not specified for protoc plugin ${plugin.name}. " + - "Use `local {}` or `remote {}` to specify it.") + "Use `local {}` or `remote {}` to specify it." + ) } } } @@ -168,7 +170,7 @@ internal open class DefaultProtocExtension @Inject constructor( val sourceSetsProtoDirFileTree = fileTree(buildSourceSetsProtoDir) val bufGenerateTask = registerBufGenerateTask( - name = baseName, + sourceSetName = baseName, workingDir = buildSourceSetsDir, outputDirectory = protoBuildDirGenerated.resolve(baseName), protoFilesDir = buildSourceSetsProtoDir, @@ -199,22 +201,32 @@ internal open class DefaultProtocExtension @Inject constructor( dependsOn(dependencies) + bufTaskDependencies.set(importsProvider.map { list -> + list.map { it.generateTask.get().name } + }) + onlyIf { !sourceSetsProtoDirFileTree.filter { it.extension == "proto" }.isEmpty } } protoSourceSet.generateTask.set(bufGenerateTask) + val compilationNameTestTag = if (baseName.lowercase().endsWith("test")) "Test" else "" + val compileTargetName = baseName.replaceFirstChar { it.uppercase() } + .removeSuffix("Main") + .removeSuffix("Test") + .removeSuffix("main") + .removeSuffix("test") + + // compileKotlin - main + // compileTestKotlin - test + // compileKotlinJvm - jvmMain + // compileTestKotlinJvm - jvmTest + // compileKotlinIosArm64 - iosArm64Main + // compileTestKotlinIosArm64 - iosArm64Test + val kotlinCompilationName = "compile${compilationNameTestTag}Kotlin${compileTargetName}" + project.tasks.withType>().all { - // compileKotlin - main - // compileTestKotlin - test - // compileKotlinJvm - jvmMain - // compileTestKotlinJvm - jvmTest - // compileKotlinIosArm64 - iosArm64Main - // compileTestKotlinIosArm64 - iosArm64Test - val isTest = name.startsWith("compileTest") - if (isTest && baseName.lowercase().endsWith("test")) { - dependsOn(bufGenerateTask) - } else if (!isTest && baseName.lowercase().endsWith("main")) { + if (name == kotlinCompilationName) { dependsOn(bufGenerateTask) } } @@ -244,15 +256,17 @@ internal open class DefaultProtocExtension @Inject constructor( bufGenerateTask = bufGenerateTask, ) -// configureCustomTasks( -// baseName = baseName, -// buildSourceSetsDir = buildSourceSetsDir, -// generateBufYamlTask = generateBufYamlTask, -// generateBufGenYamlTask = generateBufGenYamlTask, -// processProtoTask = processProtoTask, -// processImportProtoTask = processImportProtoTask, -// sourceSetsProtoDirFileTree = sourceSetsProtoDirFileTree, -// ) + configureCustomTasks( + baseName = baseName, + protoSourceSet = protoSourceSet, + buildSourceSetsDir = buildSourceSetsDir, + generateBufYamlTask = generateBufYamlTask, + generateBufGenYamlTask = generateBufGenYamlTask, + processProtoTask = processProtoTask, + processImportProtoTask = processImportProtoTask, + sourceSetsProtoDirFileTree = sourceSetsProtoDirFileTree, + importsProvider = importsProvider, + ) } private fun Project.configureSourceDirectories( @@ -294,45 +308,53 @@ internal open class DefaultProtocExtension @Inject constructor( private fun Project.configureCustomTasks( baseName: String, + protoSourceSet: DefaultProtoSourceSet, buildSourceSetsDir: File, generateBufYamlTask: TaskProvider, generateBufGenYamlTask: TaskProvider, processProtoTask: TaskProvider, - processImportProtoTask: TaskProvider?, + processImportProtoTask: TaskProvider, sourceSetsProtoDirFileTree: ConfigurableFileTree, - ) = afterEvaluate { - val baseCapital = baseName.replaceFirstChar { it.uppercase() } - buf.tasks.customTasks.get().forEach { definition -> - val capital = definition.name.replaceFirstChar { it.uppercase() } - val taskName = "buf$capital$baseCapital" - - val customTask = registerBufExecTask( - clazz = definition.kClass, + importsProvider: Provider>, + ) { + buf.tasks.customTasks.all { + val taskCapital = name.replaceFirstChar { it.uppercase() } + fun taskName(baseName: String): String { + val baseCapital = baseName.replaceFirstChar { it.uppercase() } + return "buf$taskCapital$baseCapital" + } + + val properties = BufExecTask.Properties( + isTest = baseName.lowercase().endsWith("test"), + sourceSetName = baseName, + ) + + registerBufExecTask( + clazz = kClass, workingDir = provider { buildSourceSetsDir }, - name = taskName, + properties = properties, + name = taskName(baseName), ) { dependsOn(generateBufYamlTask) dependsOn(generateBufGenYamlTask) dependsOn(processProtoTask) - if (processImportProtoTask != null) { - dependsOn(processImportProtoTask) - } - - onlyIf { !sourceSetsProtoDirFileTree.filter { it.extension == "proto" }.isEmpty } - } + dependsOn(processImportProtoTask) - when { - baseName.lowercase().endsWith("main") -> { - definition.property.mainTask.set(customTask) + val dependencies = project.provider { + protoSourceSet.getDependsOn(protoSourceSets).map { dependency -> + project.tasks.named(taskName(dependency.name), kClass.java).get() + } } - baseName.lowercase().endsWith("test") -> { - definition.property.testTask.set(customTask) - } + dependsOn(dependencies) - else -> { - throw GradleException("Unknown source set name: $baseName") - } + bufTaskDependencies.set(importsProvider.map { list -> + list.map { dependency -> + project.tasks.named(taskName(dependency.name), kClass.java).get().name + } + }) + + onlyIf { !sourceSetsProtoDirFileTree.filter { it.extension == "proto" }.isEmpty } } } } @@ -387,40 +409,40 @@ internal open class DefaultProtocExtension @Inject constructor( } } } +} - private fun DefaultProtoSourceSet.getDependsOn(protoSourceSets: ProtoSourceSets): List { - val sourceSets = languageSourceSets.get() +internal fun DefaultProtoSourceSet.getDependsOn(protoSourceSets: ProtoSourceSets): List { + val sourceSets = languageSourceSets.get() - val kmpDependsOn = sourceSets - .filterIsInstance() - .flatMap { - it.dependsOn.map { dependency -> dependency.name } - } - .distinct() - .mapNotNull { - protoSourceSets.getByName(it) as? DefaultProtoSourceSet - } - - val kmp = if (name.endsWith("Test")) { - (protoSourceSets.getByName(correspondingMainName()) as? DefaultProtoSourceSet) - } else { - null + val kmpDependsOn = sourceSets + .filterIsInstance() + .flatMap { + it.dependsOn.map { dependency -> dependency.name } } - - val jvm = if (name == SourceSet.TEST_SOURCE_SET_NAME) { - (protoSourceSets.getByName(correspondingMainName()) as? DefaultProtoSourceSet) - } else { - null + .distinct() + .mapNotNull { + protoSourceSets.getByName(it) as? DefaultProtoSourceSet } - return (kmpDependsOn + kmp + jvm).filterNotNull() + val kmp = if (name.endsWith("Test")) { + (protoSourceSets.getByName(correspondingMainName()) as? DefaultProtoSourceSet) + } else { + null } - private fun Named.correspondingMainName(): String { - return when { - name == "test" -> "main" - name.endsWith("Test") -> name.removeSuffix("Test") + "Main" - else -> throw GradleException("Unknown test source set name: $name") - } + val jvm = if (name == SourceSet.TEST_SOURCE_SET_NAME) { + (protoSourceSets.getByName(correspondingMainName()) as? DefaultProtoSourceSet) + } else { + null + } + + return (kmpDependsOn + kmp + jvm).filterNotNull() +} + +private fun Named.correspondingMainName(): String { + return when { + name == "test" -> "main" + name.endsWith("Test") -> name.removeSuffix("Test") + "Main" + else -> throw GradleException("Unknown test source set name: $name") } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt index d381eb67c..a76fef35d 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt @@ -398,4 +398,9 @@ inputs: assertEquals(TaskOutcome.UP_TO_DATE, fourthRunTest.protoTaskOutcome(generateBufGenYamlCommonMain)) assertEquals(TaskOutcome.SUCCESS, fourthRunTest.protoTaskOutcome(processCommonMainProtoFiles)) } + + @TestFactory + fun `Buf Tasks`() = runGrpcTest { + runGradle("test_tasks", "--no-configuration-cache") + } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt index 728b0365a..36a4c8391 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt @@ -160,13 +160,13 @@ class GrpcKmpProjectTest : GrpcBaseTest() { runKmpAndCheckFiles( SSets.jsMain, - SSets.commonMain, + SSets.commonMain, SSets.webMain, ) runKmpAndCheckFiles( SSets.jsTest, - SSets.commonMain, SSets.jsMain, - SSets.commonTest, + SSets.commonMain, SSets.webMain, SSets.jsMain, + SSets.commonTest, SSets.webTest ) runKmpAndCheckFiles( @@ -223,7 +223,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { assertOutcomes(firstRunCommonMain, SSets.nativeMain) assertOutcomes(firstRunCommonMain, SSets.nativeTest) assertOutcomes(firstRunCommonMain, SSets.jvmMain) - assertOutcomes(firstRunCommonMain, SSets.jsTest) + assertOutcomes(firstRunCommonMain, SSets.jvmTest) + assertOutcomes(firstRunCommonMain, SSets.webMain) + assertOutcomes(firstRunCommonMain, SSets.webTest) assertOutcomes(firstRunCommonMain, SSets.jsMain) assertOutcomes(firstRunCommonMain, SSets.jsTest) assertOutcomes(firstRunCommonMain, SSets.appleMain) @@ -330,7 +332,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { // didn't run assertOutcomes(firstRunMacosArm64Main, SSets.nativeTest) assertOutcomes(firstRunMacosArm64Main, SSets.jvmMain) - assertOutcomes(firstRunMacosArm64Main, SSets.jsTest) + assertOutcomes(firstRunMacosArm64Main, SSets.jvmTest) + assertOutcomes(firstRunMacosArm64Main, SSets.webMain) + assertOutcomes(firstRunMacosArm64Main, SSets.webTest) assertOutcomes(firstRunMacosArm64Main, SSets.jsMain) assertOutcomes(firstRunMacosArm64Main, SSets.jsTest) assertOutcomes(firstRunMacosArm64Main, SSets.appleTest) @@ -431,7 +435,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { // didn't run assertOutcomes(firstRunMacosArm64Test, SSets.jvmMain) - assertOutcomes(firstRunMacosArm64Test, SSets.jsTest) + assertOutcomes(firstRunMacosArm64Test, SSets.jvmTest) + assertOutcomes(firstRunMacosArm64Test, SSets.webMain) + assertOutcomes(firstRunMacosArm64Test, SSets.webTest) assertOutcomes(firstRunMacosArm64Test, SSets.jsMain) assertOutcomes(firstRunMacosArm64Test, SSets.jsTest) @@ -456,7 +462,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { assertOutcomes(fifthRunCommonMain, SSets.nativeMain) assertOutcomes(fifthRunCommonMain, SSets.nativeTest) assertOutcomes(fifthRunCommonMain, SSets.jvmMain) - assertOutcomes(fifthRunCommonMain, SSets.jsTest) + assertOutcomes(fifthRunCommonMain, SSets.jvmTest) + assertOutcomes(fifthRunCommonMain, SSets.webMain) + assertOutcomes(fifthRunCommonMain, SSets.webTest) assertOutcomes(fifthRunCommonMain, SSets.jsMain) assertOutcomes(fifthRunCommonMain, SSets.jsTest) assertOutcomes(fifthRunCommonMain, SSets.appleMain) @@ -521,7 +529,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { // didn't run assertOutcomes(secondRunMacosArm64Main, SSets.nativeTest) assertOutcomes(secondRunMacosArm64Main, SSets.jvmMain) - assertOutcomes(secondRunMacosArm64Main, SSets.jsTest) + assertOutcomes(secondRunMacosArm64Main, SSets.jvmTest) + assertOutcomes(secondRunMacosArm64Main, SSets.webMain) + assertOutcomes(secondRunMacosArm64Main, SSets.webTest) assertOutcomes(secondRunMacosArm64Main, SSets.jsMain) assertOutcomes(secondRunMacosArm64Main, SSets.jsTest) assertOutcomes(secondRunMacosArm64Main, SSets.appleTest) @@ -622,7 +632,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { // didn't run assertOutcomes(secondRunMacosArm64Test, SSets.jvmMain) - assertOutcomes(secondRunMacosArm64Test, SSets.jsTest) + assertOutcomes(secondRunMacosArm64Test, SSets.jvmTest) + assertOutcomes(secondRunMacosArm64Test, SSets.webMain) + assertOutcomes(secondRunMacosArm64Test, SSets.webTest) assertOutcomes(secondRunMacosArm64Test, SSets.jsMain) assertOutcomes(secondRunMacosArm64Test, SSets.jsTest) @@ -652,7 +664,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { assertOutcomes(firstRunJvmMain, SSets.commonTest) assertOutcomes(firstRunJvmMain, SSets.nativeMain) assertOutcomes(firstRunJvmMain, SSets.nativeTest) - assertOutcomes(firstRunJvmMain, SSets.jsTest) + assertOutcomes(firstRunJvmMain, SSets.jvmTest) + assertOutcomes(firstRunJvmMain, SSets.webMain) + assertOutcomes(firstRunJvmMain, SSets.webTest) assertOutcomes(firstRunJvmMain, SSets.jsMain) assertOutcomes(firstRunJvmMain, SSets.jsTest) assertOutcomes(firstRunJvmMain, SSets.appleMain) @@ -692,7 +706,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { assertOutcomes(secondRunJvmMain, SSets.commonTest) assertOutcomes(secondRunJvmMain, SSets.nativeMain) assertOutcomes(secondRunJvmMain, SSets.nativeTest) - assertOutcomes(secondRunJvmMain, SSets.jsTest) + assertOutcomes(secondRunJvmMain, SSets.jvmTest) + assertOutcomes(secondRunJvmMain, SSets.webMain) + assertOutcomes(secondRunJvmMain, SSets.webTest) assertOutcomes(secondRunJvmMain, SSets.jsMain) assertOutcomes(secondRunJvmMain, SSets.jsTest) assertOutcomes(secondRunJvmMain, SSets.appleMain) @@ -734,4 +750,9 @@ class GrpcKmpProjectTest : GrpcBaseTest() { assertEquals(protoFiles, result.protoTaskOutcomeOrNull(processProtoFiles(sourceSet))) assertEquals(protoFilesImports, result.protoTaskOutcomeOrNull(processProtoFilesImports(sourceSet))) } + + @TestFactory + fun `Buf Tasks`() = runGrpcTest { + runGradle("test_tasks", "--no-configuration-cache") + } } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt index 2483aba44..7b6203a58 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt @@ -328,6 +328,7 @@ abstract class GrpcBaseTest : BaseTest() { commonMain, commonTest, jvmMain, jvmTest, androidMain, androidTest, + webMain, webTest, jsMain, jsTest, nativeMain, nativeTest, appleMain, appleTest, diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/buf_tasks/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/buf_tasks/build.gradle.kts new file mode 100644 index 000000000..cbb0cc5d5 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/buf_tasks/build.gradle.kts @@ -0,0 +1,70 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.kotlin.dsl.version +import kotlinx.rpc.buf.tasks.* +import kotlinx.rpc.buf.* +import org.gradle.api.provider.Provider +import javax.inject.Inject + +plugins { + kotlin("jvm") version "" + id("org.jetbrains.kotlinx.rpc.plugin") +} + +public abstract class BufLintTask @Inject constructor(properties: BufExecTask.Properties) : BufExecTask(properties) { + init { + command.set("lint") + args.set(emptyList()) + } +} + +rpc { + protoc { + buf { + tasks { + registerWorkspaceTask("lint") + } + } + } +} + +fun Iterable.toNames() = map { it.name }.toSet() + +fun assertTasks( + tag: String, + tasks: Iterable, + vararg expected: String, +) { + val names = tasks.toNames() + if (expected.toSet() != names) { + throw GradleException("[$tag] Expected: ${expected.toSet()}, actual: $names") + } +} + +tasks.register("test_tasks") { + doLast { + val genTasks = rpc.protoc.get().buf.generate.allTasks() + + assertTasks("gen all", genTasks, "bufGenerateMain", "bufGenerateTest") + assertTasks("testTasks", genTasks.testTasks(), "bufGenerateTest") + assertTasks("nonTestTasks", genTasks.nonTestTasks(), "bufGenerateMain") + assertTasks("nonTestTasks testTasks", genTasks.nonTestTasks().testTasks()) + assertTasks("matchingSourceSet main", genTasks.matchingSourceSet("main"), "bufGenerateMain") + assertTasks("matchingSourceSet test", genTasks.matchingSourceSet("test"), "bufGenerateTest") + assertTasks("executedForSourceSet main", genTasks.executedForSourceSet("main"), "bufGenerateMain") + assertTasks("executedForSourceSet test", genTasks.executedForSourceSet("test"), "bufGenerateMain", "bufGenerateTest") + + assertTasks("buf depends on main", genTasks.matchingSourceSet("main").single().bufDependsOn()) + assertTasks("buf depends on test", genTasks.matchingSourceSet("test").single().bufDependsOn(), "bufGenerateMain") + + val allTasks = rpc.protoc.get().buf.tasks.all() + + assertTasks("all", allTasks, "bufGenerateMain", "bufGenerateTest", "bufLintMain", "bufLintTest") + assertTasks("all by type generate", allTasks.matchingType(), "bufGenerateMain", "bufGenerateTest") + assertTasks("all by type lint", allTasks.matchingType(), "bufLintMain", "bufLintTest") + } +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/buf_tasks/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/buf_tasks/build.gradle.kts new file mode 100644 index 000000000..b67cb1b57 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/buf_tasks/build.gradle.kts @@ -0,0 +1,272 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.kotlin.dsl.version +import kotlinx.rpc.buf.tasks.* +import kotlinx.rpc.buf.* +import org.gradle.api.provider.Provider +import javax.inject.Inject + +plugins { + kotlin("multiplatform") version "" + id("org.jetbrains.kotlinx.rpc.plugin") +} + +kotlin { + jvm() + js { + nodejs() + } + macosArm64() +} + + +public abstract class BufLintTask @Inject constructor(properties: BufExecTask.Properties) : BufExecTask(properties) { + init { + command.set("lint") + args.set(emptyList()) + } +} + +rpc { + protoc { + buf { + tasks { + registerWorkspaceTask("lint") + } + } + } +} + +fun Iterable.toNames() = map { it.name }.toSet() + +fun assertTasks( + tag: String, + tasks: Iterable, + vararg expected: String, +) { + val names = tasks.toNames() + val expectedSet = expected.toSet() + if (expectedSet != names) { + throw GradleException( + """ + [$tag] Expected: ${expectedSet}, actual: $names + Missing: ${expectedSet - names} + Extra: ${names - expectedSet} + """.trimIndent() + ) + } +} + +tasks.register("test_tasks") { + doLast { + val genTasks = rpc.protoc.get().buf.generate.allTasks() + + assertTasks( + "gen all", genTasks, + "bufGenerateCommonMain", "bufGenerateCommonTest", + "bufGenerateNativeMain", "bufGenerateNativeTest", + "bufGenerateAppleMain", "bufGenerateAppleTest", + "bufGenerateMacosMain", "bufGenerateMacosTest", + "bufGenerateMacosArm64Main", "bufGenerateMacosArm64Test", + "bufGenerateJvmMain", "bufGenerateJvmTest", + "bufGenerateWebMain", "bufGenerateWebTest", + "bufGenerateJsMain", "bufGenerateJsTest", + ) + + assertTasks( + "testTasks", genTasks.testTasks(), + "bufGenerateCommonTest", + "bufGenerateNativeTest", + "bufGenerateAppleTest", + "bufGenerateMacosTest", + "bufGenerateMacosArm64Test", + "bufGenerateJvmTest", + "bufGenerateWebTest", + "bufGenerateJsTest", + ) + + assertTasks( + "nonTestTasks", genTasks.nonTestTasks(), + "bufGenerateCommonMain", + "bufGenerateNativeMain", + "bufGenerateAppleMain", + "bufGenerateMacosMain", + "bufGenerateMacosArm64Main", + "bufGenerateJvmMain", + "bufGenerateWebMain", + "bufGenerateJsMain", + ) + + assertTasks("nonTestTasks testTasks", genTasks.nonTestTasks().testTasks()) + + assertTasks("matchingSourceSet main", genTasks.matchingSourceSet("main")) + assertTasks("matchingSourceSet test", genTasks.matchingSourceSet("test")) + + assertTasks( + "executedForSourceSet commonMain", + genTasks.executedForSourceSet("commonMain"), + "bufGenerateCommonMain", + ) + + assertTasks( + "executedForSourceSet jvmMain", + genTasks.executedForSourceSet("jvmMain"), + "bufGenerateCommonMain", + "bufGenerateJvmMain", + ) + + assertTasks( + "executedForSourceSet macosArm64Main", + genTasks.executedForSourceSet("macosArm64Main"), + "bufGenerateCommonMain", + "bufGenerateNativeMain", + "bufGenerateAppleMain", + "bufGenerateMacosMain", + "bufGenerateMacosArm64Main", + ) + + assertTasks( + "executedForSourceSet commonTest", + genTasks.executedForSourceSet("commonTest"), + "bufGenerateCommonMain", "bufGenerateCommonTest", + ) + + assertTasks( + "executedForSourceSet jvmTest", + genTasks.executedForSourceSet("jvmTest"), + "bufGenerateCommonMain", "bufGenerateCommonTest", + "bufGenerateJvmMain", "bufGenerateJvmTest", + ) + + assertTasks( + "executedForSourceSet macosArm64Test", + genTasks.executedForSourceSet("macosArm64Test"), + "bufGenerateCommonMain", "bufGenerateCommonTest", + "bufGenerateNativeMain", "bufGenerateNativeTest", + "bufGenerateAppleMain", "bufGenerateAppleTest", + "bufGenerateMacosMain", "bufGenerateMacosTest", + "bufGenerateMacosArm64Main", "bufGenerateMacosArm64Test", + ) + + assertTasks( + "executedForSourceSet macosArm64Test test tasks", + genTasks.executedForSourceSet("macosArm64Test").testTasks(), + "bufGenerateCommonTest", + "bufGenerateNativeTest", + "bufGenerateAppleTest", + "bufGenerateMacosTest", + "bufGenerateMacosArm64Test", + ) + + assertTasks( + "executedForSourceSet macosArm64Test non test tasks", + genTasks.executedForSourceSet("macosArm64Test").nonTestTasks(), + "bufGenerateCommonMain", + "bufGenerateNativeMain", + "bufGenerateAppleMain", + "bufGenerateMacosMain", + "bufGenerateMacosArm64Main", + ) + + assertTasks( + "executedForSourceSet macosArm64Test non test tasks matching macosMain", + genTasks.executedForSourceSet("macosArm64Test").nonTestTasks().matchingSourceSet("macosMain"), + "bufGenerateMacosMain", + ) + + assertTasks( + "executedForSourceSet macosArm64Test non test tasks matching jvmMain", + genTasks.executedForSourceSet("macosArm64Test").nonTestTasks().matchingSourceSet("jvmMain"), + ) + + assertTasks( + "executedForSourceSet macosArm64Test non test tasks executed for nativeMain", + genTasks.executedForSourceSet("macosArm64Test").nonTestTasks().executedForSourceSet("nativeMain"), + "bufGenerateCommonMain", + "bufGenerateNativeMain", + ) + + assertTasks("buf depends on commonMain", genTasks.matchingSourceSet("commonMain").single().bufDependsOn()) + assertTasks( + "buf depends on macosMain", genTasks.matchingSourceSet("macosMain").single().bufDependsOn(), + "bufGenerateCommonMain", + "bufGenerateNativeMain", + "bufGenerateAppleMain", + ) + assertTasks( + "buf depends on commonTest", + genTasks.matchingSourceSet("commonTest").single().bufDependsOn(), + "bufGenerateCommonMain", + ) + + assertTasks( + "buf depends on jsTest", + genTasks.matchingSourceSet("jsTest").single().bufDependsOn(), + "bufGenerateCommonMain", "bufGenerateCommonTest", + "bufGenerateWebMain", "bufGenerateWebTest", + "bufGenerateJsMain", + ) + + val allTasks = rpc.protoc.get().buf.tasks.all() + + assertTasks( + "all", allTasks, + // generate + "bufGenerateCommonMain", "bufGenerateCommonTest", + "bufGenerateNativeMain", "bufGenerateNativeTest", + "bufGenerateAppleMain", "bufGenerateAppleTest", + "bufGenerateMacosMain", "bufGenerateMacosTest", + "bufGenerateMacosArm64Main", "bufGenerateMacosArm64Test", + "bufGenerateJvmMain", "bufGenerateJvmTest", + "bufGenerateWebMain", "bufGenerateWebTest", + "bufGenerateJsMain", "bufGenerateJsTest", + + // lint + "bufLintCommonMain", "bufLintCommonTest", + "bufLintNativeMain", "bufLintNativeTest", + "bufLintAppleMain", "bufLintAppleTest", + "bufLintMacosMain", "bufLintMacosTest", + "bufLintMacosArm64Main", "bufLintMacosArm64Test", + "bufLintJvmMain", "bufLintJvmTest", + "bufLintWebMain", "bufLintWebTest", + "bufLintJsMain", "bufLintJsTest", + ) + + assertTasks( + "all by type generate", + allTasks.matchingType(), + "bufGenerateCommonMain", "bufGenerateCommonTest", + "bufGenerateNativeMain", "bufGenerateNativeTest", + "bufGenerateAppleMain", "bufGenerateAppleTest", + "bufGenerateMacosMain", "bufGenerateMacosTest", + "bufGenerateMacosArm64Main", "bufGenerateMacosArm64Test", + "bufGenerateJvmMain", "bufGenerateJvmTest", + "bufGenerateWebMain", "bufGenerateWebTest", + "bufGenerateJsMain", "bufGenerateJsTest", + ) + + assertTasks( + "all by type lint", allTasks.matchingType(), + "bufLintCommonMain", "bufLintCommonTest", + "bufLintNativeMain", "bufLintNativeTest", + "bufLintAppleMain", "bufLintAppleTest", + "bufLintMacosMain", "bufLintMacosTest", + "bufLintMacosArm64Main", "bufLintMacosArm64Test", + "bufLintJvmMain", "bufLintJvmTest", + "bufLintWebMain", "bufLintWebTest", + "bufLintJsMain", "bufLintJsTest", + ) + + assertTasks( + "all by type lint for macosMain", allTasks.matchingType().executedForSourceSet("macosMain"), + "bufLintCommonMain", + "bufLintNativeMain", + "bufLintAppleMain", + "bufLintMacosMain", + ) + } +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webMain/proto/webMain.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webMain/proto/webMain.proto new file mode 100644 index 000000000..c3686fb3a --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webMain/proto/webMain.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message WebMain { + string content = 1; +} diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webTest/proto/webTest.proto b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webTest/proto/webTest.proto new file mode 100644 index 000000000..8bdcfca84 --- /dev/null +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/kmp_hierarchy/src/webTest/proto/webTest.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message WebTest { + string content = 1; +} diff --git a/protobuf/protobuf-core/build.gradle.kts b/protobuf/protobuf-core/build.gradle.kts index 8623c62f8..9118bdf97 100644 --- a/protobuf/protobuf-core/build.gradle.kts +++ b/protobuf/protobuf-core/build.gradle.kts @@ -4,7 +4,6 @@ @file:OptIn(InternalRpcApi::class) -import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.internal.InternalRpcApi import kotlinx.rpc.internal.configureLocalProtocGenDevelopmentDependency import kotlinx.rpc.protoc.proto @@ -14,6 +13,7 @@ import util.configureCLibCInterop plugins { alias(libs.plugins.conventions.kmp) alias(libs.plugins.kotlinx.rpc) +// id("org.barfuin.gradle.taskinfo") version "2.2.0" } kotlin { @@ -69,32 +69,30 @@ kotlin { } } +fun generatedCodeDir(sourceSetName: String): File = layout.projectDirectory + .dir("src") + .dir(sourceSetName) + .dir("generated-code") + .asFile + rpc { protoc { buf.generate.comments { includeFileLevelComments = false } + + buf.generate.allTasks().matchingKotlinSourceSet(kotlin.sourceSets.commonMain).configureEach { + includeWkt = true + outputDirectory = generatedCodeDir(properties.sourceSetName) + } } } configureLocalProtocGenDevelopmentDependency("Main", "Test") -val generatedCodeDir = layout.projectDirectory - .dir("src") - .dir("commonMain") - .dir("generated-code") - .asFile - -tasks.withType().configureEach { - if (name.contains("Main")) { - includeWkt = true - outputDirectory = generatedCodeDir - } -} - // TODO: What is the correct way to declare this dependency? (KRPC-223) -// (without it fails when executing "publishAllPublicationsToBuildRepository")" -val bufGenerateCommonMain = tasks.named("bufGenerateCommonMain") +// (without it fails when executing "publishAllPublicationsToBuildRepository") +val bufGenerateCommonMain: TaskProvider = tasks.named("bufGenerateCommonMain") tasks.withType().configureEach { // Only for sources jars diff --git a/tests/protobuf-conformance/build.gradle.kts b/tests/protobuf-conformance/build.gradle.kts index c8e4c6f80..53439d359 100644 --- a/tests/protobuf-conformance/build.gradle.kts +++ b/tests/protobuf-conformance/build.gradle.kts @@ -4,7 +4,6 @@ @file:OptIn(InternalRpcApi::class) -import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.internal.InternalRpcApi import kotlinx.rpc.internal.configureLocalProtocGenDevelopmentDependency import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode @@ -36,23 +35,21 @@ dependencies { setupProtobufConformanceResources() configureLocalProtocGenDevelopmentDependency("Main") +fun generatedCodeDir(sourceSetName: String): File = project.layout.projectDirectory + .dir("src") + .dir(sourceSetName) + .dir("generated-code") + .asFile + rpc { protoc { buf.generate.comments { includeFileLevelComments = false } - } -} -val generatedCodeDir = project.layout.projectDirectory - .dir("src") - .dir("main") - .dir("generated-code") - .asFile - -tasks.withType().configureEach { - if (name.endsWith("Main")) { - outputDirectory = generatedCodeDir + buf.generate.allTasks().nonTestTasks().configureEach { + outputDirectory = generatedCodeDir(properties.sourceSetName) + } } } From 642f96d2f53655fee0d05526953d317535a9a0d0 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 24 Nov 2025 21:09:47 +0100 Subject: [PATCH 6/9] dumpAbi --- gradle-plugin/api/gradle-plugin.api | 107 +++++++++++------- .../src/main/kotlin/kotlinx/rpc/Extensions.kt | 4 +- .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 4 +- .../kotlinx/rpc/buf/tasks/BufExecTask.kt | 4 +- .../rpc/buf/tasks/GenerateBufGenYaml.kt | 2 +- .../kotlinx/rpc/buf/tasks/GenerateBufYaml.kt | 2 +- .../kotlinx/rpc/protoc/ProcessProtoFiles.kt | 2 +- .../kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt | 9 +- 8 files changed, 81 insertions(+), 53 deletions(-) diff --git a/gradle-plugin/api/gradle-plugin.api b/gradle-plugin/api/gradle-plugin.api index ea2c953c4..9300284de 100644 --- a/gradle-plugin/api/gradle-plugin.api +++ b/gradle-plugin/api/gradle-plugin.api @@ -2,9 +2,8 @@ public abstract interface annotation class kotlinx/rpc/RpcDangerousApi : java/la } public class kotlinx/rpc/RpcExtension { - public fun (Lorg/gradle/api/model/ObjectFactory;Lorg/gradle/api/Project;)V public final fun getAnnotationTypeSafetyEnabled ()Lorg/gradle/api/provider/Provider; - public final fun getProtoc ()Lkotlinx/rpc/protoc/ProtocExtension; + public final fun getProtoc ()Lorg/gradle/api/provider/Provider; public final fun getStrict ()Lkotlinx/rpc/RpcStrictModeExtension; public final fun protoc (Lorg/gradle/api/Action;)V public static synthetic fun protoc$default (Lkotlinx/rpc/RpcExtension;Lorg/gradle/api/Action;ILjava/lang/Object;)V @@ -27,7 +26,6 @@ public final class kotlinx/rpc/RpcStrictMode : java/lang/Enum { } public class kotlinx/rpc/RpcStrictModeExtension { - public fun (Lorg/gradle/api/model/ObjectFactory;)V public final fun getFields ()Lorg/gradle/api/provider/Property; public final fun getNestedFlow ()Lorg/gradle/api/provider/Property; public final fun getNotTopLevelServerFlow ()Lorg/gradle/api/provider/Property; @@ -43,13 +41,11 @@ public final class kotlinx/rpc/VersionsKt { } public class kotlinx/rpc/buf/BufCommentsExtension { - public fun (Lorg/gradle/api/Project;)V public final fun getCopyComments ()Lorg/gradle/api/provider/Property; public final fun getIncludeFileLevelComments ()Lorg/gradle/api/provider/Property; } public class kotlinx/rpc/buf/BufExtension { - public fun (Lorg/gradle/api/model/ObjectFactory;)V public final fun generate (Lorg/gradle/api/Action;)V public final fun getConfigFile ()Lorg/gradle/api/provider/Property; public final fun getGenerate ()Lkotlinx/rpc/buf/BufGenerateExtension; @@ -70,7 +66,7 @@ public final class kotlinx/rpc/buf/BufExtension$LogFormat : java/lang/Enum { } public class kotlinx/rpc/buf/BufGenerateExtension { - public fun (Lorg/gradle/api/Project;)V + public final fun allTasks ()Lkotlinx/rpc/buf/tasks/BufTasks; public final fun comments (Lorg/gradle/api/Action;)V public final fun getComments ()Lkotlinx/rpc/buf/BufCommentsExtension; public final fun getErrorFormat ()Lorg/gradle/api/provider/Property; @@ -92,13 +88,9 @@ public final class kotlinx/rpc/buf/BufGenerateExtension$ErrorFormat : java/lang/ } public class kotlinx/rpc/buf/BufTasksExtension { - public fun (Lorg/gradle/api/Project;)V - public final fun registerWorkspaceTask (Lkotlin/reflect/KClass;Ljava/lang/String;Lorg/gradle/api/Action;)Lkotlinx/rpc/buf/BufTasksExtension$TaskProvider; -} - -public abstract interface class kotlinx/rpc/buf/BufTasksExtension$TaskProvider { - public abstract fun getMainTask ()Lorg/gradle/api/provider/Provider; - public abstract fun getTestTask ()Lorg/gradle/api/provider/Provider; + public final fun all ()Lkotlinx/rpc/buf/tasks/BufAllTasks; + public final fun registerWorkspaceTask (Lkotlin/reflect/KClass;Ljava/lang/String;Lorg/gradle/api/Action;)Lkotlinx/rpc/buf/tasks/BufTasks; + public static synthetic fun registerWorkspaceTask$default (Lkotlinx/rpc/buf/BufTasksExtension;Lkotlin/reflect/KClass;Ljava/lang/String;Lorg/gradle/api/Action;ILjava/lang/Object;)Lkotlinx/rpc/buf/tasks/BufTasks; } public final class kotlinx/rpc/buf/ConstsKt { @@ -107,24 +99,39 @@ public final class kotlinx/rpc/buf/ConstsKt { public static final field BUF_YAML Ljava/lang/String; } +public abstract interface class kotlinx/rpc/buf/tasks/BufAllTasks : kotlinx/rpc/buf/tasks/BufTasks { + public abstract fun matchingType (Lkotlin/reflect/KClass;)Lkotlinx/rpc/buf/tasks/BufTasks; +} + public abstract class kotlinx/rpc/buf/tasks/BufExecTask : org/gradle/api/DefaultTask { - public fun ()V + public fun (Lkotlinx/rpc/buf/tasks/BufExecTask$Properties;)V public abstract fun getArgs ()Lorg/gradle/api/provider/ListProperty; public abstract fun getBufTimeoutInWholeSeconds ()Lorg/gradle/api/provider/Property; public abstract fun getCommand ()Lorg/gradle/api/provider/Property; public abstract fun getConfigFile ()Lorg/gradle/api/provider/Property; public abstract fun getLogFormat ()Lorg/gradle/api/provider/Property; + public final fun getProperties ()Lkotlinx/rpc/buf/tasks/BufExecTask$Properties; public abstract fun getWorkingDir ()Lorg/gradle/api/provider/Property; } +public final class kotlinx/rpc/buf/tasks/BufExecTask$AndroidProperties : kotlinx/rpc/buf/tasks/BufExecTask$Properties { + public final fun getBuildType ()Ljava/lang/String; + public final fun getFlavour ()Ljava/lang/String; + public final fun getVariant ()Ljava/lang/String; +} + +public class kotlinx/rpc/buf/tasks/BufExecTask$Properties { + public final fun getSourceSetName ()Ljava/lang/String; + public final fun isTest ()Z +} + public final class kotlinx/rpc/buf/tasks/BufExecTaskKt { - public static final fun registerBufExecTask (Lorg/gradle/api/Project;Lkotlin/reflect/KClass;Ljava/lang/String;Lorg/gradle/api/provider/Provider;Lkotlin/jvm/functions/Function1;)Lorg/gradle/api/tasks/TaskProvider; - public static synthetic fun registerBufExecTask$default (Lorg/gradle/api/Project;Lkotlin/reflect/KClass;Ljava/lang/String;Lorg/gradle/api/provider/Provider;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/gradle/api/tasks/TaskProvider; + public static final fun registerBufExecTask (Lorg/gradle/api/Project;Lkotlin/reflect/KClass;Ljava/lang/String;Lorg/gradle/api/provider/Provider;Lkotlinx/rpc/buf/tasks/BufExecTask$Properties;Lkotlin/jvm/functions/Function1;)Lorg/gradle/api/tasks/TaskProvider; + public static synthetic fun registerBufExecTask$default (Lorg/gradle/api/Project;Lkotlin/reflect/KClass;Ljava/lang/String;Lorg/gradle/api/provider/Provider;Lkotlinx/rpc/buf/tasks/BufExecTask$Properties;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/gradle/api/tasks/TaskProvider; } public abstract class kotlinx/rpc/buf/tasks/BufGenerateTask : kotlinx/rpc/buf/tasks/BufExecTask { public static final field NAME_PREFIX Ljava/lang/String; - public fun ()V public abstract fun getAdditionalArgs ()Lorg/gradle/api/provider/ListProperty; public abstract fun getErrorFormat ()Lorg/gradle/api/provider/Property; public abstract fun getExecutableFiles ()Lorg/gradle/api/provider/ListProperty; @@ -133,28 +140,36 @@ public abstract class kotlinx/rpc/buf/tasks/BufGenerateTask : kotlinx/rpc/buf/ta public abstract fun getOutputDirectory ()Lorg/gradle/api/provider/Property; } +public abstract interface class kotlinx/rpc/buf/tasks/BufTasks : org/gradle/api/tasks/TaskCollection { + public abstract fun executedForSourceSet (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun executedForSourceSet (Lorg/gradle/api/tasks/SourceSet;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun executedForSourceSet (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingBuildType (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingFlavor (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingKotlinSourceSet (Lorg/gradle/api/NamedDomainObjectProvider;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingKotlinSourceSet (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingSourceSet (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingSourceSet (Lorg/gradle/api/NamedDomainObjectProvider;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingSourceSet (Lorg/gradle/api/tasks/SourceSet;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun matchingVariant (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun nonTestTasks ()Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun testTasks ()Lkotlinx/rpc/buf/tasks/BufTasks; +} + +public final class kotlinx/rpc/buf/tasks/BufTasksKt { + public static final fun bufDependsOn (Lkotlinx/rpc/buf/tasks/BufExecTask;Lkotlin/reflect/KClass;)Lkotlinx/rpc/buf/tasks/BufTasks; +} + public abstract class kotlinx/rpc/buf/tasks/GenerateBufGenYaml : org/gradle/api/DefaultTask { public static final field NAME_PREFIX Ljava/lang/String; - public fun ()V public abstract fun getBufGenFile ()Lorg/gradle/api/provider/Property; } -public final class kotlinx/rpc/buf/tasks/GenerateBufGenYamlKt$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { - public fun (Lkotlin/jvm/functions/Function1;)V - public final synthetic fun execute (Ljava/lang/Object;)V -} - public abstract class kotlinx/rpc/buf/tasks/GenerateBufYaml : org/gradle/api/DefaultTask { public static final field NAME_PREFIX Ljava/lang/String; - public fun ()V public abstract fun getBufFile ()Lorg/gradle/api/provider/Property; } -public final class kotlinx/rpc/buf/tasks/GenerateBufYamlKt$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { - public fun (Lkotlin/jvm/functions/Function1;)V - public final synthetic fun execute (Ljava/lang/Object;)V -} - public final class kotlinx/rpc/protoc/ConstsKt { public static final field PROTOC_GEN_GRPC_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION Ljava/lang/String; public static final field PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION Ljava/lang/String; @@ -166,6 +181,7 @@ public final class kotlinx/rpc/protoc/ConstsKt { public static final field PROTO_GROUP Ljava/lang/String; public static final field PROTO_SOURCE_DIRECTORY_NAME Ljava/lang/String; public static final field PROTO_SOURCE_SETS Ljava/lang/String; + public static final field PROTO_SOURCE_SET_EXTENSION_NAME Ljava/lang/String; } public final class kotlinx/rpc/protoc/PluginJarsKt { @@ -174,32 +190,41 @@ public final class kotlinx/rpc/protoc/PluginJarsKt { } public abstract class kotlinx/rpc/protoc/ProcessProtoFiles : org/gradle/api/tasks/Copy { - public fun ()V } -public final class kotlinx/rpc/protoc/ProcessProtoFilesKt$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { - public fun (Lkotlin/jvm/functions/Function1;)V - public final synthetic fun execute (Ljava/lang/Object;)V +public abstract interface class kotlinx/rpc/protoc/ProtoSourceSet : org/gradle/api/file/SourceDirectorySet { + public abstract fun plugin (Lkotlinx/rpc/protoc/ProtocPlugin;Lorg/gradle/api/Action;)V + public abstract fun plugin (Lorg/gradle/api/Action;Lkotlin/jvm/functions/Function1;)V + public abstract fun plugin (Lorg/gradle/api/NamedDomainObjectProvider;Lorg/gradle/api/Action;)V + public abstract fun plugin (Lorg/gradle/api/provider/Provider;Lorg/gradle/api/Action;)V + public static synthetic fun plugin$default (Lkotlinx/rpc/protoc/ProtoSourceSet;Lkotlinx/rpc/protoc/ProtocPlugin;Lorg/gradle/api/Action;ILjava/lang/Object;)V + public static synthetic fun plugin$default (Lkotlinx/rpc/protoc/ProtoSourceSet;Lorg/gradle/api/Action;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun plugin$default (Lkotlinx/rpc/protoc/ProtoSourceSet;Lorg/gradle/api/NamedDomainObjectProvider;Lorg/gradle/api/Action;ILjava/lang/Object;)V + public static synthetic fun plugin$default (Lkotlinx/rpc/protoc/ProtoSourceSet;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/Action;ILjava/lang/Object;)V } -public abstract interface class kotlinx/rpc/protoc/ProtoSourceSet { - public abstract fun getName ()Ljava/lang/String; - public abstract fun getPlugins ()Lorg/gradle/api/NamedDomainObjectContainer; - public abstract fun getProto ()Lorg/gradle/api/file/SourceDirectorySet; - public abstract fun plugins (Lorg/gradle/api/Action;)V - public fun proto (Lorg/gradle/api/Action;)V +public final class kotlinx/rpc/protoc/ProtoSourceSetKt { + public static final fun getProto (Lorg/gradle/api/NamedDomainObjectProvider;)Lorg/gradle/api/provider/Provider; + public static final fun getProto (Lorg/gradle/api/tasks/SourceSet;)Lkotlinx/rpc/protoc/ProtoSourceSet; + 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/KotlinSourceSet;Lorg/gradle/api/Action;)V + 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 } public abstract interface class kotlinx/rpc/protoc/ProtocExtension { public abstract fun buf (Lorg/gradle/api/Action;)V public abstract fun getBuf ()Lkotlinx/rpc/buf/BufExtension; + public abstract fun getPlugins ()Lorg/gradle/api/NamedDomainObjectContainer; + public abstract fun plugins (Lorg/gradle/api/Action;)V } public class kotlinx/rpc/protoc/ProtocPlugin { public static final field Companion Lkotlinx/rpc/protoc/ProtocPlugin$Companion; public static final field GRPC_KOTLIN_MULTIPLATFORM Ljava/lang/String; public static final field KOTLIN_MULTIPLATFORM Ljava/lang/String; - public fun (Ljava/lang/String;Lorg/gradle/api/Project;)V public final fun getArtifact ()Lorg/gradle/api/provider/Property; public final fun getExcludeTypes ()Lorg/gradle/api/provider/ListProperty; public final fun getIncludeImports ()Lorg/gradle/api/provider/Property; @@ -217,7 +242,6 @@ public abstract class kotlinx/rpc/protoc/ProtocPlugin$Artifact { } public final class kotlinx/rpc/protoc/ProtocPlugin$Artifact$Local : kotlinx/rpc/protoc/ProtocPlugin$Artifact { - public fun (Lorg/gradle/api/Project;)V public final fun executor (Lorg/gradle/api/provider/Provider;)V public final fun executor ([Ljava/lang/String;)V public final fun getExecutableFiles ()Lorg/gradle/api/provider/ListProperty; @@ -228,7 +252,6 @@ public final class kotlinx/rpc/protoc/ProtocPlugin$Artifact$Local : kotlinx/rpc/ } public final class kotlinx/rpc/protoc/ProtocPlugin$Artifact$Remote : kotlinx/rpc/protoc/ProtocPlugin$Artifact { - public fun (Lorg/gradle/api/Project;)V public final fun getLocator ()Lorg/gradle/api/provider/Property; } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt index f8c5e2606..77a5bac5c 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt @@ -24,7 +24,7 @@ internal fun Project.rpcExtensionOrNull(): RpcExtension? = extensions.findByType internal fun Project.rpcExtension(): RpcExtension = rpcExtensionOrNull() ?: error("Rpc extension not found. Please apply the plugin to the project") -public open class RpcExtension @Inject constructor(objects: ObjectFactory, project: Project) { +public open class RpcExtension @Inject internal constructor(objects: ObjectFactory, project: Project) { /** * Controls `@Rpc` [annotation type-safety](https://github.com/Kotlin/kotlinx-rpc/pull/240) compile-time checkers. * @@ -105,7 +105,7 @@ public open class RpcExtension @Inject constructor(objects: ObjectFactory, proje } } -public open class RpcStrictModeExtension @Inject constructor(objects: ObjectFactory) { +public open class RpcStrictModeExtension @Inject internal constructor(objects: ObjectFactory) { /** * `StateFlow`s in RPC services are deprecated, * due to their error-prone nature. diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt index 363a8223a..54f4846c5 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -160,7 +160,7 @@ public open class BufTasksExtension @Inject internal constructor(internal val pr * @see "buf generate" command * @see [BUF_GEN_YAML] */ -public open class BufGenerateExtension @Inject constructor(internal val project: Project) { +public open class BufGenerateExtension @Inject internal constructor(internal val project: Project) { /** * Returns a collection of all `buf generate` tasks registered in the project. */ @@ -240,7 +240,7 @@ public open class BufGenerateExtension @Inject constructor(internal val project: /** * Extension for configuring comments in the generated code. */ -public open class BufCommentsExtension @Inject constructor(internal val project: Project) { +public open class BufCommentsExtension @Inject internal constructor(internal val project: Project) { /** * Whether to copy comments from the original source files. */ diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt index 6d0eba355..52d9b355c 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt @@ -53,12 +53,12 @@ public abstract class BufExecTask @Inject constructor( @get:Input internal abstract val debug: Property - public open class Properties( + public open class Properties internal constructor( public val isTest: Boolean, public val sourceSetName: String, ) - public class AndroidProperties( + public class AndroidProperties internal constructor( isTest: Boolean, sourceSetName: String, public val flavour: String, diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt index a01a002b0..812ab819c 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt @@ -49,7 +49,7 @@ internal data class ResolvedGrpcPlugin( /** * Generates/updates Buf `buf.gen.yaml` file. */ -public abstract class GenerateBufGenYaml : DefaultTask() { +public abstract class GenerateBufGenYaml internal constructor(): DefaultTask() { @get:Input internal abstract val plugins: ListProperty diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt index 112025541..b84456b0e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt @@ -22,7 +22,7 @@ import java.io.File /** * Generates/updates a Buf `buf.yaml` file. */ -public abstract class GenerateBufYaml : DefaultTask() { +public abstract class GenerateBufYaml internal constructor(): DefaultTask() { @get:Input internal abstract val protoSourceDir: Property diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt index fc9e56697..1b317f712 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt @@ -17,7 +17,7 @@ import java.io.File /** * Copy proto files to a temporary directory for Buf to process. */ -public abstract class ProcessProtoFiles : Copy() { +public abstract class ProcessProtoFiles internal constructor(): Copy() { init { group = PROTO_GROUP } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt index 75036b673..23c515b02 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt @@ -51,6 +51,11 @@ public fun NamedDomainObjectContainer.grpcKotlinMultiplatform(acti * Access to a specific protoc plugin. */ public open class ProtocPlugin internal constructor( + /** + * Plugin's name. + * + * Also used to create directory names for generated files. + */ public val name: String, internal val project: Project, ) { @@ -198,7 +203,7 @@ public open class ProtocPlugin internal constructor( * Buf documentation - Type of plugin * */ - public class Local(private val project: Project) : Artifact() { + public class Local internal constructor(private val project: Project) : Artifact() { /** * Command-line arguments that execute the plugin. */ @@ -271,7 +276,7 @@ public open class ProtocPlugin internal constructor( * Buf documentation - Type of plugin * */ - public class Remote(project: Project) : Artifact() { + public class Remote internal constructor(project: Project) : Artifact() { public val locator: Property = project.objects.property() } } From f88523237f908a50fc03a9cdb3b7b2057e7edc47 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 24 Nov 2025 21:17:39 +0100 Subject: [PATCH 7/9] fix tasks dependencies --- .../internal/configureLocalProtocGenDevelopmentDependency.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt index e58bf4fdd..4078e7a89 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt @@ -34,7 +34,9 @@ public fun Project.configureLocalProtocGenDevelopmentDependency( } buf.generate.allTasks() - .matching { sourceSetSuffix.any { name.endsWith(it) } } + .matching { task -> + sourceSetSuffix.any { task.name.endsWith(it) } + } .configureEach { val includedBuild = gradle.includedBuild("protoc-gen") dependsOn(includedBuild.task(":grpc:jar")) From da8b10ac24dc802c613a72b5af93b0adf728d563 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 24 Nov 2025 21:51:22 +0100 Subject: [PATCH 8/9] Added coping test --- .../kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt | 1 - .../kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt | 34 +++++++++++++++++++ .../build.gradle.kts | 6 +++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt index 23c515b02..9c60a07f0 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt @@ -281,7 +281,6 @@ public open class ProtocPlugin internal constructor( } } - // todo check if coping works internal fun copy(): ProtocPlugin { return ProtocPlugin(name, project) .also { diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt index a76fef35d..3f0ba6d08 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt @@ -265,7 +265,41 @@ plugins: - remote: my.remote.plugin out: myRemotePlugin opt: + - hello=world - explicitApiModeEnabled=true + - only=in main +inputs: + - directory: proto + """.trimIndent() + ) + + runGradle(generateBufGenYamlCommonTest) + + assertBufGenYaml( + sourceSet = testSourceSet, + content = """ +version: v2 +clean: true +plugins: + - local: [protoc-gen-kotlin-multiplatform] + out: kotlin-multiplatform + opt: + - debugOutput=protoc-gen-kotlin-multiplatform.log + - generateComments=true + - generateFileLevelComments=true + - indentSize=4 + - local: [protoc-gen-grpc-kotlin-multiplatform] + out: grpc-kotlin-multiplatform + opt: + - debugOutput=protoc-gen-grpc-kotlin-multiplatform.log + - generateComments=true + - generateFileLevelComments=true + - indentSize=4 + - remote: my.remote.plugin + out: myRemotePlugin + opt: + - hello=world + - only=in test inputs: - directory: proto """.trimIndent() diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts index e7c9e3fa6..6e50370ca 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts @@ -31,6 +31,7 @@ rpc { remote { locator = "my.remote.plugin" } + options.put("hello", "world") } } } @@ -39,6 +40,9 @@ rpc { kotlin.sourceSets { main.proto { plugin { getByName("myPlugin") } - plugin { getByName("myRemotePlugin") } + plugin({ options.put("only", "in main") }) { getByName("myRemotePlugin") } + } + test.proto { + plugin({ options.put("only", "in test") }) { getByName("myRemotePlugin") } } } From ca8ef58f4039d227958912add97dead28537987d Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 24 Nov 2025 22:18:54 +0100 Subject: [PATCH 9/9] Added missing docs and improved BufTasks implementation --- gradle-plugin/api/gradle-plugin.api | 6 +- .../kotlinx/rpc/buf/tasks/BufExecTask.kt | 27 +- .../kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt | 421 +++++++++++++++++- .../kotlinx/rpc/protoc/ProtoSourceSet.kt | 110 +++++ 4 files changed, 556 insertions(+), 8 deletions(-) diff --git a/gradle-plugin/api/gradle-plugin.api b/gradle-plugin/api/gradle-plugin.api index 9300284de..1ceaf1917 100644 --- a/gradle-plugin/api/gradle-plugin.api +++ b/gradle-plugin/api/gradle-plugin.api @@ -116,7 +116,7 @@ public abstract class kotlinx/rpc/buf/tasks/BufExecTask : org/gradle/api/Default public final class kotlinx/rpc/buf/tasks/BufExecTask$AndroidProperties : kotlinx/rpc/buf/tasks/BufExecTask$Properties { public final fun getBuildType ()Ljava/lang/String; - public final fun getFlavour ()Ljava/lang/String; + public final fun getFlavor ()Ljava/lang/String; public final fun getVariant ()Ljava/lang/String; } @@ -141,9 +141,11 @@ public abstract class kotlinx/rpc/buf/tasks/BufGenerateTask : kotlinx/rpc/buf/ta } public abstract interface class kotlinx/rpc/buf/tasks/BufTasks : org/gradle/api/tasks/TaskCollection { + public abstract fun executedForKotlinSourceSet (Lorg/gradle/api/NamedDomainObjectProvider;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun executedForKotlinSourceSet (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)Lkotlinx/rpc/buf/tasks/BufTasks; public abstract fun executedForSourceSet (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; + public abstract fun executedForSourceSet (Lorg/gradle/api/NamedDomainObjectProvider;)Lkotlinx/rpc/buf/tasks/BufTasks; public abstract fun executedForSourceSet (Lorg/gradle/api/tasks/SourceSet;)Lkotlinx/rpc/buf/tasks/BufTasks; - public abstract fun executedForSourceSet (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)Lkotlinx/rpc/buf/tasks/BufTasks; public abstract fun matchingBuildType (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; public abstract fun matchingFlavor (Ljava/lang/String;)Lkotlinx/rpc/buf/tasks/BufTasks; public abstract fun matchingKotlinSourceSet (Lorg/gradle/api/NamedDomainObjectProvider;)Lkotlinx/rpc/buf/tasks/BufTasks; diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt index 52d9b355c..2203bb6d7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt @@ -53,16 +53,41 @@ public abstract class BufExecTask @Inject constructor( @get:Input internal abstract val debug: Property + /** + * Properties of the buf task. + * + * Can be used with [BufTasks] to filter tasks. + */ public open class Properties internal constructor( + /** + * Whether the task is for a test source set. + */ public val isTest: Boolean, + /** + * Name of the [kotlinx.rpc.protoc.ProtoSourceSet] this task is associated with. + */ public val sourceSetName: String, ) + /** + * Properties of the buf task for android source sets. + * + * Can be used with [BufTasks] to filter tasks. + */ public class AndroidProperties internal constructor( isTest: Boolean, sourceSetName: String, - public val flavour: String, + /** + * Name of the android flavor this task is associated with. + */ + public val flavor: String, + /** + * Name of the android build type this task is associated with. + */ public val buildType: String, + /** + * Name of the android variant this task is associated with. + */ public val variant: String, ) : Properties(isTest, sourceSetName) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt index 2f24a95a8..a4c57ff7e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufTasks.kt @@ -13,51 +13,454 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import java.util.function.IntFunction import kotlin.reflect.KClass +/** + * Represents a collection of buf tasks of a given type. + * + * Allows for better filtering using additional method on top of Gradle's [TaskCollection]. + * + * Example: + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingSourceSet("main") + * buf.tasks.all().testTasks() + * buf.tasks.all() + * .testTasks() + * .matching { ... } + * .all { ... } + * } + * ``` + */ public sealed interface BufTasks : TaskCollection { + /** + * Filters tasks by source set name. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingSourceSet("main") + * } + * ``` + */ public fun matchingSourceSet(sourceSetName: String): BufTasks + /** + * Filters tasks by a Kotlin source set. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingSourceSet(kotlin.sourceSets.getByName("main")) + * } + * ``` + */ public fun matchingKotlinSourceSet(sourceSet: KotlinSourceSet): BufTasks + /** + * Filters tasks by a Kotlin source set. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingSourceSet(kotlin.sourceSets.commonMain) + * } + * ``` + */ public fun matchingKotlinSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks + /** + * Filters tasks by a source set. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingSourceSet(sourceSets.getByName("main")) + * } + * ``` + */ public fun matchingSourceSet(sourceSet: SourceSet): BufTasks + /** + * Filters tasks by a source set. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingSourceSet(sourceSets.main) + * } + * ``` + */ public fun matchingSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks + /** + * Returns a collection of all buf tasks of the given type [BufTask] that will be executed for the given source set. + * + * This functionality uses [KotlinSourceSet.dependsOn] and inherits it's limitations: + * > Note that the Kotlin Gradle plugin may add additional required source sets + * on late stages of Gradle configuration + * and the most reliable way to get a full final set is to use this property + * as a task input with [org.gradle.api.provider.Provider] type. + * + * Unlike [KotlinSourceSet.dependsOn], this method also considers the `test -> main` dependency. + * + * Correct example: + * ```kotlin + * // use bufTaskNames in other tasks as in input, e.g. + * // + * // returns bufGenerateCommonMain, bufGenerateNativeMain, bufGenerateAppleMain + * // in default Kotlin source set hierarchy + * val bufTaskNames = project.provider { + * rpc.protoc.get() + * .buf.tasks.all() + * .executedForSourceSet("bufGenerateAppleMain") + * .map { it.name } + * } + * ``` + * + * Incorrect example that will print only `bufGenerateAppleMain`, + * because [KotlinSourceSet.dependsOn] won't yet be resolved: + * ```kotlin + * rpc.protoc { + * buf.tasks.all() + * .executedForSourceSet("bufGenerateAppleMain") + * .all { println(it.name) } + * } + * ``` + * + * Mind the difference with [bufDependsOn]: + * [executedForSourceSet] also includes the task for the given source set. + * + * For the correct example: + * - [bufDependsOn] returns `bufGenerateCommonMain` and `bufGenerateNativeMain` + * - [executedForSourceSet] returns `bufGenerateCommonMain`, `bufGenerateNativeMain` and `bufGenerateAppleMain` + */ public fun executedForSourceSet(sourceSetName: String): BufTasks - public fun executedForSourceSet(sourceSet: KotlinSourceSet): BufTasks - + /** + * Returns a collection of all buf tasks of the given type [BufTask] that will be executed for the given source set. + * + * This functionality uses [KotlinSourceSet.dependsOn] and inherits it's limitations: + * > Note that the Kotlin Gradle plugin may add additional required source sets + * on late stages of Gradle configuration + * and the most reliable way to get a full final set is to use this property + * as a task input with [org.gradle.api.provider.Provider] type. + * + * Unlike [KotlinSourceSet.dependsOn], this method also considers the `test -> main` dependency. + * + * Correct example: + * ```kotlin + * // use bufTaskNames in other tasks as in input, e.g. + * // + * // returns bufGenerateCommonMain, bufGenerateNativeMain, bufGenerateAppleMain + * // in default Kotlin source set hierarchy + * val bufTaskNames = project.provider { + * rpc.protoc.get() + * .buf.tasks.all() + * .executedForSourceSet(kotlin.sourceSets.appleMain.get()) + * .map { it.name } + * } + * ``` + * + * Incorrect example that will print only `bufGenerateAppleMain`, + * because [KotlinSourceSet.dependsOn] won't yet be resolved: + * ```kotlin + * rpc.protoc { + * buf.tasks.all() + * .executedForSourceSet(kotlin.sourceSets.appleMain.get()) + * .all { println(it.name) } + * } + * ``` + * + * Mind the difference with [bufDependsOn]: + * [executedForKotlinSourceSet] also includes the task for the given source set. + * + * For the correct example: + * - [bufDependsOn] returns `bufGenerateCommonMain` and `bufGenerateNativeMain` + * - [executedForKotlinSourceSet] returns `bufGenerateCommonMain`, `bufGenerateNativeMain` and `bufGenerateAppleMain` + */ + public fun executedForKotlinSourceSet(sourceSet: KotlinSourceSet): BufTasks + + /** + * Returns a collection of all buf tasks of the given type [BufTask] that will be executed for the given source set. + * + * This functionality uses [KotlinSourceSet.dependsOn] and inherits it's limitations: + * > Note that the Kotlin Gradle plugin may add additional required source sets + * on late stages of Gradle configuration + * and the most reliable way to get a full final set is to use this property + * as a task input with [org.gradle.api.provider.Provider] type. + * + * Unlike [KotlinSourceSet.dependsOn], this method also considers the `test -> main` dependency. + * + * Correct example: + * ```kotlin + * // use bufTaskNames in other tasks as in input, e.g. + * // + * // returns bufGenerateCommonMain, bufGenerateNativeMain, bufGenerateAppleMain + * // in default Kotlin source set hierarchy + * val bufTaskNames = project.provider { + * rpc.protoc.get() + * .buf.tasks.all() + * .executedForSourceSet(kotlin.sourceSets.appleMain) + * .map { it.name } + * } + * ``` + * + * Incorrect example that will print only `bufGenerateAppleMain`, + * because [KotlinSourceSet.dependsOn] won't yet be resolved: + * ```kotlin + * rpc.protoc { + * buf.tasks.all() + * .executedForSourceSet(kotlin.sourceSets.appleMain) + * .all { println(it.name) } + * } + * ``` + * + * Mind the difference with [bufDependsOn]: + * [executedForKotlinSourceSet] also includes the task for the given source set. + * + * For the correct example: + * - [bufDependsOn] returns `bufGenerateCommonMain` and `bufGenerateNativeMain` + * - [executedForKotlinSourceSet] returns `bufGenerateCommonMain`, `bufGenerateNativeMain` and `bufGenerateAppleMain` + */ + public fun executedForKotlinSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks + + /** + * Returns a collection of all buf tasks of the given type [BufTask] that will be executed for the given source set. + * + * This functionality uses [KotlinSourceSet.dependsOn] and inherits it's limitations: + * > Note that the Kotlin Gradle plugin may add additional required source sets + * on late stages of Gradle configuration + * and the most reliable way to get a full final set is to use this property + * as a task input with [org.gradle.api.provider.Provider] type. + * + * Unlike [KotlinSourceSet.dependsOn], this method also considers the `test -> main` dependency. + * + * Correct example: + * ```kotlin + * // use bufTaskNames in other tasks as in input, e.g. + * // + * // returns bufGenerateMain, bufGenerateTest + * // in default Kotlin source set hierarchy + * val bufTaskNames = project.provider { + * rpc.protoc.get() + * .buf.tasks.all() + * .executedForSourceSet(sourceSets.test.get()) + * .map { it.name } + * } + * ``` + * + * Incorrect example. + * Although, the result will be correct in Kotlin/JVM projects (will print`bufGenerateMain`), + * this is a bad pattern because [KotlinSourceSet.dependsOn] won't yet be resolved: + * ```kotlin + * rpc.protoc { + * buf.tasks.all() + * .executedForSourceSet(sourceSets.test.get()) + * .all { println(it.name) } + * } + * ``` + * + * Mind the difference with [bufDependsOn]: + * [executedForSourceSet] also includes the task for the given source set. + * + * For the correct example: + * - [bufDependsOn] returns `bufGenerateMain` + * - [executedForSourceSet] returns `bufGenerateMain` and `bufGenerateTest` + */ public fun executedForSourceSet(sourceSet: SourceSet): BufTasks + /** + * Returns a collection of all buf tasks of the given type [BufTask] that will be executed for the given source set. + * + * This functionality uses [KotlinSourceSet.dependsOn] and inherits it's limitations: + * > Note that the Kotlin Gradle plugin may add additional required source sets + * on late stages of Gradle configuration + * and the most reliable way to get a full final set is to use this property + * as a task input with [org.gradle.api.provider.Provider] type. + * + * Unlike [KotlinSourceSet.dependsOn], this method also considers the `test -> main` dependency. + * + * Correct example: + * ```kotlin + * // use bufTaskNames in other tasks as in input, e.g. + * // + * // returns bufGenerateMain, bufGenerateTest + * // in default Kotlin source set hierarchy + * val bufTaskNames = project.provider { + * rpc.protoc.get() + * .buf.tasks.all() + * .executedForSourceSet(sourceSets.test) + * .map { it.name } + * } + * ``` + * + * Incorrect example. + * Although, the result will be correct in Kotlin/JVM projects (will print`bufGenerateMain`), + * this is a bad pattern because [KotlinSourceSet.dependsOn] won't yet be resolved: + * ```kotlin + * rpc.protoc { + * buf.tasks.all() + * .executedForSourceSet(sourceSets.test) + * .all { println(it.name) } + * } + * ``` + * + * Mind the difference with [bufDependsOn]: + * [executedForSourceSet] also includes the task for the given source set. + * + * For the correct example: + * - [bufDependsOn] returns `bufGenerateMain` + * - [executedForSourceSet] returns `bufGenerateMain` and `bufGenerateTest` + */ + public fun executedForSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks + + /** + * Filters tasks by where [BufExecTask.Properties.isTest] is `true`. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().testTasks() + * } + * ``` + */ public fun testTasks(): BufTasks + /** + * Filters tasks by where [BufExecTask.Properties.isTest] is `false`. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().nonTestTasks() + * } + * ``` + */ public fun nonTestTasks(): BufTasks // android + /** + * Filters tasks by where [BufExecTask.AndroidProperties.flavor] matches the given flavor. + * + * Only returns Android tasks. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingFlavor("freeApp") + * } + * ``` + */ public fun matchingFlavor(flavor: String): BufTasks + /** + * Filters tasks by where [BufExecTask.AndroidProperties.buildType] matches the given buildType. + * + * Only returns Android tasks. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingBuildType("debug") + * } + * ``` + */ public fun matchingBuildType(buildType: String): BufTasks + /** + * Filters tasks by where [BufExecTask.AndroidProperties.variant] matches the given variant. + * + * Only returns Android tasks. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingVariant("freeAppDebug") + * } + * ``` + */ public fun matchingVariant(variant: String): BufTasks } +/** + * A version of [BufTasks] that contains all buf tasks and allows filtering by type. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingType() + * } + * ``` + */ public sealed interface BufAllTasks : BufTasks { + /** + * Filters tasks by type. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingType(BufGenerateTask::class) + * } + * ``` + */ public fun matchingType(kClass: KClass): BufTasks } +/** + * Filters tasks by type. + * + * ```kotlin + * rpc.protoc { + * buf.tasks.all().matchingType() + * } + * ``` + */ public inline fun BufAllTasks.matchingType(): BufTasks { return matchingType(BufTask::class) } +/** + * Returns a collection of all buf tasks of the given type [BufTask] that this task depends on. + * + * This functionality uses [KotlinSourceSet.dependsOn] and inherits it's limitations: + * > Note that the Kotlin Gradle plugin may add additional required source sets + * on late stages of Gradle configuration + * and the most reliable way to get a full final set is to use this property + * as a task input with [org.gradle.api.provider.Provider] type. + * + * Unlike [KotlinSourceSet.dependsOn], this method also considers the `test -> main` dependency. + * + * Correct example: + * ```kotlin + * // use bufTaskNames in other tasks as in input, e.g. + * // + * // returns bufGenerateCommonMain, bufGenerateNativeMain + * // in default Kotlin source set hierarchy + * val bufTaskNames = project.provider { + * rpc.protoc.get() + * .buf.tasks.all() + * .getByName("bufGenerateAppleMain") + * .bufDependsOn() + * .map { it.name } + * } + * ``` + * + * Incorrect example that will print nothing, because [KotlinSourceSet.dependsOn] won't yet be resolved: + * ```kotlin + * rpc.protoc { + * buf.tasks.all() + * .getByName("bufGenerateAppleMain") + * .bufDependsOn() + * .all { println(it.name) } + * } + * ``` + * + * Mind the difference with [BufTasks.executedForSourceSet]: + * [bufDependsOn] doesn't include the task for the given source set. + * + * For the correct example: + * - [bufDependsOn] returns `bufGenerateCommonMain` and `bufGenerateNativeMain` + * - [BufTasks.executedForSourceSet] returns `bufGenerateCommonMain`, `bufGenerateNativeMain` and `bufGenerateAppleMain` + */ public inline fun BufTask.bufDependsOn(): BufTasks { return bufDependsOn(BufTask::class) } @PublishedApi internal fun BufTask.bufDependsOn(kClass: KClass): BufTasks { - return BufTasksImpl(project, project.tasks.withType(kClass).matching { it.name in bufTaskDependencies.get() }, kClass) + return BufTasksImpl( + project, + project.tasks.withType(kClass).matching { it.name in bufTaskDependencies.get() }, + kClass + ) } internal open class BufTasksImpl internal constructor( @@ -105,7 +508,11 @@ internal open class BufTasksImpl internal constructor( ) } - override fun executedForSourceSet(sourceSet: KotlinSourceSet): BufTasks { + override fun executedForKotlinSourceSet(sourceSet: KotlinSourceSet): BufTasks { + return executedForSourceSet(sourceSet.name) + } + + override fun executedForKotlinSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks { return executedForSourceSet(sourceSet.name) } @@ -113,6 +520,10 @@ internal open class BufTasksImpl internal constructor( return executedForSourceSet(sourceSet.name) } + override fun executedForSourceSet(sourceSet: NamedDomainObjectProvider): BufTasks { + return executedForSourceSet(sourceSet.name) + } + override fun testTasks(): BufTasks { return BufTasksImpl(project, collection.matching { it.properties.isTest }, kClass) } @@ -125,7 +536,7 @@ internal open class BufTasksImpl internal constructor( return BufTasksImpl( project = project, collection = collection.matching { - (it.properties as? BufExecTask.AndroidProperties)?.flavour == flavor + (it.properties as? BufExecTask.AndroidProperties)?.flavor == flavor }, kClass = kClass, ) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt index 95ac93cbd..208683b20 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt @@ -16,35 +16,133 @@ public typealias ProtoSourceSets = NamedDomainObjectContainer /** * Represents a source set for proto files. + * + * Acts like a [SourceDirectorySet] but also allows configuring protoc plugins. + * + * All source sets have [kotlinMultiplatform] and [grpcKotlinMultiplatform] plugins by default. + * + * Example: + * ```kotlin + * kotlin.sourceSets { + * commonMain { + * proto { + * exclude("some.proto") + * plugin { getByName("myPlugin") } + * } + * } + * } + * ``` */ public sealed interface ProtoSourceSet : SourceDirectorySet { + /** + * Add a plugin to this source set and allows to configure it specifically for this source set. + * + * Example: + * ```kotlin + * kotlin.sourceSets { + * commonMain { + * proto { + * plugin(myPlugin) { + * options.put("key", "value") // only for commonMain + * } + * } + * } + * } + * ``` + */ public fun plugin(plugin: ProtocPlugin, configure: Action? = null) + /** + * Add a plugin to this source set and allows to configure it specifically for this source set. + * + * Example: + * ```kotlin + * kotlin.sourceSets { + * commonMain { + * proto { + * plugin(myPlugin) { + * options.put("key", "value") // only for commonMain + * } + * } + * } + * } + * ``` + */ public fun plugin(provider: NamedDomainObjectProvider, configure: Action? = null) + /** + * Add a plugin to this source set and allows to configure it specifically for this source set. + * + * Example: + * ```kotlin + * kotlin.sourceSets { + * commonMain { + * proto { + * plugin(myPlugin) { + * options.put("key", "value") // only for commonMain + * } + * } + * } + * } + * ``` + */ public fun plugin(provider: Provider, configure: Action? = null) + /** + * Add a plugin to this source set and allows to configure it specifically for this source set. + * + * Example: + * ```kotlin + * kotlin.sourceSets { + * commonMain { + * proto { + * plugin { + * getByName("myPlugin") + * } + * // or + * plugin({ + * options.put("key", "value") // only for commonMain + * }) { + * getByName("myPlugin") + * } + * } + * } + * } + * ``` + */ public fun plugin( configure: Action? = null, select: NamedDomainObjectContainer.() -> ProtocPlugin, ) } +/** + * Returns the proto source set for this [KotlinSourceSet]. + */ public val KotlinSourceSet.proto: ProtoSourceSet get(): ProtoSourceSet { return project.protoSourceSets.getByName(name) } +/** + * Executes the given [action] on the proto source set for this [KotlinSourceSet]. + */ public fun KotlinSourceSet.proto(action: Action) { proto.apply(action::execute) } +/** + * Returns the proto source set for this [KotlinSourceSet]. + */ @get:JvmName("proto_kotlin") public val NamedDomainObjectProvider.proto: Provider get() { return map { it.proto } } +/** + * Executes the given [action] on the proto source set for this [KotlinSourceSet]. + */ @JvmName("proto_kotlin") public fun NamedDomainObjectProvider.proto(action: Action) { configure { @@ -52,20 +150,32 @@ public fun NamedDomainObjectProvider.proto(action: Action) { extensions.configure(ProtoSourceSet::class.java, action::execute) } +/** + * Returns the proto source set for this [SourceSet]. + */ public val NamedDomainObjectProvider.proto: Provider get() { return map { it.proto } } +/** + * Executes the given [action] on the proto source set for this [SourceSet]. + */ public fun NamedDomainObjectProvider.proto(action: Action) { configure { proto(action)