From 06a0daf89b028d8398fb057c78fc58602a7e0e4d Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Fri, 18 Jul 2025 18:36:48 +0200 Subject: [PATCH 01/15] Support KMP .proto sources sets --- .../src/main/kotlin/util/csm/template.kt | 2 +- gradle-plugin/build.gradle.kts | 12 +- .../src/main/kotlin/kotlinx/rpc/Extensions.kt | 13 +- .../main/kotlin/kotlinx/rpc/GrpcExtension.kt | 262 ------------------ .../kotlin/kotlinx/rpc/RpcGradlePlugin.kt | 5 +- .../kotlin/kotlinx/rpc/buf/BufExecutable.kt | 72 +++++ .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 26 ++ .../src/main/kotlin/kotlinx/rpc/buf/consts.kt | 9 + .../kotlinx/rpc/buf/tasks/BufExecTask.kt | 71 +++++ .../kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt | 166 +++++++++++ .../kotlinx/rpc/buf/tasks/BufGenerateTask.kt | 76 +++++ .../kotlinx/rpc/buf/tasks/BufYamlUpdate.kt | 94 +++++++ .../kotlin/kotlinx/rpc/compilerPlugins.kt | 4 +- .../kotlin/kotlinx/rpc/grpc/GrpcExtension.kt | 251 +++++++++++++++++ .../kotlin/kotlinx/rpc/grpc/protections.kt | 44 +++ .../kotlinx/rpc/proto/ProcessProtoFiles.kt | 48 ++++ .../kotlinx/rpc/proto/ProtoSourceSet.kt | 126 +++++++++ .../kotlin/kotlinx/rpc/proto/ProtocPlugin.kt | 117 ++++++++ .../main/kotlin/kotlinx/rpc/proto/consts.kt | 26 ++ .../kotlin/kotlinx/rpc/util/ProcessRunner.kt | 75 +++++ .../src/main/kotlin/kotlinx/rpc/util/files.kt | 20 ++ .../main/kotlin/kotlinx/rpc/util/gradle.kt | 17 ++ .../src/main/kotlin/kotlinx/rpc/util/kgp.kt | 31 +++ .../api/grpc-ktor-server-test.api | 0 grpc/grpc-ktor-server-test/build.gradle.kts | 41 --- grpc/grpc-ktor-server-test/gradle.properties | 5 - grpc/grpc-ktor-server/build.gradle.kts | 46 +++ .../src/jvmMain/proto/some.proto | 5 + .../rpc/grpc/ktor/server/test/TestServer.kt | 0 .../jvmTest}/proto/ktor-test-service.proto | 2 + .../src/jvmTest}/resources/logback.xml | 0 protobuf-plugin/build.gradle.kts | 33 +-- .../rpc/protobuf/ProtoToModelInterpreter.kt | 10 +- .../kotlinx/rpc/protobuf/RpcProtobufPlugin.kt | 4 +- .../{ => exclude}/empty_deprecated.proto | 0 .../proto/{ => exclude}/enum_options.proto | 16 +- .../test/proto/{ => exclude}/example.proto | 0 .../proto/{ => exclude}/multiple_files.proto | 0 .../test/proto/{ => exclude}/options.proto | 0 .../proto/{ => exclude}/with_comments.proto | 0 settings.gradle.kts | 2 - versions-root/libs.versions.toml | 1 + 42 files changed, 1377 insertions(+), 355 deletions(-) delete mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/GrpcExtension.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/util/ProcessRunner.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt delete mode 100644 grpc/grpc-ktor-server-test/api/grpc-ktor-server-test.api delete mode 100644 grpc/grpc-ktor-server-test/build.gradle.kts delete mode 100644 grpc/grpc-ktor-server-test/gradle.properties create mode 100644 grpc/grpc-ktor-server/src/jvmMain/proto/some.proto rename grpc/{grpc-ktor-server-test/src/test => grpc-ktor-server/src/jvmTest}/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt (100%) rename grpc/{grpc-ktor-server-test/src/test => grpc-ktor-server/src/jvmTest}/proto/ktor-test-service.proto (87%) rename grpc/{grpc-ktor-server-test/src/test => grpc-ktor-server/src/jvmTest}/resources/logback.xml (100%) rename protobuf-plugin/src/test/proto/{ => exclude}/empty_deprecated.proto (100%) rename protobuf-plugin/src/test/proto/{ => exclude}/enum_options.proto (50%) rename protobuf-plugin/src/test/proto/{ => exclude}/example.proto (100%) rename protobuf-plugin/src/test/proto/{ => exclude}/multiple_files.proto (100%) rename protobuf-plugin/src/test/proto/{ => exclude}/options.proto (100%) rename protobuf-plugin/src/test/proto/{ => exclude}/with_comments.proto (100%) diff --git a/gradle-conventions/src/main/kotlin/util/csm/template.kt b/gradle-conventions/src/main/kotlin/util/csm/template.kt index cbab27b58..ee10f5287 100644 --- a/gradle-conventions/src/main/kotlin/util/csm/template.kt +++ b/gradle-conventions/src/main/kotlin/util/csm/template.kt @@ -145,7 +145,7 @@ object CsmTemplateProcessor { throw GradleException("Wildcard is not allowed in 'from' part of kotlin version range: $from, $pattern") } - if (to.contains("-") || to.contains("-")) { + if (from.contains("-") || to.contains("-")) { throw GradleException("Non stable versions are not allowed in kotlin version range: $pattern") } diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index fddce380c..d1f6bed19 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -16,10 +16,11 @@ version = rootProject.libs.versions.kotlinx.rpc.get() kotlin { explicitApi() + + jvmToolchain(11) } dependencies { - implementation(libs.protobuf.gradle.plugin) compileOnly(libs.kotlin.gradle.plugin) } @@ -50,6 +51,7 @@ abstract class GeneratePluginVersionTask @Inject constructor( @get:Input val protobufVersion: String, @get:Input val grpcVersion: String, @get:Input val grpcKotlinVersion: String, + @get:Input val bufToolVersion: String, @get:OutputDirectory val sourcesDir: File ) : DefaultTask() { @TaskAction @@ -68,6 +70,7 @@ abstract class GeneratePluginVersionTask @Inject constructor( public const val PROTOBUF_VERSION: String = "$protobufVersion" public const val GRPC_VERSION: String = "$grpcVersion" public const val GRPC_KOTLIN_VERSION: String = "$grpcKotlinVersion" + public const val BUF_TOOL_VERSION: String = "$bufToolVersion" """.trimIndent() ) @@ -79,9 +82,10 @@ val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sou val generatePluginVersionTask = tasks.register( "generatePluginVersion", version.toString(), - libs.versions.protobuf.asProvider().get().toString(), - libs.versions.grpc.asProvider().get().toString(), - libs.versions.grpc.kotlin.get().toString(), + libs.versions.protobuf.asProvider().get(), + libs.versions.grpc.asProvider().get(), + libs.versions.grpc.kotlin.get(), + libs.versions.buf.tool.get(), sourcesDir, ) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt index eeb1d81cd..3e10826d2 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt @@ -6,6 +6,7 @@ package kotlinx.rpc +import kotlinx.rpc.grpc.GrpcExtension import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.model.ObjectFactory @@ -14,11 +15,12 @@ import org.gradle.kotlin.dsl.findByType import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.newInstance import org.gradle.kotlin.dsl.property +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject -public fun Project.rpcExtension(): RpcExtension = extensions.findByType() ?: RpcExtension(objects) +public fun Project.rpcExtension(): RpcExtension = extensions.findByType() ?: RpcExtension(objects, this) -public open class RpcExtension @Inject constructor(objects: ObjectFactory) { +public open class RpcExtension @Inject constructor(objects: ObjectFactory, private val project: Project) { /** * Controls `@Rpc` [annotation type-safety](https://github.com/Kotlin/kotlinx-rpc/pull/240) compile-time checkers. * @@ -42,10 +44,15 @@ public open class RpcExtension @Inject constructor(objects: ObjectFactory) { configure.execute(strict) } + internal val grpcApplied = AtomicBoolean(false) + /** * Grpc settings. */ - public val grpc: GrpcExtension = objects.newInstance() + public val grpc: GrpcExtension by lazy { + grpcApplied.set(true) + objects.newInstance() + } /** * Grpc settings. diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/GrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/GrpcExtension.kt deleted file mode 100644 index 6a9c1674e..000000000 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/GrpcExtension.kt +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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 - -import com.google.protobuf.gradle.ExecutableLocator -import com.google.protobuf.gradle.GenerateProtoTask -import com.google.protobuf.gradle.ProtobufExtension -import org.gradle.api.Action -import org.gradle.api.GradleException -import org.gradle.api.Project -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Property -import org.gradle.api.specs.Spec -import org.gradle.api.tasks.TaskCollection -import org.gradle.kotlin.dsl.findByType -import org.gradle.kotlin.dsl.property -import org.gradle.kotlin.dsl.the -import javax.inject.Inject - -public open class GrpcExtension @Inject constructor(objects: ObjectFactory, private val project: Project) { - /** - * Determines whether the gRPC support is enabled in the project. - * - * Allows for additional configuration checks. - */ - public val enabled: Property = objects.property().convention(false) - - /** - * Access for [GrpcPlugin] for [LOCATOR_NAME] name. - */ - public fun plugin(action: Action) { - pluginAccess(action, LOCATOR_NAME) - } - - /** - * Access for [GrpcPlugin] for [GRPC_JAVA_LOCATOR_NAME] name. - */ - public fun grpcJavaPlugin(action: Action) { - pluginAccess(action, GRPC_JAVA_LOCATOR_NAME) - } - - /** - * Access for [GrpcPlugin] for [GRPC_KOTLIN_LOCATOR_NAME] name. - */ - public fun grpcKotlinPlugin(action: Action) { - pluginAccess(action, GRPC_KOTLIN_LOCATOR_NAME) - } - - /** - * Shortcut for - * - * ```kotlin - * protobuf { - * generateProtoTasks.all().all { ... } - * } - * ``` - */ - public fun tasks(taskAction: Action) { - project.the().generateProtoTasks.all().all(taskAction) - } - - /** - * Shortcut for - * - * ```kotlin - * protobuf { - * generateProtoTasks.all().matching { ... } - * } - * ``` - */ - public fun tasksMatching(spec: Spec): TaskCollection { - return project.the().generateProtoTasks.all().matching(spec) - } - - private fun pluginAccess(action: Action, locatorName: String) { - val extension = project.the() - val plugin = object : GrpcPlugin { - override fun options(optionsAction: Action) { - extension.generateProtoTasks.all().all { - optionsAction.execute(plugins.maybeCreate(locatorName)) - } - } - - override fun locator(locatorAction: Action) { - extension.plugins { - locatorAction.execute(maybeCreate(locatorName)) - } - } - } - - action.execute(plugin) - } - - public companion object { - /** - * [com.google.protobuf.gradle.ExecutableLocator]'s name for the `kotlinx-rpc` plugin - * - * ```kotlin - * protobuf { - * plugins { - * named(LOCATOR_NAME) - * } - * } - * ``` - * - * Same name is used for [GenerateProtoTask] plugin in `generateProtoTasks.plugins` - */ - public const val LOCATOR_NAME: String = "kotlinx-rpc" - - /** - * [com.google.protobuf.gradle.ExecutableLocator]'s name for the `grpc-java` plugin - * - * ```kotlin - * protobuf { - * plugins { - * named(GRPC_JAVA_LOCATOR_NAME) - * } - * } - * ``` - * - * Same name is used for [GenerateProtoTask] plugin in `generateProtoTasks.plugins` - */ - public const val GRPC_JAVA_LOCATOR_NAME: String = "grpc" - - /** - * [com.google.protobuf.gradle.ExecutableLocator]'s name for the `grpc-kotlin` plugin - * - * ```kotlin - * protobuf { - * plugins { - * named(GRPC_KOTLIN_LOCATOR_NAME) - * } - * } - * ``` - * - * Same name is used for [GenerateProtoTask] plugin in `generateProtoTasks.plugins` - */ - public const val GRPC_KOTLIN_LOCATOR_NAME: String = "grpckt" - } -} - -/** - * Access to a specific protobuf plugin. - */ -public interface GrpcPlugin { - /** - * Access for [GenerateProtoTask.PluginOptions] - * - * ```kotlin - * rpc { - * grpc { - * plugin { - * options { - * option("option=value") - * } - * } - * } - * } - * ``` - */ - public fun options(optionsAction: Action) - - /** - * Access for [ExecutableLocator] - * - * ```kotlin - * rpc { - * grpc { - * plugin { - * locator { - * path = "$buildDirPath/libs/protobuf-plugin-$version-all.jar" - * } - * } - * } - * } - * ``` - */ - public fun locator(locatorAction: Action) -} - -internal fun Project.configureGrpc() { - val grpc = rpcExtension().grpc - var wasApplied = false - - pluginManager.withPlugin("com.google.protobuf") { - if (wasApplied) { - return@withPlugin - } - - wasApplied = true - - val protobuf = extensions.findByType() - ?: run { - logger.error("Protobuf plugin (com.google.protobuf) was not applied. Please report of this issue.") - return@withPlugin - } - - protobuf.configureProtobuf(project = project) - } - - afterEvaluate { - if (grpc.enabled.get() && !wasApplied) { - throw GradleException( - """ - gRPC Support is enabled, but 'com.google.protobuf' was not be applied during project evaluation. - The 'com.google.protobuf' plugin must be applied to the project first. - """.trimIndent() - ) - } - } -} - -private fun ProtobufExtension.configureProtobuf(project: Project) { - val buildDirPath: String = project.layout.buildDirectory.get().asFile.absolutePath - - protoc { - artifact = "com.google.protobuf:protoc:$PROTOBUF_VERSION" - } - - plugins { - val existed = findByName(GrpcExtension.LOCATOR_NAME) != null - maybeCreate(GrpcExtension.LOCATOR_NAME).apply { - if (!existed) { - artifact = "org.jetbrains.kotlinx:kotlinx-rpc-protobuf-plugin:$LIBRARY_VERSION:all@jar" - } - } - - val grpcJavaPluginExisted = findByName(GrpcExtension.GRPC_JAVA_LOCATOR_NAME) != null - maybeCreate(GrpcExtension.GRPC_JAVA_LOCATOR_NAME).apply { - if (!grpcJavaPluginExisted) { - artifact = "io.grpc:protoc-gen-grpc-java:$GRPC_VERSION" - } - } - - val grpcKotlinPluginExisted = findByName(GrpcExtension.GRPC_KOTLIN_LOCATOR_NAME) != null - maybeCreate(GrpcExtension.GRPC_KOTLIN_LOCATOR_NAME).apply { - if (!grpcKotlinPluginExisted) { - artifact = "io.grpc:protoc-gen-grpc-kotlin:$GRPC_KOTLIN_VERSION:jdk8@jar" - } - } - } - - generateProtoTasks { - all().all { - plugins { - val existed = findByName(GrpcExtension.LOCATOR_NAME) != null - maybeCreate(GrpcExtension.LOCATOR_NAME).apply { - if (!existed) { - option("debugOutput=$buildDirPath/protobuf-plugin.log") - option("messageMode=interface") - } - } - - maybeCreate(GrpcExtension.GRPC_JAVA_LOCATOR_NAME) - - maybeCreate(GrpcExtension.GRPC_KOTLIN_LOCATOR_NAME) - } - } - } -} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt index ff83bc2fa..66442901c 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt @@ -4,6 +4,8 @@ package kotlinx.rpc +import kotlinx.rpc.grpc.configurePluginProtections +import kotlinx.rpc.proto.createProtoExtensions import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.create @@ -15,7 +17,8 @@ public class RpcGradlePlugin : Plugin { applyCompilerPlugin(target) - target.configureGrpc() + target.createProtoExtensions() + target.configurePluginProtections() } private fun applyCompilerPlugin(target: Project) { diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt new file mode 100644 index 000000000..15199fc3a --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt @@ -0,0 +1,72 @@ +/* + * 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 + +import kotlinx.rpc.BUF_TOOL_VERSION +import kotlinx.rpc.buf.tasks.BufExecTask +import kotlinx.rpc.util.ProcessRunner +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + + +// See: https://github.com/bufbuild/buf-gradle-plugin/blob/1bc48078880887797db3aa412d6a3fea60461276/src/main/kotlin/build/buf/gradle/BufSupport.kt#L28 +internal fun Project.configureBufExecutable() { + configurations.create(BUF_EXECUTABLE_CONFIGURATION) + + val os = System.getProperty("os.name").lowercase() + val osPart = + when { + os.startsWith("windows") -> "windows" + os.startsWith("linux") -> "linux" + os.startsWith("mac") -> "osx" + else -> error("unsupported os: $os") + } + + val archPart = + when (val arch = System.getProperty("os.arch").lowercase()) { + in setOf("x86_64", "amd64") -> "x86_64" + in setOf("arm64", "aarch64") -> "aarch_64" + else -> error("unsupported arch: $arch") + } + + dependencies { + add( + BUF_EXECUTABLE_CONFIGURATION, + mapOf( + "group" to "build.buf", + "name" to "buf", + "version" to BUF_TOOL_VERSION, + "classifier" to "$osPart-$archPart", + "ext" to "exe", + ), + ) + } +} + +internal fun BufExecTask.execBuf(args: Iterable) { + val executable = bufExecutable.get() + + if (!executable.canExecute()) { + executable.setExecutable(true) + } + + val config = configFile.orNull + val configArgs = if (config != null) listOf("--config", config.absolutePath) else emptyList() + + val processArgs = listOf(executable.absolutePath) + configArgs + args + + val workingDirValue = workingDir.get() + + logger.info("Running buf from $workingDirValue: `buf ${args.joinToString(" ")}`") + + val result = ProcessRunner().use { it.shell("buf", workingDirValue, processArgs) } + + if (result.exitCode != 0) { + throw GradleException(result.formattedOutput()) + } else { + logger.info(result.formattedOutput()) + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt new file mode 100644 index 000000000..4daaa785e --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.provider.Property +import org.gradle.kotlin.dsl.property +import java.io.File +import javax.inject.Inject + +public open class BufExtension @Inject constructor(internal val project: Project) { + public val configFile: Property = project.objects.property() + + public val generate: BufGenerateExtension = project.objects.newInstance(BufGenerateExtension::class.java, project) + + public fun generate(configure: Action) { + configure.execute(generate) + } +} + +public open class BufGenerateExtension @Inject constructor(internal val project: Project) { + public val includeImports: Property = project.objects.property().convention(false) +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt new file mode 100644 index 000000000..0d2f3c0b9 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt @@ -0,0 +1,9 @@ +/* + * 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 + +public const val BUF_GEN_YAML: String = "buf.gen.yaml" +public const val BUF_YAML: String = "buf.yaml" +public const val BUF_EXECUTABLE_CONFIGURATION: String = "bufExecutable" 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 new file mode 100644 index 000000000..06f094873 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt @@ -0,0 +1,71 @@ +/* + * 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 kotlinx.rpc.buf.BUF_EXECUTABLE_CONFIGURATION +import kotlinx.rpc.buf.execBuf +import kotlinx.rpc.proto.PROTO_GROUP +import kotlinx.rpc.rpcExtension +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.register +import java.io.File +import kotlin.reflect.KClass + +public abstract class BufExecTask : DefaultTask() { + init { + group = PROTO_GROUP + } + + @get:InputFile + internal abstract val bufExecutable: Property + + @get:Input + public abstract val command: Property + + @get:Input + public abstract val args: ListProperty + + @get:Input + public abstract val workingDir: Property + + @get:InputFile + @get:Optional + public abstract val configFile: Property + + @TaskAction + public fun exec() { + execBuf(listOf(command.get()) + args.get()) + } +} + +public inline fun Project.registerBufExecTask( + name: String, + workingDir: Provider, + noinline configuration: T.() -> Unit, +): TaskProvider = registerBufExecTask(T::class, name, workingDir, configuration) + +@PublishedApi +internal fun Project.registerBufExecTask( + clazz: KClass, + name: String, + workingDir: Provider, + configuration: T.() -> Unit, +): TaskProvider = tasks.register(name, clazz) { + val executableConfiguration = configurations.getByName(BUF_EXECUTABLE_CONFIGURATION) + bufExecutable.set(executableConfiguration.singleFile) + this.workingDir.set(workingDir) + configFile.set(project.rpcExtension().grpc.buf.configFile) + + configuration() +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt new file mode 100644 index 000000000..d5338c10d --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt @@ -0,0 +1,166 @@ +/* + * 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 kotlinx.rpc.buf.BUF_GEN_YAML +import kotlinx.rpc.proto.PROTO_GROUP +import kotlinx.rpc.proto.ProtocPlugin +import kotlinx.rpc.proto.protoBuildDirSourceSets +import kotlinx.rpc.util.ensureRegularFileExists +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.register +import java.io.File +import java.io.Serializable + +internal data class ResolvedGrpcPlugin( + val type: Type, + val locator: List, + val out: String, + val options: Map, + val strategy: String?, + val includeImports: Boolean?, + val includeWrk: Boolean?, + val types: List, + val excludeTypes: List, +) : Serializable { + @Suppress("EnumEntryName") + enum class Type { + local, remote, + ; + } +} + +public abstract class BufGenYamlUpdate : DefaultTask() { + @get:Input + internal abstract val plugins: ListProperty + + @get:OutputFile + public abstract val bufGenFile: Property + + init { + group = PROTO_GROUP + } + + @TaskAction + internal fun generate() { + val file = bufGenFile.get() + if (!file.exists()) { + file.parentFile.mkdirs() + file.createNewFile() + } + + bufGenFile.get().bufferedWriter(Charsets.UTF_8).use { writer -> + writer.appendLine("version: v2") + writer.appendLine("clean: true") + writer.appendLine("plugins:") + plugins.get().forEach { plugin -> + val locatorLine = when (plugin.type) { + ResolvedGrpcPlugin.Type.local -> { + when (plugin.locator.size) { + 0 -> error("Local plugin without locators") + 1 -> plugin.locator.single() + else -> plugin.locator.joinToString(", ", prefix = "[", postfix = "]") + } + } + + ResolvedGrpcPlugin.Type.remote -> plugin.locator.single() + } + + writer.appendLine(" - ${plugin.type.name}: $locatorLine") + if (plugin.strategy != null) { + writer.appendLine(" strategy: ${plugin.strategy}") + } + if (plugin.includeImports != null) { + writer.appendLine(" include_imports: ${plugin.includeImports}") + } + if (plugin.type == ResolvedGrpcPlugin.Type.local && plugin.includeWrk != null) { + writer.appendLine(" include_wrk: ${plugin.includeWrk}") + } + if (plugin.types.isNotEmpty()) { + writer.appendLine(" types:") + plugin.types.forEach { type -> + writer.appendLine(" - $type") + } + } + if (plugin.excludeTypes.isNotEmpty()) { + writer.appendLine(" exclude_types:") + plugin.excludeTypes.forEach { type -> + writer.appendLine(" - $type") + } + } + writer.appendLine(" out: ${plugin.out}") + if (plugin.options.isNotEmpty()) { + writer.appendLine(" opt:") + plugin.options.forEach { (key, value) -> + writer.appendLine(" - $key${if (value != null) "=$value" else ""}") + } + } + } + + writer.flush() + } + } + + public companion object { + public const val NAME_PREFIX: String = "bufGenYamlUpdate" + } +} + +internal fun Project.registerBufGenYamlUpdateTask( + name: String, + dir: String, + protocPlugins: Iterable, + configure: BufGenYamlUpdate.() -> Unit = {}, +): TaskProvider { + val capitalizeName = name.replaceFirstChar { it.uppercase() } + return project.tasks.register("${BufGenYamlUpdate.NAME_PREFIX}$capitalizeName") { + val pluginsProvider = project.provider { + protocPlugins.map { plugin -> + if (!plugin.artifact.isPresent) { + throw GradleException("Artifact is not specified for protoc plugin ${plugin.name}") + } + + val artifact = plugin.artifact.get() + val locator = when (artifact) { + is ProtocPlugin.Artifact.Local -> artifact.executor.get() + is ProtocPlugin.Artifact.Remote -> listOf(artifact.locator.get()) + } + + ResolvedGrpcPlugin( + type = artifact.type, + locator = locator, + options = plugin.options.get(), + out = plugin.name, + strategy = plugin.strategy.orNull?.name?.lowercase(), + includeImports = plugin.includeImports.orNull, + includeWrk = plugin.includeWrk.orNull, + types = plugin.types.get(), + excludeTypes = plugin.excludeTypes.get(), + ) + } + } + + plugins.set(pluginsProvider) + + val bufGenYamlFile = project.protoBuildDirSourceSets + .resolve(dir) + .resolve(BUF_GEN_YAML) + .apply { + ensureRegularFileExists() + } + + bufGenFile.set(bufGenYamlFile) + + configure() + } +} 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 new file mode 100644 index 000000000..e954214c7 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt @@ -0,0 +1,76 @@ +/* + * 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 kotlinx.rpc.proto.PROTO_GROUP +import kotlinx.rpc.rpcExtension +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskProvider +import java.io.File + +public abstract class BufGenerateTask : BufExecTask() { + @get:Input + @get:Optional + public abstract val additionalArgs: ListProperty + + /** Whether to include imports. */ + @get:Input + internal abstract val includeImports: Property + + /** The input proto files. */ + @get:InputFiles + internal abstract val inputFiles: ConfigurableFileCollection + + /** The directory to output generated files. */ + @get:OutputDirectory + internal abstract val outputDirectory: Property + + init { + command.set("generate") + + val args = project.provider { + listOfNotNull( + "--output", outputDirectory.get().absolutePath, + if (includeImports.orNull == true) "--include-imports" else null, + ) + additionalArgs.orNull.orEmpty() + } + + this.args.set(args) + } + + public companion object { + internal const val NAME_PREFIX: String = "bufGenerate" + } +} + +internal fun Project.registerBufGenerateTask( + name: String, + workingDir: Provider, + inputFiles: Provider, + outputDirectory: Provider, + configure: BufGenerateTask.() -> Unit = {}, +): TaskProvider { + return registerBufExecTask(name, workingDir) { + group = PROTO_GROUP + description = "Generates code from .proto files" + + val generate = project.rpcExtension().grpc.buf.generate + + includeImports.set(generate.includeImports) + this.inputFiles.setFrom(inputFiles) + this.outputDirectory.set(outputDirectory) + + configure() + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt new file mode 100644 index 000000000..ebd5fabbc --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt @@ -0,0 +1,94 @@ +/* + * 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 kotlinx.rpc.buf.BUF_YAML +import kotlinx.rpc.proto.PROTO_FILES_DIR +import kotlinx.rpc.proto.PROTO_GROUP +import kotlinx.rpc.proto.protoBuildDirSourceSets +import kotlinx.rpc.util.ensureDirectoryExists +import kotlinx.rpc.util.ensureRegularFileExists +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.register +import java.io.File + +public abstract class BufYamlUpdate : DefaultTask() { + @get:InputDirectory + internal abstract val protoSourceDir: Property + + @get:OutputFile + public abstract val bufFile: Property + + init { + group = PROTO_GROUP + } + + @TaskAction + internal fun generate() { + val file = bufFile.get() + + if (!file.exists()) { + file.parentFile.mkdirs() + file.createNewFile() + } + + file.bufferedWriter(Charsets.UTF_8).use { writer -> + writer.appendLine("version: v2") + writer.appendLine("lint:") + writer.appendLine(" use:") + writer.appendLine(" - STANDARD") + writer.appendLine("breaking:") + writer.appendLine(" use:") + writer.appendLine(" - FILE") + + writer.appendLine("modules:") + + val protoDir = protoSourceDir.get() + if (protoDir.exists()) { + val modulePath = protoDir.relativeTo(file.parentFile) + writer.appendLine(" - path: $modulePath") + } + + writer.flush() + } + } + + public companion object { + public const val PREFIX_NAME: String = "bufYamlUpdate" + } +} + +internal fun Project.registerBufYamlUpdateTask( + name: String, + dir: String, + configure: BufYamlUpdate.() -> Unit = {}, +): TaskProvider { + val capitalizeName = name.replaceFirstChar { it.uppercase() } + return tasks.register("${BufYamlUpdate.PREFIX_NAME}$capitalizeName") { + val protoDir = project.protoBuildDirSourceSets.resolve(dir).resolve(PROTO_FILES_DIR) + protoDir.ensureDirectoryExists() + + protoSourceDir.set(protoDir) + + val bufYamlFile = project.protoBuildDirSourceSets + .resolve(dir) + .resolve(BUF_YAML) + .apply { + ensureRegularFileExists() + } + + bufFile.set(bufYamlFile) + + configure() + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt index fb74690ca..0e75571ac 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt @@ -6,7 +6,6 @@ package kotlinx.rpc -import org.gradle.kotlin.dsl.findByType import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin import org.jetbrains.kotlin.gradle.plugin.SubpluginOption @@ -26,8 +25,7 @@ internal class CompilerPluginCli : KotlinCompilerPluginSupportPlugin by compiler pluginSuffix = "-cli" applyToCompilation = { - val extension = it.target.project.extensions.findByType() - ?: RpcExtension(it.target.project.objects) + val extension = it.target.project.rpcExtension() it.target.project.provider { listOf( diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt new file mode 100644 index 000000000..7ecc64371 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt @@ -0,0 +1,251 @@ +/* + * 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.grpc + +import kotlinx.rpc.LIBRARY_VERSION +import kotlinx.rpc.buf.BufExtension +import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA +import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN +import kotlinx.rpc.proto.ProtocPlugin.Companion.KXRPC +import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA +import kotlinx.rpc.buf.configureBufExecutable +import kotlinx.rpc.buf.tasks.BufGenerateTask +import kotlinx.rpc.buf.tasks.registerBufGenYamlUpdateTask +import kotlinx.rpc.buf.tasks.registerBufGenerateTask +import kotlinx.rpc.buf.tasks.registerBufYamlUpdateTask +import kotlinx.rpc.proto.ProtoSourceSet +import kotlinx.rpc.proto.ProtocPlugin +import kotlinx.rpc.proto.configureProtoExtensions +import kotlinx.rpc.proto.grpcJava +import kotlinx.rpc.proto.grpcKotlin +import kotlinx.rpc.proto.kxrpc +import kotlinx.rpc.proto.protoBuildDirGenerated +import kotlinx.rpc.proto.protoBuildDirSourceSets +import kotlinx.rpc.proto.protoSourceSets +import kotlinx.rpc.proto.protobufJava +import kotlinx.rpc.proto.registerProcessProtoFilesTask +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.kotlin.dsl.findByType +import org.gradle.kotlin.dsl.newInstance +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import javax.inject.Inject + +public open class GrpcExtension @Inject constructor( + objects: ObjectFactory, + private val project: Project, +) { + public val protocPlugins: NamedDomainObjectContainer = + objects.domainObjectContainer(ProtocPlugin::class.java) { name -> + ProtocPlugin(name, project) + } + + public fun protocPlugins(action: Action>) { + action.execute(protocPlugins) + } + + public val buf: BufExtension = project.objects.newInstance() + public fun buf(action: Action) { + action.execute(buf) + } + + init { + project.configureBufExecutable() + + createDefaultProtocPlugins() + + project.configureProtoExtensions { _, _, protoSourceSet -> + protoSourceSet.protocPlugin(protocPlugins.protobufJava) + protoSourceSet.protocPlugin(protocPlugins.grpcJava) + protoSourceSet.protocPlugin(protocPlugins.grpcKotlin) + protoSourceSet.protocPlugin(protocPlugins.kxrpc) + } + + project.afterEvaluate { + project.protoSourceSets.forEach { sourceSet -> + configureTasks(sourceSet) + } + } + } + + private fun Project.configureTasks(protoSourceSet: ProtoSourceSet) { + val baseName = protoSourceSet.baseName.get() + val baseGenDir = project.protoBuildDirSourceSets.resolve(baseName) + + val protocPluginNames = protoSourceSet.collectProtocPlugins().distinct() + + val includedProtocPlugins = protocPluginNames.map { + protocPlugins.findByName(it) + ?: throw GradleException("Protoc plugin $it not found") + } + + val protoFiles = protoSourceSet.proto + val hasFiles = !protoFiles.isEmpty + + val bufGenUpdateTask = project.registerBufGenYamlUpdateTask( + name = protoSourceSet.name, + dir = baseName, + protocPlugins = includedProtocPlugins, + ) { + onlyIf { hasFiles } + } + + val bufUpdateTask = project.registerBufYamlUpdateTask(protoSourceSet.name, baseName) { + onlyIf { hasFiles } + } + + val processProtoTask = project.registerProcessProtoFilesTask( + name = protoSourceSet.name, + baseGenDir = project.provider { baseGenDir }, + protoFiles = protoFiles, + ) { + onlyIf { hasFiles } + + dependsOn(bufGenUpdateTask) + dependsOn(bufUpdateTask) + } + + val out = project.protoBuildDirGenerated.resolve(baseName) + + val capitalName = protoSourceSet.name.replaceFirstChar { it.uppercase() } + val bufGenerateTask = project.registerBufGenerateTask( + name = "${BufGenerateTask.NAME_PREFIX}$capitalName", + workingDir = provider { baseGenDir }, + inputFiles = processProtoTask.map { it.outputs.files }, + outputDirectory = provider { out }, + ) { + dependsOn(bufGenUpdateTask) + dependsOn(bufUpdateTask) + dependsOn(processProtoTask) + + onlyIf { hasFiles } + } + + project.tasks.withType().configureEach { + // compileKotlin - main + // compileTestKotlin - test + // compileKotlinJvm - jvmMain + // compileTestKotlinJvm - jvmTest + // compileKotlinIosArm64 - iosArm64Main + // compileTestKotlinIosArm64 - iosArm64Test + val taskNameAsSourceSet = name + .removePrefix("compile").let { + val suffix = it.substringBefore("Kotlin").takeIf { + prefix -> prefix.isNotEmpty() + } ?: "Main" + + (it.substringAfter("Kotlin") + .replaceFirstChar { ch -> ch.lowercase() } + suffix) + .takeIf { result -> result != suffix } + ?: suffix.lowercase() + } + + if (taskNameAsSourceSet == baseName) { + dependsOn(bufGenerateTask) + } + } + + project.tasks.withType().configureEach { + // compileJvmTestJava - test (java, kmp) + // compileJvmMainJava - main (java, kmp) + // compileJava - main (java) + // compileTestJava - test (java) + val taskNameAsSourceSet = when (name) { + "compileJvmTestJava" -> "test" + "compileJvmMainJava" -> "main" + "compileJava" -> "main" + "compileTestJava" -> "test" + + else -> throw GradleException("Unknown java compile task name: $name") + } + + if (taskNameAsSourceSet == baseName) { + dependsOn(bufGenerateTask) + } + } + + includedProtocPlugins.forEach { plugin -> + // locates correctly jvmMain, main jvmTest, test + val javaSourceSet = project.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' source set" + ) + } + } + } + + private fun createDefaultProtocPlugins() { + protocPlugins.create(KXRPC) { + remote { + locator.set("buf.build/kxrpc/grpc:${LIBRARY_VERSION}") + } + + options.put("debugOutput", "protobuf-kxrpc-plugin.log") + options.put("messageMode", "interface") + } + + protocPlugins.create(GRPC_JAVA) { + isJava.set(true) + + remote { + locator.set("buf.build/grpc/java") + } + } + + protocPlugins.create(GRPC_KOTLIN) { + remote { + locator.set("buf.build/grpc/kotlin") + } + } + + protocPlugins.create(PROTOBUF_JAVA) { + isJava.set(true) + + remote { + locator.set("buf.build/protocolbuffers/java") + } + } + } + + private fun ProtoSourceSet.collectProtocPlugins(): List { + return when { + name.endsWith("Main") -> { + protocPlugins.get() + } + + name.endsWith("Test") -> { + val main = project.protoSourceSets + .getByName(correspondingMainName()) + + main.collectProtocPlugins() + protocPlugins.get() + } + + else -> throw GradleException("Unknown source set name: $name") + } + } + + private fun ProtoSourceSet.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/main/kotlin/kotlinx/rpc/grpc/protections.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt new file mode 100644 index 000000000..c44c2e582 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt @@ -0,0 +1,44 @@ +/* + * 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.grpc + +import kotlinx.rpc.rpcExtension +import org.gradle.api.GradleException +import org.gradle.api.Project + +private const val BUF_PLUGIN_ID = "build.buf" +private const val PROTOBUF_PLUGIN_ID = "com.google.protobuf" + +internal fun Project.configurePluginProtections() { + var isBufPluginApplied = false + var isProtobufPluginApplied = false + + pluginManager.withPlugin(BUF_PLUGIN_ID) { isBufPluginApplied = true } + pluginManager.withPlugin(PROTOBUF_PLUGIN_ID) { isProtobufPluginApplied = true } + + afterEvaluate { + if (!rpcExtension().grpcApplied.get()) { + return@afterEvaluate + } + + if (isBufPluginApplied && !isProtobufPluginApplied) { + throw GradleException( + "Buf plugin ($BUF_PLUGIN_ID) can't be applied to the project, it is not compatible with the Rpc Gradle Plugin " + ) + } + + if (isProtobufPluginApplied && !isBufPluginApplied) { + throw GradleException( + "Protobuf plugin ($PROTOBUF_PLUGIN_ID) can't be applied to the project, it is not compatible with the Rpc Gradle Plugin " + ) + } + + if (isBufPluginApplied) { + throw GradleException( + "Both Buf ($BUF_PLUGIN_ID) and Protobuf ($PROTOBUF_PLUGIN_ID) plugins can't be applied to the project, they are not compatible with the Rpc Gradle Plugin " + ) + } + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt new file mode 100644 index 000000000..262f22c98 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt @@ -0,0 +1,48 @@ +/* + * 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.proto + +import org.gradle.api.Project +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 +import java.io.File + +public abstract class ProcessProtoFiles : Copy() { + init { + group = PROTO_GROUP + } +} + +internal fun Project.registerProcessProtoFilesTask( + name: String, + baseGenDir: Provider, + protoFiles: SourceDirectorySet, + configure: ProcessProtoFiles.() -> Unit = {}, +): TaskProvider { + val capitalName = name.replaceFirstChar { it.uppercase() } + + return tasks.register("process${capitalName}ProtoFiles") { + val protoGenDir = baseGenDir.map { it.resolve(PROTO_FILES_DIR) } + + val allFiles = protoFiles.files + + from(protoFiles.srcDirs) { + include { + it.file in allFiles + } + } + + into(protoGenDir) + + doFirst { + protoGenDir.get().deleteRecursively() + } + + configure() + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt new file mode 100644 index 000000000..5f2fe8fec --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt @@ -0,0 +1,126 @@ +/* + * 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.proto + +import kotlinx.rpc.util.findOrCreate +import kotlinx.rpc.util.withKotlinJvmExtension +import kotlinx.rpc.util.withKotlinKmpExtension +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.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.property +import javax.inject.Inject + +public typealias ProtoSourceSets = NamedDomainObjectContainer + +@Suppress("UNCHECKED_CAST") +internal val Project.protoSourceSets: ProtoSourceSets + get() = extensions.findByName(PROTO_SOURCE_SETS) as? ProtoSourceSets + ?: throw GradleException("Unable to find proto source sets in project $name") + +internal class ProtoSourceSetFactory(private val project: Project) : NamedDomainObjectFactory { + override fun create(name: String): ProtoSourceSet { + return project.objects.newInstance(ProtoSourceSet::class.java, project, name) + } +} + +public open class ProtoSourceSet @Inject constructor(internal val project: Project, public val name: String) { + internal val baseName: Property = project.objects.property() + internal val languageSourceSets: ListProperty = project.objects.listProperty() + internal val protocPlugins: ListProperty = project.objects.listProperty().convention(emptyList()) + + public fun protocPlugin(plugin: NamedDomainObjectProvider) { + protocPlugins.add(plugin.name) + } + + public fun protocPlugin(plugin: ProtocPlugin) { + protocPlugins.add(plugin.name) + } + + public val proto: SourceDirectorySet = project.objects.sourceDirectorySet(PROTO_SOURCE_DIRECTORY_NAME, "Proto sources").apply { + srcDirs(baseName.map { "src/$it/proto" }) + } + + public fun proto(action: Action) { + action.execute(proto) + } +} + +internal fun Project.configureProtoExtensions( + configure: Project.(languageSourceSetName: String, languageSourceSet: Any, protoSourceSet: ProtoSourceSet) -> Unit +) { + fun findOrCreateAndConfigure(languageSourceSetName: String, languageSourceSet: Any) { + val protoName = languageSourceSetName.sourceSetToProtoName() + val container = project.findOrCreate(PROTO_SOURCE_SETS) { + val container = objects.domainObjectContainer( + ProtoSourceSet::class.java, + ProtoSourceSetFactory(project) + ) + + project.extensions.add(PROTO_SOURCE_SETS, container) + + container + } + + val protoSourceSet = container.maybeCreate(protoName) + + configure(languageSourceSetName, languageSourceSet, protoSourceSet) + } + + project.withKotlinJvmExtension { + sourceSets.configureEach { + if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { + findOrCreateAndConfigure(name, this) + } + } + + project.extensions.configure("sourceSets") { + configureEach { + if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { + findOrCreateAndConfigure(name, this) + } + } + } + } + + project.withKotlinKmpExtension { + sourceSets.configureEach { + if (name == "jvmMain" || name == "jvmTest") { + findOrCreateAndConfigure(name, this) + } + } + } +} + +private fun String.sourceSetToProtoName(): String { + return when { + this == "main" -> "protoMain" + this == "test" -> "protoTest" + endsWith("Main") -> "${removeSuffix("Main")}ProtoMain" + endsWith("Test") -> "${removeSuffix("Test")}ProtoTest" + else -> throw IllegalArgumentException("Unsupported source set name: $this") + } +} + +internal fun Project.createProtoExtensions() { + configureProtoExtensions { languageSourceSetName, languageSourceSet, sourceSet -> + sourceSet.initExtension(languageSourceSetName, languageSourceSet) + } +} + +private fun ProtoSourceSet.initExtension(languageSourceSetName: String, languageSourceSet: Any) { + baseName.set(languageSourceSetName) + this.languageSourceSets.add(languageSourceSet) +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt new file mode 100644 index 000000000..35931121a --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt @@ -0,0 +1,117 @@ +/* + * 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.proto + +import kotlinx.rpc.buf.tasks.ResolvedGrpcPlugin +import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA +import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN +import kotlinx.rpc.proto.ProtocPlugin.Companion.KXRPC +import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA +import org.gradle.api.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.NamedDomainObjectProvider +import org.gradle.api.Project +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.mapProperty +import org.gradle.kotlin.dsl.property + +public val NamedDomainObjectContainer.kxrpc: NamedDomainObjectProvider + get() = named(KXRPC) + +public fun NamedDomainObjectContainer.kxrpc(action: Action) { + kxrpc.configure(action) +} + +public val NamedDomainObjectContainer.protobufJava: NamedDomainObjectProvider + get() = named(PROTOBUF_JAVA) + +public fun NamedDomainObjectContainer.protobufJava(action: Action) { + protobufJava.configure(action) +} + +public val NamedDomainObjectContainer.grpcJava: NamedDomainObjectProvider + get() = named(GRPC_JAVA) + +public fun NamedDomainObjectContainer.grpcJava(action: Action) { + grpcJava.configure(action) +} + +public val NamedDomainObjectContainer.grpcKotlin: NamedDomainObjectProvider + get() = named(GRPC_KOTLIN) + +public fun NamedDomainObjectContainer.grpcKotlin(action: Action) { + grpcKotlin.configure(action) +} + +/** + * Access to a specific protoc plugin. + */ +public open class ProtocPlugin( + public val name: String, + private val project: Project, +) { + public val isJava: Property = project.objects.property().convention(false) + + public val options: MapProperty = project.objects + .mapProperty() + .convention(emptyMap()) + + public fun local(action: Action) { + artifact.set(Artifact.Local(project).apply(action::execute)) + } + + public fun remote(action: Action) { + artifact.set(Artifact.Remote(project).apply(action::execute)) + } + + public val artifact: Property = project.objects.property() + + public val strategy: Property = project.objects.property().convention(null) + public val includeImports: Property = project.objects.property().convention(null) + public val includeWrk: Property = project.objects.property().convention(null) + public val types: ListProperty = project.objects.listProperty() + public val excludeTypes: ListProperty = project.objects.listProperty() + + public companion object { + public const val KXRPC: String = "kotlinx-rpc" + public const val PROTOBUF_JAVA: String = "java" + public const val GRPC_JAVA: String = "grpc-java" + public const val GRPC_KOTLIN: String = "grpc-kotlin" + } + + public enum class Strategy { + Directory, All; + } + + public sealed class Artifact { + internal abstract val type: ResolvedGrpcPlugin.Type + + public class Local(private val project: Project) : Artifact() { + public val executor: ListProperty = project.objects.listProperty() + public fun executor(elements: Provider>) { + executor.set(elements) + } + + public fun javaJar(jarPath: Provider) { + executor(jarPath.map { listOf("java", "-jar", it) }) + } + + public fun javaJar(jarPath: String) { + javaJar(project.provider { jarPath }) + } + + override val type: ResolvedGrpcPlugin.Type = ResolvedGrpcPlugin.Type.local + } + + public class Remote(project: Project) : Artifact() { + public val locator: Property = project.objects.property() + override val type: ResolvedGrpcPlugin.Type = ResolvedGrpcPlugin.Type.remote + } + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt new file mode 100644 index 000000000..be1af1165 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt @@ -0,0 +1,26 @@ +/* + * 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.proto + +import org.gradle.api.Project + +public const val PROTO_GROUP: String = "proto" +public const val PROTO_FILES_DIR: String = "proto" +public const val PROTO_BUILD_DIR: String = "protoBuild" +public const val PROTO_SOURCE_DIRECTORY_NAME: String = "proto" +public const val PROTO_SOURCE_SETS: String = "protoSourceSets" + +internal val Project.protoBuildDir + get() = + layout.buildDirectory + .dir(PROTO_BUILD_DIR) + .get() + .asFile + +internal val Project.protoBuildDirSourceSets + get() = protoBuildDir.resolve("sourceSets") + +internal val Project.protoBuildDirGenerated + get() = protoBuildDir.resolve("generated") diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/ProcessRunner.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/ProcessRunner.kt new file mode 100644 index 000000000..97c4cd663 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/ProcessRunner.kt @@ -0,0 +1,75 @@ +/* + * 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.util + +import java.io.ByteArrayOutputStream +import java.io.Closeable +import java.io.File +import java.io.InputStream +import java.nio.charset.StandardCharsets +import java.util.concurrent.Executors + +// See https://github.com/diffplug/spotless/blob/0fd20bb80c6c426d20e0a3157c3c2b89317032da/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java +internal class ProcessRunner : Closeable { + private val threadStdOut = Executors.newSingleThreadExecutor() + private val threadStdErr = Executors.newSingleThreadExecutor() + private val bufStdOut = ByteArrayOutputStream() + private val bufStdErr = ByteArrayOutputStream() + + fun shell( + name: String, + workingDir: File, + args: List, + ): Result { + val processBuilder = ProcessBuilder(args.map(Any::toString)) + processBuilder.directory(workingDir) + val process = processBuilder.start() + val out = threadStdOut.submit { drain(process.inputStream, bufStdOut) } + val err = threadStdErr.submit { drain(process.errorStream, bufStdErr) } + val exitCode = process.waitFor() + return Result(name, args, exitCode, out.get(), err.get()) + } + + private fun drain( + input: InputStream, + output: ByteArrayOutputStream, + ): ByteArray { + output.reset() + input.copyTo(output) + return output.toByteArray() + } + + override fun close() { + threadStdOut.shutdown() + threadStdErr.shutdown() + } + + class Result( + val name: String, + val args: List, + val exitCode: Int, + val stdOut: ByteArray, + val stdErr: ByteArray, + ) { + fun formattedOutput() = buildString { + appendLine("Process $name finished:") + appendLine(" - Arguments: $args") + appendLine(" - Exit code: $exitCode") + val perStream = { name: String, content: ByteArray -> + val string = content.toString(StandardCharsets.UTF_8) + if (string.isEmpty()) { + appendLine(" - $name: No output") + } else { + appendLine(" - $name:") + val lines = string.replace("\r", "").lines() + lines.forEach { appendLine(" $it") } + } + } + perStream("Stdout", stdOut) + perStream("Stderr", stdErr) + return toString() + } + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt new file mode 100644 index 000000000..3551efc63 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt @@ -0,0 +1,20 @@ +/* + * 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.util + +import java.io.File + +internal fun File.ensureDirectoryExists() { + if (!exists()) { + mkdirs() + } +} + +internal fun File.ensureRegularFileExists() { + if (!exists()) { + parentFile.ensureDirectoryExists() + createNewFile() + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt new file mode 100644 index 000000000..5e74d5de5 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt @@ -0,0 +1,17 @@ +/* + * 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.util + +import org.gradle.api.plugins.ExtensionAware + +internal inline fun Container.findOrCreate(name: String, noinline create: Container.() -> T): T = + findOrCreate(name, T::class.java, create) + +@Suppress("UNCHECKED_CAST") +private fun Container.findOrCreate(name: String, typeClass: Class, create: Container.() -> T): T = + extensions.findByName(name)?.let { + it as? T + ?: error("Extension $name is already present, but of the wrong type: ${it::class} instead of $typeClass") + } ?: create() diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt new file mode 100644 index 000000000..e417ea47c --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt @@ -0,0 +1,31 @@ +/* + * 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.util + +import org.gradle.api.Action +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.plugins.ExtensionAware +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.the +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import kotlin.reflect.KClass + +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) { + plugins.withId(KOTLIN_JVM_PLUGIN_ID) { + the().apply { action.execute(this) } + } +} + +internal fun Project.withKotlinKmpExtension(action: Action) { + plugins.withId(KOTLIN_MULTIPLATFORM_PLUGIN_ID) { + the().apply { action.execute(this) } + } +} diff --git a/grpc/grpc-ktor-server-test/api/grpc-ktor-server-test.api b/grpc/grpc-ktor-server-test/api/grpc-ktor-server-test.api deleted file mode 100644 index e69de29bb..000000000 diff --git a/grpc/grpc-ktor-server-test/build.gradle.kts b/grpc/grpc-ktor-server-test/build.gradle.kts deleted file mode 100644 index c2d5de339..000000000 --- a/grpc/grpc-ktor-server-test/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -plugins { - alias(libs.plugins.conventions.jvm) - alias(libs.plugins.kotlinx.rpc) - alias(libs.plugins.protobuf) -} - -dependencies { - // for the jar dependency - testImplementation(kotlin("test")) - testImplementation(projects.grpc.grpcCore) - testImplementation(projects.grpc.grpcKtorServer) - - testImplementation(libs.grpc.kotlin.stub) - testImplementation(libs.grpc.netty) - - testImplementation(libs.ktor.server.core) - testImplementation(libs.ktor.server.test.host) - testRuntimeOnly(libs.logback.classic) -} - -rpc { - grpc { - enabled = true - - val globalRootDir: String by extra - - plugin { - locator { - path = "$globalRootDir/protobuf-plugin/build/libs/protobuf-plugin-$version-all.jar" - } - } - - tasksMatching { it.isTest }.all { - dependsOn(project(":protobuf-plugin").tasks.jar) - } - } -} diff --git a/grpc/grpc-ktor-server-test/gradle.properties b/grpc/grpc-ktor-server-test/gradle.properties deleted file mode 100644 index b68c20f8d..000000000 --- a/grpc/grpc-ktor-server-test/gradle.properties +++ /dev/null @@ -1,5 +0,0 @@ -# -# Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -# - -kotlinx.rpc.exclude.wasmWasi=true diff --git a/grpc/grpc-ktor-server/build.gradle.kts b/grpc/grpc-ktor-server/build.gradle.kts index e71d8343e..63b59b6fa 100644 --- a/grpc/grpc-ktor-server/build.gradle.kts +++ b/grpc/grpc-ktor-server/build.gradle.kts @@ -2,6 +2,11 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +import kotlinx.rpc.buf.tasks.BufGenerateTask +import kotlinx.rpc.proto.kxrpc +import org.gradle.kotlin.dsl.kotlin +import org.gradle.kotlin.dsl.withType + plugins { alias(libs.plugins.conventions.kmp) alias(libs.plugins.kotlinx.rpc) @@ -15,5 +20,46 @@ kotlin { implementation(libs.ktor.server.core) } } + + jvmTest { + dependencies { + implementation(kotlin("test")) + + implementation(projects.grpc.grpcCore) + implementation(projects.grpc.grpcKtorServer) + + implementation(libs.grpc.kotlin.stub) + implementation(libs.grpc.netty) + + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.test.host) + + runtimeOnly(libs.logback.classic) + } + } + } +} + +protoSourceSets { + jvmProtoMain { + proto { exclude("some.proto") } + } +} + +rpc { + grpc { + val globalRootDir: String by extra + + protocPlugins.kxrpc { + local { + javaJar("$globalRootDir/protobuf-plugin/build/libs/protobuf-plugin-$version-all.jar") + } + } + + project.tasks.withType().configureEach { + if (name.endsWith("Test")) { + dependsOn(":protobuf-plugin:jar") + } + } } } diff --git a/grpc/grpc-ktor-server/src/jvmMain/proto/some.proto b/grpc/grpc-ktor-server/src/jvmMain/proto/some.proto new file mode 100644 index 000000000..e10a4b5f4 --- /dev/null +++ b/grpc/grpc-ktor-server/src/jvmMain/proto/some.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +message Some { + string message = 1; +} diff --git a/grpc/grpc-ktor-server-test/src/test/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt b/grpc/grpc-ktor-server/src/jvmTest/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt similarity index 100% rename from grpc/grpc-ktor-server-test/src/test/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt rename to grpc/grpc-ktor-server/src/jvmTest/kotlin/kotlinx/rpc/grpc/ktor/server/test/TestServer.kt diff --git a/grpc/grpc-ktor-server-test/src/test/proto/ktor-test-service.proto b/grpc/grpc-ktor-server/src/jvmTest/proto/ktor-test-service.proto similarity index 87% rename from grpc/grpc-ktor-server-test/src/test/proto/ktor-test-service.proto rename to grpc/grpc-ktor-server/src/jvmTest/proto/ktor-test-service.proto index 2dfd01e0b..a9f7063f3 100644 --- a/grpc/grpc-ktor-server-test/src/test/proto/ktor-test-service.proto +++ b/grpc/grpc-ktor-server/src/jvmTest/proto/ktor-test-service.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package kotlinx.rpc.grpc.ktor.server.test; +//import "some.proto"; + message Hello { string message = 1; } diff --git a/grpc/grpc-ktor-server-test/src/test/resources/logback.xml b/grpc/grpc-ktor-server/src/jvmTest/resources/logback.xml similarity index 100% rename from grpc/grpc-ktor-server-test/src/test/resources/logback.xml rename to grpc/grpc-ktor-server/src/jvmTest/resources/logback.xml diff --git a/protobuf-plugin/build.gradle.kts b/protobuf-plugin/build.gradle.kts index 4280e9a53..f80291f36 100644 --- a/protobuf-plugin/build.gradle.kts +++ b/protobuf-plugin/build.gradle.kts @@ -2,13 +2,15 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +import kotlinx.rpc.buf.tasks.BufGenerateTask +import kotlinx.rpc.proto.kxrpc +import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode plugins { alias(libs.plugins.conventions.jvm) alias(libs.plugins.kotlinx.rpc) alias(libs.plugins.serialization) - alias(libs.plugins.protobuf) } dependencies { @@ -30,17 +32,10 @@ dependencies { testImplementation(libs.protobuf.kotlin) } -sourceSets { - test { +protoSourceSets { + protoTest { proto { - exclude( - "**/enum_options.proto", - "**/empty_deprecated.proto", - "**/example.proto", - "**/multiple_files.proto", - "**/options.proto", - "**/with_comments.proto", - ) + exclude("exclude/**") } } } @@ -62,20 +57,18 @@ tasks.jar { ) } -val buildDirPath: String = project.layout.buildDirectory.get().asFile.absolutePath - rpc { grpc { - enabled = true - - plugin { - locator { - path = "$buildDirPath/libs/protobuf-plugin-$version-all.jar" + protocPlugins.kxrpc { + local { + javaJar(tasks.jar.map { it.archiveFile.get().asFile.absolutePath }) } } - tasksMatching { it.isTest }.all { - dependsOn(tasks.jar) + project.tasks.withType().configureEach { + if (name.endsWith("Test")) { + dependsOn(tasks.jar) + } } } } diff --git a/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt b/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt index 4922018ab..bf5c9d843 100644 --- a/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt +++ b/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/ProtoToModelInterpreter.kt @@ -294,6 +294,8 @@ class ProtoToModelInterpreter( val originalEntries = mutableMapOf() val aliases = mutableListOf() + val enumName = resolver.declarationFqName(name.fullProtoNameToKotlin(firstLetterUpper = true), parent) + valueList.forEach { enumEntry -> val original = originalEntries[enumEntry.number] if (original != null) { @@ -307,7 +309,7 @@ class ProtoToModelInterpreter( aliases.add( EnumDeclaration.Alias( - name = resolver.declarationFqName(enumEntry.name, parent), + name = resolver.declarationFqName(enumEntry.name, enumName), original = original, deprecated = enumEntry.options.deprecated, doc = null, @@ -315,7 +317,7 @@ class ProtoToModelInterpreter( ) } else { originalEntries[enumEntry.number] = EnumDeclaration.Entry( - name = resolver.declarationFqName(enumEntry.name, parent), + name = resolver.declarationFqName(enumEntry.name, enumName), deprecated = enumEntry.options.deprecated, doc = null, ) @@ -323,13 +325,13 @@ class ProtoToModelInterpreter( } originalEntries[-1] = EnumDeclaration.Entry( - name = resolver.declarationFqName(ENUM_UNRECOGNIZED, parent), + name = resolver.declarationFqName(ENUM_UNRECOGNIZED, enumName), deprecated = false, doc = null, ) return EnumDeclaration( - name = resolver.declarationFqName(name.fullProtoNameToKotlin(firstLetterUpper = true), parent), + name = enumName, outerClassName = outerClassName, originalEntries = originalEntries.values.toList(), aliases = aliases, diff --git a/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt b/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt index 77ad16b86..2bd0b5c7e 100644 --- a/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt +++ b/protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt @@ -77,7 +77,9 @@ class RpcProtobufPlugin { CodeGeneratorResponse.File.newBuilder() .apply { val dir = file.packagePath - ?.replace('.', File.separatorChar)?.plus(File.separatorChar) + ?.takeIf { it.isNotEmpty() } + ?.replace('.', File.separatorChar) + ?.plus(File.separatorChar) ?: "" // some filename already contain package (true for Google's default .proto files) diff --git a/protobuf-plugin/src/test/proto/empty_deprecated.proto b/protobuf-plugin/src/test/proto/exclude/empty_deprecated.proto similarity index 100% rename from protobuf-plugin/src/test/proto/empty_deprecated.proto rename to protobuf-plugin/src/test/proto/exclude/empty_deprecated.proto diff --git a/protobuf-plugin/src/test/proto/enum_options.proto b/protobuf-plugin/src/test/proto/exclude/enum_options.proto similarity index 50% rename from protobuf-plugin/src/test/proto/enum_options.proto rename to protobuf-plugin/src/test/proto/exclude/enum_options.proto index 0e8380ab8..1e85b4fcb 100644 --- a/protobuf-plugin/src/test/proto/enum_options.proto +++ b/protobuf-plugin/src/test/proto/exclude/enum_options.proto @@ -3,11 +3,11 @@ syntax = "proto3"; package kotlinx.rpc.protobuf.test; import "google/protobuf/descriptor.proto"; -import "options.proto"; +//import "options.proto"; -extend google.protobuf.EnumValueOptions { - optional Options options = 50000; -} +//extend google.protobuf.EnumValueOptions { +// optional Options options = 50000; +//} enum EnumOptions { option allow_alias = true; @@ -15,8 +15,8 @@ enum EnumOptions { ONE = 1; ONE_SECOND = 1; TWO = 2 [deprecated = true]; - THREE = 3 [ - (options).string = "three", - (options).inner.string = "inner three" - ]; + THREE = 3; // [ +// (options).string = "three", +// (options).inner.string = "inner three" + //]; } diff --git a/protobuf-plugin/src/test/proto/example.proto b/protobuf-plugin/src/test/proto/exclude/example.proto similarity index 100% rename from protobuf-plugin/src/test/proto/example.proto rename to protobuf-plugin/src/test/proto/exclude/example.proto diff --git a/protobuf-plugin/src/test/proto/multiple_files.proto b/protobuf-plugin/src/test/proto/exclude/multiple_files.proto similarity index 100% rename from protobuf-plugin/src/test/proto/multiple_files.proto rename to protobuf-plugin/src/test/proto/exclude/multiple_files.proto diff --git a/protobuf-plugin/src/test/proto/options.proto b/protobuf-plugin/src/test/proto/exclude/options.proto similarity index 100% rename from protobuf-plugin/src/test/proto/options.proto rename to protobuf-plugin/src/test/proto/exclude/options.proto diff --git a/protobuf-plugin/src/test/proto/with_comments.proto b/protobuf-plugin/src/test/proto/exclude/with_comments.proto similarity index 100% rename from protobuf-plugin/src/test/proto/with_comments.proto rename to protobuf-plugin/src/test/proto/exclude/with_comments.proto diff --git a/settings.gradle.kts b/settings.gradle.kts index 255cff875..8f60e4429 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,8 +32,6 @@ includePublic(":protobuf-plugin") include(":grpc") includePublic(":grpc:grpc-core") includePublic(":grpc:grpc-ktor-server") -// temporary module until KMP project structure support in Protobuf plugin -include(":grpc:grpc-ktor-server-test") includePublic(":bom") diff --git a/versions-root/libs.versions.toml b/versions-root/libs.versions.toml index c3332abaf..8c0ce15d2 100644 --- a/versions-root/libs.versions.toml +++ b/versions-root/libs.versions.toml @@ -32,6 +32,7 @@ grpc = "1.73.0" grpc-kotlin = "1.4.1" protobuf = "4.31.1" protobuf-gradle = "0.9.5" +buf-tool = "1.55.1" [libraries] # kotlinx.rpc – references to the included builds From 7053b01d90c600c86b2143221b40bd16078cd6a4 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 21 Jul 2025 12:20:02 +0200 Subject: [PATCH 02/15] Fix locators for protoc plugins --- .../kotlin/kotlinx/rpc/grpc/GrpcExtension.kt | 25 +++++++++++++---- .../kotlin/kotlinx/rpc/proto/ProtocPlugin.kt | 4 +-- .../kotlinx/rpc/proto/kxrpcPluginJar.kt | 28 +++++++++++++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt index 7ecc64371..c8bc4f646 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt @@ -4,7 +4,9 @@ package kotlinx.rpc.grpc -import kotlinx.rpc.LIBRARY_VERSION +import kotlinx.rpc.GRPC_KOTLIN_VERSION +import kotlinx.rpc.GRPC_VERSION +import kotlinx.rpc.PROTOBUF_VERSION import kotlinx.rpc.buf.BufExtension import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN @@ -15,8 +17,10 @@ import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.buf.tasks.registerBufGenYamlUpdateTask import kotlinx.rpc.buf.tasks.registerBufGenerateTask import kotlinx.rpc.buf.tasks.registerBufYamlUpdateTask +import kotlinx.rpc.proto.KXRPC_PLUGIN_JAR_CONFIGURATION import kotlinx.rpc.proto.ProtoSourceSet import kotlinx.rpc.proto.ProtocPlugin +import kotlinx.rpc.proto.configureKxRpcPluginJarConfiguration import kotlinx.rpc.proto.configureProtoExtensions import kotlinx.rpc.proto.grpcJava import kotlinx.rpc.proto.grpcKotlin @@ -35,7 +39,10 @@ import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.tasks.compile.JavaCompile import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.newInstance +import org.gradle.kotlin.dsl.the import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import javax.inject.Inject @@ -60,6 +67,7 @@ public open class GrpcExtension @Inject constructor( init { project.configureBufExecutable() + project.configureKxRpcPluginJarConfiguration() createDefaultProtocPlugins() @@ -193,25 +201,28 @@ public open class GrpcExtension @Inject constructor( private fun createDefaultProtocPlugins() { protocPlugins.create(KXRPC) { - remote { - locator.set("buf.build/kxrpc/grpc:${LIBRARY_VERSION}") + local { + javaJar(project.configurations.named(KXRPC_PLUGIN_JAR_CONFIGURATION).map { it.singleFile.absolutePath }) } options.put("debugOutput", "protobuf-kxrpc-plugin.log") options.put("messageMode", "interface") + options.put("explicitApiModeEnabled", project.provider { + project.the().explicitApi != ExplicitApiMode.Disabled + }) } protocPlugins.create(GRPC_JAVA) { isJava.set(true) remote { - locator.set("buf.build/grpc/java") + locator.set("buf.build/grpc/java:v$GRPC_VERSION") } } protocPlugins.create(GRPC_KOTLIN) { remote { - locator.set("buf.build/grpc/kotlin") + locator.set("buf.build/grpc/kotlin:v$GRPC_KOTLIN_VERSION") } } @@ -219,7 +230,9 @@ public open class GrpcExtension @Inject constructor( isJava.set(true) remote { - locator.set("buf.build/protocolbuffers/java") + // for some reason they omit the first digit in this version: + // https://buf.build/protocolbuffers/java?version=v31.1 + locator.set("buf.build/protocolbuffers/java:v${PROTOBUF_VERSION.substringAfter(".")}") } } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt index 35931121a..43ab96acd 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt @@ -58,8 +58,8 @@ public open class ProtocPlugin( ) { public val isJava: Property = project.objects.property().convention(false) - public val options: MapProperty = project.objects - .mapProperty() + public val options: MapProperty = project.objects + .mapProperty() .convention(emptyMap()) public fun local(action: Action) { diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt new file mode 100644 index 000000000..b8f823002 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt @@ -0,0 +1,28 @@ +/* + * 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.proto + +import kotlinx.rpc.BUF_TOOL_VERSION +import kotlinx.rpc.LIBRARY_VERSION +import org.gradle.api.Project + +// https://maven.pkg.jetbrains.space/public/p/krpc/grpc/org/jetbrains/kotlinx/kotlinx-rpc-protobuf-plugin/0.8.1-grpc-99/kotlinx-rpc-protobuf-plugin-0.8.1-grpc-99-all.jar + +public const val KXRPC_PLUGIN_JAR_CONFIGURATION: String = "kxrpcPluginJar" + +internal fun Project.configureKxRpcPluginJarConfiguration() { + configurations.create(KXRPC_PLUGIN_JAR_CONFIGURATION) + + dependencies.add( + KXRPC_PLUGIN_JAR_CONFIGURATION, + mapOf( + "group" to "org.jetbrains.kotlinx", + "name" to "kotlinx-rpc-protobuf-plugin", + "version" to LIBRARY_VERSION, + "classifier" to "all", + "ext" to "jar", + ), + ) +} From 84229af38435284e7dfe2088cffaa8d2d9418512 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 21 Jul 2025 20:05:10 +0200 Subject: [PATCH 03/15] Support importing main declarations in test source sets --- .../kotlinx/rpc/buf/tasks/BufExecTask.kt | 3 +- .../kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt | 12 +-- .../kotlinx/rpc/buf/tasks/BufGenerateTask.kt | 11 ++- .../kotlinx/rpc/buf/tasks/BufYamlUpdate.kt | 36 ++++++-- .../kotlin/kotlinx/rpc/grpc/GrpcExtension.kt | 86 ++++++++++++++----- .../kotlinx/rpc/proto/ProcessProtoFiles.kt | 3 +- .../kotlinx/rpc/proto/ProtoSourceSet.kt | 2 + .../main/kotlin/kotlinx/rpc/proto/consts.kt | 1 + .../src/main/kotlin/kotlinx/rpc/util/files.kt | 8 +- grpc/grpc-ktor-server/build.gradle.kts | 6 -- .../src/jvmMain/proto/some.proto | 5 -- .../src/jvmTest/proto/ktor-test-service.proto | 2 - 12 files changed, 115 insertions(+), 60 deletions(-) delete mode 100644 grpc/grpc-ktor-server/src/jvmMain/proto/some.proto 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 06f094873..397a6379f 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 @@ -14,6 +14,7 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction @@ -36,7 +37,7 @@ public abstract class BufExecTask : DefaultTask() { @get:Input public abstract val args: ListProperty - @get:Input + @get:InputDirectory public abstract val workingDir: Property @get:InputFile diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt index d5338c10d..b5e2546a3 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt @@ -5,6 +5,7 @@ package kotlinx.rpc.buf.tasks import kotlinx.rpc.buf.BUF_GEN_YAML +import kotlinx.rpc.proto.PROTO_FILES_DIR import kotlinx.rpc.proto.PROTO_GROUP import kotlinx.rpc.proto.ProtocPlugin import kotlinx.rpc.proto.protoBuildDirSourceSets @@ -26,7 +27,7 @@ internal data class ResolvedGrpcPlugin( val type: Type, val locator: List, val out: String, - val options: Map, + val options: Map, val strategy: String?, val includeImports: Boolean?, val includeWrk: Boolean?, @@ -59,7 +60,7 @@ public abstract class BufGenYamlUpdate : DefaultTask() { file.createNewFile() } - bufGenFile.get().bufferedWriter(Charsets.UTF_8).use { writer -> + file.bufferedWriter(Charsets.UTF_8).use { writer -> writer.appendLine("version: v2") writer.appendLine("clean: true") writer.appendLine("plugins:") @@ -107,6 +108,9 @@ public abstract class BufGenYamlUpdate : DefaultTask() { } } + writer.appendLine("inputs:") + writer.appendLine(" - directory: $PROTO_FILES_DIR") + writer.flush() } } @@ -155,9 +159,7 @@ internal fun Project.registerBufGenYamlUpdateTask( val bufGenYamlFile = project.protoBuildDirSourceSets .resolve(dir) .resolve(BUF_GEN_YAML) - .apply { - ensureRegularFileExists() - } + .ensureRegularFileExists() bufGenFile.set(bufGenYamlFile) 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 e954214c7..f26266409 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 @@ -24,14 +24,13 @@ public abstract class BufGenerateTask : BufExecTask() { @get:Optional public abstract val additionalArgs: ListProperty + @get:InputFiles + internal abstract val protoFiles: ListProperty + /** Whether to include imports. */ @get:Input internal abstract val includeImports: Property - /** The input proto files. */ - @get:InputFiles - internal abstract val inputFiles: ConfigurableFileCollection - /** The directory to output generated files. */ @get:OutputDirectory internal abstract val outputDirectory: Property @@ -57,8 +56,8 @@ public abstract class BufGenerateTask : BufExecTask() { internal fun Project.registerBufGenerateTask( name: String, workingDir: Provider, - inputFiles: Provider, outputDirectory: Provider, + protoFiles: Provider, configure: BufGenerateTask.() -> Unit = {}, ): TaskProvider { return registerBufExecTask(name, workingDir) { @@ -68,8 +67,8 @@ internal fun Project.registerBufGenerateTask( val generate = project.rpcExtension().grpc.buf.generate includeImports.set(generate.includeImports) - this.inputFiles.setFrom(inputFiles) this.outputDirectory.set(outputDirectory) + this.protoFiles.set(protoFiles) configure() } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt index ebd5fabbc..7ad1c82dc 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt @@ -5,6 +5,7 @@ package kotlinx.rpc.buf.tasks import kotlinx.rpc.buf.BUF_YAML +import kotlinx.rpc.proto.IMPORT_PROTO_FILES_DIR import kotlinx.rpc.proto.PROTO_FILES_DIR import kotlinx.rpc.proto.PROTO_GROUP import kotlinx.rpc.proto.protoBuildDirSourceSets @@ -12,10 +13,9 @@ import kotlinx.rpc.util.ensureDirectoryExists import kotlinx.rpc.util.ensureRegularFileExists import org.gradle.api.DefaultTask import org.gradle.api.Project -import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskProvider @@ -26,6 +26,10 @@ public abstract class BufYamlUpdate : DefaultTask() { @get:InputDirectory internal abstract val protoSourceDir: Property + @get:Optional + @get:InputDirectory + internal abstract val importSourceDir: Property + @get:OutputFile public abstract val bufFile: Property @@ -59,6 +63,12 @@ public abstract class BufYamlUpdate : DefaultTask() { writer.appendLine(" - path: $modulePath") } + val importDir = importSourceDir.orNull + if (importDir != null && importDir.exists()) { + val modulePath = importDir.relativeTo(file.parentFile) + writer.appendLine(" - path: $modulePath") + } + writer.flush() } } @@ -71,21 +81,29 @@ public abstract class BufYamlUpdate : DefaultTask() { internal fun Project.registerBufYamlUpdateTask( name: String, dir: String, + withImport: Boolean, configure: BufYamlUpdate.() -> Unit = {}, ): TaskProvider { val capitalizeName = name.replaceFirstChar { it.uppercase() } return tasks.register("${BufYamlUpdate.PREFIX_NAME}$capitalizeName") { - val protoDir = project.protoBuildDirSourceSets.resolve(dir).resolve(PROTO_FILES_DIR) - protoDir.ensureDirectoryExists() + val baseDir = project.protoBuildDirSourceSets.resolve(dir) + val protoDir = baseDir + .resolve(PROTO_FILES_DIR) + .ensureDirectoryExists() protoSourceDir.set(protoDir) - val bufYamlFile = project.protoBuildDirSourceSets - .resolve(dir) + if (withImport) { + val importDir = baseDir + .resolve(IMPORT_PROTO_FILES_DIR) + .ensureDirectoryExists() + + importSourceDir.set(importDir) + } + + val bufYamlFile = baseDir .resolve(BUF_YAML) - .apply { - ensureRegularFileExists() - } + .ensureRegularFileExists() bufFile.set(bufYamlFile) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt index c8bc4f646..1d06d0b20 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt @@ -17,7 +17,9 @@ import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.buf.tasks.registerBufGenYamlUpdateTask import kotlinx.rpc.buf.tasks.registerBufGenerateTask import kotlinx.rpc.buf.tasks.registerBufYamlUpdateTask +import kotlinx.rpc.proto.IMPORT_PROTO_FILES_DIR import kotlinx.rpc.proto.KXRPC_PLUGIN_JAR_CONFIGURATION +import kotlinx.rpc.proto.PROTO_FILES_DIR import kotlinx.rpc.proto.ProtoSourceSet import kotlinx.rpc.proto.ProtocPlugin import kotlinx.rpc.proto.configureKxRpcPluginJarConfiguration @@ -89,7 +91,10 @@ public open class GrpcExtension @Inject constructor( val baseName = protoSourceSet.baseName.get() val baseGenDir = project.protoBuildDirSourceSets.resolve(baseName) - val protocPluginNames = protoSourceSet.collectProtocPlugins().distinct() + val pairSourceSet = protoSourceSet.correspondingMainSourceSetOrNull() + + val mainProtocPlugins = pairSourceSet?.protocPlugins?.get().orEmpty() + val protocPluginNames = (protoSourceSet.protocPlugins.get() + mainProtocPlugins).distinct() val includedProtocPlugins = protocPluginNames.map { protocPlugins.findByName(it) @@ -99,46 +104,81 @@ public open class GrpcExtension @Inject constructor( val protoFiles = protoSourceSet.proto val hasFiles = !protoFiles.isEmpty - val bufGenUpdateTask = project.registerBufGenYamlUpdateTask( + val bufUpdateTask = registerBufYamlUpdateTask( + name = protoSourceSet.name, + dir = baseName, + withImport = pairSourceSet != null, + ) + + val bufGenUpdateTask = registerBufGenYamlUpdateTask( name = protoSourceSet.name, dir = baseName, protocPlugins = includedProtocPlugins, ) { - onlyIf { hasFiles } - } - - val bufUpdateTask = project.registerBufYamlUpdateTask(protoSourceSet.name, baseName) { - onlyIf { hasFiles } + dependsOn(bufUpdateTask) } - val processProtoTask = project.registerProcessProtoFilesTask( + val processProtoTask = registerProcessProtoFilesTask( name = protoSourceSet.name, - baseGenDir = project.provider { baseGenDir }, + baseGenDir = provider { baseGenDir }, protoFiles = protoFiles, + toDir = PROTO_FILES_DIR, ) { - onlyIf { hasFiles } - - dependsOn(bufGenUpdateTask) dependsOn(bufUpdateTask) + dependsOn(bufGenUpdateTask) + } + + val processImportProtoTask = if (pairSourceSet != null) { + val importProtoFiles = pairSourceSet.proto + + registerProcessProtoFilesTask( + name = "${protoSourceSet.name}Import", + baseGenDir = provider { baseGenDir }, + protoFiles = importProtoFiles, + toDir = IMPORT_PROTO_FILES_DIR, + ) { + dependsOn(bufUpdateTask) + dependsOn(bufGenUpdateTask) + dependsOn(processProtoTask) + } + } else { + null } - val out = project.protoBuildDirGenerated.resolve(baseName) + val out = protoBuildDirGenerated.resolve(baseName) val capitalName = protoSourceSet.name.replaceFirstChar { it.uppercase() } - val bufGenerateTask = project.registerBufGenerateTask( + val bufGenerateTask = registerBufGenerateTask( name = "${BufGenerateTask.NAME_PREFIX}$capitalName", workingDir = provider { baseGenDir }, - inputFiles = processProtoTask.map { it.outputs.files }, outputDirectory = provider { out }, + protoFiles = provider { + protoFiles.asFileTree.let { + if (pairSourceSet != null) { + it + pairSourceSet.proto + } else { + it + } + } + }, ) { dependsOn(bufGenUpdateTask) dependsOn(bufUpdateTask) dependsOn(processProtoTask) + if (processImportProtoTask != null) { + dependsOn(processImportProtoTask) + } + + if (pairSourceSet != null) { + dependsOn(pairSourceSet.generateTask) + } onlyIf { hasFiles } } - project.tasks.withType().configureEach { + protoSourceSet.generateTask.set(bufGenerateTask) + + tasks.withType().configureEach { // compileKotlin - main // compileTestKotlin - test // compileKotlinJvm - jvmMain @@ -147,8 +187,8 @@ public open class GrpcExtension @Inject constructor( // compileTestKotlinIosArm64 - iosArm64Test val taskNameAsSourceSet = name .removePrefix("compile").let { - val suffix = it.substringBefore("Kotlin").takeIf { - prefix -> prefix.isNotEmpty() + val suffix = it.substringBefore("Kotlin").takeIf { prefix -> + prefix.isNotEmpty() } ?: "Main" (it.substringAfter("Kotlin") @@ -162,7 +202,7 @@ public open class GrpcExtension @Inject constructor( } } - project.tasks.withType().configureEach { + tasks.withType().configureEach { // compileJvmTestJava - test (java, kmp) // compileJvmMainJava - main (java, kmp) // compileJava - main (java) @@ -183,7 +223,7 @@ public open class GrpcExtension @Inject constructor( includedProtocPlugins.forEach { plugin -> // locates correctly jvmMain, main jvmTest, test - val javaSourceSet = project.extensions.findByType() + val javaSourceSet = extensions.findByType() ?.sourceSets?.findByName(baseName)?.java if (plugin.isJava.get() && javaSourceSet != null) { @@ -237,17 +277,17 @@ public open class GrpcExtension @Inject constructor( } } - private fun ProtoSourceSet.collectProtocPlugins(): List { + private fun ProtoSourceSet.correspondingMainSourceSetOrNull(): ProtoSourceSet? { return when { name.endsWith("Main") -> { - protocPlugins.get() + null } name.endsWith("Test") -> { val main = project.protoSourceSets .getByName(correspondingMainName()) - main.collectProtocPlugins() + protocPlugins.get() + main } else -> throw GradleException("Unknown source set name: $name") diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt index 262f22c98..c10c4497f 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt @@ -22,12 +22,13 @@ internal fun Project.registerProcessProtoFilesTask( name: String, baseGenDir: Provider, protoFiles: SourceDirectorySet, + toDir: String, configure: ProcessProtoFiles.() -> Unit = {}, ): TaskProvider { val capitalName = name.replaceFirstChar { it.uppercase() } return tasks.register("process${capitalName}ProtoFiles") { - val protoGenDir = baseGenDir.map { it.resolve(PROTO_FILES_DIR) } + val protoGenDir = baseGenDir.map { it.resolve(toDir) } val allFiles = protoFiles.files diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt index 5f2fe8fec..13d61dcd6 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt @@ -4,6 +4,7 @@ package kotlinx.rpc.proto +import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.util.findOrCreate import kotlinx.rpc.util.withKotlinJvmExtension import kotlinx.rpc.util.withKotlinKmpExtension @@ -40,6 +41,7 @@ public open class ProtoSourceSet @Inject constructor(internal val project: Proje internal val baseName: Property = project.objects.property() internal val languageSourceSets: ListProperty = project.objects.listProperty() internal val protocPlugins: ListProperty = project.objects.listProperty().convention(emptyList()) + internal val generateTask: Property = project.objects.property() public fun protocPlugin(plugin: NamedDomainObjectProvider) { protocPlugins.add(plugin.name) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt index be1af1165..c3a262e21 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt @@ -8,6 +8,7 @@ import org.gradle.api.Project public const val PROTO_GROUP: String = "proto" public const val PROTO_FILES_DIR: String = "proto" +public const val IMPORT_PROTO_FILES_DIR: String = "import" public const val PROTO_BUILD_DIR: String = "protoBuild" public const val PROTO_SOURCE_DIRECTORY_NAME: String = "proto" public const val PROTO_SOURCE_SETS: String = "protoSourceSets" diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt index 3551efc63..bc1a6d684 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/files.kt @@ -6,15 +6,19 @@ package kotlinx.rpc.util import java.io.File -internal fun File.ensureDirectoryExists() { +internal fun File.ensureDirectoryExists(): File { if (!exists()) { mkdirs() } + + return this } -internal fun File.ensureRegularFileExists() { +internal fun File.ensureRegularFileExists(): File { if (!exists()) { parentFile.ensureDirectoryExists() createNewFile() } + + return this } diff --git a/grpc/grpc-ktor-server/build.gradle.kts b/grpc/grpc-ktor-server/build.gradle.kts index 63b59b6fa..e1e82de8c 100644 --- a/grpc/grpc-ktor-server/build.gradle.kts +++ b/grpc/grpc-ktor-server/build.gradle.kts @@ -40,12 +40,6 @@ kotlin { } } -protoSourceSets { - jvmProtoMain { - proto { exclude("some.proto") } - } -} - rpc { grpc { val globalRootDir: String by extra diff --git a/grpc/grpc-ktor-server/src/jvmMain/proto/some.proto b/grpc/grpc-ktor-server/src/jvmMain/proto/some.proto deleted file mode 100644 index e10a4b5f4..000000000 --- a/grpc/grpc-ktor-server/src/jvmMain/proto/some.proto +++ /dev/null @@ -1,5 +0,0 @@ -syntax = "proto3"; - -message Some { - string message = 1; -} diff --git a/grpc/grpc-ktor-server/src/jvmTest/proto/ktor-test-service.proto b/grpc/grpc-ktor-server/src/jvmTest/proto/ktor-test-service.proto index a9f7063f3..2dfd01e0b 100644 --- a/grpc/grpc-ktor-server/src/jvmTest/proto/ktor-test-service.proto +++ b/grpc/grpc-ktor-server/src/jvmTest/proto/ktor-test-service.proto @@ -2,8 +2,6 @@ syntax = "proto3"; package kotlinx.rpc.grpc.ktor.server.test; -//import "some.proto"; - message Hello { string message = 1; } From 779dd329ba93e4de9ccbd9f619d621aba5543b86 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Mon, 21 Jul 2025 20:15:48 +0200 Subject: [PATCH 04/15] Support propper java executable --- .../kotlin/kotlinx/rpc/proto/ProtocPlugin.kt | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt index 43ab96acd..6db61848e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt @@ -10,6 +10,7 @@ import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN import kotlinx.rpc.proto.ProtocPlugin.Companion.KXRPC import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA import org.gradle.api.Action +import org.gradle.api.GradleException import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project @@ -20,6 +21,7 @@ import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.listProperty import org.gradle.kotlin.dsl.mapProperty import org.gradle.kotlin.dsl.property +import java.io.File public val NamedDomainObjectContainer.kxrpc: NamedDomainObjectProvider get() = named(KXRPC) @@ -98,8 +100,17 @@ public open class ProtocPlugin( executor.set(elements) } - public fun javaJar(jarPath: Provider) { - executor(jarPath.map { listOf("java", "-jar", it) }) + public fun javaJar(jarPath: Provider, executablePath: Provider? = null) { + if (executablePath == null) { + executor(jarPath.map { listOf(javaExePath, "-jar", it) }) + return + } + + val list = jarPath.zip(executablePath) { jar, exe -> + listOf(exe, "-jar", jar) + } + + executor(list) } public fun javaJar(jarPath: String) { @@ -107,6 +118,22 @@ public open class ProtocPlugin( } override val type: ResolvedGrpcPlugin.Type = ResolvedGrpcPlugin.Type.local + + internal companion object { + internal val javaExePath: String by lazy { + val java = File(System.getProperty("java.home"), if (isWindows) "bin/java.exe" else "bin/java") + + if (!java.exists()) { + throw GradleException("Could not find java executable at " + java.path) + } + + java.path + } + + internal val isWindows: Boolean by lazy { + System.getProperty("os.name").lowercase().contains("win") + } + } } public class Remote(project: Project) : Artifact() { From f886508154bf92e409ca2964fe1ea9f33062bb7f Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 22 Jul 2025 16:40:53 +0200 Subject: [PATCH 05/15] Gradle Plugin API docs --- gradle-plugin/build.gradle.kts | 46 ++- .../src/main/kotlin/kotlinx/rpc/Extensions.kt | 7 +- .../kotlin/kotlinx/rpc/buf/BufExecutable.kt | 33 +- .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 176 +++++++++- .../src/main/kotlin/kotlinx/rpc/buf/consts.kt | 19 ++ .../kotlinx/rpc/buf/tasks/BufExecTask.kt | 53 ++- .../kotlinx/rpc/buf/tasks/BufGenerateTask.kt | 86 ++++- ...GenYamlUpdate.kt => GenerateBufGenYaml.kt} | 34 +- .../{BufYamlUpdate.kt => GenerateBufYaml.kt} | 24 +- .../kotlinx/rpc/grpc/DefaultGrpcExtension.kt | 312 +++++++++++++++++ .../kotlin/kotlinx/rpc/grpc/GrpcExtension.kt | 322 ++---------------- .../rpc/proto/DefaultProtoSourceSet.kt | 129 +++++++ .../kotlinx/rpc/proto/ProcessProtoFiles.kt | 3 + .../kotlinx/rpc/proto/ProtoSourceSet.kt | 148 ++------ .../kotlin/kotlinx/rpc/proto/ProtocPlugin.kt | 185 ++++++++-- .../main/kotlin/kotlinx/rpc/proto/consts.kt | 62 +++- .../kotlin/kotlinx/rpc/proto/derictory.kt | 25 ++ .../kotlinx/rpc/proto/kxrpcPluginJar.kt | 5 - .../src/main/kotlin/kotlinx/rpc/util/kgp.kt | 5 - .../main/kotlin/kotlinx/rpc/util/system.kt | 24 ++ 20 files changed, 1179 insertions(+), 519 deletions(-) rename gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/{BufGenYamlUpdate.kt => GenerateBufGenYaml.kt} (82%) rename gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/{BufYamlUpdate.kt => GenerateBufYaml.kt} (83%) create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/derictory.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/util/system.kt diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index d1f6bed19..4ccce943b 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -60,27 +60,51 @@ abstract class GeneratePluginVersionTask @Inject constructor( sourceFile.writeText( """ - package kotlinx.rpc +// This file is generated by a $NAME gradle task. Do not modify manually. - public const val LIBRARY_VERSION: String = "$libraryVersion" +package kotlinx.rpc - @Deprecated("Use kotlinx.rpc.LIBRARY_VERSION instead", ReplaceWith("kotlinx.rpc.LIBRARY_VERSION")) - public const val PLUGIN_VERSION: String = LIBRARY_VERSION +/** + * The version of the kotlinx.rpc library. + */ +public const val LIBRARY_VERSION: String = "$libraryVersion" - public const val PROTOBUF_VERSION: String = "$protobufVersion" - public const val GRPC_VERSION: String = "$grpcVersion" - public const val GRPC_KOTLIN_VERSION: String = "$grpcKotlinVersion" - public const val BUF_TOOL_VERSION: String = "$bufToolVersion" - - """.trimIndent() +@Deprecated("Use kotlinx.rpc.LIBRARY_VERSION instead", ReplaceWith("kotlinx.rpc.LIBRARY_VERSION")) +public const val PLUGIN_VERSION: String = LIBRARY_VERSION + +/** + * The version of the protobuf library. + */ +public const val PROTOBUF_VERSION: String = "$protobufVersion" + +/** + * The version of the grpc java library. + */ +public const val GRPC_VERSION: String = "$grpcVersion" + +/** + * The version of the grpc kotlin library. + */ +public const val GRPC_KOTLIN_VERSION: String = "$grpcKotlinVersion" + +/** + * The version of the buf tool used to generate protobuf. + */ +public const val BUF_TOOL_VERSION: String = "$bufToolVersion" + +""".trimIndent() ) } + + companion object { + const val NAME = "generatePluginVersion" + } } val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/pluginVersion") val generatePluginVersionTask = tasks.register( - "generatePluginVersion", + GeneratePluginVersionTask.NAME, version.toString(), libs.versions.protobuf.asProvider().get(), libs.versions.grpc.asProvider().get(), diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt index 3e10826d2..d02d8d458 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt @@ -6,6 +6,7 @@ package kotlinx.rpc +import kotlinx.rpc.grpc.DefaultGrpcExtension import kotlinx.rpc.grpc.GrpcExtension import org.gradle.api.Action import org.gradle.api.Project @@ -18,7 +19,7 @@ import org.gradle.kotlin.dsl.property import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject -public fun Project.rpcExtension(): RpcExtension = extensions.findByType() ?: RpcExtension(objects, this) +internal fun Project.rpcExtension(): RpcExtension = extensions.findByType() ?: RpcExtension(objects, this) public open class RpcExtension @Inject constructor(objects: ObjectFactory, private val project: Project) { /** @@ -51,13 +52,13 @@ public open class RpcExtension @Inject constructor(objects: ObjectFactory, priva */ public val grpc: GrpcExtension by lazy { grpcApplied.set(true) - objects.newInstance() + objects.newInstance() } /** * Grpc settings. */ - public fun grpc(configure: Action) { + public fun grpc(configure: Action = Action {}) { configure.execute(grpc) } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt index 15199fc3a..96f4a80e7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt @@ -9,9 +9,9 @@ import kotlinx.rpc.buf.tasks.BufExecTask import kotlinx.rpc.util.ProcessRunner import org.gradle.api.GradleException import org.gradle.api.Project +import org.gradle.api.logging.LogLevel import org.gradle.kotlin.dsl.dependencies - // See: https://github.com/bufbuild/buf-gradle-plugin/blob/1bc48078880887797db3aa412d6a3fea60461276/src/main/kotlin/build/buf/gradle/BufSupport.kt#L28 internal fun Project.configureBufExecutable() { configurations.create(BUF_EXECUTABLE_CONFIGURATION) @@ -53,20 +53,41 @@ internal fun BufExecTask.execBuf(args: Iterable) { executable.setExecutable(true) } - val config = configFile.orNull - val configArgs = if (config != null) listOf("--config", config.absolutePath) else emptyList() + val baseArgs = buildList { + val configValue = configFile.orNull + if (configValue != null) { + add("--config") + add(configValue.absolutePath) + } + + if (project.gradle.startParameter.logLevel == LogLevel.DEBUG) { + add("--debug") + } + + val logFormatValue = logFormat.get() + if (logFormatValue != BufExtension.LogFormat.Default) { + add("--log-format") + add(logFormatValue.name.lowercase()) + } + + val timeoutValue = bufTimeoutInWholeSeconds.get() + if (timeoutValue != 0L) { + add("--timeout") + add("${timeoutValue}s") + } + } - val processArgs = listOf(executable.absolutePath) + configArgs + args + val processArgs = listOf(executable.absolutePath) + args + baseArgs val workingDirValue = workingDir.get() - logger.info("Running buf from $workingDirValue: `buf ${args.joinToString(" ")}`") + logger.debug("Running buf from {}: `buf {}`", workingDirValue, processArgs.joinToString(" ")) val result = ProcessRunner().use { it.shell("buf", workingDirValue, processArgs) } if (result.exitCode != 0) { throw GradleException(result.formattedOutput()) } else { - logger.info(result.formattedOutput()) + logger.debug(result.formattedOutput()) } } 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 4daaa785e..20a33b581 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -4,23 +4,193 @@ package kotlinx.rpc.buf +import kotlinx.rpc.buf.tasks.BufExecTask import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.provider.Property import org.gradle.kotlin.dsl.property import java.io.File import javax.inject.Inject +import kotlin.time.Duration +import kotlinx.rpc.proto.ProtocPlugin +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Provider +import org.gradle.kotlin.dsl.listProperty +import kotlin.reflect.KClass -public open class BufExtension @Inject constructor(internal val project: Project) { - public val configFile: Property = project.objects.property() +/** + * Options for the Buf tasks. + * + * @see buf commands + */ +public open class BufExtension @Inject constructor(objects: ObjectFactory) { + /** + * `--config` argument value. + * + * @see buf commands + */ + public val configFile: Property = objects.property() + + /** + * `--log-format` option. + * + * @see buf --log-format + */ + public val logFormat: Property = objects.property().convention(LogFormat.Default) - public val generate: BufGenerateExtension = project.objects.newInstance(BufGenerateExtension::class.java, project) + /** + * Possible values for `--log-format` [logFormat] option. + */ + public enum class LogFormat { + Text, + Color, + Json, + + /** + * Buf's default value. + */ + Default, + ; + } + /** + * `--timeout` option. + * + * Value to Buf is passed in seconds using [Duration.inWholeSeconds]. + * + * @see buf --timeout + */ + public val timeout: Property = objects.property().convention(Duration.ZERO) + + /** + * `buf generate` options. + * + * @see "buf generate" command + * @see [BUF_GEN_YAML] + */ + public val generate: BufGenerateExtension = objects.newInstance(BufGenerateExtension::class.java) + + /** + * Configures the `buf generate` options. + * + * @see "buf generate" command + * @see [BUF_GEN_YAML] + */ public fun generate(configure: Action) { configure.execute(generate) } + + /** + * Use this extension to register custom Buf tasks + * that will operate on the generated workspace. + */ + public val tasks: BufTasksExtension = objects.newInstance(BufTasksExtension::class.java) + + /** + * Use this extension to register custom Buf tasks + * that will operate on the generated workspace. + */ + public fun tasks(configure: Action) { + configure.execute(tasks) + } } +/** + * Allows registering custom Buf tasks that can operate on the generated workspace. + */ +public open class BufTasksExtension @Inject constructor(internal val project: Project) { + // TODO change to commonMain/commonTest in docs when it's supported + /** + * Registers a custom Buf task that operates on the generated workspace. + * + * Name conventions: + * `lint` input for [name] will result in tasks + * named 'bufLintMain' and 'bufLintTest' for Kotlin/JVM projects + * and 'bufLintJvmMain' and 'bufLintJvmTest' for Kotlin/Multiplatform projects. + * + * Note the by default 'test' task doesn't depend on 'main' task. + */ + public fun registerWorkspaceTask(kClass: KClass, name: String, configure: Action): Provider { + val property = project.objects.property(kClass) + + @Suppress("UNCHECKED_CAST") + customTasks.add(Definition(name, kClass, configure, property as Property)) + + return property + } + + // TODO change to commonMain/commonTest in docs when it's supported + /** + * Registers a custom Buf task that operates on the generated workspace. + * + * Name conventions: + * `lint` input for [name] will result in tasks + * named 'bufLintMain' and 'bufLintTest' for Kotlin/JVM projects + * and 'bufLintJvmMain' and 'bufLintJvmTest' for Kotlin/Multiplatform projects. + * + * Note the by default 'test' task doesn't depend on 'main' task. + */ + public inline fun registerWorkspaceTask(name: String, configure: Action): Provider { + return registerWorkspaceTask(T::class, name, configure) + } + + internal val customTasks: ListProperty> = project.objects.listProperty() + + internal class Definition( + val name: String, + val kClass: KClass, + val configure: Action, + val property: Property, + ) +} + +/** + * Options for the `buf generate` command. + * + * @see "buf generate" command + * @see [BUF_GEN_YAML] + */ public open class BufGenerateExtension @Inject constructor(internal val project: Project) { + /** + * `--include-imports` option. + * + * @see buf generate --include-imports + * @see [ProtocPlugin.includeImports] + */ public val includeImports: Property = project.objects.property().convention(false) + + /** + * `--include-wkt` option. + * + * @see buf generate --include-wkt + * @see [ProtocPlugin.includeWkt] + */ + public val includeWkt: Property = project.objects.property().convention(false) + + /** + * `--error-format` option. + * + * @see buf generate --error-format + */ + public val errorFormat: Property = project.objects.property().convention(ErrorFormat.Default) + + /** + * Possible values for `--error-format` option. + * + * @see buf generate --error-format + */ + public enum class ErrorFormat(internal val cliValue: String) { + Text("text"), + Json("json"), + Msvs("msvs"), + Junit("junit"), + GithubActions("github-actions"), + + /** + * Buf's default value. + */ + Default(""), + ; + } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt index 0d2f3c0b9..02c00fb82 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/consts.kt @@ -4,6 +4,25 @@ package kotlinx.rpc.buf +import org.gradle.api.artifacts.Configuration + +/** + * Name of the buf.gen.yaml file. + * + * @see buf.gen.yaml reference + */ public const val BUF_GEN_YAML: String = "buf.gen.yaml" + +/** + * Name of the buf.yaml file. + * + * @see buf.yaml reference + */ public const val BUF_YAML: String = "buf.yaml" + +/** + * [Configuration] name for buf executable. + * + * @see Buf + */ public const val BUF_EXECUTABLE_CONFIGURATION: String = "bufExecutable" 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 397a6379f..49712e44e 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 @@ -5,6 +5,7 @@ package kotlinx.rpc.buf.tasks import kotlinx.rpc.buf.BUF_EXECUTABLE_CONFIGURATION +import kotlinx.rpc.buf.BufExtension import kotlinx.rpc.buf.execBuf import kotlinx.rpc.proto.PROTO_GROUP import kotlinx.rpc.rpcExtension @@ -22,7 +23,13 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.register import java.io.File import kotlin.reflect.KClass +import kotlinx.rpc.buf.BUF_GEN_YAML +import kotlinx.rpc.buf.BUF_YAML +import kotlinx.rpc.buf.BufTasksExtension +/** + * Abstract base class for `buf` tasks. + */ public abstract class BufExecTask : DefaultTask() { init { group = PROTO_GROUP @@ -31,25 +38,61 @@ public abstract class BufExecTask : DefaultTask() { @get:InputFile internal abstract val bufExecutable: Property + /** + * The `buf` command to execute. + * + * Example: `build`, `generate`, `lint`, `mod`, `push`, `version`. + */ @get:Input public abstract val command: Property + /** + * Arguments for the `buf` command. + */ @get:Input public abstract val args: ListProperty + /** + * The working directory for the `buf` command. + */ @get:InputDirectory public abstract val workingDir: Property + /** + * The `buf.yaml` file to use via `--config` option. + */ @get:InputFile @get:Optional public abstract val configFile: Property + /** + * @see [BufExtension.logFormat] + */ + @get:Input + public abstract val logFormat: Property + + /** + * @see [BufExtension.timeout] + */ + @get:Input + public abstract val bufTimeoutInWholeSeconds: Property + @TaskAction - public fun exec() { + internal fun exec() { execBuf(listOf(command.get()) + args.get()) } } +/** + * Registers a [BufExecTask] of type [T]. + * + * Use it to create custom `buf` tasks. + * + * These tasks are NOT automatically configured + * to work with the generated [BUF_GEN_YAML] and [BUF_YAML] files and the corresponding workspace. + * + * For that use [BufTasksExtension.registerWorkspaceTask]. + */ public inline fun Project.registerBufExecTask( name: String, workingDir: Provider, @@ -61,12 +104,16 @@ internal fun Project.registerBufExecTask( clazz: KClass, name: String, workingDir: Provider, - configuration: T.() -> Unit, + configuration: T.() -> Unit = {}, ): TaskProvider = tasks.register(name, clazz) { val executableConfiguration = configurations.getByName(BUF_EXECUTABLE_CONFIGURATION) bufExecutable.set(executableConfiguration.singleFile) this.workingDir.set(workingDir) - configFile.set(project.rpcExtension().grpc.buf.configFile) + + val buf = project.rpcExtension().grpc.buf + configFile.set(buf.configFile) + logFormat.set(buf.logFormat) + bufTimeoutInWholeSeconds.set(buf.timeout.map { it.inWholeSeconds }) 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 f26266409..22fa0008a 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 @@ -7,7 +7,6 @@ package kotlinx.rpc.buf.tasks import kotlinx.rpc.proto.PROTO_GROUP import kotlinx.rpc.rpcExtension import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileCollection import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property @@ -18,38 +17,85 @@ import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskProvider import java.io.File +import kotlinx.rpc.buf.BufGenerateExtension +/** + * Buf `generate` command. + * + * @see buf generate + */ public abstract class BufGenerateTask : BufExecTask() { - @get:Input - @get:Optional - public abstract val additionalArgs: ListProperty - + // unsued, but required for Gradle to properly recognize inputs @get:InputFiles internal abstract val protoFiles: ListProperty - /** Whether to include imports. */ + /** + * Whether to include imports. + * + * @see buf generate --include-imports + * @see [BufGenerateExtension.includeImports] + */ + @get:Input + public abstract val includeImports: Property + + /** + * Whether to include Well-Known Types. + * + * @see buf generate --include-wkt + * @see [BufGenerateExtension.includeWkt] + */ + @get:Input + public abstract val includeWkt: Property + + /** + * `--error-format` option. + * + * @see buf generate --error-format + * @see [BufGenerateExtension.errorFormat] + */ @get:Input - internal abstract val includeImports: Property + public abstract val errorFormat: Property - /** The directory to output generated files. */ + /** + * Additional arguments for `buf generate` command. + */ + @get:Input + @get:Optional + public abstract val additionalArgs: ListProperty + + /** + * The directory to output generated files. + */ @get:OutputDirectory - internal abstract val outputDirectory: Property + public abstract val outputDirectory: Property init { command.set("generate") val args = project.provider { - listOfNotNull( - "--output", outputDirectory.get().absolutePath, - if (includeImports.orNull == true) "--include-imports" else null, - ) + additionalArgs.orNull.orEmpty() + buildList { + add("--output"); add(outputDirectory.get().absolutePath) + + if (includeImports.get()) { + add("--include-imports") + } + + if (includeWkt.get()) { + add("--include-wkt") + } + + val errorFormatValue = errorFormat.get() + if (errorFormatValue != BufGenerateExtension.ErrorFormat.Default) { + add("--error-format"); add(errorFormatValue.cliValue) + } + } + additionalArgs.orNull.orEmpty() } this.args.set(args) } - public companion object { - internal const val NAME_PREFIX: String = "bufGenerate" + internal companion object { + const val NAME_PREFIX: String = "bufGenerate" } } @@ -60,13 +106,19 @@ internal fun Project.registerBufGenerateTask( protoFiles: Provider, configure: BufGenerateTask.() -> Unit = {}, ): TaskProvider { - return registerBufExecTask(name, workingDir) { + val capitalName = name.replaceFirstChar { it.uppercase() } + val bufGenerateTaskName = "${BufGenerateTask.NAME_PREFIX}$capitalName" + + return registerBufExecTask(bufGenerateTaskName, workingDir) { group = PROTO_GROUP - description = "Generates code from .proto files" + description = "Generates code from .proto files using 'buf generate'" val generate = project.rpcExtension().grpc.buf.generate includeImports.set(generate.includeImports) + includeWkt.set(generate.includeWkt) + errorFormat.set(generate.errorFormat) + this.outputDirectory.set(outputDirectory) this.protoFiles.set(protoFiles) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt similarity index 82% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt index b5e2546a3..60660922a 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenYamlUpdate.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt @@ -30,7 +30,7 @@ internal data class ResolvedGrpcPlugin( val options: Map, val strategy: String?, val includeImports: Boolean?, - val includeWrk: Boolean?, + val includeWkt: Boolean?, val types: List, val excludeTypes: List, ) : Serializable { @@ -41,10 +41,16 @@ internal data class ResolvedGrpcPlugin( } } -public abstract class BufGenYamlUpdate : DefaultTask() { +/** + * Generates/updates Buf `buf.gen.yaml` file. + */ +public abstract class GenerateBufGenYaml : DefaultTask() { @get:Input internal abstract val plugins: ListProperty + /** + * The `buf.gen.yaml` file to generate/update. + */ @get:OutputFile public abstract val bufGenFile: Property @@ -84,8 +90,8 @@ public abstract class BufGenYamlUpdate : DefaultTask() { if (plugin.includeImports != null) { writer.appendLine(" include_imports: ${plugin.includeImports}") } - if (plugin.type == ResolvedGrpcPlugin.Type.local && plugin.includeWrk != null) { - writer.appendLine(" include_wrk: ${plugin.includeWrk}") + if (plugin.includeWkt != null) { + writer.appendLine(" include_wkt: ${plugin.includeWkt}") } if (plugin.types.isNotEmpty()) { writer.appendLine(" types:") @@ -115,23 +121,25 @@ public abstract class BufGenYamlUpdate : DefaultTask() { } } - public companion object { - public const val NAME_PREFIX: String = "bufGenYamlUpdate" + internal companion object { + const val NAME_PREFIX: String = "generateBufGenYaml" } } -internal fun Project.registerBufGenYamlUpdateTask( +internal fun Project.registerGenerateBufGenYamlTask( name: String, dir: String, protocPlugins: Iterable, - configure: BufGenYamlUpdate.() -> Unit = {}, -): TaskProvider { + configure: GenerateBufGenYaml.() -> Unit = {}, +): TaskProvider { val capitalizeName = name.replaceFirstChar { it.uppercase() } - return project.tasks.register("${BufGenYamlUpdate.NAME_PREFIX}$capitalizeName") { + return project.tasks.register("${GenerateBufGenYaml.NAME_PREFIX}$capitalizeName") { val pluginsProvider = project.provider { protocPlugins.map { plugin -> if (!plugin.artifact.isPresent) { - throw GradleException("Artifact is not specified for protoc plugin ${plugin.name}") + throw GradleException( + "Artifact is not specified for protoc plugin ${plugin.name}. " + + "Use `local {}` or `remote {}` to specify it.") } val artifact = plugin.artifact.get() @@ -141,13 +149,13 @@ internal fun Project.registerBufGenYamlUpdateTask( } ResolvedGrpcPlugin( - type = artifact.type, + type = if (artifact is ProtocPlugin.Artifact.Local) ResolvedGrpcPlugin.Type.local else ResolvedGrpcPlugin.Type.remote, locator = locator, options = plugin.options.get(), out = plugin.name, strategy = plugin.strategy.orNull?.name?.lowercase(), includeImports = plugin.includeImports.orNull, - includeWrk = plugin.includeWrk.orNull, + includeWkt = plugin.includeWkt.orNull, types = plugin.types.get(), excludeTypes = plugin.excludeTypes.get(), ) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt similarity index 83% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt index 7ad1c82dc..23ff47bed 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufYamlUpdate.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt @@ -5,7 +5,7 @@ package kotlinx.rpc.buf.tasks import kotlinx.rpc.buf.BUF_YAML -import kotlinx.rpc.proto.IMPORT_PROTO_FILES_DIR +import kotlinx.rpc.proto.PROTO_FILES_IMPORT_DIR import kotlinx.rpc.proto.PROTO_FILES_DIR import kotlinx.rpc.proto.PROTO_GROUP import kotlinx.rpc.proto.protoBuildDirSourceSets @@ -22,7 +22,10 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.register import java.io.File -public abstract class BufYamlUpdate : DefaultTask() { +/** + * Generates/updates a Buf `buf.yaml` file. + */ +public abstract class GenerateBufYaml : DefaultTask() { @get:InputDirectory internal abstract val protoSourceDir: Property @@ -30,6 +33,9 @@ public abstract class BufYamlUpdate : DefaultTask() { @get:InputDirectory internal abstract val importSourceDir: Property + /** + * The `buf.yaml` file to generate/update. + */ @get:OutputFile public abstract val bufFile: Property @@ -73,19 +79,19 @@ public abstract class BufYamlUpdate : DefaultTask() { } } - public companion object { - public const val PREFIX_NAME: String = "bufYamlUpdate" + internal companion object { + const val NAME_PREFIX: String = "generateBufYaml" } } -internal fun Project.registerBufYamlUpdateTask( +internal fun Project.registerGenerateBufYamlTask( name: String, dir: String, withImport: Boolean, - configure: BufYamlUpdate.() -> Unit = {}, -): TaskProvider { + configure: GenerateBufYaml.() -> Unit = {}, +): TaskProvider { val capitalizeName = name.replaceFirstChar { it.uppercase() } - return tasks.register("${BufYamlUpdate.PREFIX_NAME}$capitalizeName") { + return tasks.register("${GenerateBufYaml.NAME_PREFIX}$capitalizeName") { val baseDir = project.protoBuildDirSourceSets.resolve(dir) val protoDir = baseDir .resolve(PROTO_FILES_DIR) @@ -95,7 +101,7 @@ internal fun Project.registerBufYamlUpdateTask( if (withImport) { val importDir = baseDir - .resolve(IMPORT_PROTO_FILES_DIR) + .resolve(PROTO_FILES_IMPORT_DIR) .ensureDirectoryExists() importSourceDir.set(importDir) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt new file mode 100644 index 000000000..e082e0af9 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt @@ -0,0 +1,312 @@ +/* + * 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.grpc + +import kotlinx.rpc.GRPC_KOTLIN_VERSION +import kotlinx.rpc.GRPC_VERSION +import kotlinx.rpc.PROTOBUF_VERSION +import kotlinx.rpc.buf.BufExtension +import kotlinx.rpc.buf.configureBufExecutable +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.proto.* +import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA +import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN +import kotlinx.rpc.proto.ProtocPlugin.Companion.KXRPC +import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.model.ObjectFactory +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.kotlin.dsl.findByType +import org.gradle.kotlin.dsl.newInstance +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import javax.inject.Inject + +internal open class DefaultGrpcExtension @Inject constructor( + objects: ObjectFactory, + private val project: Project, +) : GrpcExtension { + override val protocPlugins: NamedDomainObjectContainer = + objects.domainObjectContainer(ProtocPlugin::class.java) { name -> + ProtocPlugin(name, project) + } + + override fun protocPlugins(action: Action>) { + action.execute(protocPlugins) + } + + override val buf: BufExtension = project.objects.newInstance() + override fun buf(action: Action) { + action.execute(buf) + } + + init { + project.configureBufExecutable() + project.configureKxRpcPluginJarConfiguration() + + createDefaultProtocPlugins() + + project.configureProtoExtensions { _, _, protoSourceSet -> + protoSourceSet.protocPlugin(protocPlugins.protobufJava) + protoSourceSet.protocPlugin(protocPlugins.grpcJava) + protoSourceSet.protocPlugin(protocPlugins.grpcKotlin) + protoSourceSet.protocPlugin(protocPlugins.kxrpc) + } + + project.afterEvaluate { + project.protoSourceSets.forEach { sourceSet -> + if (sourceSet !is DefaultProtoSourceSet) { + return@forEach + } + + configureTasks(sourceSet) + } + } + } + + private fun Project.configureTasks(protoSourceSet: DefaultProtoSourceSet) { + val baseName = protoSourceSet.baseName.get() + val baseGenDir = project.protoBuildDirSourceSets.resolve(baseName) + + val pairSourceSet = protoSourceSet.correspondingMainSourceSetOrNull() + + val mainProtocPlugins = pairSourceSet?.protocPlugins?.get().orEmpty() + val protocPluginNames = (protoSourceSet.protocPlugins.get() + mainProtocPlugins).distinct() + + val includedProtocPlugins = protocPluginNames.map { + protocPlugins.findByName(it) + ?: throw GradleException("Protoc plugin $it not found") + } + + val protoFiles = protoSourceSet.proto + val hasFiles = !protoFiles.isEmpty + + val generateBufYamlTask = registerGenerateBufYamlTask( + name = baseName, + dir = baseName, + withImport = pairSourceSet != null, + ) + + val generateBufGenYamlTask = registerGenerateBufGenYamlTask( + name = baseName, + dir = baseName, + protocPlugins = includedProtocPlugins, + ) { + dependsOn(generateBufYamlTask) + } + + val processProtoTask = registerProcessProtoFilesTask( + name = baseName, + baseGenDir = provider { baseGenDir }, + protoFiles = protoFiles, + toDir = PROTO_FILES_DIR, + ) { + dependsOn(generateBufYamlTask) + dependsOn(generateBufGenYamlTask) + } + + val processImportProtoTask = if (pairSourceSet != null) { + val importProtoFiles = pairSourceSet.proto + + registerProcessProtoFilesTask( + name = "${baseName}Import", + baseGenDir = provider { baseGenDir }, + protoFiles = importProtoFiles, + toDir = PROTO_FILES_IMPORT_DIR, + ) { + dependsOn(generateBufYamlTask) + dependsOn(generateBufGenYamlTask) + dependsOn(processProtoTask) + } + } else { + null + } + + val out = protoBuildDirGenerated.resolve(baseName) + + val bufGenerateTask = registerBufGenerateTask( + name = baseName, + workingDir = provider { baseGenDir }, + outputDirectory = provider { out }, + protoFiles = provider { + protoFiles.asFileTree.let { + if (pairSourceSet != null) { + it + pairSourceSet.proto + } else { + it + } + } + }, + ) { + dependsOn(generateBufGenYamlTask) + dependsOn(generateBufYamlTask) + dependsOn(processProtoTask) + if (processImportProtoTask != null) { + dependsOn(processImportProtoTask) + } + + if (pairSourceSet != null) { + dependsOn(pairSourceSet.generateTask) + } + + onlyIf { hasFiles } + } + + protoSourceSet.generateTask.set(bufGenerateTask) + + tasks.withType().configureEach { + // compileKotlin - main + // compileTestKotlin - test + // compileKotlinJvm - jvmMain + // compileTestKotlinJvm - jvmTest + // compileKotlinIosArm64 - iosArm64Main + // compileTestKotlinIosArm64 - iosArm64Test + val taskNameAsSourceSet = name + .removePrefix("compile").let { + val suffix = it.substringBefore("Kotlin").takeIf { prefix -> + prefix.isNotEmpty() + } ?: "Main" + + (it.substringAfter("Kotlin") + .replaceFirstChar { ch -> ch.lowercase() } + suffix) + .takeIf { result -> result != suffix } + ?: suffix.lowercase() + } + + if (taskNameAsSourceSet == baseName) { + dependsOn(bufGenerateTask) + } + } + + tasks.withType().configureEach { + // compileJvmTestJava - test (java, kmp) + // compileJvmMainJava - main (java, kmp) + // compileJava - main (java) + // compileTestJava - test (java) + val taskNameAsSourceSet = when (name) { + "compileJvmTestJava" -> "test" + "compileJvmMainJava" -> "main" + "compileJava" -> "main" + "compileTestJava" -> "test" + + else -> throw GradleException("Unknown java compile task name: $name") + } + + if (taskNameAsSourceSet == baseName) { + dependsOn(bufGenerateTask) + } + } + + includedProtocPlugins.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' source set" + ) + } + } + + 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, workingDir = provider { baseGenDir }, name = taskName) { + dependsOn(generateBufYamlTask) + dependsOn(generateBufGenYamlTask) + dependsOn(processProtoTask) + if (processImportProtoTask != null) { + dependsOn(processImportProtoTask) + } + + onlyIf { hasFiles } + } + + definition.property.set(customTask) + } + } + + private fun createDefaultProtocPlugins() { + protocPlugins.create(KXRPC) { + local { + javaJar(project.configurations.named(KXRPC_PLUGIN_JAR_CONFIGURATION).map { it.singleFile.absolutePath }) + } + + options.put("debugOutput", "protobuf-kxrpc-plugin.log") + options.put("messageMode", "interface") + options.put("explicitApiModeEnabled", project.provider { + project.the().explicitApi != ExplicitApiMode.Disabled + }) + } + + protocPlugins.create(GRPC_JAVA) { + isJava.set(true) + + remote { + locator.set("buf.build/grpc/java:v$GRPC_VERSION") + } + } + + protocPlugins.create(GRPC_KOTLIN) { + remote { + locator.set("buf.build/grpc/kotlin:v$GRPC_KOTLIN_VERSION") + } + } + + protocPlugins.create(PROTOBUF_JAVA) { + isJava.set(true) + + remote { + // for some reason they omit the first digit in this version: + // https://buf.build/protocolbuffers/java?version=v31.1 + locator.set("buf.build/protocolbuffers/java:v${PROTOBUF_VERSION.substringAfter(".")}") + } + } + } + + private fun DefaultProtoSourceSet.correspondingMainSourceSetOrNull(): DefaultProtoSourceSet? { + return when { + name.endsWith("Main") -> { + null + } + + name.endsWith("Test") -> { + val main = project.protoSourceSets + .getByName(correspondingMainName()) as DefaultProtoSourceSet + + main + } + + else -> throw GradleException("Unknown source set name: $name") + } + } + + private fun ProtoSourceSet.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/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt index 1d06d0b20..265841fd5 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt @@ -4,301 +4,39 @@ package kotlinx.rpc.grpc -import kotlinx.rpc.GRPC_KOTLIN_VERSION -import kotlinx.rpc.GRPC_VERSION -import kotlinx.rpc.PROTOBUF_VERSION import kotlinx.rpc.buf.BufExtension -import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA -import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN -import kotlinx.rpc.proto.ProtocPlugin.Companion.KXRPC -import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA -import kotlinx.rpc.buf.configureBufExecutable -import kotlinx.rpc.buf.tasks.BufGenerateTask -import kotlinx.rpc.buf.tasks.registerBufGenYamlUpdateTask -import kotlinx.rpc.buf.tasks.registerBufGenerateTask -import kotlinx.rpc.buf.tasks.registerBufYamlUpdateTask -import kotlinx.rpc.proto.IMPORT_PROTO_FILES_DIR -import kotlinx.rpc.proto.KXRPC_PLUGIN_JAR_CONFIGURATION -import kotlinx.rpc.proto.PROTO_FILES_DIR -import kotlinx.rpc.proto.ProtoSourceSet import kotlinx.rpc.proto.ProtocPlugin -import kotlinx.rpc.proto.configureKxRpcPluginJarConfiguration -import kotlinx.rpc.proto.configureProtoExtensions -import kotlinx.rpc.proto.grpcJava -import kotlinx.rpc.proto.grpcKotlin -import kotlinx.rpc.proto.kxrpc -import kotlinx.rpc.proto.protoBuildDirGenerated -import kotlinx.rpc.proto.protoBuildDirSourceSets -import kotlinx.rpc.proto.protoSourceSets -import kotlinx.rpc.proto.protobufJava -import kotlinx.rpc.proto.registerProcessProtoFilesTask import org.gradle.api.Action -import org.gradle.api.GradleException import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.Project -import org.gradle.api.model.ObjectFactory -import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.api.tasks.compile.JavaCompile -import org.gradle.kotlin.dsl.findByType -import org.gradle.kotlin.dsl.newInstance -import org.gradle.kotlin.dsl.the -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode -import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import javax.inject.Inject -public open class GrpcExtension @Inject constructor( - objects: ObjectFactory, - private val project: Project, -) { - public val protocPlugins: NamedDomainObjectContainer = - objects.domainObjectContainer(ProtocPlugin::class.java) { name -> - ProtocPlugin(name, project) - } - - public fun protocPlugins(action: Action>) { - action.execute(protocPlugins) - } - - public val buf: BufExtension = project.objects.newInstance() - public fun buf(action: Action) { - action.execute(buf) - } - - init { - project.configureBufExecutable() - project.configureKxRpcPluginJarConfiguration() - - createDefaultProtocPlugins() - - project.configureProtoExtensions { _, _, protoSourceSet -> - protoSourceSet.protocPlugin(protocPlugins.protobufJava) - protoSourceSet.protocPlugin(protocPlugins.grpcJava) - protoSourceSet.protocPlugin(protocPlugins.grpcKotlin) - protoSourceSet.protocPlugin(protocPlugins.kxrpc) - } - - project.afterEvaluate { - project.protoSourceSets.forEach { sourceSet -> - configureTasks(sourceSet) - } - } - } - - private fun Project.configureTasks(protoSourceSet: ProtoSourceSet) { - val baseName = protoSourceSet.baseName.get() - val baseGenDir = project.protoBuildDirSourceSets.resolve(baseName) - - val pairSourceSet = protoSourceSet.correspondingMainSourceSetOrNull() - - val mainProtocPlugins = pairSourceSet?.protocPlugins?.get().orEmpty() - val protocPluginNames = (protoSourceSet.protocPlugins.get() + mainProtocPlugins).distinct() - - val includedProtocPlugins = protocPluginNames.map { - protocPlugins.findByName(it) - ?: throw GradleException("Protoc plugin $it not found") - } - - val protoFiles = protoSourceSet.proto - val hasFiles = !protoFiles.isEmpty - - val bufUpdateTask = registerBufYamlUpdateTask( - name = protoSourceSet.name, - dir = baseName, - withImport = pairSourceSet != null, - ) - - val bufGenUpdateTask = registerBufGenYamlUpdateTask( - name = protoSourceSet.name, - dir = baseName, - protocPlugins = includedProtocPlugins, - ) { - dependsOn(bufUpdateTask) - } - - val processProtoTask = registerProcessProtoFilesTask( - name = protoSourceSet.name, - baseGenDir = provider { baseGenDir }, - protoFiles = protoFiles, - toDir = PROTO_FILES_DIR, - ) { - dependsOn(bufUpdateTask) - dependsOn(bufGenUpdateTask) - } - - val processImportProtoTask = if (pairSourceSet != null) { - val importProtoFiles = pairSourceSet.proto - - registerProcessProtoFilesTask( - name = "${protoSourceSet.name}Import", - baseGenDir = provider { baseGenDir }, - protoFiles = importProtoFiles, - toDir = IMPORT_PROTO_FILES_DIR, - ) { - dependsOn(bufUpdateTask) - dependsOn(bufGenUpdateTask) - dependsOn(processProtoTask) - } - } else { - null - } - - val out = protoBuildDirGenerated.resolve(baseName) - - val capitalName = protoSourceSet.name.replaceFirstChar { it.uppercase() } - val bufGenerateTask = registerBufGenerateTask( - name = "${BufGenerateTask.NAME_PREFIX}$capitalName", - workingDir = provider { baseGenDir }, - outputDirectory = provider { out }, - protoFiles = provider { - protoFiles.asFileTree.let { - if (pairSourceSet != null) { - it + pairSourceSet.proto - } else { - it - } - } - }, - ) { - dependsOn(bufGenUpdateTask) - dependsOn(bufUpdateTask) - dependsOn(processProtoTask) - if (processImportProtoTask != null) { - dependsOn(processImportProtoTask) - } - - if (pairSourceSet != null) { - dependsOn(pairSourceSet.generateTask) - } - - onlyIf { hasFiles } - } - - protoSourceSet.generateTask.set(bufGenerateTask) - - tasks.withType().configureEach { - // compileKotlin - main - // compileTestKotlin - test - // compileKotlinJvm - jvmMain - // compileTestKotlinJvm - jvmTest - // compileKotlinIosArm64 - iosArm64Main - // compileTestKotlinIosArm64 - iosArm64Test - val taskNameAsSourceSet = name - .removePrefix("compile").let { - val suffix = it.substringBefore("Kotlin").takeIf { prefix -> - prefix.isNotEmpty() - } ?: "Main" - - (it.substringAfter("Kotlin") - .replaceFirstChar { ch -> ch.lowercase() } + suffix) - .takeIf { result -> result != suffix } - ?: suffix.lowercase() - } - - if (taskNameAsSourceSet == baseName) { - dependsOn(bufGenerateTask) - } - } - - tasks.withType().configureEach { - // compileJvmTestJava - test (java, kmp) - // compileJvmMainJava - main (java, kmp) - // compileJava - main (java) - // compileTestJava - test (java) - val taskNameAsSourceSet = when (name) { - "compileJvmTestJava" -> "test" - "compileJvmMainJava" -> "main" - "compileJava" -> "main" - "compileTestJava" -> "test" - - else -> throw GradleException("Unknown java compile task name: $name") - } - - if (taskNameAsSourceSet == baseName) { - dependsOn(bufGenerateTask) - } - } - - includedProtocPlugins.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' source set" - ) - } - } - } - - private fun createDefaultProtocPlugins() { - protocPlugins.create(KXRPC) { - local { - javaJar(project.configurations.named(KXRPC_PLUGIN_JAR_CONFIGURATION).map { it.singleFile.absolutePath }) - } - - options.put("debugOutput", "protobuf-kxrpc-plugin.log") - options.put("messageMode", "interface") - options.put("explicitApiModeEnabled", project.provider { - project.the().explicitApi != ExplicitApiMode.Disabled - }) - } - - protocPlugins.create(GRPC_JAVA) { - isJava.set(true) - - remote { - locator.set("buf.build/grpc/java:v$GRPC_VERSION") - } - } - - protocPlugins.create(GRPC_KOTLIN) { - remote { - locator.set("buf.build/grpc/kotlin:v$GRPC_KOTLIN_VERSION") - } - } - - protocPlugins.create(PROTOBUF_JAVA) { - isJava.set(true) - - remote { - // for some reason they omit the first digit in this version: - // https://buf.build/protocolbuffers/java?version=v31.1 - locator.set("buf.build/protocolbuffers/java:v${PROTOBUF_VERSION.substringAfter(".")}") - } - } - } - - private fun ProtoSourceSet.correspondingMainSourceSetOrNull(): ProtoSourceSet? { - return when { - name.endsWith("Main") -> { - null - } - - name.endsWith("Test") -> { - val main = project.protoSourceSets - .getByName(correspondingMainName()) - - main - } - - else -> throw GradleException("Unknown source set name: $name") - } - } - - private fun ProtoSourceSet.correspondingMainName(): String { - return when { - name == "test" -> "main" - name.endsWith("Test") -> name.removeSuffix("Test") + "Main" - else -> throw GradleException("Unknown test source set name: $name") - } - } +/** + * Configuration for the gRPC capabilities. + * + * To enable the gRPC capabilities, add the following minimal code to your `build.gradle.kts`: + * ```kotlin + * rpc { + * grpc() + * } + * ``` + */ +public interface GrpcExtension { + /** + * List of protoc plugins to be applied to the project. + */ + public val protocPlugins: NamedDomainObjectContainer + + /** + * Configures the protoc plugins. + */ + public fun protocPlugins(action: Action>) + + /** + * Configuration for the Buf build tool. + */ + public val buf: BufExtension + + /** + * Configures the Buf build tool. + */ + public fun buf(action: Action) } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt new file mode 100644 index 000000000..650027b48 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt @@ -0,0 +1,129 @@ +/* + * 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.proto + +import kotlinx.rpc.buf.tasks.BufGenerateTask +import kotlinx.rpc.util.findOrCreate +import kotlinx.rpc.util.withKotlinJvmExtension +import kotlinx.rpc.util.withKotlinKmpExtension +import org.gradle.api.Action +import org.gradle.api.GradleException +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.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.property +import javax.inject.Inject + +@Suppress("UNCHECKED_CAST") +internal val Project.protoSourceSets: ProtoSourceSets + get() = extensions.findByName(PROTO_SOURCE_SETS) as? ProtoSourceSets + ?: throw GradleException("Unable to find proto source sets in project $name") + +internal class ProtoSourceSetFactory(private val project: Project) : NamedDomainObjectFactory { + override fun create(name: String): ProtoSourceSet { + return project.objects.newInstance(DefaultProtoSourceSet::class.java, project, name) + } +} + +internal open class DefaultProtoSourceSet @Inject constructor( + internal val project: Project, + override val name: String, +) : ProtoSourceSet { + val baseName: Property = project.objects.property() + val languageSourceSets: ListProperty = project.objects.listProperty() + val protocPlugins: ListProperty = project.objects.listProperty().convention(emptyList()) + val generateTask: Property = project.objects.property() + + override fun protocPlugin(plugin: NamedDomainObjectProvider) { + protocPlugins.add(plugin.name) + } + + override fun protocPlugin(plugin: ProtocPlugin) { + protocPlugins.add(plugin.name) + } + + override val proto: SourceDirectorySet = project.objects.sourceDirectorySet(PROTO_SOURCE_DIRECTORY_NAME, "Proto sources").apply { + srcDirs(baseName.map { "src/$it/proto" }) + } + + override fun proto(action: Action) { + action.execute(proto) + } +} + + +internal fun Project.configureProtoExtensions( + configure: Project.(languageSourceSetName: String, languageSourceSet: Any, protoSourceSet: DefaultProtoSourceSet) -> Unit +) { + fun findOrCreateAndConfigure(languageSourceSetName: String, languageSourceSet: Any) { + val protoName = languageSourceSetName.sourceSetToProtoName() + val container = project.findOrCreate(PROTO_SOURCE_SETS) { + val container = objects.domainObjectContainer( + ProtoSourceSet::class.java, + ProtoSourceSetFactory(project) + ) + + project.extensions.add(PROTO_SOURCE_SETS, container) + + container + } + + val protoSourceSet = container.maybeCreate(protoName) as DefaultProtoSourceSet + + configure(languageSourceSetName, languageSourceSet, protoSourceSet) + } + + project.withKotlinJvmExtension { + sourceSets.configureEach { + if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { + findOrCreateAndConfigure(name, this) + } + } + + project.extensions.configure("sourceSets") { + configureEach { + if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { + findOrCreateAndConfigure(name, this) + } + } + } + } + + project.withKotlinKmpExtension { + sourceSets.configureEach { + if (name == "jvmMain" || name == "jvmTest") { + findOrCreateAndConfigure(name, this) + } + } + } +} + +private fun String.sourceSetToProtoName(): String { + return when { + this == "main" -> "protoMain" + this == "test" -> "protoTest" + endsWith("Main") -> "${removeSuffix("Main")}ProtoMain" + endsWith("Test") -> "${removeSuffix("Test")}ProtoTest" + else -> throw IllegalArgumentException("Unsupported source set name: $this") + } +} + +internal fun Project.createProtoExtensions() { + configureProtoExtensions { languageSourceSetName, languageSourceSet, sourceSet -> + sourceSet.initExtension(languageSourceSetName, languageSourceSet) + } +} + +private fun DefaultProtoSourceSet.initExtension(languageSourceSetName: String, languageSourceSet: Any) { + baseName.set(languageSourceSetName) + this.languageSourceSets.add(languageSourceSet) +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt index c10c4497f..bf7d245e0 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt @@ -12,6 +12,9 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.register import java.io.File +/** + * Copy proto files to a temporary directory for Buf to process. + */ public abstract class ProcessProtoFiles : Copy() { init { group = PROTO_GROUP diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt index 13d61dcd6..9b14d33a7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt @@ -4,125 +4,51 @@ package kotlinx.rpc.proto -import kotlinx.rpc.buf.tasks.BufGenerateTask -import kotlinx.rpc.util.findOrCreate -import kotlinx.rpc.util.withKotlinJvmExtension -import kotlinx.rpc.util.withKotlinKmpExtension 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.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.property -import javax.inject.Inject public typealias ProtoSourceSets = NamedDomainObjectContainer -@Suppress("UNCHECKED_CAST") -internal val Project.protoSourceSets: ProtoSourceSets - get() = extensions.findByName(PROTO_SOURCE_SETS) as? ProtoSourceSets - ?: throw GradleException("Unable to find proto source sets in project $name") - -internal class ProtoSourceSetFactory(private val project: Project) : NamedDomainObjectFactory { - override fun create(name: String): ProtoSourceSet { - return project.objects.newInstance(ProtoSourceSet::class.java, project, name) - } -} - -public open class ProtoSourceSet @Inject constructor(internal val project: Project, public val name: String) { - internal val baseName: Property = project.objects.property() - internal val languageSourceSets: ListProperty = project.objects.listProperty() - internal val protocPlugins: ListProperty = project.objects.listProperty().convention(emptyList()) - internal val generateTask: Property = project.objects.property() - - public fun protocPlugin(plugin: NamedDomainObjectProvider) { - protocPlugins.add(plugin.name) - } - - public fun protocPlugin(plugin: ProtocPlugin) { - protocPlugins.add(plugin.name) - } - - public val proto: SourceDirectorySet = project.objects.sourceDirectorySet(PROTO_SOURCE_DIRECTORY_NAME, "Proto sources").apply { - srcDirs(baseName.map { "src/$it/proto" }) - } - +/** + * Represents a source set for proto files. + */ +public interface ProtoSourceSet { + /** + * Name of the source set. + */ + public val name: String + + /** + * Adds a new protoc plugin. + * + * Example: + * ```kotlin + * protocPlugin(rpc.grpc.protocPlugins.myPlugin) + * ``` + */ + public fun protocPlugin(plugin: NamedDomainObjectProvider) + + /** + * Adds a new protoc plugin. + * + * Example: + * ```kotlin + * protocPlugin(rpc.grpc.protocPlugins.myPlugin.get()) + * ``` + */ + public fun protocPlugin(plugin: ProtocPlugin) + + /** + * Default [SourceDirectorySet] for proto files. + */ + public val proto: SourceDirectorySet + + /** + * Configures [proto] source directory set. + */ public fun proto(action: Action) { action.execute(proto) } } - -internal fun Project.configureProtoExtensions( - configure: Project.(languageSourceSetName: String, languageSourceSet: Any, protoSourceSet: ProtoSourceSet) -> Unit -) { - fun findOrCreateAndConfigure(languageSourceSetName: String, languageSourceSet: Any) { - val protoName = languageSourceSetName.sourceSetToProtoName() - val container = project.findOrCreate(PROTO_SOURCE_SETS) { - val container = objects.domainObjectContainer( - ProtoSourceSet::class.java, - ProtoSourceSetFactory(project) - ) - - project.extensions.add(PROTO_SOURCE_SETS, container) - - container - } - - val protoSourceSet = container.maybeCreate(protoName) - - configure(languageSourceSetName, languageSourceSet, protoSourceSet) - } - - project.withKotlinJvmExtension { - sourceSets.configureEach { - if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { - findOrCreateAndConfigure(name, this) - } - } - - project.extensions.configure("sourceSets") { - configureEach { - if (name == SourceSet.MAIN_SOURCE_SET_NAME || name == SourceSet.TEST_SOURCE_SET_NAME) { - findOrCreateAndConfigure(name, this) - } - } - } - } - - project.withKotlinKmpExtension { - sourceSets.configureEach { - if (name == "jvmMain" || name == "jvmTest") { - findOrCreateAndConfigure(name, this) - } - } - } -} - -private fun String.sourceSetToProtoName(): String { - return when { - this == "main" -> "protoMain" - this == "test" -> "protoTest" - endsWith("Main") -> "${removeSuffix("Main")}ProtoMain" - endsWith("Test") -> "${removeSuffix("Test")}ProtoTest" - else -> throw IllegalArgumentException("Unsupported source set name: $this") - } -} - -internal fun Project.createProtoExtensions() { - configureProtoExtensions { languageSourceSetName, languageSourceSet, sourceSet -> - sourceSet.initExtension(languageSourceSetName, languageSourceSet) - } -} - -private fun ProtoSourceSet.initExtension(languageSourceSetName: String, languageSourceSet: Any) { - baseName.set(languageSourceSetName) - this.languageSourceSets.add(languageSourceSet) -} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt index 6db61848e..fd9ee8500 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt @@ -4,13 +4,12 @@ package kotlinx.rpc.proto -import kotlinx.rpc.buf.tasks.ResolvedGrpcPlugin import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_JAVA import kotlinx.rpc.proto.ProtocPlugin.Companion.GRPC_KOTLIN import kotlinx.rpc.proto.ProtocPlugin.Companion.KXRPC import kotlinx.rpc.proto.ProtocPlugin.Companion.PROTOBUF_JAVA +import kotlinx.rpc.util.OS import org.gradle.api.Action -import org.gradle.api.GradleException import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project @@ -21,32 +20,55 @@ import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.listProperty import org.gradle.kotlin.dsl.mapProperty import org.gradle.kotlin.dsl.property -import java.io.File +/** + * Access to the `kotlinx-rpc` protoc plugin. + */ public val NamedDomainObjectContainer.kxrpc: NamedDomainObjectProvider get() = named(KXRPC) +/** + * Configures the `kotlinx-rpc` protoc plugin. + */ public fun NamedDomainObjectContainer.kxrpc(action: Action) { kxrpc.configure(action) } +/** + * Access to the `protobuf-java` protoc plugin. + */ public val NamedDomainObjectContainer.protobufJava: NamedDomainObjectProvider get() = named(PROTOBUF_JAVA) +/** + * Configures the `protobuf-java` protoc plugin. + */ public fun NamedDomainObjectContainer.protobufJava(action: Action) { protobufJava.configure(action) } +/** + * Access to the `grpc-java` protoc plugin. + */ public val NamedDomainObjectContainer.grpcJava: NamedDomainObjectProvider get() = named(GRPC_JAVA) +/** + * Configures the grpc-java protoc plugin. + */ public fun NamedDomainObjectContainer.grpcJava(action: Action) { grpcJava.configure(action) } +/** + * Access to the `grpc-kotlin` protoc plugin. + */ public val NamedDomainObjectContainer.grpcKotlin: NamedDomainObjectProvider get() = named(GRPC_KOTLIN) +/** + * Configures the `grpc-kotlin` protoc plugin. + */ public fun NamedDomainObjectContainer.grpcKotlin(action: Action) { grpcKotlin.configure(action) } @@ -58,51 +80,169 @@ public open class ProtocPlugin( public val name: String, private val project: Project, ) { + /** + * Whether the plugin generates Java code. + * + * Plugins that have this property set to `true` will have their output directory + * added to the source set's `java` source directory set if present. + */ public val isJava: Property = project.objects.property().convention(false) + /** + * Protoc plugins options. + * + * @see Buf documentation - opt + */ public val options: MapProperty = project.objects .mapProperty() .convention(emptyMap()) + /** + * Local protoc plugin artifact. + * + * @see Buf documentation - Type of plugin + */ public fun local(action: Action) { artifact.set(Artifact.Local(project).apply(action::execute)) } + /** + * Remote protoc plugin artifact. + * + * @see Buf documentation - Type of plugin + */ public fun remote(action: Action) { artifact.set(Artifact.Remote(project).apply(action::execute)) } + /** + * Protoc plugin artifact. + * + * Can be either [Artifact.Local] or [Artifact.Remote]. + * + * @see Buf documentation - Type of plugin + */ public val artifact: Property = project.objects.property() + /** + * Strategy for this protoc plugin. + * + * Optional. + * Default is Buf's default. + * + * @see Buf documentation - strategy + */ public val strategy: Property = project.objects.property().convention(null) + + /** + * Whether to include imports except for Well-Known Types. + * + * Optional. + * Default is Buf's default. + * + * @see Buf documentation - include_imports + */ public val includeImports: Property = project.objects.property().convention(null) - public val includeWrk: Property = project.objects.property().convention(null) + + /** + * Whether to include Well-Known Types. + * + * Optional. + * Default is Buf's default. + * + * @see Buf documentation - include_wkt + */ + public val includeWkt: Property = project.objects.property().convention(null) + + /** + * Include only the specified types when generating with this plugin. + * + * Optional. + * + * @see Buf documentation - types + */ public val types: ListProperty = project.objects.listProperty() + + /** + * Exclude the specified types when generating with this plugin. + * + * Optional. + * + * @see Buf documentation - exclude-types + */ public val excludeTypes: ListProperty = project.objects.listProperty() public companion object { + /** + * The name of the kotlinx-rpc protoc plugin. + * + * @see [kxrpc] + */ public const val KXRPC: String = "kotlinx-rpc" + + /** + * The name of the protobuf-java protoc plugin. + * + * @see [protobufJava] + */ public const val PROTOBUF_JAVA: String = "java" + + /** + * The name of the grpc-java protoc plugin. + * + * @see [grpcJava] + */ public const val GRPC_JAVA: String = "grpc-java" + + /** + * The name of the grpc-kotlin protoc plugin. + * + * @see [grpcKotlin] + */ public const val GRPC_KOTLIN: String = "grpc-kotlin" } + /** + * Strategy for a protoc plugin. + * + * @see [strategy] + */ public enum class Strategy { Directory, All; } + /** + * Artifact for a protoc plugin. + */ public sealed class Artifact { - internal abstract val type: ResolvedGrpcPlugin.Type - + /** + * Local protoc plugin artifact. + * + * Local artifact is defined by a list of command-line arguments that execute the plugin - [executor] + * + * @see Buf documentation - Type of plugin + */ public class Local(private val project: Project) : Artifact() { + /** + * Command-line arguments that execute the plugin. + */ public val executor: ListProperty = project.objects.listProperty() + + /** + * Command-line arguments that execute the plugin. + */ public fun executor(elements: Provider>) { executor.set(elements) } + /** + * Protoc plugin jar file path. + * + * If [executablePath] is not specified, the jar will be executed with Java used for the Gradle build. + */ public fun javaJar(jarPath: Provider, executablePath: Provider? = null) { if (executablePath == null) { - executor(jarPath.map { listOf(javaExePath, "-jar", it) }) + executor(jarPath.map { listOf(OS.javaExePath, "-jar", it) }) return } @@ -113,32 +253,25 @@ public open class ProtocPlugin( executor(list) } + /** + * Protoc plugin jar file path. + * + * Uses default Java executable, the same as for the Gradle build. + */ public fun javaJar(jarPath: String) { javaJar(project.provider { jarPath }) } - - override val type: ResolvedGrpcPlugin.Type = ResolvedGrpcPlugin.Type.local - - internal companion object { - internal val javaExePath: String by lazy { - val java = File(System.getProperty("java.home"), if (isWindows) "bin/java.exe" else "bin/java") - - if (!java.exists()) { - throw GradleException("Could not find java executable at " + java.path) - } - - java.path - } - - internal val isWindows: Boolean by lazy { - System.getProperty("os.name").lowercase().contains("win") - } - } } + /** + * Remote protoc plugin artifact. + * + * Locator is a BSR Url. + * + * @see Buf documentation - Type of plugin + */ public class Remote(project: Project) : Artifact() { public val locator: Property = project.objects.property() - override val type: ResolvedGrpcPlugin.Type = ResolvedGrpcPlugin.Type.remote } } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt index c3a262e21..dd800ad6f 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt @@ -4,24 +4,56 @@ package kotlinx.rpc.proto -import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +/** + * `proto` group for related gradle tasks. + */ public const val PROTO_GROUP: String = "proto" -public const val PROTO_FILES_DIR: String = "proto" -public const val IMPORT_PROTO_FILES_DIR: String = "import" -public const val PROTO_BUILD_DIR: String = "protoBuild" -public const val PROTO_SOURCE_DIRECTORY_NAME: String = "proto" + +/** + * Container for proto source sets. + */ public const val PROTO_SOURCE_SETS: String = "protoSourceSets" -internal val Project.protoBuildDir - get() = - layout.buildDirectory - .dir(PROTO_BUILD_DIR) - .get() - .asFile +/** + * Name of the default source directory set for proto files in [PROTO_SOURCE_SETS]. + */ +public const val PROTO_SOURCE_DIRECTORY_NAME: String = "proto" + +/** + * Directory for proto build artifacts. + */ +public const val PROTO_BUILD_DIR: String = "protoBuild" + +/** + * Directory for proto build generated files. + */ +public const val PROTO_BUILD_GENERATED: String = "generated" + +/** + * Directory for proto build temporary files. + * Files there are constructed to form a valid Buf workspace. + */ +public const val PROTO_BUILD_SOURCE_SETS: String = "sourceSets" + +/** + * Source directory for proto files in [PROTO_BUILD_SOURCE_SETS]. + * + * For these files the `buf generate` task will be called. + */ +public const val PROTO_FILES_DIR: String = "proto" -internal val Project.protoBuildDirSourceSets - get() = protoBuildDir.resolve("sourceSets") +/** + * Source directory for proto imported files in [PROTO_BUILD_SOURCE_SETS]. + * + * These files are included as imports in the `buf generate` task, but don't get processed by it. + */ +public const val PROTO_FILES_IMPORT_DIR: String = "import" -internal val Project.protoBuildDirGenerated - get() = protoBuildDir.resolve("generated") +/** + * [Configuration] name for the `kotlinx-rpc` protoc plugin artifact. + * + * MUST be a single file. + */ +public const val KXRPC_PLUGIN_JAR_CONFIGURATION: String = "kxrpcPluginJar" diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/derictory.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/derictory.kt new file mode 100644 index 000000000..00cbe83f7 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/derictory.kt @@ -0,0 +1,25 @@ +/* + * 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.proto + +import org.gradle.api.Project +import java.io.File + +internal val Project.protoBuildDir + get() = + layout.buildDirectory + .dir(PROTO_BUILD_DIR) + .get() + .asFile + +internal val Project.protoBuildDirSourceSets: File + get() { + return protoBuildDir.resolve(PROTO_BUILD_SOURCE_SETS) + } + +internal val Project.protoBuildDirGenerated: File + get() { + return protoBuildDir.resolve(PROTO_BUILD_GENERATED) + } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt index b8f823002..96789aed8 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt @@ -4,14 +4,9 @@ package kotlinx.rpc.proto -import kotlinx.rpc.BUF_TOOL_VERSION import kotlinx.rpc.LIBRARY_VERSION import org.gradle.api.Project -// https://maven.pkg.jetbrains.space/public/p/krpc/grpc/org/jetbrains/kotlinx/kotlinx-rpc-protobuf-plugin/0.8.1-grpc-99/kotlinx-rpc-protobuf-plugin-0.8.1-grpc-99-all.jar - -public const val KXRPC_PLUGIN_JAR_CONFIGURATION: String = "kxrpcPluginJar" - internal fun Project.configureKxRpcPluginJarConfiguration() { configurations.create(KXRPC_PLUGIN_JAR_CONFIGURATION) 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 e417ea47c..dce7f0305 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/kgp.kt @@ -5,15 +5,10 @@ package kotlinx.rpc.util import org.gradle.api.Action -import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project -import org.gradle.api.artifacts.ConfigurationContainer -import org.gradle.api.plugins.ExtensionAware -import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.the import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import kotlin.reflect.KClass private const val KOTLIN_MULTIPLATFORM_PLUGIN_ID = "org.jetbrains.kotlin.multiplatform" private const val KOTLIN_JVM_PLUGIN_ID = "org.jetbrains.kotlin.jvm" diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/system.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/system.kt new file mode 100644 index 000000000..e1aa39ded --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/system.kt @@ -0,0 +1,24 @@ +/* + * 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.util + +import org.gradle.api.GradleException +import java.io.File + +internal object OS { + internal val javaExePath: String by lazy { + val java = File(System.getProperty("java.home"), if (isWindows) "bin/java.exe" else "bin/java") + + if (!java.exists()) { + throw GradleException("Could not find java executable at " + java.path) + } + + java.path + } + + internal val isWindows: Boolean by lazy { + System.getProperty("os.name").lowercase().contains("win") + } +} From 45bd9335f4d3d44944ca895348cfffb3720164f4 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 22 Jul 2025 16:43:24 +0200 Subject: [PATCH 06/15] apiDump --- gradle-plugin/api/gradle-plugin.api | 237 ++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 gradle-plugin/api/gradle-plugin.api diff --git a/gradle-plugin/api/gradle-plugin.api b/gradle-plugin/api/gradle-plugin.api new file mode 100644 index 000000000..6338a7d95 --- /dev/null +++ b/gradle-plugin/api/gradle-plugin.api @@ -0,0 +1,237 @@ +public abstract interface annotation class kotlinx/rpc/RpcDangerousApi : java/lang/annotation/Annotation { +} + +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 getGrpc ()Lkotlinx/rpc/grpc/GrpcExtension; + public final fun getStrict ()Lkotlinx/rpc/RpcStrictModeExtension; + public final fun grpc (Lorg/gradle/api/Action;)V + public static synthetic fun grpc$default (Lkotlinx/rpc/RpcExtension;Lorg/gradle/api/Action;ILjava/lang/Object;)V + public final fun strict (Lorg/gradle/api/Action;)V +} + +public final class kotlinx/rpc/RpcGradlePlugin : org/gradle/api/Plugin { + public fun ()V + public synthetic fun apply (Ljava/lang/Object;)V + public fun apply (Lorg/gradle/api/Project;)V +} + +public final class kotlinx/rpc/RpcStrictMode : java/lang/Enum { + public static final field ERROR Lkotlinx/rpc/RpcStrictMode; + public static final field NONE Lkotlinx/rpc/RpcStrictMode; + public static final field WARNING Lkotlinx/rpc/RpcStrictMode; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/RpcStrictMode; + public static fun values ()[Lkotlinx/rpc/RpcStrictMode; +} + +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; + public final fun getSharedFlow ()Lorg/gradle/api/provider/Property; + public final fun getStateFlow ()Lorg/gradle/api/provider/Property; + public final fun getStreamScopedFunctions ()Lorg/gradle/api/provider/Property; + public final fun getSuspendingServerStreaming ()Lorg/gradle/api/provider/Property; +} + +public final class kotlinx/rpc/VersionsKt { + public static final field BUF_TOOL_VERSION Ljava/lang/String; + public static final field GRPC_KOTLIN_VERSION Ljava/lang/String; + public static final field GRPC_VERSION Ljava/lang/String; + public static final field LIBRARY_VERSION Ljava/lang/String; + public static final field PLUGIN_VERSION Ljava/lang/String; + public static final field PROTOBUF_VERSION Ljava/lang/String; +} + +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; + public final fun getLogFormat ()Lorg/gradle/api/provider/Property; + public final fun getTasks ()Lkotlinx/rpc/buf/BufTasksExtension; + public final fun getTimeout ()Lorg/gradle/api/provider/Property; + public final fun tasks (Lorg/gradle/api/Action;)V +} + +public final class kotlinx/rpc/buf/BufExtension$LogFormat : java/lang/Enum { + public static final field Color Lkotlinx/rpc/buf/BufExtension$LogFormat; + public static final field Default Lkotlinx/rpc/buf/BufExtension$LogFormat; + public static final field Json Lkotlinx/rpc/buf/BufExtension$LogFormat; + public static final field Text Lkotlinx/rpc/buf/BufExtension$LogFormat; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/buf/BufExtension$LogFormat; + public static fun values ()[Lkotlinx/rpc/buf/BufExtension$LogFormat; +} + +public class kotlinx/rpc/buf/BufGenerateExtension { + public fun (Lorg/gradle/api/Project;)V + public final fun getErrorFormat ()Lorg/gradle/api/provider/Property; + public final fun getIncludeImports ()Lorg/gradle/api/provider/Property; + public final fun getIncludeWkt ()Lorg/gradle/api/provider/Property; +} + +public final class kotlinx/rpc/buf/BufGenerateExtension$ErrorFormat : java/lang/Enum { + public static final field Default Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; + public static final field GithubActions Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; + public static final field Json Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; + public static final field Junit Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; + public static final field Msvs Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; + public static final field Text Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; + public static fun values ()[Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; +} + +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;)Lorg/gradle/api/provider/Provider; +} + +public final class kotlinx/rpc/buf/ConstsKt { + public static final field BUF_EXECUTABLE_CONFIGURATION Ljava/lang/String; + public static final field BUF_GEN_YAML Ljava/lang/String; + public static final field BUF_YAML Ljava/lang/String; +} + +public abstract class kotlinx/rpc/buf/tasks/BufExecTask : org/gradle/api/DefaultTask { + public fun ()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 abstract fun getWorkingDir ()Lorg/gradle/api/provider/Property; +} + +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 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 getIncludeImports ()Lorg/gradle/api/provider/Property; + public abstract fun getIncludeWkt ()Lorg/gradle/api/provider/Property; + public abstract fun getOutputDirectory ()Lorg/gradle/api/provider/Property; +} + +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 abstract interface class kotlinx/rpc/grpc/GrpcExtension { + public abstract fun buf (Lorg/gradle/api/Action;)V + public abstract fun getBuf ()Lkotlinx/rpc/buf/BufExtension; + public abstract fun getProtocPlugins ()Lorg/gradle/api/NamedDomainObjectContainer; + public abstract fun protocPlugins (Lorg/gradle/api/Action;)V +} + +public final class kotlinx/rpc/proto/ConstsKt { + public static final field KXRPC_PLUGIN_JAR_CONFIGURATION Ljava/lang/String; + public static final field PROTO_BUILD_DIR Ljava/lang/String; + public static final field PROTO_BUILD_GENERATED Ljava/lang/String; + public static final field PROTO_BUILD_SOURCE_SETS Ljava/lang/String; + public static final field PROTO_FILES_DIR Ljava/lang/String; + public static final field PROTO_FILES_IMPORT_DIR Ljava/lang/String; + 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 abstract class kotlinx/rpc/proto/ProcessProtoFiles : org/gradle/api/tasks/Copy { + public fun ()V +} + +public final class kotlinx/rpc/proto/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/proto/ProtoSourceSet { + public abstract fun getName ()Ljava/lang/String; + public abstract fun getProto ()Lorg/gradle/api/file/SourceDirectorySet; + public fun proto (Lorg/gradle/api/Action;)V + public abstract fun protocPlugin (Lkotlinx/rpc/proto/ProtocPlugin;)V + public abstract fun protocPlugin (Lorg/gradle/api/NamedDomainObjectProvider;)V +} + +public class kotlinx/rpc/proto/ProtocPlugin { + public static final field Companion Lkotlinx/rpc/proto/ProtocPlugin$Companion; + public static final field GRPC_JAVA Ljava/lang/String; + public static final field GRPC_KOTLIN Ljava/lang/String; + public static final field KXRPC Ljava/lang/String; + public static final field PROTOBUF_JAVA 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; + public final fun getIncludeWkt ()Lorg/gradle/api/provider/Property; + public final fun getName ()Ljava/lang/String; + public final fun getOptions ()Lorg/gradle/api/provider/MapProperty; + public final fun getStrategy ()Lorg/gradle/api/provider/Property; + public final fun getTypes ()Lorg/gradle/api/provider/ListProperty; + public final fun isJava ()Lorg/gradle/api/provider/Property; + public final fun local (Lorg/gradle/api/Action;)V + public final fun remote (Lorg/gradle/api/Action;)V +} + +public abstract class kotlinx/rpc/proto/ProtocPlugin$Artifact { +} + +public final class kotlinx/rpc/proto/ProtocPlugin$Artifact$Local : kotlinx/rpc/proto/ProtocPlugin$Artifact { + public fun (Lorg/gradle/api/Project;)V + public final fun executor (Lorg/gradle/api/provider/Provider;)V + public final fun getExecutor ()Lorg/gradle/api/provider/ListProperty; + public final fun javaJar (Ljava/lang/String;)V + public final fun javaJar (Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;)V + public static synthetic fun javaJar$default (Lkotlinx/rpc/proto/ProtocPlugin$Artifact$Local;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;ILjava/lang/Object;)V +} + +public final class kotlinx/rpc/proto/ProtocPlugin$Artifact$Remote : kotlinx/rpc/proto/ProtocPlugin$Artifact { + public fun (Lorg/gradle/api/Project;)V + public final fun getLocator ()Lorg/gradle/api/provider/Property; +} + +public final class kotlinx/rpc/proto/ProtocPlugin$Companion { +} + +public final class kotlinx/rpc/proto/ProtocPlugin$Strategy : java/lang/Enum { + public static final field All Lkotlinx/rpc/proto/ProtocPlugin$Strategy; + public static final field Directory Lkotlinx/rpc/proto/ProtocPlugin$Strategy; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/proto/ProtocPlugin$Strategy; + public static fun values ()[Lkotlinx/rpc/proto/ProtocPlugin$Strategy; +} + +public final class kotlinx/rpc/proto/ProtocPluginKt { + public static final fun getGrpcJava (Lorg/gradle/api/NamedDomainObjectContainer;)Lorg/gradle/api/NamedDomainObjectProvider; + public static final fun getGrpcKotlin (Lorg/gradle/api/NamedDomainObjectContainer;)Lorg/gradle/api/NamedDomainObjectProvider; + public static final fun getKxrpc (Lorg/gradle/api/NamedDomainObjectContainer;)Lorg/gradle/api/NamedDomainObjectProvider; + public static final fun getProtobufJava (Lorg/gradle/api/NamedDomainObjectContainer;)Lorg/gradle/api/NamedDomainObjectProvider; + public static final fun grpcJava (Lorg/gradle/api/NamedDomainObjectContainer;Lorg/gradle/api/Action;)V + public static final fun grpcKotlin (Lorg/gradle/api/NamedDomainObjectContainer;Lorg/gradle/api/Action;)V + public static final fun kxrpc (Lorg/gradle/api/NamedDomainObjectContainer;Lorg/gradle/api/Action;)V + public static final fun protobufJava (Lorg/gradle/api/NamedDomainObjectContainer;Lorg/gradle/api/Action;)V +} + From b712b2fb620af1df28025c649db9738cb3a29e5d Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 22 Jul 2025 17:04:21 +0200 Subject: [PATCH 07/15] detekt --- .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 18 +++++++--- .../kotlinx/rpc/buf/tasks/BufGenerateTask.kt | 4 ++- .../rpc/buf/tasks/GenerateBufGenYaml.kt | 18 ++++++++-- .../kotlinx/rpc/grpc/DefaultGrpcExtension.kt | 19 ++++++---- .../kotlin/kotlinx/rpc/grpc/protections.kt | 10 ++++-- .../rpc/proto/DefaultProtoSourceSet.kt | 12 +++++-- .../kotlin/kotlinx/rpc/proto/ProtocPlugin.kt | 36 ++++++++++++++----- .../kotlinx/rpc/util/{system.kt => OS.kt} | 0 .../main/kotlin/kotlinx/rpc/util/gradle.kt | 11 ++++-- 9 files changed, 96 insertions(+), 32 deletions(-) rename gradle-plugin/src/main/kotlin/kotlinx/rpc/util/{system.kt => OS.kt} (100%) 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 20a33b581..50044826e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -111,7 +111,11 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr * * Note the by default 'test' task doesn't depend on 'main' task. */ - public fun registerWorkspaceTask(kClass: KClass, name: String, configure: Action): Provider { + public fun registerWorkspaceTask( + kClass: KClass, + name: String, + configure: Action, + ): Provider { val property = project.objects.property(kClass) @Suppress("UNCHECKED_CAST") @@ -131,7 +135,10 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr * * Note the by default 'test' task doesn't depend on 'main' task. */ - public inline fun registerWorkspaceTask(name: String, configure: Action): Provider { + public inline fun registerWorkspaceTask( + name: String, + configure: Action, + ): Provider { return registerWorkspaceTask(T::class, name, configure) } @@ -155,7 +162,9 @@ public open class BufGenerateExtension @Inject constructor(internal val project: /** * `--include-imports` option. * - * @see buf generate --include-imports + * @see + * buf generate --include-imports + * * @see [ProtocPlugin.includeImports] */ public val includeImports: Property = project.objects.property().convention(false) @@ -173,7 +182,8 @@ public open class BufGenerateExtension @Inject constructor(internal val project: * * @see buf generate --error-format */ - public val errorFormat: Property = project.objects.property().convention(ErrorFormat.Default) + public val errorFormat: Property = project.objects.property() + .convention(ErrorFormat.Default) /** * Possible values for `--error-format` option. 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 22fa0008a..88c2bf490 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 @@ -32,7 +32,9 @@ public abstract class BufGenerateTask : BufExecTask() { /** * Whether to include imports. * - * @see buf generate --include-imports + * @see + * buf generate --include-imports + * * @see [BufGenerateExtension.includeImports] */ @get:Input 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 60660922a..5f523b755 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 @@ -34,11 +34,16 @@ internal data class ResolvedGrpcPlugin( val types: List, val excludeTypes: List, ) : Serializable { - @Suppress("EnumEntryName") + @Suppress("EnumEntryName", "detekt.EnumNaming") enum class Type { local, remote, ; } + + companion object { + @Suppress("unused") + private const val serialVersionUID: Long = 1L + } } /** @@ -59,6 +64,7 @@ public abstract class GenerateBufGenYaml : DefaultTask() { } @TaskAction + @Suppress("detekt.CyclomaticComplexMethod", "detekt.NestedBlockDepth") internal fun generate() { val file = bufGenFile.get() if (!file.exists()) { @@ -80,7 +86,9 @@ public abstract class GenerateBufGenYaml : DefaultTask() { } } - ResolvedGrpcPlugin.Type.remote -> plugin.locator.single() + ResolvedGrpcPlugin.Type.remote -> { + plugin.locator.single() + } } writer.appendLine(" - ${plugin.type.name}: $locatorLine") @@ -149,7 +157,11 @@ internal fun Project.registerGenerateBufGenYamlTask( } ResolvedGrpcPlugin( - type = if (artifact is ProtocPlugin.Artifact.Local) ResolvedGrpcPlugin.Type.local else ResolvedGrpcPlugin.Type.remote, + type = if (artifact is ProtocPlugin.Artifact.Local) { + ResolvedGrpcPlugin.Type.local + } else { + ResolvedGrpcPlugin.Type.remote + }, locator = locator, options = plugin.options.get(), out = plugin.name, diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt index e082e0af9..5fdab57a4 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt @@ -77,6 +77,7 @@ internal open class DefaultGrpcExtension @Inject constructor( } } + @Suppress("detekt.LongMethod", "detekt.CyclomaticComplexMethod") private fun Project.configureTasks(protoSourceSet: DefaultProtoSourceSet) { val baseName = protoSourceSet.baseName.get() val baseGenDir = project.protoBuildDirSourceSets.resolve(baseName) @@ -222,7 +223,8 @@ internal open class DefaultGrpcExtension @Inject constructor( (it as KotlinSourceSet) .kotlin.srcDirs(out.resolve(plugin.name)) } ?: error( - "Unable to find fitting source directory set for plugin '${plugin.name}' in '$protoSourceSet' source set" + "Unable to find fitting source directory set " + + "for plugin '${plugin.name}' in '$protoSourceSet' source set" ) } } @@ -232,7 +234,11 @@ internal open class DefaultGrpcExtension @Inject constructor( val capital = definition.name.replaceFirstChar { it.uppercase() } val taskName = "buf$capital$baseCapital" - val customTask = registerBufExecTask(clazz = definition.kClass, workingDir = provider { baseGenDir }, name = taskName) { + val customTask = registerBufExecTask( + clazz = definition.kClass, + workingDir = provider { baseGenDir }, + name = taskName, + ) { dependsOn(generateBufYamlTask) dependsOn(generateBufGenYamlTask) dependsOn(processProtoTask) @@ -292,13 +298,12 @@ internal open class DefaultGrpcExtension @Inject constructor( } name.endsWith("Test") -> { - val main = project.protoSourceSets - .getByName(correspondingMainName()) as DefaultProtoSourceSet - - main + project.protoSourceSets.getByName(correspondingMainName()) as DefaultProtoSourceSet } - else -> throw GradleException("Unknown source set name: $name") + else -> { + throw GradleException("Unknown source set name: $name") + } } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt index c44c2e582..cea7ccae7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt @@ -11,6 +11,7 @@ import org.gradle.api.Project private const val BUF_PLUGIN_ID = "build.buf" private const val PROTOBUF_PLUGIN_ID = "com.google.protobuf" +@Suppress("detekt.ThrowsCount") internal fun Project.configurePluginProtections() { var isBufPluginApplied = false var isProtobufPluginApplied = false @@ -25,19 +26,22 @@ internal fun Project.configurePluginProtections() { if (isBufPluginApplied && !isProtobufPluginApplied) { throw GradleException( - "Buf plugin ($BUF_PLUGIN_ID) can't be applied to the project, it is not compatible with the Rpc Gradle Plugin " + "Buf plugin ($BUF_PLUGIN_ID) can't be applied to the project, " + + "it is not compatible with the Rpc Gradle Plugin " ) } if (isProtobufPluginApplied && !isBufPluginApplied) { throw GradleException( - "Protobuf plugin ($PROTOBUF_PLUGIN_ID) can't be applied to the project, it is not compatible with the Rpc Gradle Plugin " + "Protobuf plugin ($PROTOBUF_PLUGIN_ID) can't be applied to the project, " + + "it is not compatible with the Rpc Gradle Plugin " ) } if (isBufPluginApplied) { throw GradleException( - "Both Buf ($BUF_PLUGIN_ID) and Protobuf ($PROTOBUF_PLUGIN_ID) plugins can't be applied to the project, they are not compatible with the Rpc Gradle Plugin " + "Both Buf ($BUF_PLUGIN_ID) and Protobuf ($PROTOBUF_PLUGIN_ID) " + + "plugins can't be applied to the project, they are not compatible with the Rpc Gradle Plugin " ) } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt index 650027b48..605fe26aa 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt @@ -51,7 +51,10 @@ internal open class DefaultProtoSourceSet @Inject constructor( protocPlugins.add(plugin.name) } - override val proto: SourceDirectorySet = project.objects.sourceDirectorySet(PROTO_SOURCE_DIRECTORY_NAME, "Proto sources").apply { + override val proto: SourceDirectorySet = project.objects.sourceDirectorySet( + PROTO_SOURCE_DIRECTORY_NAME, + "Proto sources", + ).apply { srcDirs(baseName.map { "src/$it/proto" }) } @@ -60,9 +63,12 @@ internal open class DefaultProtoSourceSet @Inject constructor( } } - internal fun Project.configureProtoExtensions( - configure: Project.(languageSourceSetName: String, languageSourceSet: Any, protoSourceSet: DefaultProtoSourceSet) -> Unit + configure: Project.( + languageSourceSetName: String, + languageSourceSet: Any, + protoSourceSet: DefaultProtoSourceSet, + ) -> Unit ) { fun findOrCreateAndConfigure(languageSourceSetName: String, languageSourceSet: Any) { val protoName = languageSourceSetName.sourceSetToProtoName() diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt index fd9ee8500..e293ba26e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt @@ -100,7 +100,9 @@ public open class ProtocPlugin( /** * Local protoc plugin artifact. * - * @see Buf documentation - Type of plugin + * @see + * Buf documentation - Type of plugin + * */ public fun local(action: Action) { artifact.set(Artifact.Local(project).apply(action::execute)) @@ -109,7 +111,9 @@ public open class ProtocPlugin( /** * Remote protoc plugin artifact. * - * @see Buf documentation - Type of plugin + * @see + * Buf documentation - Type of plugin + * */ public fun remote(action: Action) { artifact.set(Artifact.Remote(project).apply(action::execute)) @@ -120,7 +124,9 @@ public open class ProtocPlugin( * * Can be either [Artifact.Local] or [Artifact.Remote]. * - * @see Buf documentation - Type of plugin + * @see + * Buf documentation - Type of plugin + * */ public val artifact: Property = project.objects.property() @@ -140,7 +146,9 @@ public open class ProtocPlugin( * Optional. * Default is Buf's default. * - * @see Buf documentation - include_imports + * @see + * Buf documentation - include_imports + * */ public val includeImports: Property = project.objects.property().convention(null) @@ -150,7 +158,9 @@ public open class ProtocPlugin( * Optional. * Default is Buf's default. * - * @see Buf documentation - include_wkt + * @see + * Buf documentation - include_wkt + * */ public val includeWkt: Property = project.objects.property().convention(null) @@ -159,7 +169,9 @@ public open class ProtocPlugin( * * Optional. * - * @see Buf documentation - types + * @see + * Buf documentation - types + * */ public val types: ListProperty = project.objects.listProperty() @@ -168,7 +180,9 @@ public open class ProtocPlugin( * * Optional. * - * @see Buf documentation - exclude-types + * @see + * Buf documentation - exclude-types + * */ public val excludeTypes: ListProperty = project.objects.listProperty() @@ -220,7 +234,9 @@ public open class ProtocPlugin( * * Local artifact is defined by a list of command-line arguments that execute the plugin - [executor] * - * @see Buf documentation - Type of plugin + * @see + * Buf documentation - Type of plugin + * */ public class Local(private val project: Project) : Artifact() { /** @@ -268,7 +284,9 @@ public open class ProtocPlugin( * * Locator is a BSR Url. * - * @see Buf documentation - Type of plugin + * @see + * Buf documentation - Type of plugin + * */ public class Remote(project: Project) : Artifact() { public val locator: Property = project.objects.property() diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/system.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/OS.kt similarity index 100% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/util/system.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/util/OS.kt diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt index 5e74d5de5..9f3628824 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/util/gradle.kt @@ -6,11 +6,18 @@ package kotlinx.rpc.util import org.gradle.api.plugins.ExtensionAware -internal inline fun Container.findOrCreate(name: String, noinline create: Container.() -> T): T = +internal inline fun Container.findOrCreate( + name: String, + noinline create: Container.() -> T, +): T = findOrCreate(name, T::class.java, create) @Suppress("UNCHECKED_CAST") -private fun Container.findOrCreate(name: String, typeClass: Class, create: Container.() -> T): T = +private fun Container.findOrCreate( + name: String, + typeClass: Class, + create: Container.() -> T, +): T = extensions.findByName(name)?.let { it as? T ?: error("Extension $name is already present, but of the wrong type: ${it::class} instead of $typeClass") From e827f37446894ab0c40d108e4057d659ea9e22cb Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 22 Jul 2025 17:08:25 +0200 Subject: [PATCH 08/15] fix --debug option --- .../src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt | 3 +-- .../src/main/kotlin/kotlinx/rpc/buf/tasks/BufExecTask.kt | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt index 96f4a80e7..fd4a9acea 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExecutable.kt @@ -9,7 +9,6 @@ import kotlinx.rpc.buf.tasks.BufExecTask import kotlinx.rpc.util.ProcessRunner import org.gradle.api.GradleException import org.gradle.api.Project -import org.gradle.api.logging.LogLevel import org.gradle.kotlin.dsl.dependencies // See: https://github.com/bufbuild/buf-gradle-plugin/blob/1bc48078880887797db3aa412d6a3fea60461276/src/main/kotlin/build/buf/gradle/BufSupport.kt#L28 @@ -60,7 +59,7 @@ internal fun BufExecTask.execBuf(args: Iterable) { add(configValue.absolutePath) } - if (project.gradle.startParameter.logLevel == LogLevel.DEBUG) { + if (debug.get()) { add("--debug") } 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 49712e44e..a321684b9 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 @@ -26,6 +26,7 @@ import kotlin.reflect.KClass import kotlinx.rpc.buf.BUF_GEN_YAML import kotlinx.rpc.buf.BUF_YAML import kotlinx.rpc.buf.BufTasksExtension +import org.gradle.api.logging.LogLevel /** * Abstract base class for `buf` tasks. @@ -38,6 +39,9 @@ public abstract class BufExecTask : DefaultTask() { @get:InputFile internal abstract val bufExecutable: Property + @get:Input + internal abstract val debug: Property + /** * The `buf` command to execute. * @@ -114,6 +118,7 @@ internal fun Project.registerBufExecTask( configFile.set(buf.configFile) logFormat.set(buf.logFormat) bufTimeoutInWholeSeconds.set(buf.timeout.map { it.inWholeSeconds }) + debug.set(project.gradle.startParameter.logLevel == LogLevel.DEBUG) configuration() } From ce416054e403f957223a5d4fb8f7181c5e0b5f62 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 22 Jul 2025 22:39:28 +0200 Subject: [PATCH 09/15] Update source set names --- .../kotlinx/rpc/grpc/DefaultGrpcExtension.kt | 10 +++---- .../rpc/proto/DefaultProtoSourceSet.kt | 26 +++++-------------- protobuf-plugin/build.gradle.kts | 2 +- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt index 5fdab57a4..25a539f20 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt @@ -59,7 +59,7 @@ internal open class DefaultGrpcExtension @Inject constructor( createDefaultProtocPlugins() - project.configureProtoExtensions { _, _, protoSourceSet -> + project.configureProtoExtensions { _, protoSourceSet -> protoSourceSet.protocPlugin(protocPlugins.protobufJava) protoSourceSet.protocPlugin(protocPlugins.grpcJava) protoSourceSet.protocPlugin(protocPlugins.grpcKotlin) @@ -79,7 +79,7 @@ internal open class DefaultGrpcExtension @Inject constructor( @Suppress("detekt.LongMethod", "detekt.CyclomaticComplexMethod") private fun Project.configureTasks(protoSourceSet: DefaultProtoSourceSet) { - val baseName = protoSourceSet.baseName.get() + val baseName = protoSourceSet.name val baseGenDir = project.protoBuildDirSourceSets.resolve(baseName) val pairSourceSet = protoSourceSet.correspondingMainSourceSetOrNull() @@ -224,7 +224,7 @@ internal open class DefaultGrpcExtension @Inject constructor( .kotlin.srcDirs(out.resolve(plugin.name)) } ?: error( "Unable to find fitting source directory set " + - "for plugin '${plugin.name}' in '$protoSourceSet' source set" + "for plugin '${plugin.name}' in '$protoSourceSet' proto source set" ) } } @@ -293,11 +293,11 @@ internal open class DefaultGrpcExtension @Inject constructor( private fun DefaultProtoSourceSet.correspondingMainSourceSetOrNull(): DefaultProtoSourceSet? { return when { - name.endsWith("Main") -> { + name.lowercase().endsWith("main") -> { null } - name.endsWith("Test") -> { + name.lowercase().endsWith("test") -> { project.protoSourceSets.getByName(correspondingMainName()) as DefaultProtoSourceSet } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt index 605fe26aa..f8fd271f2 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt @@ -38,7 +38,6 @@ internal open class DefaultProtoSourceSet @Inject constructor( internal val project: Project, override val name: String, ) : ProtoSourceSet { - val baseName: Property = project.objects.property() val languageSourceSets: ListProperty = project.objects.listProperty() val protocPlugins: ListProperty = project.objects.listProperty().convention(emptyList()) val generateTask: Property = project.objects.property() @@ -55,7 +54,7 @@ internal open class DefaultProtoSourceSet @Inject constructor( PROTO_SOURCE_DIRECTORY_NAME, "Proto sources", ).apply { - srcDirs(baseName.map { "src/$it/proto" }) + srcDirs("src/$name/proto") } override fun proto(action: Action) { @@ -65,13 +64,11 @@ internal open class DefaultProtoSourceSet @Inject constructor( internal fun Project.configureProtoExtensions( configure: Project.( - languageSourceSetName: String, languageSourceSet: Any, protoSourceSet: DefaultProtoSourceSet, ) -> Unit ) { fun findOrCreateAndConfigure(languageSourceSetName: String, languageSourceSet: Any) { - val protoName = languageSourceSetName.sourceSetToProtoName() val container = project.findOrCreate(PROTO_SOURCE_SETS) { val container = objects.domainObjectContainer( ProtoSourceSet::class.java, @@ -83,9 +80,9 @@ internal fun Project.configureProtoExtensions( container } - val protoSourceSet = container.maybeCreate(protoName) as DefaultProtoSourceSet + val protoSourceSet = container.maybeCreate(languageSourceSetName) as DefaultProtoSourceSet - configure(languageSourceSetName, languageSourceSet, protoSourceSet) + configure(languageSourceSet, protoSourceSet) } project.withKotlinJvmExtension { @@ -113,23 +110,12 @@ internal fun Project.configureProtoExtensions( } } -private fun String.sourceSetToProtoName(): String { - return when { - this == "main" -> "protoMain" - this == "test" -> "protoTest" - endsWith("Main") -> "${removeSuffix("Main")}ProtoMain" - endsWith("Test") -> "${removeSuffix("Test")}ProtoTest" - else -> throw IllegalArgumentException("Unsupported source set name: $this") - } -} - internal fun Project.createProtoExtensions() { - configureProtoExtensions { languageSourceSetName, languageSourceSet, sourceSet -> - sourceSet.initExtension(languageSourceSetName, languageSourceSet) + configureProtoExtensions { languageSourceSet, sourceSet -> + sourceSet.initExtension(languageSourceSet) } } -private fun DefaultProtoSourceSet.initExtension(languageSourceSetName: String, languageSourceSet: Any) { - baseName.set(languageSourceSetName) +private fun DefaultProtoSourceSet.initExtension(languageSourceSet: Any) { this.languageSourceSets.add(languageSourceSet) } diff --git a/protobuf-plugin/build.gradle.kts b/protobuf-plugin/build.gradle.kts index f80291f36..4ec6fcb6d 100644 --- a/protobuf-plugin/build.gradle.kts +++ b/protobuf-plugin/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { } protoSourceSets { - protoTest { + test { proto { exclude("exclude/**") } From 68303a77ee2d11e4684f9122d8621fb8b071c1a4 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Tue, 22 Jul 2025 22:48:12 +0200 Subject: [PATCH 10/15] Add convenient api to java path customisation --- gradle-plugin/api/gradle-plugin.api | 4 ++++ .../kotlinx/rpc/grpc/DefaultGrpcExtension.kt | 2 +- .../kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/gradle-plugin/api/gradle-plugin.api b/gradle-plugin/api/gradle-plugin.api index 6338a7d95..1c4dcb973 100644 --- a/gradle-plugin/api/gradle-plugin.api +++ b/gradle-plugin/api/gradle-plugin.api @@ -160,6 +160,10 @@ public final class kotlinx/rpc/proto/ConstsKt { public static final field PROTO_SOURCE_SETS Ljava/lang/String; } +public final class kotlinx/rpc/proto/KxrpcPluginJarKt { + public static final fun getKxrpcProtocPluginJarPath (Lorg/gradle/api/Project;)Lorg/gradle/api/provider/Provider; +} + public abstract class kotlinx/rpc/proto/ProcessProtoFiles : org/gradle/api/tasks/Copy { public fun ()V } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt index 25a539f20..55d068130 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt @@ -256,7 +256,7 @@ internal open class DefaultGrpcExtension @Inject constructor( private fun createDefaultProtocPlugins() { protocPlugins.create(KXRPC) { local { - javaJar(project.configurations.named(KXRPC_PLUGIN_JAR_CONFIGURATION).map { it.singleFile.absolutePath }) + javaJar(project.kxrpcProtocPluginJarPath) } options.put("debugOutput", "protobuf-kxrpc-plugin.log") diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt index 96789aed8..e5477a15f 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kxrpcPluginJar.kt @@ -6,6 +6,22 @@ package kotlinx.rpc.proto import kotlinx.rpc.LIBRARY_VERSION import org.gradle.api.Project +import org.gradle.api.provider.Provider + +/** + * Absolute path to the `kotlinx-rpc-protobuf-plugin` jar. + * + * Can be used to customise the java executable path: + * ```kotlin + * rpc.grpc.protocPlugins.kxrpc { + * local { + * javaJar(kxrpcProtocPluginJarPath, provider { "my-path-to-java" }) + * } + * } + * ``` + */ +public val Project.kxrpcProtocPluginJarPath: Provider + get() = project.configurations.named(KXRPC_PLUGIN_JAR_CONFIGURATION).map { it.singleFile.absolutePath } internal fun Project.configureKxRpcPluginJarConfiguration() { configurations.create(KXRPC_PLUGIN_JAR_CONFIGURATION) From ba396a34326564824abfe213005edc1e6b08c8d0 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 23 Jul 2025 13:43:01 +0200 Subject: [PATCH 11/15] Add foojay to all included builds --- compiler-plugin/settings.gradle.kts | 3 ++- dokka-plugin/settings.gradle.kts | 3 ++- gradle-plugin/settings.gradle.kts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler-plugin/settings.gradle.kts b/compiler-plugin/settings.gradle.kts index dc064c5e2..b7ad5e63e 100644 --- a/compiler-plugin/settings.gradle.kts +++ b/compiler-plugin/settings.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ rootProject.name = "compiler-plugin" @@ -15,6 +15,7 @@ plugins { id("conventions-repositories") id("conventions-version-resolution") id("conventions-develocity") + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } includeRootAsPublic() diff --git a/dokka-plugin/settings.gradle.kts b/dokka-plugin/settings.gradle.kts index 42d761a1f..cc02f5e7a 100644 --- a/dokka-plugin/settings.gradle.kts +++ b/dokka-plugin/settings.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ rootProject.name = "dokka-rpc-plugin" @@ -15,4 +15,5 @@ plugins { id("conventions-repositories") id("conventions-version-resolution") id("conventions-develocity") + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } diff --git a/gradle-plugin/settings.gradle.kts b/gradle-plugin/settings.gradle.kts index df60f9e3d..264b38dd0 100644 --- a/gradle-plugin/settings.gradle.kts +++ b/gradle-plugin/settings.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ rootProject.name = "gradle-plugin" @@ -14,6 +14,7 @@ pluginManagement { plugins { id("conventions-repositories") id("conventions-version-resolution") + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } includeRootAsPublic() From 55fe96d7c351fd3cea9e1a76be46b362e8e5b2e0 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 23 Jul 2025 14:06:50 +0200 Subject: [PATCH 12/15] fix test --- .../src/testData/diagnostics/rpcChecked.fir.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/rpcChecked.fir.txt b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcChecked.fir.txt index 9c77a3020..9499a936f 100644 --- a/tests/compiler-plugin-tests/src/testData/diagnostics/rpcChecked.fir.txt +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcChecked.fir.txt @@ -61,6 +61,12 @@ FILE: rpcChecked.kt } @R|Grpc|() public abstract interface MyGrpcService : R|kotlin/Any| { + public final class $rpcServiceStub : R|kotlin/Any| { + public final companion object Companion : R|kotlin/Any| { + } + + } + } @R|Grpc|() public final class WrongGrpcTarget : R|kotlin/Any| { public constructor(): R|WrongGrpcTarget| { From f7692d2dd5a138588a038a23413b59543e0fa657 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 23 Jul 2025 14:08:45 +0200 Subject: [PATCH 13/15] added issue links to todos --- .../src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 50044826e..71e0f8ff2 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -100,7 +100,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) { - // TODO change to commonMain/commonTest in docs when it's supported + // TODO change to commonMain/commonTest in docs when it's supported KRPC-180 /** * Registers a custom Buf task that operates on the generated workspace. * @@ -124,7 +124,7 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr return property } - // TODO change to commonMain/commonTest in docs when it's supported + // TODO change to commonMain/commonTest in docs when it's supported KRPC-180 /** * Registers a custom Buf task that operates on the generated workspace. * From bbcd63f3aba15bb5c84696af2d21597c663ea4b4 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 23 Jul 2025 15:24:18 +0200 Subject: [PATCH 14/15] Add vararg executor overload --- .../src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt index e293ba26e..30fcf3077 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt @@ -251,6 +251,13 @@ public open class ProtocPlugin( executor.set(elements) } + /** + * Command-line arguments that execute the plugin. + */ + public fun executor(vararg elements: String) { + executor.set(elements.toList()) + } + /** * Protoc plugin jar file path. * From 3edf3cd186fde5bc47c80c1f997a8b0c48fcf19d Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 23 Jul 2025 17:10:14 +0200 Subject: [PATCH 15/15] Fix custom workspace tasks --- .../kotlin/kotlinx/rpc/buf/BufExtensions.kt | 35 +++++++++++++++---- .../kotlinx/rpc/grpc/DefaultGrpcExtension.kt | 14 +++++++- 2 files changed, 42 insertions(+), 7 deletions(-) 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 71e0f8ff2..7b66a5087 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -115,13 +115,16 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr kClass: KClass, name: String, configure: Action, - ): Provider { - val property = project.objects.property(kClass) + ): TaskProvider { + val mainProperty = project.objects.property(kClass) + val testProperty = project.objects.property(kClass) + + val provider = TaskProperty(mainProperty, testProperty) @Suppress("UNCHECKED_CAST") - customTasks.add(Definition(name, kClass, configure, property as Property)) + customTasks.add(Definition(name, kClass, configure, provider as TaskProperty)) - return property + return provider } // TODO change to commonMain/commonTest in docs when it's supported KRPC-180 @@ -138,7 +141,7 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr public inline fun registerWorkspaceTask( name: String, configure: Action, - ): Provider { + ): TaskProvider { return registerWorkspaceTask(T::class, name, configure) } @@ -148,8 +151,28 @@ public open class BufTasksExtension @Inject constructor(internal val project: Pr val name: String, val kClass: KClass, val configure: Action, - val property: Property, + 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 } /** diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt index 55d068130..5f180791a 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt @@ -249,7 +249,19 @@ internal open class DefaultGrpcExtension @Inject constructor( onlyIf { hasFiles } } - definition.property.set(customTask) + when { + baseName.lowercase().endsWith("main") -> { + definition.property.mainTask.set(customTask) + } + + baseName.lowercase().endsWith("test") -> { + definition.property.testTask.set(customTask) + } + + else -> { + throw GradleException("Unknown source set name: $baseName") + } + } } }