diff --git a/.idea/kotlinx-rpc.iml b/.idea/kotlinx-rpc.iml index 41ef6c5e5..5a1d82917 100644 --- a/.idea/kotlinx-rpc.iml +++ b/.idea/kotlinx-rpc.iml @@ -8,6 +8,13 @@ + + + + + + + - + \ No newline at end of file diff --git a/grpc/grpcpp-c/.bazelrc b/cinterop-c/.bazelrc similarity index 100% rename from grpc/grpcpp-c/.bazelrc rename to cinterop-c/.bazelrc diff --git a/grpc/grpcpp-c/.gitignore b/cinterop-c/.gitignore similarity index 100% rename from grpc/grpcpp-c/.gitignore rename to cinterop-c/.gitignore diff --git a/grpc/grpcpp-c/BUILD.bazel b/cinterop-c/BUILD.bazel similarity index 100% rename from grpc/grpcpp-c/BUILD.bazel rename to cinterop-c/BUILD.bazel diff --git a/grpc/grpcpp-c/MODULE.bazel b/cinterop-c/MODULE.bazel similarity index 100% rename from grpc/grpcpp-c/MODULE.bazel rename to cinterop-c/MODULE.bazel diff --git a/grpc/grpcpp-c/MODULE.bazel.lock b/cinterop-c/MODULE.bazel.lock similarity index 100% rename from grpc/grpcpp-c/MODULE.bazel.lock rename to cinterop-c/MODULE.bazel.lock diff --git a/grpc/grpcpp-c/include/grpcpp_c.h b/cinterop-c/include/grpcpp_c.h similarity index 100% rename from grpc/grpcpp-c/include/grpcpp_c.h rename to cinterop-c/include/grpcpp_c.h diff --git a/grpc/grpcpp-c/include/protowire.h b/cinterop-c/include/protowire.h similarity index 100% rename from grpc/grpcpp-c/include/protowire.h rename to cinterop-c/include/protowire.h diff --git a/grpc/grpcpp-c/src/grpcpp_c.cpp b/cinterop-c/src/grpcpp_c.cpp similarity index 100% rename from grpc/grpcpp-c/src/grpcpp_c.cpp rename to cinterop-c/src/grpcpp_c.cpp diff --git a/grpc/grpcpp-c/src/protowire.cpp b/cinterop-c/src/protowire.cpp similarity index 100% rename from grpc/grpcpp-c/src/protowire.cpp rename to cinterop-c/src/protowire.cpp diff --git a/gradle-conventions/src/main/kotlin/conventions-protoc-gen.gradle.kts b/gradle-conventions/src/main/kotlin/conventions-protoc-gen.gradle.kts new file mode 100644 index 000000000..8c228ee27 --- /dev/null +++ b/gradle-conventions/src/main/kotlin/conventions-protoc-gen.gradle.kts @@ -0,0 +1,40 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import util.other.libs +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + +plugins { + id("conventions-jvm") +} + +dependencies { + implementation(libs.protobuf.java) + + implementation(libs.slf4j.api) + implementation(libs.logback.classic) + + testImplementation(libs.kotlin.test) +} + +tasks.jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + archiveClassifier = "all" + + // Protoc plugins are all fat jars basically (the ones built on jvm) + // be really careful of what you put in the classpath here + from( + configurations.runtimeClasspath.map { prop -> + prop.map { if (it.isDirectory()) it else zipTree(it) } + } + ) +} + +kotlin { + explicitApi = ExplicitApiMode.Disabled +} + +tasks.test { + useJUnitPlatform() +} diff --git a/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts b/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts index 6f3d3b95f..26dcef37b 100644 --- a/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts +++ b/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts @@ -10,7 +10,7 @@ import util.other.isPublicModule import util.tasks.ValidatePublishedArtifactsTask val isGradlePlugin = project.name == "gradle-plugin" -val isProtocGen = project.name == "protoc-gen" +val isProtocGen = project.rootProject.name.startsWith(PROTOC_GEN) val publishingExtension = project.extensions.findByType() val globalRootDir: String by extra diff --git a/gradle-conventions/src/main/kotlin/util/cinterop.kt b/gradle-conventions/src/main/kotlin/util/cinterop.kt new file mode 100644 index 000000000..6a28b9f6c --- /dev/null +++ b/gradle-conventions/src/main/kotlin/util/cinterop.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 util + +import org.gradle.api.GradleException +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.tasks.Exec +import org.gradle.internal.extensions.stdlib.capitalized +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.register +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultCInteropSettings +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.tasks.CInteropProcess +import java.io.File +import kotlin.io.resolve + +// works with the cinterop-c Bazel project +fun KotlinMultiplatformExtension.configureCLibCInterop( + project: Project, + bazelTask: String, + configureCinterop: NamedDomainObjectContainer.(cinteropCLib: File) -> Unit, +) { + val globalRootDir: String by project.extra + + val cinteropCLib = project.layout.projectDirectory.dir("$globalRootDir/cinterop-c").asFile + + // TODO: Replace function implementation, so it does not use an internal API + fun findProgram(name: String) = org.gradle.internal.os.OperatingSystem.current().findInPath(name) + val checkBazel = project.tasks.register("checkBazel") { + doLast { + val bazelPath = findProgram("bazel") + if (bazelPath != null) { + logger.debug("bazel: {}", bazelPath) + } else { + throw GradleException("'bazel' not found on PATH. Please install Bazel (https://bazel.build/).") + } + } + } + + val buildCinteropCLib = project.tasks.register("buildCinteropCLib") { + group = "build" + workingDir = cinteropCLib + commandLine("bash", "-c", "bazel build $bazelTask --config=release") + inputs.files(project.fileTree(cinteropCLib) { exclude("bazel-*/**") }) + outputs.dir(cinteropCLib.resolve("bazel-bin")) + + dependsOn(checkBazel) + } + + targets.filterIsInstance().forEach { + it.compilations.getByName("main") { + cinterops { + configureCinterop(cinteropCLib) + } + + cinterops.all { + val interop = this + + val interopTask = "cinterop${interop.name.capitalized()}${it.targetName.capitalized()}" + project.tasks.named(interopTask, CInteropProcess::class) { + dependsOn(buildCinteropCLib) + } + } + } + } +} diff --git a/gradle-conventions/src/main/kotlin/util/publication.kt b/gradle-conventions/src/main/kotlin/util/publication.kt index ea8cc7e02..e64d4d0b3 100644 --- a/gradle-conventions/src/main/kotlin/util/publication.kt +++ b/gradle-conventions/src/main/kotlin/util/publication.kt @@ -18,7 +18,7 @@ import java.io.File const val KOTLINX_RPC_PREFIX = "kotlinx-rpc" const val PROTOC_GEN = "protoc-gen" -const val PROTOC_GEN_KOTLIN_MULTIPLATFORM = "protoc-gen-kotlin-multiplatform" +const val PROTOC_GEN_KOTLIN_MULTIPLATFORM = "kotlin-multiplatform" /** * Important to configure inside [KotlinTarget.mavenPublication] @@ -28,8 +28,13 @@ const val PROTOC_GEN_KOTLIN_MULTIPLATFORM = "protoc-gen-kotlin-multiplatform" fun MavenPublication.setPublicArtifactId(project: Project) { val publication = this - if (publication.artifactId == PROTOC_GEN) { - publication.artifactId = PROTOC_GEN_KOTLIN_MULTIPLATFORM + if (project.rootProject.name == PROTOC_GEN) { + val middle = when (project.name) { + "grpc" -> "grpc-" + "protobuf" -> "" + else -> error("Unsupported project name in $PROTOC_GEN: ${project.name}") + } + publication.artifactId = "$PROTOC_GEN-$middle$PROTOC_GEN_KOTLIN_MULTIPLATFORM" project.logger.info("Altered artifactId for $name publication: $artifactId") } else if (!publication.artifactId.startsWith(KOTLINX_RPC_PREFIX)) { publication.artifactId = "$KOTLINX_RPC_PREFIX-$artifactId" diff --git a/gradle-plugin/api/gradle-plugin.api b/gradle-plugin/api/gradle-plugin.api index 28ced8ae7..87a07f8f1 100644 --- a/gradle-plugin/api/gradle-plugin.api +++ b/gradle-plugin/api/gradle-plugin.api @@ -4,10 +4,10 @@ public abstract interface annotation class kotlinx/rpc/RpcDangerousApi : java/la public class kotlinx/rpc/RpcExtension { public fun (Lorg/gradle/api/model/ObjectFactory;Lorg/gradle/api/Project;)V public final fun getAnnotationTypeSafetyEnabled ()Lorg/gradle/api/provider/Provider; - public final fun getGrpc ()Lkotlinx/rpc/grpc/GrpcExtension; + public final fun getProtoc ()Lkotlinx/rpc/protoc/ProtocExtension; 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 protoc (Lorg/gradle/api/Action;)V + public static synthetic fun protoc$default (Lkotlinx/rpc/RpcExtension;Lorg/gradle/api/Action;ILjava/lang/Object;)V public final fun strict (Lorg/gradle/api/Action;)V } @@ -21,6 +21,7 @@ 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 getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/RpcStrictMode; public static fun values ()[Lkotlinx/rpc/RpcStrictMode; } @@ -61,6 +62,7 @@ public final class kotlinx/rpc/buf/BufExtension$LogFormat : java/lang/Enum { 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 getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/buf/BufExtension$LogFormat; public static fun values ()[Lkotlinx/rpc/buf/BufExtension$LogFormat; } @@ -79,6 +81,7 @@ public final class kotlinx/rpc/buf/BufGenerateExtension$ErrorFormat : java/lang/ 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 getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; public static fun values ()[Lkotlinx/rpc/buf/BufGenerateExtension$ErrorFormat; } @@ -119,6 +122,7 @@ public abstract class kotlinx/rpc/buf/tasks/BufGenerateTask : kotlinx/rpc/buf/ta public fun ()V public abstract fun getAdditionalArgs ()Lorg/gradle/api/provider/ListProperty; public abstract fun getErrorFormat ()Lorg/gradle/api/provider/Property; + public abstract fun getExecutableFiles ()Lorg/gradle/api/provider/ListProperty; 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; @@ -146,14 +150,8 @@ public final class kotlinx/rpc/buf/tasks/GenerateBufYamlKt$inlined$sam$i$org_gra 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 final class kotlinx/rpc/protoc/ConstsKt { + public static final field PROTOC_GEN_GRPC_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION Ljava/lang/String; public static final field PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION Ljava/lang/String; public static final field PROTO_BUILD_DIR Ljava/lang/String; public static final field PROTO_BUILD_GENERATED Ljava/lang/String; @@ -165,33 +163,39 @@ public final class kotlinx/rpc/proto/ConstsKt { public static final field PROTO_SOURCE_SETS Ljava/lang/String; } -public final class kotlinx/rpc/proto/KotlinMultiplatformPluginJarKt { +public final class kotlinx/rpc/protoc/PluginJarsKt { + public static final fun getGrpcKotlinMultiplatformProtocPluginJarPath (Lorg/gradle/api/Project;)Lorg/gradle/api/provider/Provider; public static final fun getKotlinMultiplatformProtocPluginJarPath (Lorg/gradle/api/Project;)Lorg/gradle/api/provider/Provider; } -public abstract class kotlinx/rpc/proto/ProcessProtoFiles : org/gradle/api/tasks/Copy { +public abstract class kotlinx/rpc/protoc/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 final class kotlinx/rpc/protoc/ProcessProtoFilesKt$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { public fun (Lkotlin/jvm/functions/Function1;)V public final synthetic fun execute (Ljava/lang/Object;)V } -public abstract interface class kotlinx/rpc/proto/ProtoSourceSet { +public abstract interface class kotlinx/rpc/protoc/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 (Lkotlinx/rpc/protoc/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 abstract interface class kotlinx/rpc/protoc/ProtocExtension { + public abstract fun buf (Lorg/gradle/api/Action;)V + public abstract fun getBuf ()Lkotlinx/rpc/buf/BufExtension; + public abstract fun getPlugins ()Lorg/gradle/api/NamedDomainObjectContainer; + public abstract fun plugins (Lorg/gradle/api/Action;)V +} + +public class kotlinx/rpc/protoc/ProtocPlugin { + public static final field Companion Lkotlinx/rpc/protoc/ProtocPlugin$Companion; + public static final field GRPC_KOTLIN_MULTIPLATFORM Ljava/lang/String; public static final field KOTLIN_MULTIPLATFORM Ljava/lang/String; - public 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; @@ -206,42 +210,40 @@ public class kotlinx/rpc/proto/ProtocPlugin { public final fun remote (Lorg/gradle/api/Action;)V } -public abstract class kotlinx/rpc/proto/ProtocPlugin$Artifact { +public abstract class kotlinx/rpc/protoc/ProtocPlugin$Artifact { } -public final class kotlinx/rpc/proto/ProtocPlugin$Artifact$Local : kotlinx/rpc/proto/ProtocPlugin$Artifact { +public final class kotlinx/rpc/protoc/ProtocPlugin$Artifact$Local : kotlinx/rpc/protoc/ProtocPlugin$Artifact { public fun (Lorg/gradle/api/Project;)V public final fun executor (Lorg/gradle/api/provider/Provider;)V public final fun executor ([Ljava/lang/String;)V + public final fun getExecutableFiles ()Lorg/gradle/api/provider/ListProperty; 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 static synthetic fun javaJar$default (Lkotlinx/rpc/protoc/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 final class kotlinx/rpc/protoc/ProtocPlugin$Artifact$Remote : kotlinx/rpc/protoc/ProtocPlugin$Artifact { public fun (Lorg/gradle/api/Project;)V public final fun getLocator ()Lorg/gradle/api/provider/Property; } -public final class kotlinx/rpc/proto/ProtocPlugin$Companion { +public final class kotlinx/rpc/protoc/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/protoc/ProtocPlugin$Strategy : java/lang/Enum { + public static final field All Lkotlinx/rpc/protoc/ProtocPlugin$Strategy; + public static final field Directory Lkotlinx/rpc/protoc/ProtocPlugin$Strategy; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/rpc/protoc/ProtocPlugin$Strategy; + public static fun values ()[Lkotlinx/rpc/protoc/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 final class kotlinx/rpc/protoc/ProtocPluginKt { + public static final fun getGrpcKotlinMultiplatform (Lorg/gradle/api/NamedDomainObjectContainer;)Lorg/gradle/api/NamedDomainObjectProvider; public static final fun getKotlinMultiplatform (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 grpcKotlinMultiplatform (Lorg/gradle/api/NamedDomainObjectContainer;Lorg/gradle/api/Action;)V public static final fun kotlinMultiplatform (Lorg/gradle/api/NamedDomainObjectContainer;Lorg/gradle/api/Action;)V - public static final fun protobufJava (Lorg/gradle/api/NamedDomainObjectContainer;Lorg/gradle/api/Action;)V } diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 6f384d816..8b7d974c7 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -54,7 +54,9 @@ tasks.test { useJUnitPlatform() - dependsOn(gradle.includedBuild("protoc-gen").task(":publishAllPublicationsToBuildRepoRepository")) + val includedBuild = gradle.includedBuild("protoc-gen") + dependsOn(includedBuild.task(":grpc:publishAllPublicationsToBuildRepoRepository")) + dependsOn(includedBuild.task(":protobuf:publishAllPublicationsToBuildRepoRepository")) } // This block is needed to show plugin tasks on --dry-run diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt index d02d8d458..b16a2afe7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt @@ -6,8 +6,8 @@ package kotlinx.rpc -import kotlinx.rpc.grpc.DefaultGrpcExtension -import kotlinx.rpc.grpc.GrpcExtension +import kotlinx.rpc.protoc.DefaultProtocExtension +import kotlinx.rpc.protoc.ProtocExtension import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.model.ObjectFactory @@ -45,21 +45,21 @@ public open class RpcExtension @Inject constructor(objects: ObjectFactory, priva configure.execute(strict) } - internal val grpcApplied = AtomicBoolean(false) + internal val protocApplied = AtomicBoolean(false) /** - * Grpc settings. + * Protoc settings. */ - public val grpc: GrpcExtension by lazy { - grpcApplied.set(true) - objects.newInstance() + public val protoc: ProtocExtension by lazy { + protocApplied.set(true) + objects.newInstance() } /** - * Grpc settings. + * Protoc settings. */ - public fun grpc(configure: Action = Action {}) { - configure.execute(grpc) + public fun protoc(configure: Action = Action {}) { + configure.execute(protoc) } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt index 66442901c..f9f8cfa6f 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt @@ -4,8 +4,8 @@ package kotlinx.rpc -import kotlinx.rpc.grpc.configurePluginProtections -import kotlinx.rpc.proto.createProtoExtensions +import kotlinx.rpc.protoc.configurePluginProtections +import kotlinx.rpc.protoc.createProtoExtensions import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.create 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 a69e37b7e..28941c66d 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/BufExtensions.kt @@ -5,7 +5,7 @@ package kotlinx.rpc.buf import kotlinx.rpc.buf.tasks.BufExecTask -import kotlinx.rpc.proto.ProtocPlugin +import kotlinx.rpc.protoc.ProtocPlugin import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.model.ObjectFactory 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 ce85238e8..57e393cf1 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 @@ -7,7 +7,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.protoc.PROTO_GROUP import kotlinx.rpc.rpcExtension import org.gradle.api.DefaultTask import org.gradle.api.Project @@ -117,7 +117,7 @@ internal fun Project.registerBufExecTask( bufExecutable.set(executableConfiguration.singleFile) this.workingDir.set(workingDir) - val buf = project.rpcExtension().grpc.buf + val buf = project.rpcExtension().protoc.buf configFile.set(buf.configFile) logFormat.set(buf.logFormat) bufTimeoutInWholeSeconds.set(buf.timeout.map { it.inWholeSeconds }) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/BufGenerateTask.kt index 27f14f9ca..e92f21555 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 @@ -4,8 +4,9 @@ package kotlinx.rpc.buf.tasks -import kotlinx.rpc.proto.PROTO_GROUP +import kotlinx.rpc.protoc.PROTO_GROUP import kotlinx.rpc.rpcExtension +import kotlinx.rpc.protoc.ProtocPlugin import org.gradle.api.Project import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property @@ -16,6 +17,7 @@ import org.gradle.api.tasks.TaskProvider import java.io.File import kotlinx.rpc.buf.BufGenerateExtension import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFiles /** * Buf `generate` command. @@ -31,6 +33,15 @@ public abstract class BufGenerateTask : BufExecTask() { @get:InputDirectory internal abstract val importFilesDir: Property + /** + * List of files used during `buf generate` command execution. + * + * @see [ProtocPlugin.Artifact.Local.executableFiles] + */ + // unsued, but required for Gradle to properly recognise inputs + @get:InputFiles + public abstract val executableFiles: ListProperty + /** * Whether to include imports. * @@ -118,7 +129,7 @@ internal fun Project.registerBufGenerateTask( group = PROTO_GROUP description = "Generates code from .proto files using 'buf generate'" - val generate = project.rpcExtension().grpc.buf.generate + val generate = project.rpcExtension().protoc.buf.generate includeImports.set(generate.includeImports) includeWkt.set(generate.includeWkt) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufGenYaml.kt index 42c033386..455d99867 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 @@ -5,9 +5,9 @@ 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.protoc.PROTO_FILES_DIR +import kotlinx.rpc.protoc.PROTO_GROUP +import kotlinx.rpc.protoc.ProtocPlugin import kotlinx.rpc.util.ensureRegularFileExists import org.gradle.api.DefaultTask import org.gradle.api.GradleException diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt index 75e6d2859..3b2d8b63e 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/buf/tasks/GenerateBufYaml.kt @@ -5,7 +5,7 @@ package kotlinx.rpc.buf.tasks import kotlinx.rpc.buf.BUF_YAML -import kotlinx.rpc.proto.PROTO_GROUP +import kotlinx.rpc.protoc.PROTO_GROUP import kotlinx.rpc.util.ensureRegularFileExists import org.gradle.api.DefaultTask import org.gradle.api.Project diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/InternalRpcApi.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/InternalRpcApi.kt new file mode 100644 index 000000000..a6aa1bc55 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/InternalRpcApi.kt @@ -0,0 +1,8 @@ +/* + * 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.internal + +@RequiresOptIn("This API is internal and should not be used outside of kotlinx-rpc") +public annotation class InternalRpcApi diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt new file mode 100644 index 000000000..15ef97c3e --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/internal/configureLocalProtocGenDevelopmentDependency.kt @@ -0,0 +1,42 @@ +/* + * 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.internal + +import kotlinx.rpc.RpcExtension +import kotlinx.rpc.buf.tasks.BufGenerateTask +import kotlinx.rpc.protoc.grpcKotlinMultiplatform +import kotlinx.rpc.protoc.kotlinMultiplatform +import org.gradle.api.Project +import org.gradle.internal.extensions.core.extra +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType + +@InternalRpcApi +public fun Project.configureLocalProtocGenDevelopmentDependency() { + val globalRootDir: String by extra + + the().protoc.plugins { + kotlinMultiplatform { + local { + javaJar("$globalRootDir/protoc-gen/protobuf/build/libs/protobuf-$version-all.jar") + } + } + + grpcKotlinMultiplatform { + local { + javaJar("$globalRootDir/protoc-gen/grpc/build/libs/grpc-$version-all.jar") + } + } + } + + tasks.withType().configureEach { + if (name.endsWith("Test")) { + val includedBuild = gradle.includedBuild("protoc-gen") + dependsOn(includedBuild.task(":grpc:jar")) + dependsOn(includedBuild.task(":protobuf:jar")) + } + } +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kotlinMultiplatformPluginJar.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kotlinMultiplatformPluginJar.kt deleted file mode 100644 index a1dcf08ac..000000000 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/kotlinMultiplatformPluginJar.kt +++ /dev/null @@ -1,40 +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.proto - -import kotlinx.rpc.LIBRARY_VERSION -import org.gradle.api.Project -import org.gradle.api.provider.Provider - -/** - * Absolute path to the `protoc-gen-kotlin-multiplatform` jar. - * - * Can be used to customise the java executable path: - * ```kotlin - * rpc.grpc.protocPlugins.kotlinMultiplatform { - * local { - * javaJar(kotlinMultiplatformProtocPluginJarPath, provider { "my-path-to-java" }) - * } - * } - * ``` - */ -public val Project.kotlinMultiplatformProtocPluginJarPath: Provider - get() = project.configurations.named(PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION) - .map { it.singleFile.absolutePath } - -internal fun Project.configureKotlinMultiplatformPluginJarConfiguration() { - configurations.create(PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION) - - dependencies.add( - PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION, - mapOf( - "group" to "org.jetbrains.kotlinx", - "name" to "protoc-gen-kotlin-multiplatform", - "version" to LIBRARY_VERSION, - "classifier" to "all", - "ext" to "jar", - ), - ) -} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtoSourceSet.kt similarity index 99% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtoSourceSet.kt index 6bc7d7858..35107ec4b 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/DefaultProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtoSourceSet.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.protoc import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.util.findOrCreate diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt similarity index 81% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt index c24effdd5..5ec5800e2 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/DefaultGrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/DefaultProtocExtension.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.protoc import kotlinx.rpc.buf.BufExtension import kotlinx.rpc.buf.configureBufExecutable @@ -10,8 +10,8 @@ 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.KOTLIN_MULTIPLATFORM +import kotlinx.rpc.protoc.ProtocPlugin.Companion.GRPC_KOTLIN_MULTIPLATFORM +import kotlinx.rpc.protoc.ProtocPlugin.Companion.KOTLIN_MULTIPLATFORM import kotlinx.rpc.util.ensureDirectoryExists import org.gradle.api.Action import org.gradle.api.GradleException @@ -27,20 +27,20 @@ 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 org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask import javax.inject.Inject -internal open class DefaultGrpcExtension @Inject constructor( +internal open class DefaultProtocExtension @Inject constructor( objects: ObjectFactory, private val project: Project, -) : GrpcExtension { - override val protocPlugins: NamedDomainObjectContainer = +) : ProtocExtension { + override val plugins: NamedDomainObjectContainer = objects.domainObjectContainer(ProtocPlugin::class.java) { name -> ProtocPlugin(name, project) } - override fun protocPlugins(action: Action>) { - action.execute(protocPlugins) + override fun plugins(action: Action>) { + action.execute(plugins) } override val buf: BufExtension = project.objects.newInstance() @@ -51,11 +51,13 @@ internal open class DefaultGrpcExtension @Inject constructor( init { project.configureBufExecutable() project.configureKotlinMultiplatformPluginJarConfiguration() + project.configureGrpcKotlinMultiplatformPluginJarConfiguration() createDefaultProtocPlugins() project.protoSourceSets.forEach { protoSourceSet -> - protoSourceSet.protocPlugin(protocPlugins.kotlinMultiplatform) + protoSourceSet.protocPlugin(plugins.kotlinMultiplatform) + protoSourceSet.protocPlugin(plugins.grpcKotlinMultiplatform) } project.afterEvaluate { @@ -71,16 +73,6 @@ internal open class DefaultGrpcExtension @Inject constructor( @Suppress("detekt.LongMethod", "detekt.CyclomaticComplexMethod", "detekt.ThrowsCount") private fun Project.configureTasks(protoSourceSet: DefaultProtoSourceSet) { - // todo remove after KRPC-180 - if (!protoSourceSet.languageSourceSets.isPresent || protoSourceSet.languageSourceSets.get().isEmpty()) { - logger.debug( - "Language source sets are not set for proto source set '${protoSourceSet.name}', " + - "skipping buf tasks configuration" - ) - - return - } - val baseName = protoSourceSet.name val buildSourceSetsDir = project.protoBuildDirSourceSets.resolve(baseName) @@ -98,7 +90,7 @@ internal open class DefaultGrpcExtension @Inject constructor( val protocPluginNames = (protoSourceSet.protocPlugins.get() + mainProtocPlugins).distinct() val includedProtocPlugins = protocPluginNames.map { - protocPlugins.findByName(it) + this@DefaultProtocExtension.plugins.findByName(it) ?: throw GradleException("Protoc plugin $it not found") } @@ -156,6 +148,18 @@ internal open class DefaultGrpcExtension @Inject constructor( protoFilesDir = buildSourceSetsProtoDir, importFilesDir = buildSourceSetsImportDir, ) { + includedProtocPlugins.forEach { plugin -> + executableFiles.addAll( + plugin.artifact.map { + if (it is ProtocPlugin.Artifact.Local) { + it.executableFiles.get() + } else { + emptyList() + } + } + ) + } + dependsOn(generateBufGenYamlTask) dependsOn(generateBufYamlTask) dependsOn(processProtoTask) @@ -172,32 +176,22 @@ internal open class DefaultGrpcExtension @Inject constructor( protoSourceSet.generateTask.set(bufGenerateTask) - // todo fix for common source sets - tasks.withType().configureEach { + project.tasks.withType>().all { // 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) { + val isTest = name.startsWith("compileTest") + if (isTest && baseName.lowercase().endsWith("test")) { + dependsOn(bufGenerateTask) + } else if (!isTest && baseName.lowercase().endsWith("main")) { dependsOn(bufGenerateTask) } } - tasks.withType().configureEach { + project.tasks.withType().all { // compileJvmTestJava - test (java, kmp) // compileJvmMainJava - main (java, kmp) // compileJava - main (java) @@ -271,19 +265,31 @@ internal open class DefaultGrpcExtension @Inject constructor( } private fun createDefaultProtocPlugins() { - protocPlugins.create(KOTLIN_MULTIPLATFORM) { + val explicitApiModeEnabled = project.provider { + project.the().explicitApi != ExplicitApiMode.Disabled + } + + plugins.create(KOTLIN_MULTIPLATFORM) { local { javaJar(project.kotlinMultiplatformProtocPluginJarPath) } options.put("debugOutput", "protoc-gen-kotlin-multiplatform.log") - options.put("explicitApiModeEnabled", project.provider { - project.the().explicitApi != ExplicitApiMode.Disabled - }) + options.put("explicitApiModeEnabled", explicitApiModeEnabled) + } + + plugins.create(GRPC_KOTLIN_MULTIPLATFORM) { + local { + javaJar(project.grpcKotlinMultiplatformProtocPluginJarPath) + } + + options.put("debugOutput", "protoc-gen-grpc-kotlin-multiplatform.log") + options.put("explicitApiModeEnabled", explicitApiModeEnabled) } // ignore for bufGenerate task caching project.normalization.runtimeClasspath.ignore("**/protoc-gen-kotlin-multiplatform.log") + project.normalization.runtimeClasspath.ignore("**/protoc-gen-grpc-kotlin-multiplatform.log") project.normalization.runtimeClasspath.ignore("**/.keep") } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt similarity index 98% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt index ed6b7e61d..7bc197c2b 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProcessProtoFiles.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProcessProtoFiles.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.protoc import kotlinx.rpc.util.ensureRegularFileExists import org.gradle.api.Project diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt similarity index 89% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt index 9b14d33a7..455e6182f 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtoSourceSet.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtoSourceSet.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.protoc import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer @@ -25,7 +25,7 @@ public interface ProtoSourceSet { * * Example: * ```kotlin - * protocPlugin(rpc.grpc.protocPlugins.myPlugin) + * protocPlugin(rpc.protoc.plugins.myPlugin) * ``` */ public fun protocPlugin(plugin: NamedDomainObjectProvider) @@ -35,7 +35,7 @@ public interface ProtoSourceSet { * * Example: * ```kotlin - * protocPlugin(rpc.grpc.protocPlugins.myPlugin.get()) + * protocPlugin(rpc.protoc.plugins.myPlugin.get()) * ``` */ public fun protocPlugin(plugin: ProtocPlugin) diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocExtension.kt similarity index 73% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocExtension.kt index 265841fd5..ff8e843f9 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/GrpcExtension.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocExtension.kt @@ -2,10 +2,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.grpc +package kotlinx.rpc.protoc import kotlinx.rpc.buf.BufExtension -import kotlinx.rpc.proto.ProtocPlugin import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer @@ -15,20 +14,20 @@ import org.gradle.api.NamedDomainObjectContainer * To enable the gRPC capabilities, add the following minimal code to your `build.gradle.kts`: * ```kotlin * rpc { - * grpc() + * protoc() * } * ``` */ -public interface GrpcExtension { +public interface ProtocExtension { /** * List of protoc plugins to be applied to the project. */ - public val protocPlugins: NamedDomainObjectContainer + public val plugins: NamedDomainObjectContainer /** * Configures the protoc plugins. */ - public fun protocPlugins(action: Action>) + public fun plugins(action: Action>) /** * Configuration for the Buf build tool. diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt similarity index 82% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt index 496169fe7..c5f0c7774 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/ProtocPlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/ProtocPlugin.kt @@ -2,9 +2,11 @@ * 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 +package kotlinx.rpc.protoc -import kotlinx.rpc.proto.ProtocPlugin.Companion.KOTLIN_MULTIPLATFORM +import kotlinx.rpc.protoc.ProtocPlugin.Companion.GRPC_KOTLIN_MULTIPLATFORM +import kotlinx.rpc.protoc.ProtocPlugin.Companion.KOTLIN_MULTIPLATFORM +import kotlinx.rpc.buf.tasks.BufGenerateTask import kotlinx.rpc.util.OS import org.gradle.api.Action import org.gradle.api.NamedDomainObjectContainer @@ -17,6 +19,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 /** * Access to the `kotlin-multiplatform` protoc plugin. @@ -31,6 +34,19 @@ public fun NamedDomainObjectContainer.kotlinMultiplatform(action: kotlinMultiplatform.configure(action) } +/** + * Access to the `grpc-kotlin-multiplatform` protoc plugin. + */ +public val NamedDomainObjectContainer.grpcKotlinMultiplatform: NamedDomainObjectProvider + get() = named(GRPC_KOTLIN_MULTIPLATFORM) + +/** + * Configures the `grpc-kotlin-multiplatform` protoc plugin. + */ +public fun NamedDomainObjectContainer.grpcKotlinMultiplatform(action: Action) { + grpcKotlinMultiplatform.configure(action) +} + /** * Access to a specific protoc plugin. */ @@ -151,6 +167,13 @@ public open class ProtocPlugin( * @see [kotlinMultiplatform] */ public const val KOTLIN_MULTIPLATFORM: String = "kotlin-multiplatform" + + /** + * The name of the `grpc-kotlin-multiplatform` protoc plugin. + * + * @see [grpcKotlinMultiplatform] + */ + public const val GRPC_KOTLIN_MULTIPLATFORM: String = "grpc-kotlin-multiplatform" } /** @@ -181,6 +204,20 @@ public open class ProtocPlugin( */ public val executor: ListProperty = project.objects.listProperty() + /** + * Specifies the files that will be used during execution by the plugin. + * + * Useful to properly handle caching in [BufGenerateTask]. + * + * Automatically includes the plugin's .jar file if [javaJar] was used. + * + * Optional. + * + * @see [BufGenerateTask.executableFiles] + */ + public val executableFiles: ListProperty = project.objects.listProperty() + .convention(emptyList()) + /** * Command-line arguments that execute the plugin. */ @@ -201,6 +238,8 @@ public open class ProtocPlugin( * 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) { + executableFiles.add(jarPath.map { project.file(it) }) + if (executablePath == null) { executor(jarPath.map { listOf(OS.javaExePath, "-jar", it) }) return diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/consts.kt similarity index 85% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/consts.kt index dd9dfb093..5448e8156 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/consts.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/consts.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.protoc import org.gradle.api.artifacts.Configuration @@ -57,3 +57,10 @@ public const val PROTO_FILES_IMPORT_DIR: String = "import" * MUST be a single file. */ public const val PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION: String = "protocGenKotlinMultiplatformJar" + +/** + * [Configuration] name for the `protoc-gen-grpc-kotlin-multiplatform` protoc plugin artifact. + * + * MUST be a single file. + */ +public const val PROTOC_GEN_GRPC_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION: String = "protocGenGrpcKotlinMultiplatformJar" diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/directory.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/directory.kt similarity index 96% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/directory.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/directory.kt index d3231e6b5..02fb6a789 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/proto/directory.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/directory.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.protoc import org.gradle.api.Project import java.io.File diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/pluginJars.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/pluginJars.kt new file mode 100644 index 000000000..f12033e8e --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/pluginJars.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.protoc + +import kotlinx.rpc.LIBRARY_VERSION +import org.gradle.api.Project +import org.gradle.api.provider.Provider + +/** + * Absolute path to the `protoc-gen-kotlin-multiplatform` jar. + * + * Can be used to customize the java executable path: + * ```kotlin + * rpc.protoc.plugins.kotlinMultiplatform { + * local { + * javaJar(kotlinMultiplatformProtocPluginJarPath, provider { "my-path-to-java" }) + * } + * } + * ``` + */ +public val Project.kotlinMultiplatformProtocPluginJarPath: Provider + get() = jarPathAccessor(PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION) + +internal fun Project.configureKotlinMultiplatformPluginJarConfiguration() { + configureJarConfiguration( + configurationName = PROTOC_GEN_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION, + pluginArtifactName = "protoc-gen-kotlin-multiplatform", + ) +} + +/** + * Absolute path to the `protoc-gen-grpc-kotlin-multiplatform` jar. + * + * Can be used to customize the java executable path: + * ```kotlin + * rpc.protoc.plugins.grpcKotlinMultiplatform { + * local { + * javaJar(grpcKotlinMultiplatformProtocPluginJarPath, provider { "my-path-to-java" }) + * } + * } + * ``` + */ +public val Project.grpcKotlinMultiplatformProtocPluginJarPath: Provider + get() = jarPathAccessor(PROTOC_GEN_GRPC_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION) + +internal fun Project.configureGrpcKotlinMultiplatformPluginJarConfiguration() { + configureJarConfiguration( + configurationName = PROTOC_GEN_GRPC_KOTLIN_MULTIPLATFORM_JAR_CONFIGURATION, + pluginArtifactName = "protoc-gen-grpc-kotlin-multiplatform", + ) +} + +private fun Project.jarPathAccessor(name: String): Provider = + project.configurations.named(name) + .map { it.singleFile.absolutePath } + +private fun Project.configureJarConfiguration(configurationName: String, pluginArtifactName: String) { + configurations.create(configurationName) + + dependencies.add( + configurationName, + mapOf( + "group" to "org.jetbrains.kotlinx", + "name" to pluginArtifactName, + "version" to LIBRARY_VERSION, + "classifier" to "all", + "ext" to "jar", + ), + ) +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/protections.kt similarity index 95% rename from gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt rename to gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/protections.kt index cea7ccae7..10067bdec 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/grpc/protections.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/protoc/protections.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.protoc import kotlinx.rpc.rpcExtension import org.gradle.api.GradleException @@ -20,7 +20,7 @@ internal fun Project.configurePluginProtections() { pluginManager.withPlugin(PROTOBUF_PLUGIN_ID) { isProtobufPluginApplied = true } afterEvaluate { - if (!rpcExtension().grpcApplied.get()) { + if (!rpcExtension().protocApplied.get()) { return@afterEvaluate } diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt index a4977b2d2..66d55d109 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcJvmProjectTest.kt @@ -139,7 +139,11 @@ plugins: out: kotlin-multiplatform opt: - debugOutput=protoc-gen-kotlin-multiplatform.log - - messageMode=interface + - explicitApiModeEnabled=true + - local: [protoc-gen-grpc-kotlin-multiplatform] + out: grpc-kotlin-multiplatform + opt: + - debugOutput=protoc-gen-grpc-kotlin-multiplatform.log - explicitApiModeEnabled=true - local: [path, to, protoc-gen-myplugin.exe] out: myPlugin diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt index bc9836ac5..bd0dafedd 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/GrpcKmpProjectTest.kt @@ -95,7 +95,6 @@ class GrpcKmpProjectTest : GrpcBaseTest() { } @Test - // todo remove after KRPC-180 fun `No JVM Targets`() = runGrpcTest { runNonExistentTask(bufGenerateMain) runNonExistentTask(bufGenerateTest) diff --git a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt index 15e73d7a4..89ca6c01a 100644 --- a/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt +++ b/gradle-plugin/src/test/kotlin/kotlinx/rpc/base/GrpcBaseTest.kt @@ -101,10 +101,16 @@ abstract class GrpcBaseTest : BaseTest() { } val fileContent = file.readLines().joinToString("\n") { - if (it.contains("protoc-gen-kotlin-multiplatform")) { - it.replace(localPluginExecRegex, "[protoc-gen-kotlin-multiplatform]") - } else { - it + when { + it.contains("protoc-gen-kotlin-multiplatform") -> { + it.replace(localPluginExecRegex, "[protoc-gen-kotlin-multiplatform]") + } + it.contains("protoc-gen-grpc-kotlin-multiplatform") -> { + it.replace(localPluginExecRegex, "[protoc-gen-grpc-kotlin-multiplatform]") + } + else -> { + it + } } } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts index 526a93fd8..437f5e141 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/can_add_custom_protoc_plugins/build.gradle.kts @@ -3,7 +3,7 @@ */ import org.gradle.kotlin.dsl.version -import kotlinx.rpc.proto.* +import kotlinx.rpc.protoc.* plugins { kotlin("jvm") version "" @@ -11,8 +11,8 @@ plugins { } rpc { - grpc { - protocPlugins { + protoc { + plugins { create("myPlugin") { local { executor("path", "to", "protoc-gen-myplugin.exe") @@ -38,7 +38,7 @@ rpc { protoSourceSets { main { - protocPlugin(rpc.grpc.protocPlugins.named("myPlugin")) - protocPlugin(rpc.grpc.protocPlugins.named("myRemotePlugin")) + protocPlugin(rpc.protoc.plugins.named("myPlugin")) + protocPlugin(rpc.protoc.plugins.named("myRemotePlugin")) } } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_buf_cli_flags/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_buf_cli_flags/build.gradle.kts index f3e6962e7..aa739abe1 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_buf_cli_flags/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_buf_cli_flags/build.gradle.kts @@ -3,7 +3,7 @@ */ import org.gradle.kotlin.dsl.version -import kotlinx.rpc.proto.* +import kotlinx.rpc.protoc.* import kotlinx.rpc.buf.* import java.io.File import kotlin.time.Duration.Companion.seconds @@ -14,7 +14,7 @@ plugins { } rpc { - grpc { + protoc { buf { configFile = File("some.buf.yaml") logFormat = BufExtension.LogFormat.Json diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts index 6acb669c7..beec798d6 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/custom_protoc_plugins_must_declare_an_artifact/build.gradle.kts @@ -3,7 +3,7 @@ */ import org.gradle.kotlin.dsl.version -import kotlinx.rpc.proto.* +import kotlinx.rpc.protoc.* plugins { kotlin("jvm") version "" @@ -11,8 +11,8 @@ plugins { } rpc { - grpc { - protocPlugins { + protoc { + plugins { create("myPlugin") {} } } @@ -20,6 +20,6 @@ rpc { protoSourceSets { main { - protocPlugin(rpc.grpc.protocPlugins.named("myPlugin")) + protocPlugin(rpc.protoc.plugins.named("myPlugin")) } } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/build.gradle.kts index aafd2083f..60d6d646d 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/exclude_and_include_in_protosourcesets/build.gradle.kts @@ -27,5 +27,5 @@ protoSourceSets { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/main_and_test_mixed_sources/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/main_and_test_mixed_sources/build.gradle.kts index e9b01b47d..f1d4dc607 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/main_and_test_mixed_sources/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/main_and_test_mixed_sources/build.gradle.kts @@ -10,5 +10,5 @@ plugins { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/minimal_grpc_configuration/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/minimal_grpc_configuration/build.gradle.kts index e9b01b47d..f1d4dc607 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/minimal_grpc_configuration/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/minimal_grpc_configuration/build.gradle.kts @@ -10,5 +10,5 @@ plugins { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/proto_tasks_are_cached_properly/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/proto_tasks_are_cached_properly/build.gradle.kts index 3b6317883..56ee2e979 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/proto_tasks_are_cached_properly/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/proto_tasks_are_cached_properly/build.gradle.kts @@ -3,7 +3,7 @@ */ import org.gradle.kotlin.dsl.version -import kotlinx.rpc.proto.kotlinMultiplatform +import kotlinx.rpc.protoc.kotlinMultiplatform plugins { kotlin("jvm") version "" @@ -11,5 +11,5 @@ plugins { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts index 4ce1593cb..9d75c4634 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/skip_generation_when_no_proto_files/build.gradle.kts @@ -19,5 +19,5 @@ protoSourceSets { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/test_only_sources/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/test_only_sources/build.gradle.kts index e9b01b47d..f1d4dc607 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/test_only_sources/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcJvmProjectTest/test_only_sources/build.gradle.kts @@ -10,5 +10,5 @@ plugins { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/build.gradle.kts index cbd88cc2c..a4500a1c1 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/main_and_test_mixed_sources/build.gradle.kts @@ -14,5 +14,5 @@ kotlin { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/build.gradle.kts index cbd88cc2c..a4500a1c1 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/minimal_grpc_configuration/build.gradle.kts @@ -14,5 +14,5 @@ kotlin { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/build.gradle.kts index cbd88cc2c..a4500a1c1 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/test_only_sources/build.gradle.kts @@ -14,5 +14,5 @@ kotlin { } rpc { - grpc() + protoc() } diff --git a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts index adf702591..1a8ba4661 100644 --- a/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts +++ b/gradle-plugin/src/test/resources/projects/GrpcKmpProjectTest/with_other_targets/build.gradle.kts @@ -21,5 +21,5 @@ protoSourceSets { } rpc { - grpc() + protoc() } diff --git a/grpc/grpc-core/build.gradle.kts b/grpc/grpc-core/build.gradle.kts index 5fae426b6..7fcd3dad7 100644 --- a/grpc/grpc-core/build.gradle.kts +++ b/grpc/grpc-core/build.gradle.kts @@ -2,12 +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.kotlinMultiplatform -import org.gradle.internal.extensions.stdlib.capitalized -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import org.jetbrains.kotlin.gradle.tasks.CInteropProcess -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +@file:OptIn(InternalRpcApi::class) + +import kotlinx.rpc.internal.InternalRpcApi +import kotlinx.rpc.internal.configureLocalProtocGenDevelopmentDependency +import util.configureCLibCInterop plugins { alias(libs.plugins.conventions.kmp) @@ -31,7 +30,6 @@ kotlin { implementation(libs.atomicfu) implementation(libs.kotlinx.io.core) - implementation(libs.kotlinx.collections.immutable) } } @@ -52,89 +50,28 @@ kotlin { api(libs.grpc.util) api(libs.grpc.stub) api(libs.grpc.protobuf) - api("io.grpc:grpc-protobuf-lite:${libs.versions.grpc.asProvider().get()}") + api(libs.grpc.protobuf.lite) implementation(libs.grpc.kotlin.stub) // causes problems to jpms if api - api(libs.protobuf.java.util) - implementation(libs.protobuf.kotlin) } } jvmTest { dependencies { - implementation(projects.grpc.grpcCore) - implementation(libs.coroutines.core) - implementation(libs.coroutines.test) - - implementation(libs.grpc.stub) implementation(libs.grpc.netty) - implementation(libs.grpc.protobuf) - implementation(libs.grpc.kotlin.stub) - implementation(libs.protobuf.java.util) - implementation(libs.protobuf.kotlin) - } - } - } - - val grpcppCLib = projectDir.resolve("../grpcpp-c") - - // TODO: Replace function implementation, so it does not use an internal API - fun findProgram(name: String) = org.gradle.internal.os.OperatingSystem.current().findInPath(name) - val checkBazel by tasks.registering { - doLast { - val bazelPath = findProgram("bazel") - if (bazelPath != null) { - logger.debug("bazel: {}", bazelPath) - } else { - throw GradleException("'bazel' not found on PATH. Please install Bazel (https://bazel.build/).") } } } - val buildGrpcppCLib = tasks.register("buildGrpcppCLib") { - group = "build" - workingDir = grpcppCLib - commandLine("bash", "-c", "bazel build :grpcpp_c_static :protowire_static --config=release") - inputs.files(fileTree(grpcppCLib) { exclude("bazel-*/**") }) - outputs.dir(grpcppCLib.resolve("bazel-bin")) - - dependsOn(checkBazel) - } - - - targets.filterIsInstance().forEach { - it.compilations.getByName("main") { - cinterops { - val libgrpcpp_c by creating { - includeDirs( - grpcppCLib.resolve("include"), - grpcppCLib.resolve("bazel-grpcpp-c/external/grpc+/include") - ) - extraOpts( - "-libraryPath", "${grpcppCLib.resolve("bazel-out/darwin_arm64-opt/bin")}", - ) - } - - val interopTask = "cinterop${libgrpcpp_c.name.capitalized()}${it.targetName.capitalized()}" - tasks.named(interopTask, CInteropProcess::class) { - dependsOn(buildGrpcppCLib) - } - - - val libprotowire by creating { - includeDirs( - grpcppCLib.resolve("include") - ) - extraOpts( - "-libraryPath", "${grpcppCLib.resolve("bazel-out/darwin_arm64-opt/bin")}", - ) - } - - val libProtowireTask = "cinterop${libprotowire.name.capitalized()}${it.targetName.capitalized()}" - tasks.named(libProtowireTask, CInteropProcess::class) { - dependsOn(buildGrpcppCLib) - } - - } + configureCLibCInterop(project, ":grpcpp_c_static") { cinteropCLib -> + @Suppress("unused") + val libgrpcpp_c by creating { + includeDirs( + cinteropCLib.resolve("include"), + cinteropCLib.resolve("bazel-cinterop-c/external/grpc+/include"), + ) + extraOpts( + "-libraryPath", "${cinteropCLib.resolve("bazel-out/darwin_arm64-opt/bin")}", + ) } } } @@ -147,28 +84,4 @@ protoSourceSets { } } - -rpc { - grpc { - val globalRootDir: String by extra - - protocPlugins.kotlinMultiplatform { - local { - javaJar("$globalRootDir/protoc-gen/build/libs/protoc-gen-$version-all.jar") - } - } - - project.tasks.withType().configureEach { - if (name.endsWith("Test")) { - dependsOn(gradle.includedBuild("protoc-gen").task(":jar")) - } - } - - // generate protos before compiling tests - project.tasks.withType().configureEach { - if (name.startsWith("compileTest")) { - dependsOn(project.tasks.withType()) - } - } - } -} +configureLocalProtocGenDevelopmentDependency() diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/BaseGrpcServiceTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/BaseGrpcServiceTest.kt similarity index 98% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/BaseGrpcServiceTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/BaseGrpcServiceTest.kt index 04e08f276..08ed33cd4 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/BaseGrpcServiceTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/BaseGrpcServiceTest.kt @@ -2,7 +2,7 @@ * 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.service +package kotlinx.rpc.grpc.test import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/CustomResolverGrpcServiceTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/CustomResolverGrpcServiceTest.kt similarity index 99% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/CustomResolverGrpcServiceTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/CustomResolverGrpcServiceTest.kt index e68c002aa..eb8ee4d63 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/CustomResolverGrpcServiceTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/CustomResolverGrpcServiceTest.kt @@ -2,7 +2,7 @@ * 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.service +package kotlinx.rpc.grpc.test import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/RawClientServerTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/RawClientServerTest.kt similarity index 95% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/RawClientServerTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/RawClientServerTest.kt index 98840b0a3..4d5257274 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/RawClientServerTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/RawClientServerTest.kt @@ -2,7 +2,7 @@ * 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 +package kotlinx.rpc.grpc.test import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -14,6 +14,10 @@ import kotlinx.io.Buffer import kotlinx.io.Source import kotlinx.io.readString import kotlinx.io.writeString +import kotlinx.rpc.grpc.ManagedChannelBuilder +import kotlinx.rpc.grpc.Server +import kotlinx.rpc.grpc.ServerBuilder +import kotlinx.rpc.grpc.buildChannel import kotlinx.rpc.grpc.codec.MessageCodec import kotlinx.rpc.grpc.internal.GrpcChannel import kotlinx.rpc.grpc.internal.MethodDescriptor @@ -29,6 +33,7 @@ import kotlinx.rpc.grpc.internal.serverStreamingServerMethodDefinition import kotlinx.rpc.grpc.internal.serviceDescriptor import kotlinx.rpc.grpc.internal.unaryRpc import kotlinx.rpc.grpc.internal.unaryServerMethodDefinition +import kotlinx.rpc.grpc.serverServiceDefinition import kotlin.reflect.typeOf import kotlin.test.Test import kotlin.test.assertEquals diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/SerializationGrpcServiceTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/SerializationGrpcServiceTest.kt similarity index 98% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/SerializationGrpcServiceTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/SerializationGrpcServiceTest.kt index 0ac1e2905..5c3467bf3 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/service/SerializationGrpcServiceTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/SerializationGrpcServiceTest.kt @@ -2,7 +2,7 @@ * 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.service +package kotlinx.rpc.grpc.test import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcServerTest.kt similarity index 100% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/GrpcServerTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcServerTest.kt diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/StreamingTest.kt similarity index 100% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/StreamingTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/StreamingTest.kt diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/TestPrimitiveService.kt similarity index 100% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestPrimitiveService.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/TestPrimitiveService.kt diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/TestReferenceService.kt similarity index 100% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/core/test/TestReferenceService.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/TestReferenceService.kt diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.js.kt deleted file mode 100644 index 2218a5078..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.js.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package kotlinx.rpc.grpc - -/** - * Same as [ManagedChannel], but is platform-exposed. - */ -public actual abstract class ManagedChannelPlatform - -/** - * Builder class for [ManagedChannel]. - */ -public actual abstract class ManagedChannelBuilder> - -internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel { - error("JS target is not supported in gRPC") -} - -internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> { - error("JS target is not supported in gRPC") -} - -internal actual fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*> { - error("JS target is not supported in gRPC") -} diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/MutableHandlerRegistry.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/MutableHandlerRegistry.js.kt deleted file mode 100644 index aa6ba04a1..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/MutableHandlerRegistry.js.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package kotlinx.rpc.grpc - -/** - * Registry of services and their methods used by servers to dispatching incoming calls. - */ -public actual abstract class HandlerRegistry - -internal actual class MutableHandlerRegistry : HandlerRegistry() { - actual fun addService(service: ServerServiceDefinition): ServerServiceDefinition? { - error("JS target is not supported in gRPC") - } - - actual fun removeService(service: ServerServiceDefinition): Boolean { - error("JS target is not supported in gRPC") - } -} diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/Server.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/Server.js.kt deleted file mode 100644 index c09084882..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/Server.js.kt +++ /dev/null @@ -1,22 +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.grpc - -/** - * Platform-specific gRPC server builder. - */ -public actual abstract class ServerBuilder> { - public actual abstract fun addService(service: ServerServiceDefinition): T - - public actual abstract fun fallbackHandlerRegistry(registry: HandlerRegistry?): T -} - -internal actual fun ServerBuilder(port: Int): ServerBuilder<*> { - error("JS target is not supported in gRPC") -} - -internal actual fun Server(builder: ServerBuilder<*>): Server { - error("JS target is not supported in gRPC") -} diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.js.kt deleted file mode 100644 index 0ac0a6e29..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.js.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package kotlinx.rpc.grpc - -/** - * Definition of a service to be exposed via a Server. - */ -public actual class ServerServiceDefinition diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt deleted file mode 100644 index c030ce564..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.js.kt +++ /dev/null @@ -1,17 +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.grpc.internal - -internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { - TODO("Not yet implemented") -} - -internal actual fun WireDecoder.popLimit(limit: Int) { - TODO("Not yet implemented") -} - -internal actual fun WireDecoder.bytesUntilLimit(): Int { - TODO("Not yet implemented") -} diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.js.kt deleted file mode 100644 index 4cff6ef95..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.js.kt +++ /dev/null @@ -1,11 +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.grpc.pb - -import kotlinx.io.Buffer - -internal actual fun WireDecoder(source: Buffer): WireDecoder { - TODO("Not yet implemented") -} \ No newline at end of file diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.js.kt deleted file mode 100644 index 733950eba..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.js.kt +++ /dev/null @@ -1,11 +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.grpc.pb - -import kotlinx.io.Sink - -internal actual fun WireEncoder(sink: Sink): WireEncoder { - TODO("Not yet implemented") -} \ No newline at end of file diff --git a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt b/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt deleted file mode 100644 index 041674e94..000000000 --- a/grpc/grpc-core/src/jsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.js.kt +++ /dev/null @@ -1,29 +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.grpc.pb - -internal actual fun WireSize.int32(value: Int): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.int64(value: Long): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt32(value: UInt): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt64(value: ULong): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt32(value: Int): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt64(value: Long): Int { - TODO("Not yet implemented") -} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/BridgeTest.kt b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/test/BridgeTest.kt similarity index 97% rename from grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/BridgeTest.kt rename to grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/test/BridgeTest.kt index 5c3b8b97a..e0ef885b2 100644 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/BridgeTest.kt +++ b/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/test/BridgeTest.kt @@ -2,7 +2,7 @@ * 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.internal +package kotlinx.rpc.grpc.test import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.coroutines.runBlocking diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.wasmJs.kt deleted file mode 100644 index 8e9101a03..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.wasmJs.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package kotlinx.rpc.grpc - -/** - * Same as [ManagedChannel], but is platform-exposed. - */ -public actual abstract class ManagedChannelPlatform - -/** - * Builder class for [ManagedChannel]. - */ -public actual abstract class ManagedChannelBuilder> - -internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel { - error("WasmJS target is not supported in gRPC") -} - -internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> { - error("WasmJS target is not supported in gRPC") -} - -internal actual fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*> { - error("WasmJS target is not supported in gRPC") -} diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/MutableHandlerRegistry.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/MutableHandlerRegistry.wasmJs.kt deleted file mode 100644 index f3b50fc10..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/MutableHandlerRegistry.wasmJs.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package kotlinx.rpc.grpc - -/** - * Registry of services and their methods used by servers to dispatching incoming calls. - */ -public actual abstract class HandlerRegistry - -internal actual class MutableHandlerRegistry : HandlerRegistry() { - actual fun addService(service: ServerServiceDefinition): ServerServiceDefinition? { - error("WasmJS target is not supported in gRPC") - } - - actual fun removeService(service: ServerServiceDefinition): Boolean { - error("WasmJS target is not supported in gRPC") - } -} diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/Server.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/Server.wasmJs.kt deleted file mode 100644 index 698a08f9a..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/Server.wasmJs.kt +++ /dev/null @@ -1,23 +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.grpc - -/** - * Platform-specific gRPC server builder. - */ - -public actual abstract class ServerBuilder> { - public actual abstract fun addService(service: ServerServiceDefinition): T - - public actual abstract fun fallbackHandlerRegistry(registry: HandlerRegistry?): T -} - -internal actual fun ServerBuilder(port: Int): ServerBuilder<*> { - error("WasmJS target is not supported in gRPC") -} - -internal actual fun Server(builder: ServerBuilder<*>): Server { - error("WasmJS target is not supported in gRPC") -} diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.wasmJs.kt deleted file mode 100644 index 0ac0a6e29..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/ServerServiceDefinition.wasmJs.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package kotlinx.rpc.grpc - -/** - * Definition of a service to be exposed via a Server. - */ -public actual class ServerServiceDefinition diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt deleted file mode 100644 index c030ce564..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.wasmJs.kt +++ /dev/null @@ -1,17 +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.grpc.internal - -internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { - TODO("Not yet implemented") -} - -internal actual fun WireDecoder.popLimit(limit: Int) { - TODO("Not yet implemented") -} - -internal actual fun WireDecoder.bytesUntilLimit(): Int { - TODO("Not yet implemented") -} diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.wasmJs.kt deleted file mode 100644 index 4cff6ef95..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.wasmJs.kt +++ /dev/null @@ -1,11 +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.grpc.pb - -import kotlinx.io.Buffer - -internal actual fun WireDecoder(source: Buffer): WireDecoder { - TODO("Not yet implemented") -} \ No newline at end of file diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.wasmJs.kt deleted file mode 100644 index 733950eba..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.wasmJs.kt +++ /dev/null @@ -1,11 +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.grpc.pb - -import kotlinx.io.Sink - -internal actual fun WireEncoder(sink: Sink): WireEncoder { - TODO("Not yet implemented") -} \ No newline at end of file diff --git a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt b/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt deleted file mode 100644 index 041674e94..000000000 --- a/grpc/grpc-core/src/wasmJsMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.wasmJs.kt +++ /dev/null @@ -1,29 +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.grpc.pb - -internal actual fun WireSize.int32(value: Int): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.int64(value: Long): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt32(value: UInt): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.uInt64(value: ULong): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt32(value: Int): Int { - TODO("Not yet implemented") -} - -internal actual fun WireSize.sInt64(value: Long): Int { - TODO("Not yet implemented") -} \ No newline at end of file diff --git a/grpc/grpc-ktor-server/build.gradle.kts b/grpc/grpc-ktor-server/build.gradle.kts index 8fa4115d7..9084429c2 100644 --- a/grpc/grpc-ktor-server/build.gradle.kts +++ b/grpc/grpc-ktor-server/build.gradle.kts @@ -2,10 +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.kotlinMultiplatform +@file:OptIn(InternalRpcApi::class) + +import kotlinx.rpc.internal.InternalRpcApi +import kotlinx.rpc.internal.configureLocalProtocGenDevelopmentDependency import org.gradle.kotlin.dsl.kotlin -import org.gradle.kotlin.dsl.withType plugins { alias(libs.plugins.conventions.kmp) @@ -40,20 +41,4 @@ kotlin { } } -rpc { - grpc { - val globalRootDir: String by extra - - protocPlugins.kotlinMultiplatform { - local { - javaJar("$globalRootDir/protoc-gen/build/libs/protoc-gen-$version-all.jar") - } - } - - project.tasks.withType().configureEach { - if (name.endsWith("Test")) { - dependsOn(gradle.includedBuild("protoc-gen").task(":jar")) - } - } - } -} +configureLocalProtocGenDevelopmentDependency() diff --git a/protobuf/protobuf-core/build.gradle.kts b/protobuf/protobuf-core/build.gradle.kts new file mode 100644 index 000000000..fdc7b1592 --- /dev/null +++ b/protobuf/protobuf-core/build.gradle.kts @@ -0,0 +1,69 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:OptIn(InternalRpcApi::class) + +import kotlinx.rpc.internal.InternalRpcApi +import kotlinx.rpc.internal.configureLocalProtocGenDevelopmentDependency +import util.configureCLibCInterop + +plugins { + alias(libs.plugins.conventions.kmp) + alias(libs.plugins.kotlinx.rpc) +} + +kotlin { + sourceSets { + commonMain { + dependencies { + api(projects.utils) + api(libs.kotlinx.io.core) + } + } + + commonTest { + dependencies { + implementation(projects.grpc.grpcCodec) + + implementation(libs.kotlin.test) + implementation(libs.coroutines.test) + } + } + + jvmMain { + dependencies { + api(libs.protobuf.java.util) + implementation(libs.protobuf.kotlin) + } + } + + nativeMain { + dependencies { + implementation(libs.kotlinx.collections.immutable) + } + } + } + + configureCLibCInterop(project, ":protowire_static") { cinteropCLib -> + @Suppress("unused") + val libprotowire by creating { + includeDirs( + cinteropCLib.resolve("include") + ) + extraOpts( + "-libraryPath", "${cinteropCLib.resolve("bazel-out/darwin_arm64-opt/bin")}", + ) + } + } +} + +protoSourceSets { + commonTest { + proto { + exclude("exclude/**") + } + } +} + +configureLocalProtocGenDevelopmentDependency() diff --git a/protobuf/protobuf-core/gradle.properties b/protobuf/protobuf-core/gradle.properties new file mode 100644 index 000000000..4ae0c96a1 --- /dev/null +++ b/protobuf/protobuf-core/gradle.properties @@ -0,0 +1,21 @@ +# +# 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 +kotlinx.rpc.exclude.js=true +kotlinx.rpc.exclude.wasmJs=true +kotlinx.rpc.exclude.iosArm64=true +kotlinx.rpc.exclude.iosX64=true +kotlinx.rpc.exclude.iosSimulatorArm64=true +kotlinx.rpc.exclude.linuxArm64=true +kotlinx.rpc.exclude.linuxX64=true +kotlinx.rpc.exclude.macosX64=true +kotlinx.rpc.exclude.mingwX64=true +kotlinx.rpc.exclude.tvosArm64=true +kotlinx.rpc.exclude.tvosSimulatorArm64=true +kotlinx.rpc.exclude.tvosX64=true +kotlinx.rpc.exclude.watchosArm32=true +kotlinx.rpc.exclude.watchosArm64=true +kotlinx.rpc.exclude.watchosDeviceArm64=true +kotlinx.rpc.exclude.watchosSimulatorArm64=true +kotlinx.rpc.exclude.watchosX64=true diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/utils/BitSet.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/BitSet.kt similarity index 88% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/utils/BitSet.kt rename to protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/BitSet.kt index 85520ca04..e8fe19bb7 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/utils/BitSet.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/BitSet.kt @@ -2,9 +2,10 @@ * 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.utils +package kotlinx.rpc.protobuf.internal import kotlinx.rpc.internal.utils.InternalRpcApi +import kotlin.countOneBits /** * A fixed-sized vector of bits, allowing one to set/clear/read bits from it by a bit index. @@ -24,14 +25,14 @@ public class BitSet(public val size: Int) { /** Clears the bit at [index] (sets to 0). */ public fun clear(index: Int) { - require(index >= 0 && index < size) { "Index $index out of bounds for length $size" } + require(index in 0..= 0 && index < size) { "Index $index out of bounds for length $size" } + require(index in 0..( this@MsgFieldDelegate.value = value valueSet = true } -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/KTag.kt similarity index 85% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt rename to protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/KTag.kt index c6b9ffe2e..2e6510c34 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/KTag.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/KTag.kt @@ -2,7 +2,7 @@ * 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.pb +package kotlinx.rpc.protobuf.internal import kotlinx.rpc.internal.utils.InternalRpcApi @@ -25,7 +25,7 @@ public data class KTag(val fieldNr: Int, val wireType: WireType) { internal companion object { // Number of bits in a tag which identify the wire type. - const val K_TAG_TYPE_BITS: Int = 3; + const val K_TAG_TYPE_BITS: Int = 3 // Mask for those bits. (just 0b111) val K_TAG_TYPE_MASK: UInt = (1u shl K_TAG_TYPE_BITS) - 1u @@ -33,7 +33,7 @@ public data class KTag(val fieldNr: Int, val wireType: WireType) { } internal fun KTag.toRawKTag(): UInt { - return (fieldNr.toUInt() shl KTag.Companion.K_TAG_TYPE_BITS) or wireType.ordinal.toUInt() + return (fieldNr.toUInt() shl KTag.K_TAG_TYPE_BITS) or wireType.ordinal.toUInt() } internal fun KTag.Companion.from(rawKTag: UInt): KTag { @@ -49,5 +49,5 @@ internal fun KTag.Companion.from(rawKTag: UInt): KTag { } internal fun KTag.Companion.isValidFieldNr(fieldNr: Int): Boolean { - return 1 <= fieldNr && fieldNr <= 536_870_911 -} \ No newline at end of file + return fieldNr in 1..536_870_911 +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/ProtobufException.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/ProtobufException.kt similarity index 97% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/ProtobufException.kt rename to protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/ProtobufException.kt index 3830c6f66..39c27a42f 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/ProtobufException.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/ProtobufException.kt @@ -2,7 +2,7 @@ * 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.pb +package kotlinx.rpc.protobuf.internal public sealed class ProtobufException : RuntimeException { protected constructor(message: String, cause: Throwable? = null) : super(message, cause) @@ -37,4 +37,4 @@ public class ProtobufDecodingException : ProtobufException { public class ProtobufEncodingException : ProtobufException { public constructor(message: String, cause: Throwable? = null) : super(message, cause) -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.kt similarity index 96% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt rename to protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.kt index 2961558c9..6b5d6fd4c 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.kt @@ -2,11 +2,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.grpc.pb +package kotlinx.rpc.protobuf.internal import kotlinx.io.Buffer -import kotlinx.rpc.grpc.internal.popLimit -import kotlinx.rpc.grpc.internal.pushLimit import kotlinx.rpc.internal.utils.InternalRpcApi // TODO: Evaluate if this buffer size is suitable for all targets (KRPC-186) @@ -118,4 +116,4 @@ public expect inline fun checkForPlatformDecodeException(block: () -> Unit) * @param source The buffer containing the encoded wire-format data. */ @InternalRpcApi -public expect fun WireDecoder(source: Buffer): WireDecoder \ No newline at end of file +public expect fun WireDecoder(source: Buffer): WireDecoder diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.kt similarity index 97% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt rename to protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.kt index dc6177d8a..856550539 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.kt @@ -2,7 +2,7 @@ * 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.pb +package kotlinx.rpc.protobuf.internal import kotlinx.io.Sink import kotlinx.rpc.internal.utils.InternalRpcApi @@ -69,4 +69,4 @@ public interface WireEncoder { public expect inline fun checkForPlatformEncodeException(block: () -> Unit) @InternalRpcApi -public expect fun WireEncoder(sink: Sink): WireEncoder \ No newline at end of file +public expect fun WireEncoder(sink: Sink): WireEncoder diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.kt similarity index 96% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt rename to protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.kt index dbd2b5bc5..c1193697a 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.kt @@ -2,7 +2,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.grpc.pb +@file:Suppress("UnusedReceiverParameter") + +package kotlinx.rpc.protobuf.internal import kotlinx.io.bytestring.encodeToByteString import kotlinx.rpc.internal.utils.InternalRpcApi @@ -10,6 +12,7 @@ import kotlinx.rpc.internal.utils.InternalRpcApi @InternalRpcApi public object WireSize +@InternalRpcApi public fun WireSize.tag(fieldNumber: Int, wireType: WireType): Int = uInt32(KTag(fieldNumber, wireType).toRawKTag()) diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.kt similarity index 88% rename from grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt rename to protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.kt index c20bd0e79..2e0377405 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.kt @@ -2,10 +2,7 @@ * 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.internal - -import kotlinx.rpc.grpc.pb.ProtobufDecodingException -import kotlinx.rpc.grpc.pb.WireDecoder +package kotlinx.rpc.protobuf.internal internal expect fun WireDecoder.pushLimit(byteLen: Int): Int internal expect fun WireDecoder.popLimit(limit: Int) diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/BitSetTest.kt b/protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/BitSetTest.kt similarity index 97% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/BitSetTest.kt rename to protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/BitSetTest.kt index 0644f91c6..707a05a03 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/internal/BitSetTest.kt +++ b/protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/BitSetTest.kt @@ -2,10 +2,14 @@ * 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.internal +package kotlinx.rpc.protobuf.test -import kotlinx.rpc.grpc.utils.BitSet -import kotlin.test.* +import kotlinx.rpc.protobuf.internal.BitSet +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue class BitSetTest { @@ -302,4 +306,4 @@ class BitSetTest { // Verify cardinality assertEquals(size, bitSet.cardinality(), "Cardinality should equal size when all bits are set") } -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt b/protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/ProtosTest.kt similarity index 97% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt rename to protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/ProtosTest.kt index 49c349ebc..1c26009ae 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/ProtosTest.kt +++ b/protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/ProtosTest.kt @@ -2,7 +2,7 @@ * 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.pb +package kotlinx.rpc.protobuf.test import OneOfMsg import OneOfMsgInternal @@ -16,12 +16,19 @@ import kotlinx.io.Buffer import kotlinx.rpc.grpc.codec.MessageCodec import kotlinx.rpc.grpc.test.* import kotlinx.rpc.grpc.test.common.* +import kotlinx.rpc.grpc.test.common.invoke +import kotlinx.rpc.grpc.test.invoke +import kotlinx.rpc.protobuf.internal.ProtobufDecodingException +import kotlinx.rpc.protobuf.internal.WireEncoder import test.nested.* +import test.nested.invoke import test.recursive.Recursive import test.recursive.RecursiveInternal import test.recursive.RecursiveReq import test.recursive.invoke import test.submsg.* +import test.submsg.invoke +import kotlin.collections.iterator import kotlin.test.* class ProtosTest { diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.kt b/protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.kt similarity index 96% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.kt rename to protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.kt index e3a41b112..aaeacbe0d 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.kt +++ b/protobuf/protobuf-core/src/commonTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.kt @@ -2,9 +2,22 @@ * 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.pb +package kotlinx.rpc.protobuf.test import kotlinx.io.Buffer +import kotlinx.rpc.protobuf.internal.ProtobufDecodingException +import kotlinx.rpc.protobuf.internal.WireDecoder +import kotlinx.rpc.protobuf.internal.WireEncoder +import kotlinx.rpc.protobuf.internal.WireSize +import kotlinx.rpc.protobuf.internal.WireType +import kotlinx.rpc.protobuf.internal.checkForPlatformDecodeException +import kotlinx.rpc.protobuf.internal.packedEnum +import kotlinx.rpc.protobuf.internal.packedInt32 +import kotlinx.rpc.protobuf.internal.packedInt64 +import kotlinx.rpc.protobuf.internal.packedSInt32 +import kotlinx.rpc.protobuf.internal.packedSInt64 +import kotlinx.rpc.protobuf.internal.packedUInt32 +import kotlinx.rpc.protobuf.internal.packedUInt64 import kotlin.test.* enum class TestPlatform { diff --git a/grpc/grpc-core/src/commonTest/proto/all_primitives.proto b/protobuf/protobuf-core/src/commonTest/proto/all_primitives.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/all_primitives.proto rename to protobuf/protobuf-core/src/commonTest/proto/all_primitives.proto diff --git a/grpc/grpc-core/src/commonTest/proto/enum.proto b/protobuf/protobuf-core/src/commonTest/proto/enum.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/enum.proto rename to protobuf/protobuf-core/src/commonTest/proto/enum.proto diff --git a/grpc/grpc-core/src/commonTest/proto/nested.proto b/protobuf/protobuf-core/src/commonTest/proto/nested.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/nested.proto rename to protobuf/protobuf-core/src/commonTest/proto/nested.proto diff --git a/grpc/grpc-core/src/commonTest/proto/oneof.proto b/protobuf/protobuf-core/src/commonTest/proto/oneof.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/oneof.proto rename to protobuf/protobuf-core/src/commonTest/proto/oneof.proto diff --git a/grpc/grpc-core/src/commonTest/proto/presence_check.proto b/protobuf/protobuf-core/src/commonTest/proto/presence_check.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/presence_check.proto rename to protobuf/protobuf-core/src/commonTest/proto/presence_check.proto diff --git a/grpc/grpc-core/src/commonTest/proto/recursive.proto b/protobuf/protobuf-core/src/commonTest/proto/recursive.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/recursive.proto rename to protobuf/protobuf-core/src/commonTest/proto/recursive.proto diff --git a/grpc/grpc-core/src/commonTest/proto/repeated.proto b/protobuf/protobuf-core/src/commonTest/proto/repeated.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/repeated.proto rename to protobuf/protobuf-core/src/commonTest/proto/repeated.proto diff --git a/grpc/grpc-core/src/commonTest/proto/sub_message.proto b/protobuf/protobuf-core/src/commonTest/proto/sub_message.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/sub_message.proto rename to protobuf/protobuf-core/src/commonTest/proto/sub_message.proto diff --git a/grpc/grpc-core/src/commonTest/proto/submsg.proto b/protobuf/protobuf-core/src/commonTest/proto/submsg.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/submsg.proto rename to protobuf/protobuf-core/src/commonTest/proto/submsg.proto diff --git a/grpc/grpc-core/src/commonTest/proto/test_map.proto b/protobuf/protobuf-core/src/commonTest/proto/test_map.proto similarity index 100% rename from grpc/grpc-core/src/commonTest/proto/test_map.proto rename to protobuf/protobuf-core/src/commonTest/proto/test_map.proto diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.jvm.kt similarity index 97% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt rename to protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.jvm.kt index 77095c84d..a899713ab 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.jvm.kt +++ b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.jvm.kt @@ -2,13 +2,12 @@ * 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.pb +package kotlinx.rpc.protobuf.internal import com.google.protobuf.CodedInputStream import com.google.protobuf.InvalidProtocolBufferException import kotlinx.io.Buffer import kotlinx.io.asInputStream -import kotlinx.rpc.grpc.internal.readPackedVarInternal internal class WireDecoderJvm(source: Buffer) : WireDecoder { // there is no way to omit coping here diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.jvm.kt b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.jvm.kt similarity index 99% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.jvm.kt rename to protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.jvm.kt index ffa4b2f3c..a0461a899 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.jvm.kt +++ b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.jvm.kt @@ -2,7 +2,7 @@ * 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.pb +package kotlinx.rpc.protobuf.internal import com.google.protobuf.CodedOutputStream import kotlinx.io.IOException diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.jvm.kt similarity index 96% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt rename to protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.jvm.kt index 71896a28e..038bb1f9b 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.jvm.kt +++ b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.jvm.kt @@ -2,7 +2,7 @@ * 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.pb +package kotlinx.rpc.protobuf.internal import com.google.protobuf.CodedOutputStream.* import kotlinx.rpc.internal.utils.InternalRpcApi diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.jvm.kt similarity index 81% rename from grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt rename to protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.jvm.kt index c8501d4f9..79ea1da70 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.jvm.kt +++ b/protobuf/protobuf-core/src/jvmMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.jvm.kt @@ -2,10 +2,7 @@ * 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.internal - -import kotlinx.rpc.grpc.pb.WireDecoder -import kotlinx.rpc.grpc.pb.WireDecoderJvm +package kotlinx.rpc.protobuf.internal internal actual fun WireDecoder.pushLimit(byteLen: Int): Int { return (this as WireDecoderJvm).codedInputStream.pushLimit(byteLen) diff --git a/protobuf/protobuf-core/src/jvmTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.jvm.kt b/protobuf/protobuf-core/src/jvmTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.jvm.kt new file mode 100644 index 000000000..1199a0e5c --- /dev/null +++ b/protobuf/protobuf-core/src/jvmTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.jvm.kt @@ -0,0 +1,7 @@ +/* + * 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.protobuf.test + +actual val testPlatform: TestPlatform = TestPlatform.Jvm diff --git a/grpc/grpc-core/src/nativeInterop/cinterop/libprotowire.def b/protobuf/protobuf-core/src/nativeInterop/cinterop/libprotowire.def similarity index 100% rename from grpc/grpc-core/src/nativeInterop/cinterop/libprotowire.def rename to protobuf/protobuf-core/src/nativeInterop/cinterop/libprotowire.def diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.native.kt similarity index 98% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt rename to protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.native.kt index f26b9da9a..55d161dc6 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireDecoder.native.kt +++ b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireDecoder.native.kt @@ -2,13 +2,11 @@ * 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.pb +package kotlinx.rpc.protobuf.internal import kotlinx.cinterop.* import kotlinx.collections.immutable.persistentListOf import kotlinx.io.Buffer -import kotlinx.rpc.grpc.internal.ZeroCopyInputSource -import kotlinx.rpc.grpc.internal.readPackedVarInternal import libprotowire.* import kotlin.experimental.ExperimentalNativeApi import kotlin.math.min @@ -304,4 +302,4 @@ public actual fun WireDecoder(source: Buffer): WireDecoder = WireDecoderNative(s public actual inline fun checkForPlatformDecodeException(block: () -> Unit) { block() -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.native.kt b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.native.kt similarity index 98% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.native.kt rename to protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.native.kt index de5266f30..e0321b2db 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireEncoder.native.kt +++ b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireEncoder.native.kt @@ -2,11 +2,10 @@ * 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.pb +package kotlinx.rpc.protobuf.internal import kotlinx.cinterop.* import kotlinx.io.Sink -import kotlinx.rpc.grpc.internal.writeFully import libprotowire.* import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner @@ -40,9 +39,12 @@ internal class WireEncoderNative(private val sink: Sink) : WireEncoder { }) ?: error("Failed to create proto wire encoder") } + @Suppress("unused") private val contextCleaner = createCleaner(context) { it.dispose() } + + @Suppress("unused") private val rawCleaner = createCleaner(raw) { pw_encoder_delete(it) } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.native.kt similarity index 95% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt rename to protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.native.kt index 72ccaddd6..81adfae42 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/pb/WireSize.native.kt +++ b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/WireSize.native.kt @@ -4,7 +4,7 @@ @file:OptIn(ExperimentalForeignApi::class) -package kotlinx.rpc.grpc.pb +package kotlinx.rpc.protobuf.internal import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.rpc.internal.utils.InternalRpcApi diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ZeroCopyInputSource.kt b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/ZeroCopyInputSource.kt similarity index 95% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ZeroCopyInputSource.kt rename to protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/ZeroCopyInputSource.kt index e20df0607..c5b3781d5 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/ZeroCopyInputSource.kt +++ b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/ZeroCopyInputSource.kt @@ -2,7 +2,7 @@ * 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.internal +package kotlinx.rpc.protobuf.internal import kotlinx.cinterop.* import kotlinx.io.Buffer @@ -11,7 +11,6 @@ import kotlinx.io.InternalIoApi import kotlinx.io.UnsafeIoApi import kotlinx.io.unsafe.UnsafeBufferOperations - /** * Handles (almost) zero-copy input operations on a [Buffer], allowing efficient transfer of data * without creating intermediate copies. This class provides mechanisms to iterate over @@ -54,7 +53,7 @@ internal class ZeroCopyInputSource(private val inner: Buffer) : AutoCloseable { // released by the buffer. this is done by releaseLatestReadSegement // which releases the segment in the inner buffer. private var latestReadSegementArray: Pinned? = null - private var closed = false; + private var closed = false /** * Get access to a segment of continuous bytes in the underlying [Buffer]. @@ -83,15 +82,15 @@ internal class ZeroCopyInputSource(private val inner: Buffer) : AutoCloseable { val segmentSize = end - start outData.pointed.value = latestReadSegementArray!!.addressOf(start) - outSize.pointed.value = segmentSize; + outSize.pointed.value = segmentSize - byteCount += segmentSize; + byteCount += segmentSize // we are not yet advancing the inner buffer head. // this ensures that the segment array is not released by the buffer and remains valid 0 } - return true; + return true } /** @@ -115,7 +114,7 @@ internal class ZeroCopyInputSource(private val inner: Buffer) : AutoCloseable { check(latestReadSegementArray != null) { "next() must be immediately before backUp()" } val readBytes = releaseLatestReadSegment(count) check(readBytes >= 0) { "backUp() must not be called more than the number of bytes that were read in next()" } - byteCount -= count; + byteCount -= count } /** @@ -155,7 +154,7 @@ internal class ZeroCopyInputSource(private val inner: Buffer) : AutoCloseable { if (latestReadSegementArray != null) { releaseLatestReadSegment() } - closed = true; + closed = true } /** @@ -175,8 +174,9 @@ internal class ZeroCopyInputSource(private val inner: Buffer) : AutoCloseable { // the return value of the readFromHead defines the number of bytes that are getting released in the underlying // buffer. UnsafeBufferOperations.readFromHead(inner.buffer) { arr, start, end -> - check(latestReadSegementArray?.get() == arr) { - "array to advance must be the SAME as the currArr, was there some access to the underlying buffer?" } + check(latestReadSegementArray?.get() === arr) { + "array to advance must be the SAME as the currArr, was there some access to the underlying buffer?" + } // release the whole segmentSize - the backup count. readBytes = end - start - backUpCount // prevent the value from being negative. @@ -185,7 +185,7 @@ internal class ZeroCopyInputSource(private val inner: Buffer) : AutoCloseable { } // remove tracking of the released segment latestReadSegementArray?.unpin() - latestReadSegementArray = null; + latestReadSegementArray = null return readBytes } -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/bufferUnsafeExtensions.kt b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/bufferUnsafeExtensions.kt similarity index 95% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/bufferUnsafeExtensions.kt rename to protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/bufferUnsafeExtensions.kt index f1e1495ae..822155c02 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/bufferUnsafeExtensions.kt +++ b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/bufferUnsafeExtensions.kt @@ -2,7 +2,7 @@ * 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.internal +package kotlinx.rpc.protobuf.internal import kotlinx.cinterop.* import kotlinx.io.InternalIoApi @@ -27,4 +27,4 @@ internal fun Sink.writeFully(buffer: CPointer, offset: Long, length: Lo size.toInt() } } -} \ No newline at end of file +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.native.kt similarity index 86% rename from grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt rename to protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.native.kt index a45418e62..94ddd5825 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/readPacked.native.kt +++ b/protobuf/protobuf-core/src/nativeMain/kotlin/kotlinx/rpc/protobuf/internal/readPacked.native.kt @@ -4,11 +4,9 @@ @file:OptIn(ExperimentalForeignApi::class) -package kotlinx.rpc.grpc.internal +package kotlinx.rpc.protobuf.internal import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.rpc.grpc.pb.WireDecoder -import kotlinx.rpc.grpc.pb.WireDecoderNative import libprotowire.pw_decoder_bytes_until_limit import libprotowire.pw_decoder_pop_limit import libprotowire.pw_decoder_push_limit diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.native.kt b/protobuf/protobuf-core/src/nativeTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.native.kt similarity index 84% rename from grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.native.kt rename to protobuf/protobuf-core/src/nativeTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.native.kt index 23fb4eae1..3d760283d 100644 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.native.kt +++ b/protobuf/protobuf-core/src/nativeTest/kotlin/kotlinx/rpc/protobuf/test/WireCodecTest.native.kt @@ -2,6 +2,6 @@ * 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.pb +package kotlinx.rpc.protobuf.test actual val testPlatform: TestPlatform = TestPlatform.Native diff --git a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/ZeroCopyInputSourceTest.kt b/protobuf/protobuf-core/src/nativeTest/kotlin/kotlinx/rpc/protobuf/test/ZeroCopyInputSourceTest.kt similarity index 99% rename from grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/ZeroCopyInputSourceTest.kt rename to protobuf/protobuf-core/src/nativeTest/kotlin/kotlinx/rpc/protobuf/test/ZeroCopyInputSourceTest.kt index 234d0941a..2941da507 100644 --- a/grpc/grpc-core/src/nativeTest/kotlin/kotlinx/rpc/grpc/internal/ZeroCopyInputSourceTest.kt +++ b/protobuf/protobuf-core/src/nativeTest/kotlin/kotlinx/rpc/protobuf/test/ZeroCopyInputSourceTest.kt @@ -2,7 +2,7 @@ * 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.internal +package kotlinx.rpc.protobuf.test import kotlinx.cinterop.ByteVar import kotlinx.cinterop.CPointerVar @@ -15,6 +15,7 @@ import kotlinx.cinterop.ptr import kotlinx.cinterop.usePinned import kotlinx.cinterop.value import kotlinx.io.Buffer +import kotlinx.rpc.protobuf.internal.ZeroCopyInputSource import platform.posix.memcpy import kotlin.experimental.ExperimentalNativeApi import kotlin.test.Test @@ -327,4 +328,4 @@ private fun ZeroCopyInputSource.nextIntoArray(): ByteArray = memScoped { memcpy(it.addressOf(0), data.value, size.value.toULong()) } result -} \ No newline at end of file +} diff --git a/protoc-gen/build.gradle.kts b/protoc-gen/build.gradle.kts index fbef3d641..3a2714048 100644 --- a/protoc-gen/build.gradle.kts +++ b/protoc-gen/build.gradle.kts @@ -2,47 +2,13 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode - plugins { alias(libs.plugins.conventions.jvm) } -group = "org.jetbrains.kotlinx" -version = rootProject.libs.versions.kotlinx.rpc.get() - -dependencies { - implementation(libs.protobuf.java) - - implementation(libs.slf4j.api) - implementation(libs.logback.classic) - - testImplementation(libs.kotlin.test) -} - -tasks.jar { - manifest { - attributes["Main-Class"] = "kotlinx.rpc.protobuf.MainKt" - } - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - archiveClassifier = "all" - - // Protoc plugins are all fat jars basically (the ones built on jvm) - // be really careful of what you put in the classpath here - from( - configurations.runtimeClasspath.map { prop -> - prop.map { if (it.isDirectory()) it else zipTree(it) } - } - ) -} - -kotlin { - explicitApi = ExplicitApiMode.Disabled -} - -tasks.test { - useJUnitPlatform() +allprojects { + group = "org.jetbrains.kotlinx" + version = rootProject.libs.versions.kotlinx.rpc.get() } -logger.lifecycle("[Protoc Plugin] kotlinx.rpc project version: $version") +logger.lifecycle("[Protoc Gen] kotlinx.rpc project version: $version") diff --git a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt b/protoc-gen/common/build.gradle.kts similarity index 60% rename from grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt rename to protoc-gen/common/build.gradle.kts index 88e83bb57..b460b64b0 100644 --- a/grpc/grpc-core/src/jvmTest/kotlin/kotlinx/rpc/grpc/pb/WireCodecTest.jvm.kt +++ b/protoc-gen/common/build.gradle.kts @@ -2,6 +2,6 @@ * 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.pb - -actual val testPlatform: TestPlatform = TestPlatform.Jvm \ No newline at end of file +plugins { + alias(libs.plugins.conventions.protoc.gen) +} diff --git a/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/AModelToKotlinCommonGenerator.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/AModelToKotlinCommonGenerator.kt new file mode 100644 index 000000000..47d63bcd7 --- /dev/null +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/AModelToKotlinCommonGenerator.kt @@ -0,0 +1,189 @@ +/* + * 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.protoc.gen.core + +import kotlinx.rpc.protoc.gen.core.model.FieldDeclaration +import kotlinx.rpc.protoc.gen.core.model.FieldType +import kotlinx.rpc.protoc.gen.core.model.FileDeclaration +import kotlinx.rpc.protoc.gen.core.model.FqName +import kotlinx.rpc.protoc.gen.core.model.MessageDeclaration +import kotlinx.rpc.protoc.gen.core.model.Model +import kotlinx.rpc.protoc.gen.core.model.fullName +import org.slf4j.Logger + +const val RPC_INTERNAL_PACKAGE_SUFFIX = "_rpc_internal" +const val MSG_INTERNAL_SUFFIX = "Internal" +const val PB_PKG = "kotlinx.rpc.protobuf.internal" +const val INTERNAL_RPC_API_ANNO = "kotlinx.rpc.internal.utils.InternalRpcApi" +const val WITH_CODEC_ANNO = "kotlinx.rpc.grpc.codec.WithCodec" + +abstract class AModelToKotlinCommonGenerator( + protected val model: Model, + protected val logger: Logger, +) { + protected abstract fun CodeGenerator.generatePublicDeclaredEntities(fileDeclaration: FileDeclaration) + protected abstract fun CodeGenerator.generateInternalDeclaredEntities(fileDeclaration: FileDeclaration) + + protected abstract val hasPublicGeneratedFiles: Boolean + protected abstract val hasInternalGeneratedFiles: Boolean + + protected var currentPackage: FqName = FqName.Package.Root + protected val additionalPublicImports = mutableSetOf() + protected val additionalInternalImports = mutableSetOf() + + fun generateKotlinFiles(): List { + return model.files.flatMap { it.generateKotlinFiles() } + } + + private fun FileDeclaration.generateKotlinFiles(): List { + additionalPublicImports.clear() + additionalInternalImports.clear() + + return listOfNotNull( + if (hasPublicGeneratedFiles) generatePublicKotlinFile() else null, + if (hasInternalGeneratedFiles) generateInternalKotlinFile() else null, + ) + } + + private fun FileDeclaration.generatePublicKotlinFile(): FileGenerator { + currentPackage = packageName + + return file(logger = logger) { + filename = this@generatePublicKotlinFile.name + packageName = this@generatePublicKotlinFile.packageName.safeFullName() + packagePath = this@generatePublicKotlinFile.packageName.safeFullName() + + dependencies.forEach { dependency -> + importPackage(dependency.packageName.safeFullName()) + } + + fileOptIns = listOf("ExperimentalRpcApi::class", "InternalRpcApi::class") + + generatePublicDeclaredEntities(this@generatePublicKotlinFile) + + import("kotlinx.rpc.internal.utils.*") + import("kotlinx.coroutines.flow.*") + + additionalPublicImports.forEach { + import(it) + } + } + } + + private fun FileDeclaration.generateInternalKotlinFile(): FileGenerator { + currentPackage = packageName + + return file(logger = logger) { + filename = this@generateInternalKotlinFile.name + packageName = this@generateInternalKotlinFile.packageName.safeFullName() + packagePath = + this@generateInternalKotlinFile.packageName.safeFullName() + .packageNameSuffixed(RPC_INTERNAL_PACKAGE_SUFFIX) + + fileOptIns = listOf("ExperimentalRpcApi::class", "$INTERNAL_RPC_API_ANNO::class") + + dependencies.forEach { dependency -> + importPackage(dependency.packageName.safeFullName()) + } + + generateInternalDeclaredEntities(this@generateInternalKotlinFile) + + import("$PB_PKG.*") + import("kotlinx.rpc.internal.utils.*") + import("kotlinx.coroutines.flow.*") + + additionalInternalImports.forEach { + import(it) + } + } + } + + protected fun FieldDeclaration.typeFqName(): String { + return when (type) { + is FieldType.Message -> { + type.dec.value.name.safeFullName() + } + + is FieldType.Enum -> type.dec.name.safeFullName() + + is FieldType.OneOf -> type.dec.name.safeFullName() + + is FieldType.IntegralType -> { + type.fqName.simpleName + } + + is FieldType.List -> { + val fqValue = when (val value = type.value) { + is FieldType.Message -> value.dec.value.name + is FieldType.IntegralType -> value.fqName + else -> error("Unsupported type: $value") + } + + "List<${fqValue.safeFullName()}>" + } + + is FieldType.Map -> { + val entry = type.entry + + val fqKey = when (val key = entry.key) { + is FieldType.Message -> key.dec.value.name + is FieldType.IntegralType -> key.fqName + else -> error("Unsupported type: $key") + } + + val fqValue = when (val value = entry.value) { + is FieldType.Message -> value.dec.value.name + is FieldType.IntegralType -> value.fqName + else -> error("Unsupported type: $value") + } + + "Map<${fqKey.safeFullName()}, ${fqValue.safeFullName()}>" + } + + }.withNullability(nullable) + } + + protected fun String.wrapInFlowIf(condition: Boolean): String { + return if (condition) "Flow<$this>" else this + } + + protected fun FqName.safeFullName(classSuffix: String = ""): String { + importRootDeclarationIfNeeded(this) + + return fullName(classSuffix) + } + + protected fun importRootDeclarationIfNeeded( + declaration: FqName, + nameToImport: String = declaration.simpleName, + internalOnly: Boolean = false, + ) { + if (declaration.parent == FqName.Package.Root && currentPackage != FqName.Package.Root && nameToImport.isNotBlank()) { + additionalInternalImports.add(nameToImport) + if (!internalOnly) { + additionalPublicImports.add(nameToImport) + } + } + } + + protected fun FieldType.Message.internalConstructor() = + dec.value.internalClassFullName() + "()" + + protected fun MessageDeclaration.internalClassFullName(): String { + return name.safeFullName(MSG_INTERNAL_SUFFIX) + } + + protected fun MessageDeclaration.internalClassName(): String { + return name.simpleName + MSG_INTERNAL_SUFFIX + } + + protected fun String.withNullability(nullable: Boolean): String { + return "$this${if (nullable) "?" else ""}" + } + + protected fun String.packageNameSuffixed(suffix: String): String { + return if (isEmpty()) suffix else "$this.$suffix" + } +} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/CodeGenerator.kt similarity index 98% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/CodeGenerator.kt index cd8ddfbb5..ea43f0283 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/CodeGenerator.kt @@ -2,7 +2,7 @@ * 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.protobuf +package kotlinx.rpc.protoc.gen.core import org.slf4j.Logger import org.slf4j.helpers.NOPLogger @@ -67,7 +67,7 @@ open class CodeGenerator( CodeGenerator("$indent$ONE_INDENT", builder, logger).block() } - internal fun scope( + fun scope( prefix: String, suffix: String = "", nlAfterClosed: Boolean = true, @@ -79,7 +79,7 @@ open class CodeGenerator( scopeWithSuffix(suffix, openingBracket, nlAfterClosed, paramDecl, block) } - internal fun ifBranch( + fun ifBranch( prefix: String = "", condition: String, ifBlock: (CodeGenerator.() -> Unit), @@ -94,14 +94,14 @@ open class CodeGenerator( scopeWithSuffix(block = elseBlock) } - internal fun whileBlock( + fun whileBlock( condition: String, block: (CodeGenerator.() -> Unit), ) { scope("while ($condition)", block = block) } - internal fun whenBlock( + fun whenBlock( condition: String? = null, prefix: String = "", block: (CodeGenerator.() -> Unit), @@ -111,14 +111,13 @@ open class CodeGenerator( scope("${pre}when$cond", block = block) } - internal fun whenCase( + fun whenCase( condition: String, block: (CodeGenerator.() -> Unit), ) { scope("$condition ->", block = block) } - private fun scopeWithSuffix( suffix: String = "", openingBracket: Boolean = true, @@ -346,7 +345,7 @@ class FileGenerator( newLine() } - var packageName = packageName + val packageName = packageName if (packageName != null && packageName.isNotEmpty()) { appendLine("package $packageName") } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/ProtocGenPlugin.kt similarity index 88% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/ProtocGenPlugin.kt index 4ff31adbf..8b3a2bc79 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/ProtocGenPlugin.kt @@ -2,7 +2,7 @@ * 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.protobuf +package kotlinx.rpc.protoc.gen.core import ch.qos.logback.classic.Level import ch.qos.logback.classic.LoggerContext @@ -12,12 +12,13 @@ import ch.qos.logback.core.FileAppender import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.Feature +import kotlinx.rpc.protoc.gen.core.model.Model import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.helpers.NOPLogger import java.io.File -class RpcProtobufPlugin { +abstract class ProtocGenPlugin { companion object { private const val DEBUG_OUTPUT_OPTION = "debugOutput" } @@ -54,7 +55,7 @@ class RpcProtobufPlugin { debugOutput = parameters[DEBUG_OUTPUT_OPTION] - val files = input.generateKotlinCommonFiles() + val files = input.runGeneration() .map { file -> CodeGeneratorResponse.File.newBuilder() .apply { @@ -81,10 +82,9 @@ class RpcProtobufPlugin { .build() } - private fun CodeGeneratorRequest.generateKotlinCommonFiles(): List { - val model = this.toModel() - val fileGenerator = ModelToKotlinCommonGenerator(model, logger) - return fileGenerator.generateKotlinFiles() + private fun CodeGeneratorRequest.runGeneration(): List { + return generateKotlinByModel(this.toModel(), logger) } -} + protected abstract fun generateKotlinByModel(model: Model, logger: Logger): List +} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/codeRequestToModel.kt similarity index 95% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/codeRequestToModel.kt index 6f85829b8..1c15b453c 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/codeRequestToModel.kt @@ -2,12 +2,21 @@ * 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.protobuf +package kotlinx.rpc.protoc.gen.core import com.google.protobuf.DescriptorProtos import com.google.protobuf.Descriptors import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest -import kotlinx.rpc.protobuf.model.* +import kotlinx.rpc.protoc.gen.core.model.EnumDeclaration +import kotlinx.rpc.protoc.gen.core.model.FieldDeclaration +import kotlinx.rpc.protoc.gen.core.model.FieldType +import kotlinx.rpc.protoc.gen.core.model.FileDeclaration +import kotlinx.rpc.protoc.gen.core.model.FqName +import kotlinx.rpc.protoc.gen.core.model.MessageDeclaration +import kotlinx.rpc.protoc.gen.core.model.MethodDeclaration +import kotlinx.rpc.protoc.gen.core.model.Model +import kotlinx.rpc.protoc.gen.core.model.OneOfDeclaration +import kotlinx.rpc.protoc.gen.core.model.ServiceDeclaration private val nameCache = mutableMapOf() private val modelCache = mutableMapOf() diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldType.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/FieldType.kt similarity index 98% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldType.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/FieldType.kt index c2de6b18d..296bf640b 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FieldType.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/FieldType.kt @@ -2,7 +2,7 @@ * 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.protobuf.model +package kotlinx.rpc.protoc.gen.core.model enum class WireType { VARINT, diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FqName.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/FqName.kt similarity index 96% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FqName.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/FqName.kt index f6f470faf..c06e919a1 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/FqName.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/FqName.kt @@ -2,7 +2,7 @@ * 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.protobuf.model +package kotlinx.rpc.protoc.gen.core.model sealed interface FqName { val simpleName: String @@ -53,7 +53,7 @@ sealed interface FqName { } } -internal fun FqName.fullName(classSuffix: String = ""): String { +fun FqName.fullName(classSuffix: String = ""): String { val parentName = parent val name = if (this is FqName.Declaration) "$simpleName$classSuffix" else simpleName return when { diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/NameResolver.kt similarity index 99% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/NameResolver.kt index 8596245e8..481024ea6 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/NameResolver.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/NameResolver.kt @@ -2,7 +2,7 @@ * 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.protobuf.model +package kotlinx.rpc.protoc.gen.core.model import org.slf4j.Logger diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/model.kt similarity index 98% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/model.kt index dfe05671b..f60d463a9 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/model/model.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/model/model.kt @@ -2,7 +2,7 @@ * 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.protobuf.model +package kotlinx.rpc.protoc.gen.core.model import com.google.protobuf.Descriptors diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/Main.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/runGenerator.kt similarity index 83% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/Main.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/runGenerator.kt index 9671859cd..e75cacc43 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/Main.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/runGenerator.kt @@ -2,14 +2,13 @@ * 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.protobuf +package kotlinx.rpc.protoc.gen.core import com.google.protobuf.compiler.PluginProtos -fun main() { +fun runGenerator(plugin: ProtocGenPlugin) { val inputBytes = System.`in`.readBytes() val request = PluginProtos.CodeGeneratorRequest.parseFrom(inputBytes) - val plugin = RpcProtobufPlugin() val output: PluginProtos.CodeGeneratorResponse = plugin.run(request) output.writeTo(System.out) } diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/utils.kt b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/utils.kt similarity index 85% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/utils.kt rename to protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/utils.kt index 2b44fdcac..ebb718224 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/utils.kt +++ b/protoc-gen/common/src/main/kotlin/kotlinx/rpc/protoc/gen/core/utils.kt @@ -2,8 +2,8 @@ * 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.protobuf +package kotlinx.rpc.protoc.gen.core internal fun String.decapitalize(): String { return this.replaceFirstChar { it.lowercase() } -} \ No newline at end of file +} diff --git a/protoc-gen/src/test/kotlin/kotlinx/rpc/protobuf/test/FqNameTest.kt b/protoc-gen/common/src/test/kotlin/kotlinx/rpc/protoc/gen/test/FqNameTest.kt similarity index 87% rename from protoc-gen/src/test/kotlin/kotlinx/rpc/protobuf/test/FqNameTest.kt rename to protoc-gen/common/src/test/kotlin/kotlinx/rpc/protoc/gen/test/FqNameTest.kt index 4cc871c47..575c0dfab 100644 --- a/protoc-gen/src/test/kotlin/kotlinx/rpc/protobuf/test/FqNameTest.kt +++ b/protoc-gen/common/src/test/kotlin/kotlinx/rpc/protoc/gen/test/FqNameTest.kt @@ -2,11 +2,11 @@ * 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.protobuf.test +package kotlinx.rpc.protoc.gen.test -import kotlinx.rpc.protobuf.model.FqName -import kotlinx.rpc.protobuf.model.asParentsAndSimpleName -import kotlinx.rpc.protobuf.model.fullName +import kotlinx.rpc.protoc.gen.core.model.FqName +import kotlinx.rpc.protoc.gen.core.model.asParentsAndSimpleName +import kotlinx.rpc.protoc.gen.core.model.fullName import kotlin.test.Test import kotlin.test.assertEquals diff --git a/protoc-gen/src/test/kotlin/kotlinx/rpc/protobuf/test/NameResolverTest.kt b/protoc-gen/common/src/test/kotlin/kotlinx/rpc/protoc/gen/test/NameResolverTest.kt similarity index 98% rename from protoc-gen/src/test/kotlin/kotlinx/rpc/protobuf/test/NameResolverTest.kt rename to protoc-gen/common/src/test/kotlin/kotlinx/rpc/protoc/gen/test/NameResolverTest.kt index 8196c1ca5..93d2ea674 100644 --- a/protoc-gen/src/test/kotlin/kotlinx/rpc/protobuf/test/NameResolverTest.kt +++ b/protoc-gen/common/src/test/kotlin/kotlinx/rpc/protoc/gen/test/NameResolverTest.kt @@ -2,10 +2,10 @@ * 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.protobuf.test +package kotlinx.rpc.protoc.gen.test -import kotlinx.rpc.protobuf.model.FqName -import kotlinx.rpc.protobuf.model.NameResolver +import kotlinx.rpc.protoc.gen.core.model.FqName +import kotlinx.rpc.protoc.gen.core.model.NameResolver import org.junit.jupiter.api.assertThrows import org.slf4j.helpers.NOPLogger import kotlin.test.Test diff --git a/protoc-gen/grpc/build.gradle.kts b/protoc-gen/grpc/build.gradle.kts new file mode 100644 index 000000000..f4322aa09 --- /dev/null +++ b/protoc-gen/grpc/build.gradle.kts @@ -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. + */ + +plugins { + alias(libs.plugins.conventions.protoc.gen) +} + +tasks.jar { + manifest { + attributes["Main-Class"] = "kotlinx.rpc.protoc.gen.grpc.MainKt" + } +} + +dependencies { + implementation(projects.common) +} diff --git a/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/GrpcProtocGenPlugin.kt b/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/GrpcProtocGenPlugin.kt new file mode 100644 index 000000000..85dcb7274 --- /dev/null +++ b/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/GrpcProtocGenPlugin.kt @@ -0,0 +1,19 @@ +/* + * 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.protoc.gen.grpc + +import kotlinx.rpc.protoc.gen.core.FileGenerator +import kotlinx.rpc.protoc.gen.core.ProtocGenPlugin +import kotlinx.rpc.protoc.gen.core.model.Model +import org.slf4j.Logger + +object GrpcProtocGenPlugin : ProtocGenPlugin() { + override fun generateKotlinByModel( + model: Model, + logger: Logger, + ): List { + return ModelToGrpcKotlinCommonGenerator(model, logger).generateKotlinFiles() + } +} diff --git a/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/Main.kt b/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/Main.kt new file mode 100644 index 000000000..5bdfedcae --- /dev/null +++ b/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/Main.kt @@ -0,0 +1,11 @@ +/* + * 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.protoc.gen.grpc + +import kotlinx.rpc.protoc.gen.core.runGenerator + +fun main() { + runGenerator(GrpcProtocGenPlugin) +} diff --git a/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/ModelToGrpcKotlinCommonGenerator.kt b/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/ModelToGrpcKotlinCommonGenerator.kt new file mode 100644 index 000000000..e8d966404 --- /dev/null +++ b/protoc-gen/grpc/src/main/kotlin/kotlinx/rpc/protoc/gen/grpc/ModelToGrpcKotlinCommonGenerator.kt @@ -0,0 +1,43 @@ +/* + * 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.protoc.gen.grpc + +import kotlinx.rpc.protoc.gen.core.AModelToKotlinCommonGenerator +import kotlinx.rpc.protoc.gen.core.CodeGenerator +import kotlinx.rpc.protoc.gen.core.model.FileDeclaration +import kotlinx.rpc.protoc.gen.core.model.Model +import kotlinx.rpc.protoc.gen.core.model.ServiceDeclaration +import org.slf4j.Logger + +class ModelToGrpcKotlinCommonGenerator( + model: Model, + logger: Logger, +) : AModelToKotlinCommonGenerator(model, logger) { + override val hasPublicGeneratedFiles: Boolean = model.files.any { it.serviceDeclarations.isNotEmpty() } + override val hasInternalGeneratedFiles: Boolean = false + + override fun CodeGenerator.generatePublicDeclaredEntities(fileDeclaration: FileDeclaration) { + fileDeclaration.serviceDeclarations.forEach { generatePublicService(it) } + } + + override fun CodeGenerator.generateInternalDeclaredEntities(fileDeclaration: FileDeclaration) { } + + @Suppress("detekt.LongMethod") + private fun CodeGenerator.generatePublicService(service: ServiceDeclaration) { + code("@kotlinx.rpc.grpc.annotations.Grpc") + clazz(service.name.simpleName, declarationType = CodeGenerator.DeclarationType.Interface) { + service.methods.forEach { method -> + val inputType = method.inputType + val outputType = method.outputType + function( + name = method.name, + modifiers = if (method.dec.isServerStreaming) "" else "suspend", + args = "message: ${inputType.name.safeFullName().wrapInFlowIf(method.dec.isClientStreaming)}", + returnType = outputType.name.safeFullName().wrapInFlowIf(method.dec.isServerStreaming), + ) + } + } + } +} diff --git a/protoc-gen/protobuf/build.gradle.kts b/protoc-gen/protobuf/build.gradle.kts new file mode 100644 index 000000000..0d6be072d --- /dev/null +++ b/protoc-gen/protobuf/build.gradle.kts @@ -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. + */ + +plugins { + alias(libs.plugins.conventions.protoc.gen) +} + +tasks.jar { + manifest { + attributes["Main-Class"] = "kotlinx.rpc.protoc.gen.MainKt" + } +} + +dependencies { + implementation(projects.common) +} diff --git a/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/Main.kt b/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/Main.kt new file mode 100644 index 000000000..24d96f54a --- /dev/null +++ b/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/Main.kt @@ -0,0 +1,11 @@ +/* + * 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.protoc.gen + +import kotlinx.rpc.protoc.gen.core.runGenerator + +fun main() { + runGenerator(ProtobufProtocGenPlugin) +} diff --git a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt b/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/ModelToProtobufKotlinCommonGenerator.kt similarity index 77% rename from protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt rename to protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/ModelToProtobufKotlinCommonGenerator.kt index fb13b0873..e48b73d86 100644 --- a/protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt +++ b/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/ModelToProtobufKotlinCommonGenerator.kt @@ -4,101 +4,39 @@ @file:Suppress("detekt.all") -package kotlinx.rpc.protobuf - -import kotlinx.rpc.protobuf.CodeGenerator.DeclarationType -import kotlinx.rpc.protobuf.model.* +package kotlinx.rpc.protoc.gen + +import kotlinx.rpc.protoc.gen.core.AModelToKotlinCommonGenerator +import kotlinx.rpc.protoc.gen.core.CodeGenerator +import kotlinx.rpc.protoc.gen.core.INTERNAL_RPC_API_ANNO +import kotlinx.rpc.protoc.gen.core.PB_PKG +import kotlinx.rpc.protoc.gen.core.WITH_CODEC_ANNO +import kotlinx.rpc.protoc.gen.core.model.EnumDeclaration +import kotlinx.rpc.protoc.gen.core.model.FieldDeclaration +import kotlinx.rpc.protoc.gen.core.model.FieldType +import kotlinx.rpc.protoc.gen.core.model.FileDeclaration +import kotlinx.rpc.protoc.gen.core.model.MessageDeclaration +import kotlinx.rpc.protoc.gen.core.model.Model +import kotlinx.rpc.protoc.gen.core.model.OneOfDeclaration +import kotlinx.rpc.protoc.gen.core.model.WireType import org.slf4j.Logger -private const val RPC_INTERNAL_PACKAGE_SUFFIX = "_rpc_internal" -private const val MSG_INTERNAL_SUFFIX = "Internal" -private const val PB_PKG = "kotlinx.rpc.grpc.pb" -private const val INTERNAL_RPC_API_ANNO = "kotlinx.rpc.internal.utils.InternalRpcApi" -private const val WITH_CODEC_ANNO = "kotlinx.rpc.grpc.codec.WithCodec" - -class ModelToKotlinCommonGenerator( - private val model: Model, - private val logger: Logger, -) { - fun generateKotlinFiles(): List { - return model.files.flatMap { it.generateKotlinFiles() } - } - - private fun FileDeclaration.generateKotlinFiles(): List { - additionalPublicImports.clear() - additionalInternalImports.clear() - - return listOf( - generatePublicKotlinFile(), - generateInternalKotlinFile(), - ) +class ModelToProtobufKotlinCommonGenerator( + model: Model, + logger: Logger, +) : AModelToKotlinCommonGenerator(model, logger) { + override val hasPublicGeneratedFiles: Boolean = model.files.any { + it.enumDeclarations.isNotEmpty() || it.messageDeclarations.isNotEmpty() } - private var currentPackage: FqName = FqName.Package.Root - - private fun FileDeclaration.generatePublicKotlinFile(): FileGenerator { - currentPackage = packageName - - return file(logger = logger) { - filename = this@generatePublicKotlinFile.name - packageName = this@generatePublicKotlinFile.packageName.safeFullName() - packagePath = this@generatePublicKotlinFile.packageName.safeFullName() - - dependencies.forEach { dependency -> - importPackage(dependency.packageName.safeFullName()) - } - - fileOptIns = listOf("ExperimentalRpcApi::class", "InternalRpcApi::class") - - generatePublicDeclaredEntities(this@generatePublicKotlinFile) - - import("kotlinx.rpc.internal.utils.*") - import("kotlinx.coroutines.flow.*") - - additionalPublicImports.forEach { - import(it) - } - } - } - - private fun FileDeclaration.generateInternalKotlinFile(): FileGenerator { - currentPackage = packageName - - return file(logger = logger) { - filename = this@generateInternalKotlinFile.name - packageName = this@generateInternalKotlinFile.packageName.safeFullName() - packagePath = - this@generateInternalKotlinFile.packageName.safeFullName() - .packageNameSuffixed(RPC_INTERNAL_PACKAGE_SUFFIX) - - fileOptIns = listOf("ExperimentalRpcApi::class", "$INTERNAL_RPC_API_ANNO::class") - - dependencies.forEach { dependency -> - importPackage(dependency.packageName.safeFullName()) - } + override val hasInternalGeneratedFiles: Boolean = hasPublicGeneratedFiles - generateInternalDeclaredEntities(this@generateInternalKotlinFile) - - import("kotlinx.rpc.grpc.pb.*") - import("kotlinx.rpc.internal.utils.*") - import("kotlinx.coroutines.flow.*") - - additionalInternalImports.forEach { - import(it) - } - } - } - - private val additionalPublicImports = mutableSetOf() - private val additionalInternalImports = mutableSetOf() - - private fun CodeGenerator.generatePublicDeclaredEntities(fileDeclaration: FileDeclaration) { + override fun CodeGenerator.generatePublicDeclaredEntities(fileDeclaration: FileDeclaration) { fileDeclaration.messageDeclarations.forEach { generatePublicMessage(it) } fileDeclaration.enumDeclarations.forEach { generatePublicEnum(it) } - fileDeclaration.serviceDeclarations.forEach { generatePublicService(it) } } - private fun CodeGenerator.generateInternalDeclaredEntities(fileDeclaration: FileDeclaration) { + override fun CodeGenerator.generateInternalDeclaredEntities(fileDeclaration: FileDeclaration) { generateInternalMessageEntities(fileDeclaration.messageDeclarations) val allEnums = @@ -136,7 +74,7 @@ class ModelToKotlinCommonGenerator( clazz( name = declaration.name.simpleName, - declarationType = DeclarationType.Interface, + declarationType = CodeGenerator.DeclarationType.Interface, annotations = listOf("@$WITH_CODEC_ANNO(${declaration.internalClassFullName()}.CODEC::class)") ) { declaration.fields().forEach { (fieldDeclaration, _) -> @@ -158,7 +96,7 @@ class ModelToKotlinCommonGenerator( generatePublicEnum(enum) } - clazz("", modifiers = "companion", declarationType = DeclarationType.Object) + clazz("", modifiers = "companion", declarationType = CodeGenerator.DeclarationType.Object) } } @@ -179,7 +117,7 @@ class ModelToKotlinCommonGenerator( clazz( name = internalClassName, annotations = annotations, - declarationType = DeclarationType.Class, + declarationType = CodeGenerator.DeclarationType.Class, superTypes = superTypes, ) { @@ -195,7 +133,9 @@ class ModelToKotlinCommonGenerator( } field.type is FieldType.Message -> - "by MsgFieldDelegate(PresenceIndices.${field.name}) { ${field.type.dec.value.internalClassFullName()}() }" + "by MsgFieldDelegate(PresenceIndices.${field.name}) { " + + "${(field.type as FieldType.Message).dec.value.internalClassFullName()}() " + + "}" else -> { val fieldPresence = if (field.presenceIdx != null) "PresenceIndices.${field.name}" else "" @@ -242,7 +182,7 @@ class ModelToKotlinCommonGenerator( function("encode", modifiers = "override", args = "value: $msgFqName", returnType = sourceFqName) { code("val buffer = $bufferFqName()") code("val encoder = $PB_PKG.WireEncoder(buffer)") - scope("$PB_PKG.checkForPlatformEncodeException", nlAfterClosed = false) { + scope("${PB_PKG}.checkForPlatformEncodeException", nlAfterClosed = false) { code("value.asInternal().encodeWith(encoder)") } code("encoder.flush()") @@ -252,7 +192,7 @@ class ModelToKotlinCommonGenerator( function("decode", modifiers = "override", args = "stream: $sourceFqName", returnType = msgFqName) { scope("$PB_PKG.WireDecoder(stream as $bufferFqName).use") { code("val msg = ${declaration.internalClassFullName()}()") - scope("$PB_PKG.checkForPlatformDecodeException", nlAfterClosed = false) { + scope("${PB_PKG}.checkForPlatformDecodeException", nlAfterClosed = false) { code("${declaration.internalClassFullName()}.decodeWith(msg, it)") } code("msg.checkRequiredFields()") @@ -334,12 +274,13 @@ class ModelToKotlinCommonGenerator( val variantName = "${fieldType.dec.name.safeFullName()}.${variant.name}" if (variant.type is FieldType.Message) { // in case of a message, we must construct an empty message before reading the message + val message = variant.type as FieldType.Message readMatchCase( field = variant, lvalue = "field.value", beforeValueDecoding = { beforeValueDecoding() - scope("val field = ($lvalue as? $variantName) ?: $variantName(${variant.type.internalConstructor()}).also") { + scope("val field = ($lvalue as? $variantName) ?: $variantName(${message.internalConstructor()}).also") { // write the constructed oneof variant to the field code("$lvalue = it") } @@ -488,10 +429,10 @@ class ModelToKotlinCommonGenerator( packedWithFixedSize: Boolean, ) { var encFunc = type.decodeEncodeFuncName() - when (val fieldType = type) { + when (type) { is FieldType.IntegralType -> code("encoder.write${encFunc!!}(fieldNr = $number, value = $valueVar)") is FieldType.List -> { - encFunc = fieldType.value.decodeEncodeFuncName() + encFunc = type.value.decodeEncodeFuncName() when { isPacked && packedWithFixedSize -> code("encoder.writePacked${encFunc!!}(fieldNr = $number, value = $valueVar)") @@ -503,12 +444,12 @@ class ModelToKotlinCommonGenerator( })" ) - fieldType.value is FieldType.Message -> scope("$valueVar.forEach") { + type.value is FieldType.Message -> scope("$valueVar.forEach") { code("encoder.writeMessage(fieldNr = ${number}, value = it.asInternal()) { encodeWith(it) }") } else -> { - require(encFunc != null) { "No encode function for list type: $fieldType" } + require(encFunc != null) { "No encode function for list type: $type" } scope("$valueVar.forEach") { code("encoder.write${encFunc}($number, it)") } @@ -519,8 +460,8 @@ class ModelToKotlinCommonGenerator( is FieldType.Enum -> code("encoder.write${encFunc!!}(fieldNr = $number, value = ${valueVar}.number)") is FieldType.OneOf -> whenBlock("val value = $valueVar") { - fieldType.dec.variants.forEach { variant -> - whenCase("is ${fieldType.dec.name.safeFullName()}.${variant.name}") { + type.dec.variants.forEach { variant -> + whenCase("is ${type.dec.name.safeFullName()}.${variant.name}") { generateEncodeFieldValue(variant, "value.value") } } @@ -528,11 +469,11 @@ class ModelToKotlinCommonGenerator( is FieldType.Map -> { scope("$valueVar.forEach", paramDecl = "kEntry ->") { - generateMapConstruction(fieldType, "kEntry.key", "kEntry.value") + generateMapConstruction(type, "kEntry.key", "kEntry.value") scope(".also", paramDecl = "entry ->") { generateEncodeFieldValue( valueVar = "entry", - type = FieldType.Message(lazy { fieldType.entry.dec }), + type = FieldType.Message(lazy { type.entry.dec }), number = number, isPacked = false, packedWithFixedSize = false ) } @@ -584,7 +525,7 @@ class ModelToKotlinCommonGenerator( requiredFields.forEach { field -> ifBranch(condition = "!presenceMask[${field.presenceIdx}]", ifBlock = { - code("throw $PB_PKG.ProtobufDecodingException.missingRequiredField(\"${declaration.name.simpleName}\", \"${field.name}\")") + code("throw ${PB_PKG}.ProtobufDecodingException.missingRequiredField(\"${declaration.name.simpleName}\", \"${field.name}\")") }) } @@ -604,7 +545,8 @@ class ModelToKotlinCommonGenerator( scope("${field.name}?.also") { whenBlock { messageVariants.forEach { variant -> - val variantClassName = "${field.type.dec.name.safeFullName()}.${variant.name}" + val variantClassName = + "${(field.type as FieldType.OneOf).dec.name.safeFullName()}.${variant.name}" whenCase("it is $variantClassName") { code("it.value.asInternal().checkRequiredFields()") } @@ -633,7 +575,6 @@ class ModelToKotlinCommonGenerator( code("it.asInternal().checkRequiredFields()") } } - } private fun CodeGenerator.generateInternalComputeSize(declaration: MessageDeclaration) { @@ -685,8 +626,10 @@ class ModelToKotlinCommonGenerator( } } - - private fun CodeGenerator.generateFieldComputeSizeCall(field: FieldDeclaration, variable: String) { + private fun CodeGenerator.generateFieldComputeSizeCall( + field: FieldDeclaration, + variable: String, + ) { val valueSize by lazy { field.type.valueSizeCall(variable, field.number, field.dec.isPacked) } val tagSize = tagSizeCall(field.number, field.type.wireType) @@ -704,14 +647,14 @@ class ModelToKotlinCommonGenerator( is FieldType.Map -> { scope("result += ${field.name}.entries.sumOf", paramDecl = "kEntry ->") { - generateMapConstruction(field.type, "kEntry.key", "kEntry.value") + generateMapConstruction(field.type as FieldType.Map, "kEntry.key", "kEntry.value") code("._size") } } is FieldType.OneOf -> whenBlock("val value = $variable") { - field.type.dec.variants.forEach { variant -> - val variantName = "${field.type.dec.name.safeFullName()}.${variant.name}" + (field.type as FieldType.OneOf).dec.variants.forEach { variant -> + val variantName = "${(field.type as FieldType.OneOf).dec.name.safeFullName()}.${variant.name}" whenCase("is $variantName") { generateFieldComputeSizeCall(variant, "value.value") } @@ -736,7 +679,11 @@ class ModelToKotlinCommonGenerator( } } - private fun CodeGenerator.generateMapConstruction(map: FieldType.Map, keyVar: String, valueVar: String) { + private fun CodeGenerator.generateMapConstruction( + map: FieldType.Map, + keyVar: String, + valueVar: String, + ) { val entryClass = map.entry.dec.internalClassFullName() scope("$entryClass().apply", nlAfterClosed = false) { code("key = $keyVar") @@ -745,7 +692,7 @@ class ModelToKotlinCommonGenerator( } private fun FieldType.valueSizeCall(variable: String, number: Int, isPacked: Boolean = false): String { - val sizeFunName = decodeEncodeFuncName()?.decapitalize() + val sizeFunName = decodeEncodeFuncName()?.replaceFirstChar { it.lowercase() } val sizeFunc = "$PB_PKG.WireSize.$sizeFunName($variable)" return when (this) { @@ -772,6 +719,7 @@ class ModelToKotlinCommonGenerator( return "$PB_PKG.WireSize.tag($number, $PB_PKG.WireType.$wireType)" } + @Suppress("SameParameterValue") private fun int32SizeCall(number: String): String { return "$PB_PKG.WireSize.int32($number)" } @@ -819,59 +767,10 @@ class ModelToKotlinCommonGenerator( return "${name}: ${typeFqName()}" } - private fun FieldDeclaration.typeFqName(): String { - return when (type) { - is FieldType.Message -> { - type.dec.value.name.safeFullName() - } - - is FieldType.Enum -> type.dec.name.safeFullName() - - is FieldType.OneOf -> type.dec.name.safeFullName() - - is FieldType.IntegralType -> { - type.fqName.simpleName - } - - is FieldType.List -> { - val fqValue = when (val value = type.value) { - is FieldType.Message -> value.dec.value.name - is FieldType.IntegralType -> value.fqName - else -> error("Unsupported type: $value") - } - - "List<${fqValue.safeFullName()}>" - } - - is FieldType.Map -> { - val entry = type.entry - - val fqKey = when (val key = entry.key) { - is FieldType.Message -> key.dec.value.name - is FieldType.IntegralType -> key.fqName - else -> error("Unsupported type: $key") - } - - val fqValue = when (val value = entry.value) { - is FieldType.Message -> value.dec.value.name - is FieldType.IntegralType -> value.fqName - else -> error("Unsupported type: $value") - } - - "Map<${fqKey.safeFullName()}, ${fqValue.safeFullName()}>" - } - - }.withNullability(nullable) - } - - private fun String.withNullability(nullable: Boolean): String { - return "$this${if (nullable) "?" else ""}" - } - private fun CodeGenerator.generateOneOfPublic(declaration: OneOfDeclaration) { val interfaceName = declaration.name.simpleName - clazz(interfaceName, "sealed", declarationType = DeclarationType.Interface) { + clazz(interfaceName, "sealed", declarationType = CodeGenerator.DeclarationType.Interface) { declaration.variants.forEach { variant -> clazz( name = variant.name, @@ -900,7 +799,7 @@ class ModelToKotlinCommonGenerator( declaration.originalEntries.forEach { variant -> clazz( name = variant.name.simpleName, - declarationType = DeclarationType.Object, + declarationType = CodeGenerator.DeclarationType.Object, superTypes = listOf("$className(number = ${variant.dec.number})"), ) } @@ -915,7 +814,7 @@ class ModelToKotlinCommonGenerator( newLine() - clazz("", modifiers = "companion", declarationType = DeclarationType.Object) { + clazz("", modifiers = "companion", declarationType = CodeGenerator.DeclarationType.Object) { declaration.aliases.forEach { alias: EnumDeclaration.Alias -> code( "val ${alias.name.simpleName}: $className " + @@ -929,57 +828,6 @@ class ModelToKotlinCommonGenerator( } } - - @Suppress("detekt.LongMethod") - private fun CodeGenerator.generatePublicService(service: ServiceDeclaration) { - code("@kotlinx.rpc.grpc.annotations.Grpc") - clazz(service.name.simpleName, declarationType = DeclarationType.Interface) { - service.methods.forEach { method -> - val inputType = method.inputType - val outputType = method.outputType - function( - name = method.name, - modifiers = if (method.dec.isServerStreaming) "" else "suspend", - args = "message: ${inputType.name.safeFullName().wrapInFlowIf(method.dec.isClientStreaming)}", - returnType = outputType.name.safeFullName().wrapInFlowIf(method.dec.isServerStreaming), - ) - } - } - } - - private fun String.wrapInFlowIf(condition: Boolean): String { - return if (condition) "Flow<$this>" else this - } - - private fun FqName.safeFullName(classSuffix: String = ""): String { - importRootDeclarationIfNeeded(this) - - return fullName(classSuffix) - } - - private fun importRootDeclarationIfNeeded( - declaration: FqName, - nameToImport: String = declaration.simpleName, - internalOnly: Boolean = false, - ) { - if (declaration.parent == FqName.Package.Root && currentPackage != FqName.Package.Root && nameToImport.isNotBlank()) { - additionalInternalImports.add(nameToImport) - if (!internalOnly) { - additionalPublicImports.add(nameToImport) - } - } - } - - private fun FieldType.Message.internalConstructor() = - dec.value.internalClassFullName() + "()" - - private fun MessageDeclaration.internalClassFullName(): String { - return name.safeFullName(MSG_INTERNAL_SUFFIX) - } - - private fun MessageDeclaration.internalClassName(): String { - return name.simpleName + MSG_INTERNAL_SUFFIX - } } private fun MessageDeclaration.allEnumsRecursively(): List = @@ -988,6 +836,3 @@ private fun MessageDeclaration.allEnumsRecursively(): List = private fun MessageDeclaration.allNestedRecursively(): List = nestedDeclarations + nestedDeclarations.flatMap(MessageDeclaration::allNestedRecursively) -private fun String.packageNameSuffixed(suffix: String): String { - return if (isEmpty()) suffix else "$this.$suffix" -} diff --git a/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/ProtobufProtocGenPlugin.kt b/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/ProtobufProtocGenPlugin.kt new file mode 100644 index 000000000..813a2516d --- /dev/null +++ b/protoc-gen/protobuf/src/main/kotlin/kotlinx/rpc/protoc/gen/ProtobufProtocGenPlugin.kt @@ -0,0 +1,19 @@ +/* + * 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.protoc.gen + +import kotlinx.rpc.protoc.gen.core.FileGenerator +import kotlinx.rpc.protoc.gen.core.ProtocGenPlugin +import kotlinx.rpc.protoc.gen.core.model.Model +import org.slf4j.Logger + +object ProtobufProtocGenPlugin : ProtocGenPlugin() { + override fun generateKotlinByModel( + model: Model, + logger: Logger, + ): List { + return ModelToProtobufKotlinCommonGenerator(model, logger).generateKotlinFiles() + } +} diff --git a/protoc-gen/settings.gradle.kts b/protoc-gen/settings.gradle.kts index 23bec0e46..f6e6ebe50 100644 --- a/protoc-gen/settings.gradle.kts +++ b/protoc-gen/settings.gradle.kts @@ -17,4 +17,6 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } -includeRootAsPublic() +include(":common") // NOT PUBLIC, grpc and protobuf are fat jars +includePublic(":grpc") // protoc-gen-grpc-kotlin-multiplatform +includePublic(":protobuf") // protoc-gen-kotlin-multiplatform diff --git a/settings.gradle.kts b/settings.gradle.kts index ebde63547..7b86eba05 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,9 @@ dependencyResolutionManagement { includeBuild("protoc-gen") } +include(":protobuf") +includePublic(":protobuf:protobuf-core") + include(":grpc") includePublic(":grpc:grpc-core") includePublic(":grpc:grpc-ktor-server") diff --git a/versions-root/libs.versions.toml b/versions-root/libs.versions.toml index 654b82282..a05c9ed7c 100644 --- a/versions-root/libs.versions.toml +++ b/versions-root/libs.versions.toml @@ -113,6 +113,7 @@ grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpc" } grpc-util = { module = "io.grpc:grpc-util", version.ref = "grpc" } grpc-netty = { module = "io.grpc:grpc-netty", version.ref = "grpc" } grpc-protobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" } +grpc-protobuf-lite = { module = "io.grpc:grpc-protobuf-lite", version.ref = "grpc" } grpc-kotlin-stub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpc-kotlin" } grpc-protoc-gen-java = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc" } grpc-protoc-gen-kotlin = { module = "io.grpc:protoc-gen-grpc-kotlin", version.ref = "grpc-kotlin" } @@ -160,6 +161,7 @@ conventions-root = { id = "conventions-root" } conventions-dokka-spec = { id = "conventions-dokka-spec" } conventions-dokka-public = { id = "conventions-dokka-public" } conventions-no-psi-element = { id = "conventions-no-psi-element" } +conventions-protoc-gen = { id = "conventions-protoc-gen" } compiler-specific-module = { id = "compiler-specific-module" } # gradle-plugin project