├── jitpack.yml ├── example ├── gradle.properties ├── buildSrc │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── Versions.kt ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── common │ ├── src │ │ ├── androidMain │ │ │ └── AndroidManifext.xml │ │ └── commonMain │ │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── grpc_multiplatform │ │ │ └── example │ │ │ └── common │ │ │ └── GreetingLogic.kt │ └── build.gradle.kts ├── settings.gradle.kts ├── protos │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── proto │ │ └── hello.proto ├── js │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── index.html │ │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── grpc_multiplatform │ │ │ └── example │ │ │ └── js │ │ │ └── web.kt │ └── build.gradle.kts ├── jvm │ ├── build.gradle.kts │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── grpc_multiplatform │ │ │ └── example │ │ │ └── jvm │ │ │ ├── Client.kt │ │ │ └── Server.kt │ └── envoy-custom.yaml ├── readme.md ├── build.gradle.kts └── gradlew.bat ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── grpc-mp-test ├── src │ ├── androidMain │ │ └── AndroidManifest.xml │ ├── commonTest │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_plugin │ │ │ └── test │ │ │ ├── EqTest.kt │ │ │ ├── defaults.kt │ │ │ └── RpcTest.kt │ ├── jvmTest │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_plugin │ │ │ └── test │ │ │ └── JVMSerializationTest.kt │ ├── iosTest │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_plugin │ │ │ └── test │ │ │ └── IOSSerializationTest.kt │ ├── jsTest │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_plugin │ │ │ └── test │ │ │ └── JSSerializationTest.kt │ ├── serializationTest │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_plugin │ │ │ └── test │ │ │ └── SerializationTest.kt │ └── commonMain │ │ └── proto │ │ └── basicMessages.proto ├── test-android-protos │ ├── src │ │ └── main │ │ │ └── AndroidManifest.xml │ └── build.gradle.kts └── test-jvm-protos │ └── build.gradle.kts ├── grpc-multiplatform-lib ├── src │ ├── androidMain │ │ └── AndroidManifest.xml │ ├── commonMain │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_lib │ │ │ ├── message │ │ │ └── KMMessage.kt │ │ │ ├── KMStatus.kt │ │ │ ├── KMStatusException.kt │ │ │ ├── util │ │ │ └── TimeUnit.kt │ │ │ ├── stub │ │ │ └── KMStub.kt │ │ │ ├── KMChannel.kt │ │ │ ├── KMMetadata.kt │ │ │ └── KMCode.kt │ ├── iosJvmCommon │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_lib │ │ │ ├── io │ │ │ ├── ParseException.kt │ │ │ ├── const.kt │ │ │ ├── wireformat.kt │ │ │ ├── CodedInputStream.kt │ │ │ ├── message_writer.kt │ │ │ ├── size_computation.kt │ │ │ └── message_reader.kt │ │ │ └── message │ │ │ ├── MessageDeserializer.kt │ │ │ └── DataType.kt │ ├── jsMain │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_lib │ │ │ ├── message │ │ │ ├── JSImpl.kt │ │ │ ├── KMMessage.kt │ │ │ └── MessageDeserializer.kt │ │ │ ├── util │ │ │ ├── TimeUnit.kt │ │ │ └── FunctionWrapper.kt │ │ │ ├── metadata_util.kt │ │ │ ├── stub │ │ │ └── JsStub.kt │ │ │ ├── KMChannel.kt │ │ │ ├── rpc │ │ │ ├── GrpcWebClientBase.kt │ │ │ └── rpc_implementation.kt │ │ │ └── JSPBMap.kt │ ├── androidJvmCommon │ │ └── kotlin │ │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_lib │ │ │ ├── message │ │ │ ├── DataType.kt │ │ │ └── KMMessage.kt │ │ │ ├── metadata_util.kt │ │ │ ├── util │ │ │ └── TimeUnit.kt │ │ │ ├── stub │ │ │ └── AndroidJvmKMStub.kt │ │ │ ├── KMChannel.kt │ │ │ └── io │ │ │ ├── size_computation.kt │ │ │ └── CodedInputStream.kt │ └── iosMain │ │ └── kotlin │ │ └── io │ │ └── github │ │ └── timortel │ │ └── kotlin_multiplatform_grpc_lib │ │ ├── io │ │ ├── GPBCodedInputStreamWrapper.kt │ │ ├── size_computation.kt │ │ └── CodedInputStream.kt │ │ ├── util │ │ └── TimeUnit.kt │ │ ├── message │ │ ├── DataType.kt │ │ └── KMMessage.kt │ │ ├── stub │ │ └── IOSKMStub.kt │ │ ├── KMChannel.kt │ │ └── rpc │ │ └── rpc_implementation.kt └── build.gradle.kts ├── gradle.properties ├── plugin ├── src │ └── main │ │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── timortel │ │ │ └── kotlin_multiplatform_grpc_plugin │ │ │ ├── generate_mulitplatform_sources │ │ │ ├── content │ │ │ │ ├── ProtoEnumField.kt │ │ │ │ ├── ProtoEnum.kt │ │ │ │ ├── ProtoService.kt │ │ │ │ ├── ProtoFile.kt │ │ │ │ ├── ProtoRpc.kt │ │ │ │ ├── ProtoOneOf.kt │ │ │ │ ├── ProtoMessage.kt │ │ │ │ └── ProtoMessageAttribute.kt │ │ │ ├── message_tree │ │ │ │ ├── PackageNode.kt │ │ │ │ ├── MessageNode.kt │ │ │ │ ├── Proto3MessageTreeBuilder.kt │ │ │ │ └── PacketTreeBuilder.kt │ │ │ ├── lib_packages.kt │ │ │ ├── generators │ │ │ │ ├── proto_file │ │ │ │ │ ├── DefaultChildClassName.kt │ │ │ │ │ ├── JsProtoFileWriter.kt │ │ │ │ │ ├── IOSProtoFileWriter.kt │ │ │ │ │ └── CommonProtoFileWriter.kt │ │ │ │ ├── common │ │ │ │ │ ├── JsCommonFunctionGenerator.kt │ │ │ │ │ ├── JvmCommonFunctionGenerator.kt │ │ │ │ │ └── CommonFunctionGenerator.kt │ │ │ │ ├── proto3_ios_service_writer.kt │ │ │ │ ├── proto3_jvm_service_writer.kt │ │ │ │ ├── dsl │ │ │ │ │ ├── CommonDslBuilder.kt │ │ │ │ │ ├── IosJvmDslBuilder.kt │ │ │ │ │ └── JsDslBuilder.kt │ │ │ │ ├── proto3_file_dsl_builder.kt │ │ │ │ ├── map │ │ │ │ │ ├── CommonMapMessageMethodGenerator.kt │ │ │ │ │ ├── IosJvmMapMessageMethodGenerator.kt │ │ │ │ │ ├── mapper │ │ │ │ │ │ ├── JsToCommonMapMapper.kt │ │ │ │ │ │ ├── JvmToCommonMapMapper.kt │ │ │ │ │ │ ├── CommonToJvmMapMapper.kt │ │ │ │ │ │ ├── CommonToJsMapMapper.kt │ │ │ │ │ │ └── MapMapper.kt │ │ │ │ │ ├── MapMessageMethodGenerator.kt │ │ │ │ │ └── JsMapMessageMethodGenerator.kt │ │ │ │ ├── oneof │ │ │ │ │ ├── CommonOneOfMethodAndClassGenerator.kt │ │ │ │ │ ├── JsOneOfMethodAndClassGenerator.kt │ │ │ │ │ └── IosJvmOneOfMethodAndClassGenerator.kt │ │ │ │ ├── service │ │ │ │ │ ├── serviceOverrides.kt │ │ │ │ │ ├── CommonServiceWriter.kt │ │ │ │ │ ├── ActualServiceWriter.kt │ │ │ │ │ └── IOSServiceWriter.kt │ │ │ │ ├── repeated │ │ │ │ │ ├── CommonRepeatedMessageMethodGenerator.kt │ │ │ │ │ ├── IosJvmRepeatedMessageMethodGenerator.kt │ │ │ │ │ ├── JsRepeatedMessageMethodGenerator.kt │ │ │ │ │ └── RepeatedMessageMethodGenerator.kt │ │ │ │ ├── proto3_ios_file_writer.kt │ │ │ │ ├── proto3_jvm_file_writer.kt │ │ │ │ ├── scalar │ │ │ │ │ ├── CommonScalarMessageMethodGenerator.kt │ │ │ │ │ ├── IosJvmScalarMessageMethodGenerator.kt │ │ │ │ │ ├── ScalarMessageMethodGenerator.kt │ │ │ │ │ └── JsScalarMessageMethodGenerator.kt │ │ │ │ ├── proto3_service_writer.kt │ │ │ │ └── proto3_file_writer.kt │ │ │ ├── iosClasses.kt │ │ │ ├── android_ios_common.kt │ │ │ ├── Types.kt │ │ │ ├── platform_file_names.kt │ │ │ └── libClasses.kt │ │ │ ├── util │ │ │ └── string_util.kt │ │ │ ├── GrpcMultiplatformExtension.kt │ │ │ └── GrpcMultiplatformPlugin.kt │ │ └── antlr │ │ └── io │ │ └── github │ │ └── timortel │ │ └── kotlin_multiplatform_grpc_plugin │ │ └── anltr │ │ └── Proto3.g4 └── build.gradle.kts ├── .gitignore ├── settings.gradle.kts └── gradlew.bat /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 -------------------------------------------------------------------------------- /example/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.pluginLoadedInMultipleProjects.ignore=true -------------------------------------------------------------------------------- /example/buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | gradlePluginPortal() 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/gRPC-KMP/latest_build/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/gRPC-KMP/latest_build/example/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/common/src/androidMain/AndroidManifext.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "grpc-multiplaform-example" 2 | 3 | include("common") 4 | include("protos") 5 | include("js") 6 | include("jvm") -------------------------------------------------------------------------------- /grpc-mp-test/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/protos/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = "unspecified" 2 | plugins { 3 | `java-library` 4 | } 5 | 6 | java { 7 | sourceSets.getByName("main").resources.srcDir("src/main/proto") 8 | } -------------------------------------------------------------------------------- /grpc-mp-test/test-android-protos/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.mpp.enableCInteropCommonization=true 2 | org.gradle.jvmargs=-Xmx4096M 3 | kotlin.native.binary.memoryModel=experimental 4 | kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true 5 | -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/KMMessage.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | expect interface KMMessage -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/ParseException.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | class ParseException : Exception() -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMStatus.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | data class KMStatus(val code: KMCode, val statusMessage: String) { 4 | 5 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/JSImpl.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | interface JSImpl { 4 | fun serializeBinary(): dynamic 5 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/KMMessage.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | actual interface KMMessage { 4 | val jsImpl: JSImpl 5 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /example/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMStatusException.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | class KMStatusException(val status: KMStatus, cause: Throwable?) : RuntimeException(cause) { 4 | } -------------------------------------------------------------------------------- /example/js/src/main/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | Check the console for the grpc output. 9 | 10 | 11 | -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/DataType.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | import com.google.protobuf.WireFormat.FieldType 4 | 5 | actual typealias DataType = FieldType -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/MessageDeserializer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | interface MessageDeserializer { 4 | fun deserialize(`data`: K): T 5 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/MessageDeserializer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | interface MessageDeserializer { 4 | fun deserializeBinary(bytes: dynamic): T 5 | } -------------------------------------------------------------------------------- /example/buildSrc/src/main/kotlin/Versions.kt: -------------------------------------------------------------------------------- 1 | object Versions { 2 | 3 | val PROTOBUF_PLUGIN = "0.8.17" 4 | 5 | val GRPC = "1.46.0" 6 | val GRPC_KOTLIN = "1.2.1" 7 | val PROTOBUF = "3.20.1" //3.21.0 seems to have removed the js-protoc 8 | val COROUTINES = "1.5.2-native-mt" 9 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/util/TimeUnit.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.util 2 | 3 | expect enum class TimeUnit { 4 | DAYS, 5 | HOURS, 6 | MILLISECONDS, 7 | MINUTES, 8 | SECONDS 9 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoEnumField.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | data class ProtoEnumField(val name: String, val num: Int) 4 | -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoEnum.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | data class ProtoEnum(val name: String, val fields: List) 4 | -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoService.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | data class ProtoService(val serviceName: String, val rpcs: List) -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/GPBCodedInputStreamWrapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import cocoapods.Protobuf.GPBCodedInputStream 4 | 5 | data class GPBCodedInputStreamWrapper(val stream: GPBCodedInputStream, var recursionDepth: Int = 0) -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/message_tree/PackageNode.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.message_tree 2 | 3 | data class PackageNode(val packageName: String, val nodes: List, val children: List) -------------------------------------------------------------------------------- /example/jvm/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | group = "de.ortel.grpc_multiplatform.jvm" 6 | version = "1.0-SNAPSHOT" 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | implementation(project(":common")) 14 | implementation("io.grpc:grpc-netty-shaded:${Versions.GRPC}") 15 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/util/string_util.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.util 2 | 3 | val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex() 4 | 5 | fun String.camelToSnakeUpperCase(): String { 6 | return camelRegex.replace(this) { 7 | "_${it.value}" 8 | }.uppercase() 9 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/stub/KMStub.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.stub 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.util.TimeUnit 4 | 5 | abstract class KMStub> { 6 | abstract fun withDeadlineAfter(duration: Long, unit: TimeUnit): S 7 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/const.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | //https://github.com/protocolbuffers/protobuf/blob/520c601c99012101c816b6ccc89e8d6fc28fdbb8/objectivec/GPBDictionary.m#L66 4 | const val kMapKeyFieldNumber = 1 5 | const val kMapValueFieldNumber = 2 -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/util/TimeUnit.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.util 2 | 3 | actual enum class TimeUnit(val toMilliFactor: Long) { 4 | DAYS(24 * 60 * 60 * 1000), 5 | HOURS(60 * 60 * 1000), 6 | MILLISECONDS(1), 7 | MINUTES(60 * 1000), 8 | SECONDS(1000) 9 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/util/TimeUnit.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.util 2 | 3 | actual enum class TimeUnit(val toMilliFactor: Long) { 4 | DAYS(24 * 60 * 60 * 1000), 5 | HOURS(60 * 60 * 1000), 6 | MILLISECONDS(1), 7 | MINUTES(60 * 1000), 8 | SECONDS(1000) 9 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/metadata_util.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | val KMMetadata.jsMetadata: dynamic 4 | get() = { 5 | val json = js("{}") 6 | metadataMap.forEach { (key, value) -> 7 | json[key] = value 8 | } 9 | 10 | val jsoner = js("JSON") 11 | jsoner.stringify(json).toString() 12 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/lib_packages.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources 2 | 3 | const val PACKAGE_IO = "io.github.timortel.kotlin_multiplatform_grpc_lib.io" 4 | const val PACKAGE_MESSAGE = "io.github.timortel.kotlin_multiplatform_grpc_lib.message" 5 | const val PACKAGE_STUB = "io.github.timortel.kotlin_multiplatform_grpc_lib.stub" 6 | -------------------------------------------------------------------------------- /example/protos/src/main/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package io.github.timortel.grpc_multiplatform.example; 4 | 5 | option java_multiple_files = true; 6 | 7 | message HelloRequest { 8 | string greeting = 1; 9 | } 10 | 11 | message Response { 12 | string response = 1; 13 | } 14 | 15 | service HelloService { 16 | rpc sayHello (HelloRequest) returns (Response); 17 | 18 | rpc sayHelloMultipleTimes(HelloRequest) returns (stream Response); 19 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/metadata_util.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | val KMMetadata.jvmMetadata: io.grpc.Metadata 4 | get() = io.grpc.Metadata().apply { 5 | metadataMap.forEach { (key, value) -> 6 | val metadataKey = io.grpc.Metadata.Key.of(key, io.grpc.Metadata.ASCII_STRING_MARSHALLER) 7 | put(metadataKey, value) 8 | } 9 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMChannel.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | /** 4 | * Wrapps around the GRPC-Channel 5 | */ 6 | expect class KMChannel { 7 | class Builder { 8 | companion object { 9 | fun forAddress(name: String, port: Int): Builder 10 | } 11 | 12 | fun usePlaintext(): Builder 13 | 14 | fun build(): KMChannel 15 | } 16 | } -------------------------------------------------------------------------------- /example/js/src/main/kotlin/io/github/timortel/grpc_multiplatform/example/js/web.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.grpc_multiplatform.example.js 2 | 3 | import io.github.timortel.grpc_multiplatform.example.common.performGreeting 4 | import io.github.timortel.grpc_multiplatform.example.common.performMultipleGreetings 5 | 6 | suspend fun main() { 7 | val response = performGreeting("Hello from web", 8082) 8 | 9 | console.log(response) 10 | 11 | performMultipleGreetings("Multiple greetings from web", 8082) 12 | } -------------------------------------------------------------------------------- /example/readme.md: -------------------------------------------------------------------------------- 1 | Illustrates how grpc multiplatform can work. 2 | 3 | ## 1. Run the server 4 | Run the server located in the jvm module. 5 | 6 | ## Android/JVM example 7 | Run the client in the jvm module. 8 | 9 | ## Web/JS example 10 | 1. Start the envoy server: 11 | ``` 12 | cd jvm 13 | docker run --rm -it -v $(pwd)/envoy-custom.yaml:/envoy-custom.yaml --network=host envoyproxy/envoy-dev:8ec461e3a6ff2503a05e599029c47252d732d87b -c /envoy-custom.yaml 14 | ``` 15 | 2. Run the gradle task js:browserDevelopmentRun -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/DataType.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | expect enum class DataType { 4 | DOUBLE, 5 | FLOAT, 6 | INT64, 7 | UINT64, 8 | INT32, 9 | FIXED64, 10 | FIXED32, 11 | BOOL, 12 | STRING, 13 | GROUP, 14 | MESSAGE, 15 | BYTES, 16 | UINT32, 17 | ENUM, 18 | SFIXED32, 19 | SFIXED64, 20 | SINT32, 21 | SINT64 22 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/util/TimeUnit.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.util 2 | 3 | actual enum class TimeUnit(val javaTimeUnit: java.util.concurrent.TimeUnit) { 4 | DAYS(java.util.concurrent.TimeUnit.DAYS), 5 | HOURS(java.util.concurrent.TimeUnit.HOURS), 6 | MILLISECONDS(java.util.concurrent.TimeUnit.MILLISECONDS), 7 | MINUTES(java.util.concurrent.TimeUnit.MINUTES), 8 | SECONDS(java.util.concurrent.TimeUnit.SECONDS) 9 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoFile.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | data class ProtoFile( 4 | val pkg: String, 5 | val fileNameWithoutExtension: String, 6 | val fileName: String, 7 | val messages: List, 8 | val services: List, 9 | val enums: List 10 | ) { 11 | override fun toString(): String = "$fileName" 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java template 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | *.hprof 25 | 26 | ./local.properties 27 | 28 | .gradle 29 | .idea 30 | build 31 | generated 32 | local.properties 33 | *.podspec 34 | *.lock -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto_file/DefaultChildClassName.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | 5 | interface DefaultChildClassName { 6 | 7 | fun getChildClassName(parentClass: ClassName?, childName: String, pkg: String): ClassName { 8 | return parentClass?.nestedClass(childName) ?: ClassName(pkg, childName) 9 | } 10 | } -------------------------------------------------------------------------------- /example/jvm/src/main/kotlin/io/github/timortel/grpc_multiplatform/example/jvm/Client.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.grpc_multiplatform.example.jvm 2 | 3 | import io.github.timortel.grpc_multiplatform.example.common.performGreeting 4 | import io.github.timortel.grpc_multiplatform.example.common.performMultipleGreetings 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun main() { 8 | runBlocking { 9 | println(performGreeting("Greeting from JVM", 17600)) 10 | 11 | performMultipleGreetings("Multiple greetings from JVM", 17600) 12 | } 13 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoRpc.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.Types 4 | 5 | class ProtoRpc( 6 | val rpcName: String, 7 | val request: Types, 8 | val response: Types, 9 | val method: Method, 10 | ) { 11 | enum class Method { 12 | UNARY, 13 | SERVER_STREAMING 14 | } 15 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMMetadata.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | /** 4 | * Metadata wrapper. 5 | */ 6 | data class KMMetadata(val metadataMap: MutableMap = mutableMapOf()) { 7 | 8 | operator fun set(key: String, value: String) { 9 | metadataMap[key] = value 10 | } 11 | 12 | operator fun get(key: String) = metadataMap[key] 13 | 14 | operator fun plus(other: KMMetadata) = KMMetadata((metadataMap + other.metadataMap).toMutableMap()) 15 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/iosClasses.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.MemberName 5 | 6 | const val COCOAPODS_PROTOBUF_PACKAGE = "cocoapods.Protobuf" 7 | 8 | val GPBCodedInputStream = ClassName(COCOAPODS_PROTOBUF_PACKAGE, "GPBCodedInputStream") 9 | val NSData = ClassName("platform.Foundation", "NSData") 10 | val NSMutableData = ClassName("platform.Foundation", "NSMutableData") -------------------------------------------------------------------------------- /example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | google() 5 | mavenLocal() 6 | gradlePluginPortal() 7 | } 8 | 9 | dependencies { 10 | classpath(kotlin("gradle-plugin", version = "1.8.10")) 11 | classpath(kotlin("serialization", version = "1.8.10")) 12 | 13 | classpath("com.google.protobuf:protobuf-gradle-plugin:0.8.18") 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenLocal() 21 | mavenCentral() 22 | maven(url = "https://jitpack.io") 23 | } 24 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "kotlin-multiplatform-grpc-plugin" 2 | 3 | pluginManagement { 4 | includeBuild("plugin") 5 | 6 | repositories { 7 | gradlePluginPortal() 8 | google() 9 | } 10 | val kotlinVersion = "1.8.10" 11 | 12 | plugins { 13 | kotlin("jvm") version kotlinVersion 14 | kotlin("multiplatform") version kotlinVersion 15 | id("com.android.library") version "7.4.2" 16 | 17 | id("com.google.protobuf") version "0.8.19" apply false 18 | } 19 | } 20 | //include("plugin") 21 | include("grpc-multiplatform-lib") 22 | //include("grpc-mp-test") 23 | -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/android_ios_common.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.MemberName 5 | 6 | val WireFormatMakeTag = MemberName(PACKAGE_IO, "wireFormatMakeTag") 7 | val WireFormatForType = MemberName(PACKAGE_IO, "wireFormatForType") 8 | val ComputeTagSize = MemberName(PACKAGE_IO, "computeTagSize") 9 | val ComputeInt32SizeNoTag = MemberName(PACKAGE_IO, "computeInt32SizeNoTag") 10 | 11 | val DataType = ClassName(PACKAGE_MESSAGE, "DataType") -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/common/JsCommonFunctionGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.common 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 5 | 6 | class JsCommonFunctionGenerator(private val builder: FileSpec.Builder) : CommonFunctionGenerator() { 7 | 8 | override fun addFunction(funSpec: FunSpec) { 9 | builder.addFunction(funSpec) 10 | } 11 | 12 | override fun getNativePlatformType(message: ProtoMessage): TypeName = message.jsType 13 | 14 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/common/JvmCommonFunctionGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.common 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 5 | 6 | class JvmCommonFunctionGenerator(private val builder: FileSpec.Builder) : CommonFunctionGenerator() { 7 | 8 | override fun addFunction(funSpec: FunSpec) { 9 | builder.addFunction(funSpec) 10 | } 11 | 12 | override fun getNativePlatformType(message: ProtoMessage): TypeName = message.jvmType 13 | 14 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoOneOf.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 5 | 6 | /** 7 | * @property oneOfIndex the index of the oneof in the message. 8 | */ 9 | data class ProtoOneOf(val name: String, val attributes: List, val oneOfIndex: Int) { 10 | val capitalizedName: String = name.capitalize() 11 | 12 | fun defaultValue(message: ProtoMessage): CodeBlock = CodeBlock.of("%T", Const.Message.OneOf.notSetClassName(message, this)) 13 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/message_tree/MessageNode.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.message_tree 2 | 3 | sealed class Node(val name: String, val parent: MessageNode?) { 4 | val path: List = parent?.path.orEmpty() + if (parent != null) listOf(parent) else emptyList() 5 | } 6 | 7 | class MessageNode(name: String, val children: List, parent: MessageNode?): Node(name, parent) { 8 | 9 | override fun toString(): String { 10 | return "MessageNode(name=$name, children=${children.map { it.name }}, parent=$parent)" 11 | } 12 | } 13 | 14 | class EnumNode(name: String, parent: MessageNode?) : Node(name, parent) -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto3_ios_service_writer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoService 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.service.IOSServiceWriter 6 | import java.io.File 7 | 8 | fun writeIOSServiceFile(protoFile: ProtoFile, service: ProtoService, iosOutputFolder: File) { 9 | IOSServiceWriter.writeServiceStub(protoFile, service, iosOutputFolder) 10 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto3_jvm_service_writer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoService 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.service.JvmServiceWriter 6 | import java.io.File 7 | 8 | fun writeJvmServiceFile(protoFile: ProtoFile, service: ProtoService, jvmOutputFolder: File) { 9 | JvmServiceWriter.writeServiceStub(protoFile, service, jvmOutputFolder) 10 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/dsl/CommonDslBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.dsl 2 | 3 | import com.squareup.kotlinpoet.FunSpec 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper.MapMapper 7 | 8 | object CommonDslBuilder : DslBuilder(false) { 9 | 10 | override fun modifyBuildFunction(builder: FunSpec.Builder, message: ProtoMessage) = Unit 11 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/stub/JsStub.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.stub 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMChannel 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMMetadata 5 | import io.github.timortel.kotlin_multiplatform_grpc_lib.util.TimeUnit 6 | 7 | interface JsStub> { 8 | 9 | val channel: KMChannel 10 | val callOptions: KMMetadata 11 | 12 | fun build(channel: KMChannel, callOptions: KMMetadata): S 13 | 14 | fun withDeadlineAfter(duration: Long, unit: TimeUnit): S { 15 | val newMetadata = callOptions.copy() 16 | newMetadata["deadline"] = (duration * unit.toMilliFactor).toString() 17 | 18 | return build(channel, newMetadata) 19 | } 20 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/stub/AndroidJvmKMStub.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.stub 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMChannel 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.util.TimeUnit 5 | import io.grpc.CallOptions 6 | import io.grpc.stub.AbstractStub 7 | 8 | //Additional layer of abstraction for the second generic argument. 9 | interface AndroidJvmKMStub> { 10 | 11 | val channel: KMChannel 12 | val callOptions: CallOptions 13 | 14 | fun build(channel: KMChannel, callOptions: CallOptions): S 15 | 16 | fun withDeadlineAfter(duration: Long, unit: TimeUnit): S { 17 | return build(channel, callOptions.withDeadlineAfter(duration, unit.javaTimeUnit)) 18 | } 19 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/Types.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | 5 | /** 6 | * @property isEnum if this type is a proto enum type 7 | * @property isNullable if this type can be null. 8 | */ 9 | data class Types( 10 | val commonType: ClassName, 11 | val jvmType: ClassName, 12 | val jsType: ClassName, 13 | val iosType: ClassName, 14 | val doDiffer: Boolean, 15 | val isEnum: Boolean, 16 | val isNullable: Boolean, 17 | val protoType: ProtoType 18 | ) 19 | 20 | enum class ProtoType { 21 | DOUBLE, 22 | FLOAT, 23 | INT_32, 24 | INT_64, 25 | BOOL, 26 | STRING, 27 | MAP, 28 | MESSAGE, 29 | ENUM 30 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/platform_file_names.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | 5 | fun getCommonClassName(pkg: String, baseFileNames: List): ClassName = 6 | ClassName(pkg, baseFileNames.map { "KM$it" }) 7 | 8 | @Suppress("DefaultLocale") 9 | fun getJVMClassName( 10 | pkg: String, 11 | protoFileName: String, 12 | useMultipleFiles: Boolean, 13 | baseFileNames: List 14 | ): ClassName { 15 | return if (useMultipleFiles) ClassName(pkg, baseFileNames) 16 | else ClassName(pkg, listOf(protoFileName.capitalize()) + baseFileNames) 17 | } 18 | 19 | fun getJSClassName(pkg: String, baseFileNames: List): ClassName = ClassName(pkg, baseFileNames.map { "JS_$it" }) -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto3_file_dsl_builder.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.dsl.DslBuilder 6 | import java.io.File 7 | 8 | fun writeDSLBuilder(protoFile: ProtoFile, dslBuilder: DslBuilder, outputDir: File) { 9 | val builder = FileSpec 10 | .builder(protoFile.pkg, protoFile.fileNameWithoutExtension + "_dsl_builder") 11 | 12 | dslBuilder.generateDslBuilders(protoFile, builder) 13 | 14 | builder 15 | .build() 16 | .writeTo(outputDir) 17 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMChannel.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | import io.grpc.ManagedChannel 4 | import io.grpc.ManagedChannelBuilder 5 | 6 | actual class KMChannel private constructor(val managedChannel: ManagedChannel) { 7 | actual class Builder(private val impl: ManagedChannelBuilder<*>) { 8 | 9 | actual companion object { 10 | actual fun forAddress( 11 | name: String, 12 | port: Int 13 | ): Builder { 14 | return Builder(ManagedChannelBuilder.forAddress(name, port)) 15 | } 16 | } 17 | 18 | actual fun usePlaintext(): Builder = apply { 19 | impl.usePlaintext() 20 | } 21 | 22 | actual fun build(): KMChannel = KMChannel(impl.build()) 23 | } 24 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/DataType.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | import cocoapods.Protobuf.* 4 | 5 | actual enum class DataType(val nativeValue: UByte) { 6 | DOUBLE(GPBDataTypeBool), 7 | FLOAT(GPBDataTypeFloat), 8 | INT64(GPBDataTypeInt64), 9 | UINT64(GPBDataTypeUInt64), 10 | INT32(GPBDataTypeInt32), 11 | FIXED64(GPBDataTypeFixed64), 12 | FIXED32(GPBDataTypeFixed32), 13 | BOOL(GPBDataTypeBool), 14 | STRING(GPBDataTypeString), 15 | GROUP(GPBDataTypeGroup), 16 | MESSAGE(GPBDataTypeMessage), 17 | BYTES(GPBDataTypeBytes), 18 | UINT32(GPBDataTypeUInt32), 19 | ENUM(GPBDataTypeEnum), 20 | SFIXED32(GPBDataTypeSFixed32), 21 | SFIXED64(GPBDataTypeSFixed64), 22 | SINT32(GPBDataTypeSInt32), 23 | SINT64(GPBDataTypeSFixed64) 24 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/GrpcMultiplatformExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin 2 | 3 | import org.gradle.api.provider.ListProperty 4 | import org.gradle.api.provider.MapProperty 5 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet 6 | import java.io.File 7 | 8 | abstract class GrpcMultiplatformExtension { 9 | 10 | /** 11 | * Maps the output target to the source sets that require it. 12 | */ 13 | abstract val targetSourcesMap: MapProperty> 14 | 15 | abstract val protoSourceFolders: ListProperty 16 | 17 | enum class OutputTarget { 18 | COMMON, 19 | JVM, 20 | JS, 21 | IOS 22 | } 23 | 24 | init { 25 | targetSourcesMap.convention(emptyMap()) 26 | protoSourceFolders.convention(emptyList()) 27 | } 28 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/KMMessage.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.io.CodedOutputStream 4 | import java.nio.ByteBuffer 5 | 6 | actual interface KMMessage { 7 | 8 | val requiredSize: Int 9 | 10 | fun serialize(): ByteArray { 11 | val data = ByteArray(requiredSize) 12 | val stream = com.google.protobuf.CodedOutputStream.newInstance(ByteBuffer.wrap(data)) 13 | serialize(CodedOutputStream(stream)) 14 | 15 | return data 16 | } 17 | 18 | fun serialize(stream: CodedOutputStream) 19 | } 20 | 21 | 22 | val serializeMessage: (KMMessage, CodedOutputStream) -> Unit = { message, stream -> message.serialize(stream) } 23 | val requiredSizeMessage: (KMMessage) -> UInt = { message -> message.requiredSize.toUInt() } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/util/FunctionWrapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.util 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.JSPB 4 | 5 | fun ((JSPB.BinaryWriter, K, L) -> J).wrap(writer: JSPB.BinaryWriter): dynamic { 6 | val f = this 7 | 8 | return { a: K, b: L -> f(writer, a, b) } 9 | } 10 | 11 | fun ((JSPB.BinaryWriter, K, L, J) -> F).wrap(writer: JSPB.BinaryWriter): dynamic { 12 | val f = this 13 | 14 | return { a: K, b: L, c: J -> f(writer, a, b, c) } 15 | } 16 | 17 | fun ((JSPB.BinaryReader) -> K).wrap(reader: JSPB.BinaryReader): dynamic { 18 | val f = this 19 | return { f(reader) } 20 | } 21 | 22 | fun ((JSPB.BinaryReader, K, J) -> L).wrap(reader: JSPB.BinaryReader): dynamic { 23 | val f = this 24 | return { a: K, b: J -> f(reader, a, b) } 25 | } 26 | -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/CommonMapMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.PropertySpec 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 7 | 8 | object CommonMapMessageMethodGenerator : MapMessageMethodGenerator(false) { 9 | 10 | override val modifiers: List = listOf(KModifier.EXPECT) 11 | 12 | override fun modifyMapProperty( 13 | builder: PropertySpec.Builder, 14 | protoMessage: ProtoMessage, 15 | messageAttribute: ProtoMessageAttribute 16 | ) = Unit 17 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/oneof/CommonOneOfMethodAndClassGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.KModifier 5 | import com.squareup.kotlinpoet.PropertySpec 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoOneOf 8 | 9 | object CommonOneOfMethodAndClassGenerator : OneOfMethodAndClassGenerator(false) { 10 | 11 | override val attrs: List = emptyList() 12 | 13 | override fun modifyOneOfProperty( 14 | builder: PropertySpec.Builder, 15 | message: ProtoMessage, 16 | oneOf: ProtoOneOf 17 | ) = Unit 18 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/service/serviceOverrides.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.service 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.FunSpec 5 | import com.squareup.kotlinpoet.KModifier 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.kmTimeUnit 8 | 9 | fun overrideWithDeadlineAfter(builder: TypeSpec.Builder, serviceClass: ClassName) { 10 | builder.addFunction( 11 | FunSpec.builder("withDeadlineAfter") 12 | .addModifiers(KModifier.OVERRIDE) 13 | .addParameter("duration", Long::class) 14 | .addParameter("unit", kmTimeUnit) 15 | .returns(serviceClass) 16 | .addStatement("return super.withDeadlineAfter(duration, unit)") 17 | .build() 18 | ) 19 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMChannel.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | 4 | actual class KMChannel private constructor( 5 | name: String, 6 | port: Int, 7 | usePlainText: Boolean, 8 | val metadata: KMMetadata 9 | ) { 10 | 11 | val connectionString = (if (usePlainText) "http://" else "https://") + "$name:$port" 12 | 13 | actual data class Builder(val name: String, val port: Int) { 14 | 15 | private var usePlainText: Boolean = false 16 | 17 | actual companion object { 18 | actual fun forAddress( 19 | name: String, 20 | port: Int 21 | ): Builder = Builder(name, port) 22 | } 23 | 24 | actual fun usePlaintext(): Builder { 25 | usePlainText = true 26 | return this 27 | } 28 | 29 | actual fun build(): KMChannel = KMChannel(name, port, usePlainText, KMMetadata()) 30 | } 31 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/repeated/CommonRepeatedMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.PropertySpec 5 | import com.squareup.kotlinpoet.TypeName 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 8 | 9 | object CommonRepeatedMessageMethodGenerator : RepeatedMessageMethodGenerator(false) { 10 | 11 | override val attrs: List = emptyList() 12 | 13 | override fun getType(messageAttribute: ProtoMessageAttribute): TypeName = messageAttribute.commonType 14 | 15 | override fun modifyListProperty(builder: PropertySpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) = Unit 16 | 17 | } -------------------------------------------------------------------------------- /example/js/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | } 4 | 5 | version = "unspecified" 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | kotlin { 12 | js(IR) { 13 | useCommonJs() 14 | 15 | browser { 16 | webpackTask { 17 | output.libraryTarget = "commonjs2" 18 | } 19 | } 20 | 21 | binaries.executable() 22 | } 23 | 24 | sourceSets { 25 | val jsMain by getting { 26 | kotlin.srcDir("src/main/kotlin") 27 | resources.srcDir("src/main/resources") 28 | 29 | dependencies { 30 | implementation(project(":common")) 31 | 32 | api(npm("google-protobuf", "^3.19.1")) 33 | api(npm("grpc-web", "^1.3.0")) 34 | api(npm("protobufjs", "^6.11.2")) 35 | } 36 | } 37 | } 38 | } 39 | 40 | afterEvaluate { 41 | rootProject.extensions.configure { 42 | versions.webpackCli.version = "4.10.0" 43 | } 44 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/message/KMMessage.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.message 2 | 3 | import cocoapods.Protobuf.GPBCodedOutputStream 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.io.CodedOutputStream 5 | import platform.Foundation.NSData 6 | import platform.Foundation.NSMutableData 7 | import platform.Foundation.NSStream 8 | import platform.posix.size_t 9 | 10 | /** 11 | * Base specification. 12 | */ 13 | actual interface KMMessage { 14 | 15 | val requiredSize: Int 16 | 17 | fun serialize(): NSData { 18 | val data = NSMutableData().apply { setLength(requiredSize.toULong()) } 19 | val stream = GPBCodedOutputStream(data) 20 | serialize(CodedOutputStream(stream)) 21 | 22 | return data 23 | } 24 | 25 | fun serialize(stream: CodedOutputStream) 26 | } 27 | 28 | 29 | val serializeMessage: (KMMessage, CodedOutputStream) -> Unit = { message, stream -> message.serialize(stream) } 30 | val requiredSizeMessage: (KMMessage) -> UInt = { message -> message.requiredSize.toUInt() } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/IosJvmMapMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.PropertySpec 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 8 | 9 | object IosJvmMapMessageMethodGenerator : MapMessageMethodGenerator(true) { 10 | override val modifiers: List = listOf(KModifier.ACTUAL) 11 | 12 | override fun modifyMapProperty( 13 | builder: PropertySpec.Builder, 14 | protoMessage: ProtoMessage, 15 | messageAttribute: ProtoMessageAttribute 16 | ) { 17 | builder.initializer(Const.Message.Attribute.propertyName(protoMessage, messageAttribute)) 18 | } 19 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto3_ios_file_writer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.dsl.IosJvmDslBuilder 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file.IOSProtoFileWriter 6 | import java.io.File 7 | 8 | fun writeIOSFiles(protoFile: ProtoFile, iosOutputDir: File) { 9 | IOSProtoFileWriter(protoFile).writeFile(iosOutputDir) 10 | 11 | //JVM helper 12 | // FileSpec 13 | // .builder(protoFile.pkg, protoFile.fileNameWithoutExtension + "_jvm_helper") 14 | // .apply { 15 | // JvmCommonFunctionGenerator(this).generateCommonGetter(protoFile.messages) 16 | // } 17 | // .build() 18 | // .writeTo(jvmOutputDir) 19 | 20 | writeDSLBuilder(protoFile, IosJvmDslBuilder, iosOutputDir) 21 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto3_jvm_file_writer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file.JvmProtoFileWriter 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.dsl.IosJvmDslBuilder 6 | import java.io.File 7 | 8 | fun writeJvmFiles(protoFile: ProtoFile, jvmOutputDir: File) { 9 | JvmProtoFileWriter(protoFile).writeFile(jvmOutputDir) 10 | 11 | //JVM helper 12 | // FileSpec 13 | // .builder(protoFile.pkg, protoFile.fileNameWithoutExtension + "_jvm_helper") 14 | // .apply { 15 | // JvmCommonFunctionGenerator(this).generateCommonGetter(protoFile.messages) 16 | // } 17 | // .build() 18 | // .writeTo(jvmOutputDir) 19 | 20 | writeDSLBuilder(protoFile, IosJvmDslBuilder, jvmOutputDir) 21 | } -------------------------------------------------------------------------------- /grpc-mp-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/EqTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.kmEmptyMessage 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.kmSimpleMessage 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | import kotlin.test.assertNotEquals 8 | 9 | class EqTest { 10 | 11 | @Test 12 | fun emptyMessageEquals() { 13 | assertEquals(kmEmptyMessage { }, kmEmptyMessage { }) 14 | } 15 | 16 | @Test 17 | fun scalarMessageEquals() { 18 | assertEquals(createScalarMessage(), createScalarMessage()) 19 | } 20 | 21 | @Test 22 | fun scalarMessageDiffer() { 23 | val one = kmSimpleMessage { 24 | field1 = "Foo" 25 | } 26 | 27 | val two = kmSimpleMessage { 28 | field1 = "Bar" 29 | } 30 | 31 | assertNotEquals(one, two) 32 | } 33 | 34 | @Test 35 | fun messageWithAllTypesEquals() { 36 | assertEquals(createMessageWithAllTypes(), createMessageWithAllTypes()) 37 | } 38 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/rpc/GrpcWebClientBase.kt: -------------------------------------------------------------------------------- 1 | @file:JsModule("grpc-web") 2 | 3 | package io.github.timortel.kotlin_multiplatform_grpc_lib.rpc 4 | 5 | external class GrpcWebClientBase(options: ClientOptions) { 6 | fun rpcCall( 7 | method: String, 8 | requestMessage: dynamic, 9 | metadata: dynamic, 10 | methodDescriptor: dynamic, 11 | callback: dynamic 12 | ) 13 | 14 | fun serverStreaming( 15 | method: String, 16 | requestMessage: dynamic, 17 | metadata: dynamic, 18 | methodDescriptor: dynamic 19 | ): dynamic 20 | } 21 | 22 | external class ClientOptions( 23 | suppressCorsPreflight: dynamic, 24 | withCredentials: dynamic, 25 | unaryInterceptors: dynamic, 26 | streamInterceptors: dynamic, 27 | format: dynamic, 28 | workerScope: dynamic, 29 | useFetchDownloadStreams: dynamic 30 | ) { 31 | var format: dynamic 32 | } 33 | 34 | external class MethodDescriptor( 35 | name: String, 36 | methodType: String, 37 | requestType: dynamic, 38 | responseType: dynamic, 39 | requestSerializeFn: dynamic, 40 | responseDeserializeFn: dynamic 41 | ) -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/scalar/CommonScalarMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar 2 | 3 | import com.squareup.kotlinpoet.FunSpec 4 | import com.squareup.kotlinpoet.KModifier 5 | import com.squareup.kotlinpoet.PropertySpec 6 | import com.squareup.kotlinpoet.TypeName 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 9 | 10 | object CommonScalarMessageMethodGenerator : ScalarMessageMethodGenerator(false) { 11 | 12 | override val attrs: List = emptyList() 13 | 14 | override fun getTypeForAttribute(protoMessageAttribute: ProtoMessageAttribute): TypeName = protoMessageAttribute.commonType 15 | 16 | override fun modifyProperty(builder: PropertySpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) = Unit 17 | 18 | override fun modifyHasFunction(builder: FunSpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) = Unit 19 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/repeated/IosJvmRepeatedMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.PropertySpec 5 | import com.squareup.kotlinpoet.TypeName 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 9 | 10 | object IosJvmRepeatedMessageMethodGenerator : RepeatedMessageMethodGenerator(true) { 11 | 12 | override val attrs: List = listOf(KModifier.ACTUAL) 13 | 14 | override fun getType(messageAttribute: ProtoMessageAttribute): TypeName = messageAttribute.commonType 15 | 16 | override fun modifyListProperty(builder: PropertySpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) { 17 | builder.initializer("%N", Const.Message.Attribute.propertyName(message, attr)) 18 | } 19 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/message_tree/Proto3MessageTreeBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.message_tree 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.anltr.Proto3BaseVisitor 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.anltr.Proto3Parser 5 | 6 | class Proto3MessageTreeBuilder : Proto3BaseVisitor>() { 7 | 8 | override fun visitFile(ctx: Proto3Parser.FileContext): List { 9 | return ctx.message().map { visitMessage(it, null) }.flatten() + 10 | ctx.proto_enum().map { protoEnum -> EnumNode(protoEnum.enumName.text, null) } 11 | } 12 | 13 | private fun visitMessage(ctx: Proto3Parser.MessageContext, parentMessageNode: MessageNode?): List { 14 | val children = mutableListOf() 15 | val newMessageNode = MessageNode(ctx.messageName.text, children, parentMessageNode) 16 | 17 | children.addAll(ctx.message().map { visitMessage(it, newMessageNode) }.flatten()) 18 | children.addAll(ctx.proto_enum().map { protoEnum -> EnumNode(protoEnum.enumName.text, newMessageNode) }) 19 | 20 | return listOf(newMessageNode) 21 | } 22 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/stub/IOSKMStub.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.stub 2 | 3 | import cocoapods.GRPCClient.GRPCCallOptions 4 | import cocoapods.GRPCClient.GRPCMutableCallOptions 5 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMChannel 6 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMMetadata 7 | import io.github.timortel.kotlin_multiplatform_grpc_lib.util.TimeUnit 8 | 9 | interface IOSKMStub> { 10 | 11 | val channel: KMChannel 12 | val callOptions: GRPCCallOptions 13 | 14 | fun build(channel: KMChannel, callOptions: GRPCCallOptions): S 15 | 16 | fun withMetadata(metadata: KMMetadata): S { 17 | val mutableOptions = callOptions.mutableCopy() as GRPCMutableCallOptions 18 | mutableOptions.setInitialMetadata(metadata.metadataMap.toMap()) 19 | 20 | return build(channel, mutableOptions) 21 | } 22 | 23 | fun withDeadlineAfter(duration: Long, unit: TimeUnit): S { 24 | val mutableOptions = callOptions.mutableCopy() as GRPCMutableCallOptions 25 | 26 | val millis = (duration * unit.toMilliFactor).toDouble() 27 | val seconds = millis / 1000.0 28 | 29 | mutableOptions.setTimeout(seconds) 30 | 31 | return build(channel, mutableOptions) 32 | } 33 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoMessage.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.getJVMClassName 5 | import java.util.* 6 | 7 | data class ProtoMessage( 8 | val pkg: String, 9 | val name: String, 10 | val attributes: List, 11 | val oneOfs: List, 12 | val enums: List, 13 | val parent: ProtoMessage?, 14 | val children: List, 15 | val protoFileName: String, 16 | val javaUseMultipleFiles: Boolean, 17 | ) { 18 | val capitalizedName = name.capitalize(Locale.ROOT) 19 | 20 | val commonName = "KM${capitalizedName}" 21 | 22 | val commonType: ClassName = parent?.commonType?.nestedClass(commonName) ?: ClassName(pkg, commonName) 23 | val jsType: ClassName = 24 | parent?.jsType?.nestedClass("JS_${name.capitalize()}") ?: ClassName(pkg, "JS_${name.capitalize()}") 25 | val jvmType: ClassName = parent?.jvmType?.nestedClass(name) ?: getJVMClassName(pkg, protoFileName, javaUseMultipleFiles, listOf(name)) 26 | val iosType = commonType 27 | 28 | override fun toString(): String = name 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/dsl/IosJvmDslBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.dsl 2 | 3 | import com.squareup.kotlinpoet.FunSpec 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 6 | 7 | object IosJvmDslBuilder : DslBuilder(true) { 8 | 9 | override fun modifyBuildFunction(builder: FunSpec.Builder, message: ProtoMessage) { 10 | builder.apply { 11 | addCode("return %T(", message.iosType) 12 | message.attributes.filter { !it.isOneOfAttribute }.forEach { attr -> 13 | val propertyName = Const.Message.Attribute.propertyName(message, attr) 14 | 15 | addCode("\n%N = %N ?: ", propertyName, propertyName) 16 | addCode(attr.commonDefaultValue(mutable = false, useEmptyMessage = false)) 17 | addCode(",") 18 | } 19 | message.oneOfs.forEach { oneOf -> 20 | addCode( 21 | "\n%N = %N,", 22 | Const.Message.OneOf.propertyName(message, oneOf), 23 | Const.DSL.OneOf.propertyName(message, oneOf) 24 | ) 25 | } 26 | addCode("\n)") 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto3_service_writer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.GrpcMultiplatformExtension 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.service.CommonServiceWriter 6 | import java.io.File 7 | 8 | fun writeServiceFile( 9 | protoFile: ProtoFile, 10 | generateTarget: Map, 11 | commonOutputFolder: File, 12 | jvmOutputFolder: File, 13 | jsOutputFolder: File, 14 | iosOutputFolder: File 15 | ) { 16 | protoFile.services.forEach { service -> 17 | CommonServiceWriter.writeServiceStub(protoFile, service, commonOutputFolder) 18 | 19 | if (generateTarget[GrpcMultiplatformExtension.OutputTarget.JS] == true) 20 | writeJsServiceFile(protoFile, service, jsOutputFolder) 21 | if (generateTarget[GrpcMultiplatformExtension.OutputTarget.JVM] == true) 22 | writeJvmServiceFile(protoFile, service, jvmOutputFolder) 23 | if (generateTarget[GrpcMultiplatformExtension.OutputTarget.IOS] == true) { 24 | writeIOSServiceFile(protoFile, service, iosOutputFolder) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/JSPBMap.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | class JSPBMap(private val impl: dynamic) : AbstractMap() { 4 | 5 | override val size: Int 6 | get() = impl.getLength() as Int 7 | 8 | override fun containsKey(key: K): Boolean = impl.has(key) as Boolean 9 | 10 | override fun containsValue(value: V): Boolean = values.any { it == value } 11 | 12 | override fun get(key: K): V? { 13 | val result = impl.get(key) 14 | if (result == undefined) return null 15 | return result as V? 16 | } 17 | 18 | override fun isEmpty(): Boolean = size == 0 19 | 20 | override val entries: Set> by lazy { 21 | val internal = impl.entries() 22 | 23 | val set = mutableSetOf() 24 | while (true) { 25 | val next = internal.next() 26 | if (next.done as Boolean) break 27 | 28 | set += ArrayEntry(next.value as Array<*>) 29 | } 30 | 31 | set 32 | } 33 | 34 | override val keys: Set 35 | get() = (impl.keys() as Iterable).toSet() 36 | 37 | override val values: Collection 38 | get() = (impl.values() as Iterable).toSet() 39 | 40 | @Suppress("UNCHECKED_CAST") 41 | private inner class ArrayEntry(entry: Array<*>) : Map.Entry { 42 | override val key: K = entry[0] as K 43 | override val value: V = entry[1] as V 44 | } 45 | } -------------------------------------------------------------------------------- /example/jvm/src/main/kotlin/io/github/timortel/grpc_multiplatform/example/jvm/Server.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.grpc_multiplatform.example.jvm 2 | 3 | import io.grpc.ServerBuilder 4 | import io.github.timortel.grpc_multiplatform.example.HelloServiceGrpcKt; 5 | import io.github.timortel.grpc_multiplatform.example.HelloRequest; 6 | import io.github.timortel.grpc_multiplatform.example.Response; 7 | import kotlinx.coroutines.delay 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.flow 10 | 11 | fun main() { 12 | ServerBuilder.forPort(17600) 13 | .addService(object : HelloServiceGrpcKt.HelloServiceCoroutineImplBase() { 14 | override suspend fun sayHello(request: HelloRequest): Response { 15 | return Response.newBuilder() 16 | .setResponse("Response from server to ${request.greeting}") 17 | .build() 18 | } 19 | 20 | override fun sayHelloMultipleTimes(request: HelloRequest): Flow { 21 | return flow { 22 | for (i in 0 until 5) { 23 | emit( 24 | Response.newBuilder() 25 | .setResponse("Response from server to ${request.greeting}; $i") 26 | .build() 27 | ) 28 | delay(1000) 29 | } 30 | } 31 | } 32 | }) 33 | .build() 34 | .start() 35 | .awaitTermination() 36 | } -------------------------------------------------------------------------------- /grpc-mp-test/src/jvmTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/JVMSerializationTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.MessageDeserializer 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.* 6 | 7 | class JVMSerializationTest : SerializationTest() { 8 | 9 | private fun serializeImpl(msg: T, deserializer: MessageDeserializer): T { 10 | return deserializer.deserialize(msg.serialize()) 11 | } 12 | 13 | override fun serialize(message: KMLongMessage): KMLongMessage = 14 | serializeImpl(message, KMLongMessage.Companion) 15 | 16 | override fun serialize(message: KMRepeatedLongMessage): KMRepeatedLongMessage = 17 | serializeImpl(message, KMRepeatedLongMessage.Companion) 18 | 19 | override fun serialize(message: KMScalarTypes): KMScalarTypes = serializeImpl(message, KMScalarTypes.Companion) 20 | 21 | override fun serialize(message: KMMessageWithSubMessage): KMMessageWithSubMessage = 22 | serializeImpl(message, KMMessageWithSubMessage.Companion) 23 | 24 | override fun serialize(message: KMMessageWithEverything): KMMessageWithEverything = 25 | serializeImpl(message, KMMessageWithEverything.Companion) 26 | 27 | override fun serialize(message: KMOneOfMessage): KMOneOfMessage = serializeImpl(message, KMOneOfMessage.Companion) 28 | } -------------------------------------------------------------------------------- /grpc-mp-test/src/iosTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/IOSSerializationTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.MessageDeserializer 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.* 6 | import platform.Foundation.NSData 7 | 8 | class IOSSerializationTest : SerializationTest() { 9 | 10 | private fun serializeImpl(msg: T, deserializer: MessageDeserializer): T { 11 | return deserializer.deserialize(msg.serialize()) 12 | } 13 | 14 | override fun serialize(message: KMLongMessage): KMLongMessage = 15 | serializeImpl(message, KMLongMessage.Companion) 16 | 17 | override fun serialize(message: KMRepeatedLongMessage): KMRepeatedLongMessage = 18 | serializeImpl(message, KMRepeatedLongMessage.Companion) 19 | 20 | override fun serialize(message: KMScalarTypes): KMScalarTypes = serializeImpl(message, KMScalarTypes.Companion) 21 | 22 | override fun serialize(message: KMMessageWithSubMessage): KMMessageWithSubMessage = 23 | serializeImpl(message, KMMessageWithSubMessage.Companion) 24 | 25 | override fun serialize(message: KMMessageWithEverything): KMMessageWithEverything = 26 | serializeImpl(message, KMMessageWithEverything.Companion) 27 | 28 | override fun serialize(message: KMOneOfMessage): KMOneOfMessage = serializeImpl(message, KMOneOfMessage.Companion) 29 | } -------------------------------------------------------------------------------- /example/common/src/commonMain/kotlin/io/github/timortel/grpc_multiplatform/example/common/GreetingLogic.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.grpc_multiplatform.example.common 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMChannel 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.util.TimeUnit 5 | import io.github.timortel.grpc_multiplatform.example.KMHelloServiceStub 6 | import io.github.timortel.grpc_multiplatform.example.KMResponse 7 | import io.github.timortel.grpc_multiplatform.example.kmHelloRequest 8 | 9 | suspend fun performGreeting(message: String, port: Int): String { 10 | val channel = KMChannel.Builder 11 | .forAddress("localhost", port) 12 | .usePlaintext() 13 | .build() 14 | 15 | val stub = KMHelloServiceStub(channel) 16 | 17 | val request = kmHelloRequest { 18 | greeting = message 19 | } 20 | 21 | val response: KMResponse = stub 22 | .withDeadlineAfter(10, TimeUnit.SECONDS) 23 | .sayHello(request) 24 | 25 | return response.response 26 | } 27 | 28 | suspend fun performMultipleGreetings(message: String, port: Int) { 29 | val channel = KMChannel.Builder 30 | .forAddress("localhost", port) 31 | .usePlaintext() 32 | .build() 33 | 34 | val stub = KMHelloServiceStub(channel) 35 | 36 | val request = kmHelloRequest { 37 | greeting = message 38 | } 39 | 40 | stub 41 | .withDeadlineAfter(10, TimeUnit.SECONDS) 42 | .sayHelloMultipleTimes(request) 43 | .collect { 44 | println("Received: ${it.response}") 45 | } 46 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto3_file_writer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.GrpcMultiplatformExtension 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.dsl.CommonDslBuilder 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file.CommonProtoFileWriter 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 7 | import java.io.File 8 | 9 | /** 10 | * Function called to add all proto files, common, js and jvm 11 | */ 12 | fun writeProtoFile( 13 | protoFile: ProtoFile, 14 | generateTarget: Map, 15 | commonOutputDir: File, 16 | jvmOutputDir: File, 17 | jsOutputDir: File, 18 | iosOutputDir: File 19 | ) { 20 | CommonProtoFileWriter(protoFile).writeFile(commonOutputDir) 21 | 22 | if (generateTarget[GrpcMultiplatformExtension.OutputTarget.JS] == true) writeJsFiles(protoFile, jsOutputDir) 23 | if (generateTarget[GrpcMultiplatformExtension.OutputTarget.JVM] == true) writeJvmFiles(protoFile, jvmOutputDir) 24 | if (generateTarget[GrpcMultiplatformExtension.OutputTarget.IOS] == true) writeIOSFiles(protoFile, iosOutputDir) 25 | writeDSLBuilder(protoFile, CommonDslBuilder, commonOutputDir) 26 | } 27 | 28 | const val unrecognizedEnumField = "UNRECOGNIZED" -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMChannel.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | import cocoapods.GRPCClient.* 4 | 5 | actual class KMChannel(private val name: String, private val port: Int, private val usePlaintext: Boolean) { 6 | 7 | fun buildRequestOptions(path: String) = GRPCRequestOptions("$name:$port", path, safety = GRPCCallSafetyDefault) 8 | 9 | /** 10 | * Applies configuration of the channel to the given call options. 11 | * If any mutations are performed, a new copy of call options is returned. The original call options 12 | * are left unmodified. 13 | */ 14 | fun applyToCallOptions(callOptions: GRPCCallOptions): GRPCCallOptions { 15 | return if (usePlaintext) { 16 | val newCallOptions = callOptions.mutableCopy() as GRPCMutableCallOptions 17 | newCallOptions.setTransport(GRPCDefaultTransportImplList_.core_insecure) 18 | newCallOptions 19 | } else callOptions 20 | } 21 | 22 | actual class Builder(private val name: String, private val port: Int) { 23 | 24 | private var usePlaintext = false 25 | 26 | actual companion object { 27 | actual fun forAddress( 28 | name: String, 29 | port: Int 30 | ): Builder = Builder(name, port) 31 | } 32 | 33 | actual fun usePlaintext(): Builder { 34 | usePlaintext = true 35 | return this 36 | } 37 | 38 | actual fun build(): KMChannel = KMChannel(name, port, usePlaintext) 39 | } 40 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/commonMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/KMCode.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib 2 | 3 | /** 4 | * Copied from [the Java GRPC Status implementation](https://github.com/grpc/grpc-java/blob/master/api/src/main/java/io/grpc/Status.java). 5 | * Also see this link if you want to know further details on what each code means. 6 | */ 7 | enum class KMCode(val value: Int) { 8 | OK(0), 9 | CANCELLED(1), 10 | UNKNOWN(2), 11 | INVALID_ARGUMENT(3), 12 | DEADLINE_EXCEEDED(4), 13 | NOT_FOUND(5), 14 | ALREADY_EXISTS(6), 15 | PERMISSION_DENIED(7), 16 | RESOURCE_EXHAUSTED(8), 17 | FAILED_PRECONDITION(9), 18 | ABORTED(10), 19 | OUT_OF_RANGE(11), 20 | UNIMPLEMENTED(12), 21 | INTERNAL(13), 22 | UNAVAILABLE(14), 23 | DATA_LOSS(15), 24 | UNAUTHENTICATED(16); 25 | 26 | companion object { 27 | fun getCodeForValue(value: Int): KMCode = when (value) { 28 | 0 -> OK 29 | 1 -> CANCELLED 30 | 2 -> UNKNOWN 31 | 3 -> INVALID_ARGUMENT 32 | 4 -> DEADLINE_EXCEEDED 33 | 5 -> NOT_FOUND 34 | 6 -> ALREADY_EXISTS 35 | 7 -> PERMISSION_DENIED 36 | 8 -> RESOURCE_EXHAUSTED 37 | 9 -> FAILED_PRECONDITION 38 | 10 -> ABORTED 39 | 11 -> OUT_OF_RANGE 40 | 12 -> UNIMPLEMENTED 41 | 13 -> INTERNAL 42 | 14 -> UNAVAILABLE 43 | 15 -> DATA_LOSS 44 | 16 -> UNAUTHENTICATED 45 | else -> throw IllegalArgumentException("Value=$value not known.") 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /grpc-mp-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/defaults.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.KMSimpleEnum 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.kmMessageWithEverything 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.kmScalarTypes 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.kmSimpleMessage 7 | 8 | fun createScalarMessage() = kmScalarTypes { 9 | field1 = "Test" 10 | field2 = true 11 | field3 = 12 12 | field4 = 25L 13 | field5 = 3f 14 | field6 = 7.0 15 | } 16 | 17 | fun createMessageWithAllTypes() = kmMessageWithEverything { 18 | field1 = "Test" 19 | field2 = true 20 | field3 = 12 21 | field4 = 25L 22 | field5 = 3f 23 | field6 = 7.0 24 | field7 = KMSimpleEnum.ONE 25 | field8 = kmSimpleMessage { field1 = "Foo" } 26 | 27 | field9List += listOf("Foo", "Bar", "Baz") 28 | field10List += listOf(true, false, true, true) 29 | field11List += listOf(1, 2, 3, 4, -12, 1341) 30 | field12List += listOf(12L, 23424L, 10312313L, -123131L) 31 | field13List += listOf(-1f, 2f, 2.5f, -0.5f) 32 | field14List += listOf(-0.5, 15.0) 33 | field15List += listOf(KMSimpleEnum.ZERO, KMSimpleEnum.ZERO, KMSimpleEnum.ONE, KMSimpleEnum.TWO) 34 | 35 | field16Map += mapOf("foo" to 1, "bar" to -13, "baz" to 112) 36 | field17Map += mapOf(1 to kmSimpleMessage { field1 = "Foo" }, 13 to kmSimpleMessage { field1 = "Baz" }) 37 | field18Map += mapOf(-15 to KMSimpleEnum.ONE, 23 to KMSimpleEnum.TWO) 38 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/wireformat.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.DataType 4 | 5 | /** 6 | * From: https://github.com/protocolbuffers/protobuf/blob/fcd3b9a85ef36e46643dc30176cea1a7ad62e02b/objectivec/GPBWireFormat.m 7 | */ 8 | 9 | private const val TYPE_BITS = 3 10 | private const val TYPE_MASK = 7 11 | 12 | fun wireFormatMakeTag(fieldNumber: Int, wireType: WireFormat): Int = (fieldNumber shl TYPE_BITS) or wireType.value 13 | 14 | fun wireFormatGetTagWireType(tag: Int): Int = tag and TYPE_MASK 15 | 16 | fun wireFormatGetTagFieldNumber(tag: Int): Int = tag ushr TYPE_BITS 17 | 18 | fun wireFormatIsValidTag(tag: Int): Boolean { 19 | val formatBits = tag and TYPE_MASK 20 | return formatBits <= 5 21 | } 22 | 23 | fun wireFormatForType(dataType: DataType, isPacked: Boolean): WireFormat { 24 | if (isPacked) return WireFormat.LENGTH_DELIMITED 25 | 26 | return when (dataType) { 27 | DataType.BOOL, DataType.INT32, DataType.INT64, DataType.SINT32, DataType.SINT64, DataType.UINT32, DataType.UINT64, DataType.ENUM -> WireFormat.VARINT 28 | DataType.FIXED32, DataType.SFIXED32, DataType.FLOAT -> WireFormat.FIXED32 29 | DataType.FIXED64, DataType.SFIXED64, DataType.DOUBLE -> WireFormat.FIXED64 30 | DataType.BYTES, DataType.STRING, DataType.MESSAGE -> WireFormat.LENGTH_DELIMITED 31 | DataType.GROUP -> WireFormat.START_GROUP 32 | else -> throw IllegalArgumentException("DataType $dataType unknown.") 33 | } 34 | } 35 | 36 | enum class WireFormat(val value: Int) { 37 | VARINT(0), 38 | FIXED64(1), 39 | LENGTH_DELIMITED(2), 40 | START_GROUP(3), 41 | END_GROUP(4), 42 | FIXED32(5) 43 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/mapper/JsToCommonMapMapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.Types 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.MapType 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 9 | 10 | object JsToCommonMapMapper : MapMapper() { 11 | 12 | override fun handleCreatedMap( 13 | builderVariable: String, 14 | mapToPutVariable: CodeBlock, 15 | message: ProtoMessage, 16 | attribute: ProtoMessageAttribute, 17 | mapType: MapType 18 | ): CodeBlock { 19 | return CodeBlock.builder() 20 | .add(mapToPutVariable) 21 | .build() 22 | } 23 | 24 | override fun mapVariable( 25 | variableName: String, 26 | message: ProtoMessage, 27 | attribute: ProtoMessageAttribute, 28 | types: Types 29 | ): CodeBlock { 30 | return if (types.isEnum) { 31 | CodeBlock.of( 32 | "%T.%N(%N)", 33 | types.commonType, 34 | Const.Enum.getEnumForNumFunctionName, 35 | variableName 36 | ) 37 | } else { 38 | CodeBlock.of("%T(%T(%N))", types.commonType, types.jsType, variableName) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/mapper/JvmToCommonMapMapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.Types 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.MapType 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 9 | 10 | object JvmToCommonMapMapper : MapMapper() { 11 | 12 | override fun handleCreatedMap( 13 | builderVariable: String, 14 | mapToPutVariable: CodeBlock, 15 | message: ProtoMessage, 16 | attribute: ProtoMessageAttribute, 17 | mapType: MapType 18 | ): CodeBlock { 19 | return CodeBlock.builder() 20 | .add(mapToPutVariable) 21 | .build() 22 | } 23 | 24 | override fun mapVariable( 25 | variableName: String, 26 | message: ProtoMessage, 27 | attribute: ProtoMessageAttribute, 28 | types: Types 29 | ): CodeBlock { 30 | return if (types.isEnum) { 31 | CodeBlock.of( 32 | "%T.%N(%N.number)", 33 | types.commonType, 34 | Const.Enum.getEnumForNumFunctionName, 35 | variableName 36 | ) 37 | } else { 38 | CodeBlock.of("%M(%N)", Const.Message.CommonFunction.JVM.commonFunction(types.jvmType), variableName) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /grpc-mp-test/src/jsTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/JSSerializationTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.JSImpl 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 5 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.MessageDeserializer 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.* 7 | import kotlin.test.Test 8 | import kotlin.test.assertEquals 9 | 10 | class JSSerializationTest : SerializationTest() { 11 | 12 | private fun serializeImpl(msg: T, deserializer: MessageDeserializer, creator: (T) -> J): J { 13 | return creator(deserializer.deserializeBinary(msg.serializeBinary())) 14 | } 15 | 16 | override fun serialize(message: KMLongMessage): KMLongMessage = 17 | serializeImpl(message.jsImpl, JS_LongMessage.Companion, ::KMLongMessage) 18 | 19 | override fun serialize(message: KMRepeatedLongMessage): KMRepeatedLongMessage = 20 | serializeImpl(message.jsImpl, JS_RepeatedLongMessage.Companion, ::KMRepeatedLongMessage) 21 | 22 | override fun serialize(message: KMScalarTypes): KMScalarTypes = 23 | serializeImpl(message.jsImpl, JS_ScalarTypes.Companion, ::KMScalarTypes) 24 | 25 | override fun serialize(message: KMMessageWithSubMessage): KMMessageWithSubMessage = 26 | serializeImpl(message.jsImpl, JS_MessageWithSubMessage.Companion, ::KMMessageWithSubMessage) 27 | 28 | override fun serialize(message: KMMessageWithEverything): KMMessageWithEverything = 29 | serializeImpl(message.jsImpl, JS_MessageWithEverything.Companion, ::KMMessageWithEverything) 30 | 31 | override fun serialize(message: KMOneOfMessage): KMOneOfMessage = 32 | serializeImpl(message.jsImpl, JS_OneOfMessage.Companion, ::KMOneOfMessage) 33 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/libClasses.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.MemberName 5 | 6 | val kmMetadata = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib", "KMMetadata") 7 | val kmStub = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib.stub", "KMStub") 8 | val kmChannel = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib", "KMChannel") 9 | val kmAndroidJVMStub = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib.stub", "AndroidJvmKMStub") 10 | val kmTimeUnit = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib.util", "TimeUnit") 11 | 12 | val JSImpl = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib.message", "JSImpl") 13 | val MessageDeserializer = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib.message", "MessageDeserializer") 14 | 15 | val kmMessage = ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib.message", "KMMessage") 16 | 17 | val writeKMMessage = MemberName(PACKAGE_IO, "writeKMMessage") 18 | val readKMMessage = MemberName(PACKAGE_IO, "readKMMessage") 19 | val computeMapSize = MemberName(PACKAGE_IO, "computeMapSize") 20 | val computeMessageSize = MemberName(PACKAGE_IO, "computeMessageSize") 21 | 22 | val CodedOutputStream = ClassName(PACKAGE_IO, "CodedOutputStream") 23 | val CodedInputStream = ClassName(PACKAGE_IO, "CodedInputStream") 24 | 25 | val writeMap = MemberName(PACKAGE_IO, "writeMap") 26 | val readMapEntry = MemberName(PACKAGE_IO, "readMapEntry") 27 | 28 | val iosUnaryCallImplementation = MemberName("io.github.timortel.kotlin_multiplatform_grpc_lib.rpc", "unaryCallImplementation") 29 | val iosServerSideStreamingCallImplementation = MemberName("io.github.timortel.kotlin_multiplatform_grpc_lib.rpc", "serverSideStreamingCallImplementation") -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/mapper/CommonToJvmMapMapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.Types 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.MapType 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 9 | 10 | object CommonToJvmMapMapper : MapMapper() { 11 | 12 | override fun handleCreatedMap( 13 | builderVariable: String, 14 | mapToPutVariable: CodeBlock, 15 | message: ProtoMessage, 16 | attribute: ProtoMessageAttribute, 17 | mapType: MapType 18 | ): CodeBlock { 19 | return CodeBlock 20 | .builder() 21 | .add( 22 | "%N.%N(", 23 | builderVariable, 24 | Const.Message.Attribute.Map.JVM.putAllFunctionName(attribute) 25 | ) 26 | .add(mapToPutVariable) 27 | .add(")\n") 28 | .build() 29 | } 30 | 31 | override fun mapVariable( 32 | variableName: String, 33 | message: ProtoMessage, 34 | attribute: ProtoMessageAttribute, 35 | types: Types 36 | ): CodeBlock { 37 | return if (types.isEnum) { 38 | CodeBlock.of("%T.forNumber(%N.value)", types.jvmType, variableName) 39 | } else { 40 | CodeBlock.of("%N.%N", variableName, Const.Message.Constructor.JVM.PARAM_IMPL) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/CodedInputStream.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.DataType 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 5 | 6 | expect class CodedInputStream { 7 | 8 | var recursionDepth: Int 9 | 10 | val bytesUntilLimit: Int 11 | 12 | val isAtEnd: Boolean 13 | 14 | fun readTag(): Int 15 | 16 | @Throws(ParseException::class) 17 | fun checkLastTagWas(value: Int) 18 | 19 | fun getLastTag(): Int 20 | 21 | fun skipField(tag: Int): Boolean 22 | 23 | fun skipMessage() 24 | 25 | fun readDouble(): Double 26 | 27 | fun readFloat(): Float 28 | 29 | fun readUInt64(): ULong 30 | 31 | fun readInt64(): Long 32 | 33 | fun readInt32(): Int 34 | 35 | fun readFixed32(): Int 36 | 37 | fun readBool(): Boolean 38 | 39 | fun readString(): String 40 | 41 | fun readKMMessage(messageFactory: (CodedInputStream) -> M): M 42 | 43 | fun readMapEntry( 44 | map: MutableMap, 45 | keyDataType: DataType, 46 | valueDataType: DataType, 47 | defaultKey: K?, 48 | defaultValue: V?, 49 | readKey: CodedInputStream.() -> K, 50 | readValue: CodedInputStream.() -> V 51 | ) 52 | 53 | fun readBytes(): ByteArray 54 | 55 | fun readByteArray(): ByteArray 56 | 57 | fun readUInt32(): UInt 58 | 59 | fun readEnum(): Int 60 | 61 | fun readSFixed32(): Int 62 | 63 | fun readSFixed64(): Long 64 | 65 | fun readSInt32(): Int 66 | 67 | fun readSInt64(): Long 68 | 69 | fun readRawVarint32(): Int 70 | 71 | fun readRawVarint64(): Long 72 | 73 | fun readRawByte(): Byte 74 | 75 | fun pushLimit(newLimit: Int): Int 76 | 77 | fun popLimit(oldLimit: Int) 78 | 79 | fun setSizeLimit(newLimit: Int): Int 80 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/common/CommonFunctionGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.common 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import com.squareup.kotlinpoet.FunSpec 5 | import com.squareup.kotlinpoet.TypeName 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 8 | 9 | abstract class CommonFunctionGenerator { 10 | 11 | /** 12 | * Recursively adds all common getters. Common getters transform the native values to common values. 13 | */ 14 | fun generateCommonGetter(messages: List) { 15 | messages.forEach { message -> 16 | //Common property, that converts this JS type to a common type 17 | addFunction( 18 | FunSpec 19 | .builder(Const.Message.CommonFunction.NAME) 20 | .addParameter( 21 | Const.Message.CommonFunction.PARAMETER_NATIVE, 22 | getNativePlatformType(message) 23 | ) 24 | .addCode(getGetter(message)) 25 | .returns(message.commonType) 26 | .build() 27 | ) 28 | 29 | generateCommonGetter(message.children) 30 | } 31 | } 32 | 33 | private fun getGetter(message: ProtoMessage): CodeBlock = 34 | CodeBlock.builder() 35 | .add("return·%T(%N)", message.commonType, Const.Message.CommonFunction.PARAMETER_NATIVE) 36 | .build() 37 | 38 | protected abstract fun addFunction(funSpec: FunSpec) 39 | 40 | /** 41 | * @return the type of the message for the native type, so for JVM returns message.jvmType 42 | */ 43 | protected abstract fun getNativePlatformType(message: ProtoMessage): TypeName 44 | 45 | } -------------------------------------------------------------------------------- /example/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.GenerateMultiplatformSourcesTask 2 | 3 | val libVersion = "0.3.1" 4 | 5 | plugins { 6 | kotlin("multiplatform") 7 | id("io.github.timortel.kotlin-multiplatform-grpc-plugin") version "0.3.1" 8 | } 9 | 10 | group = "io.github.timortel.grpc_multiplaform.example.common" 11 | version = "1.0-SNAPSHOT" 12 | 13 | dependencies { 14 | commonMainApi("io.github.timortel:grpc-multiplatform-lib:$libVersion") 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | maven(url = "https://jitpack.io") 20 | mavenLocal() 21 | } 22 | 23 | kotlin { 24 | jvm("jvm") 25 | 26 | js(IR) { 27 | useCommonJs() 28 | browser() 29 | } 30 | 31 | sourceSets { 32 | val commonMain by getting { 33 | dependencies { 34 | implementation(kotlin("stdlib-common")) 35 | api("com.github.TimOrtel.GRPC-Kotlin-Multiplatform:grpc-multiplatform-lib:$libVersion") 36 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") 37 | } 38 | 39 | kotlin.srcDir(projectDir.resolve("build/generated/source/kmp-grpc/commonMain/kotlin").canonicalPath) 40 | } 41 | 42 | val jvmMain by getting { 43 | dependencies { 44 | api("com.github.TimOrtel.GRPC-Kotlin-Multiplatform:grpc-multiplatform-lib-jvm:$libVersion") 45 | } 46 | 47 | kotlin.srcDir(projectDir.resolve("build/generated/source/kmp-grpc/jvmMain/kotlin").canonicalPath) 48 | } 49 | 50 | val jsMain by getting { 51 | dependencies { 52 | implementation("com.github.TimOrtel.GRPC-Kotlin-Multiplatform:grpc-multiplatform-lib-js:$libVersion") 53 | } 54 | kotlin.srcDir(projectDir.resolve("build/generated/source/kmp-grpc/jsMain/kotlin").canonicalPath) 55 | } 56 | } 57 | } 58 | 59 | tasks.register("generateMPProtos") { 60 | protoSourceFolders.set(listOf(projectDir.resolve("../protos/src/main/proto"))) 61 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/service/CommonServiceWriter.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.service 2 | 3 | import com.squareup.kotlinpoet.* 4 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoRpc 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoService 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.kmMetadata 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.kmStub 10 | 11 | object CommonServiceWriter : ServiceWriter(false) { 12 | 13 | override val classAndFunctionModifiers: List = listOf(KModifier.EXPECT) 14 | override val channelConstructorModifiers: List = emptyList() 15 | override val primaryConstructorModifiers: List = listOf(KModifier.PRIVATE) 16 | 17 | override fun applyToClass(builder: TypeSpec.Builder, protoFile: ProtoFile, service: ProtoService, serviceName: ClassName) = Unit 18 | 19 | override fun applyToRpcFunction( 20 | builder: FunSpec.Builder, 21 | protoFile: ProtoFile, 22 | service: ProtoService, 23 | rpc: ProtoRpc 24 | ) = Unit 25 | 26 | override fun applyToMetadataParameter(builder: ParameterSpec.Builder, service: ProtoService) { 27 | builder.defaultValue("%T()", kmMetadata) 28 | } 29 | 30 | override fun applyToChannelConstructor(builder: FunSpec.Builder, protoFile: ProtoFile, service: ProtoService) = Unit 31 | 32 | override fun specifyInheritance( 33 | builder: TypeSpec.Builder, 34 | serviceClass: ClassName, 35 | protoFile: ProtoFile, 36 | service: ProtoService 37 | ) { 38 | builder.superclass(kmStub.parameterizedBy(serviceClass)) 39 | } 40 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/repeated/JsRepeatedMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import com.squareup.kotlinpoet.KModifier 5 | import com.squareup.kotlinpoet.PropertySpec 6 | import com.squareup.kotlinpoet.TypeName 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.ProtoType 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 11 | 12 | object JsRepeatedMessageMethodGenerator : RepeatedMessageMethodGenerator(true) { 13 | 14 | override val attrs: List = listOf(KModifier.ACTUAL) 15 | 16 | override fun getType(messageAttribute: ProtoMessageAttribute): TypeName = messageAttribute.commonType 17 | 18 | override fun modifyListProperty(builder: PropertySpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) { 19 | val initBlock = CodeBlock.builder() 20 | 21 | initBlock.add( 22 | "jsImpl.%N()", 23 | Const.Message.Attribute.Repeated.JS.getListFunctionName(attr) 24 | ) 25 | 26 | if (attr.types.isEnum) { 27 | initBlock.add( 28 | ".map·{ %T.%N(it) }.toList()", 29 | attr.types.commonType, 30 | Const.Enum.getEnumForNumFunctionName 31 | ) 32 | } else if (attr.types.doDiffer) { 33 | initBlock.add(".map·{ %M(it) }.toList()", Const.Message.CommonFunction.JS.commonFunction(attr)) 34 | } else if (attr.types.protoType == ProtoType.INT_64) { 35 | initBlock.add(".map·{ it.toLong() }") 36 | } else { 37 | initBlock.add(".toList()") 38 | } 39 | 40 | builder.initializer(initBlock.build()) 41 | } 42 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/MapMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 5 | import com.squareup.kotlinpoet.PropertySpec 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import com.squareup.kotlinpoet.asTypeName 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.MapType 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 11 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 12 | 13 | abstract class MapMessageMethodGenerator(private val isActual: Boolean) { 14 | 15 | private companion object { 16 | private val mapType = Map::class.asTypeName() 17 | } 18 | 19 | protected abstract val modifiers: List 20 | 21 | fun generateFunctions( 22 | builder: TypeSpec.Builder, 23 | protoMessage: ProtoMessage, 24 | messageAttribute: ProtoMessageAttribute 25 | ) { 26 | val attributeType = messageAttribute.attributeType as MapType 27 | 28 | builder.addProperty( 29 | PropertySpec 30 | .builder( 31 | Const.Message.Attribute.Map.propertyName(messageAttribute), 32 | mapType.parameterizedBy(attributeType.keyTypes.commonType, attributeType.valueTypes.commonType) 33 | ) 34 | .addModifiers(modifiers) 35 | .apply { 36 | modifyMapProperty(this, protoMessage, messageAttribute) 37 | } 38 | .build() 39 | ) 40 | } 41 | 42 | protected abstract fun modifyMapProperty(builder: PropertySpec.Builder, protoMessage: ProtoMessage, messageAttribute: ProtoMessageAttribute) 43 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/mapper/CommonToJsMapMapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.Types 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.MapType 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.objPropertyName 10 | 11 | object CommonToJsMapMapper : MapMapper() { 12 | 13 | override fun handleCreatedMap( 14 | builderVariable: String, 15 | mapToPutVariable: CodeBlock, 16 | message: ProtoMessage, 17 | attribute: ProtoMessageAttribute, 18 | mapType: MapType 19 | ): CodeBlock { 20 | val internalMapVariableName = attribute.name 21 | 22 | return CodeBlock 23 | .builder() 24 | .addStatement( 25 | "val %N = %N.%N(false)", 26 | internalMapVariableName, 27 | builderVariable, 28 | Const.Message.Attribute.Map.JS.getMapFunctionName(attribute) 29 | ) 30 | .add(mapToPutVariable) 31 | .add(".forEach·{ (k, v) -> %N.set(k, v) }\n", internalMapVariableName) 32 | .build() 33 | } 34 | 35 | override fun mapVariable( 36 | variableName: String, 37 | message: ProtoMessage, 38 | attribute: ProtoMessageAttribute, 39 | types: Types 40 | ): CodeBlock { 41 | return if (types.isEnum) { 42 | CodeBlock.of("%N.value", variableName) 43 | } else CodeBlock.of("%N.%N.%N", variableName, Const.Message.Constructor.JS.PARAM_IMPL, objPropertyName) 44 | } 45 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/JsMapMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.KModifier 6 | import com.squareup.kotlinpoet.PropertySpec 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.MapType 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 11 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper.JsToCommonMapMapper 12 | 13 | object JsMapMessageMethodGenerator : MapMessageMethodGenerator(true) { 14 | 15 | override val modifiers: List = listOf(KModifier.ACTUAL) 16 | 17 | override fun modifyMapProperty( 18 | builder: PropertySpec.Builder, 19 | protoMessage: ProtoMessage, 20 | messageAttribute: ProtoMessageAttribute 21 | ) { 22 | val mapType = messageAttribute.attributeType as MapType 23 | 24 | val mapVariable = CodeBlock.of( 25 | "%T<%T, %T>(%N.%N(false))", 26 | ClassName("io.github.timortel.kotlin_multiplatform_grpc_lib", "JSPBMap"), 27 | mapType.keyTypes.jsType, 28 | mapType.valueTypes.jsType, 29 | Const.Message.Constructor.JS.PARAM_IMPL, 30 | Const.Message.Attribute.Map.JS.getMapFunctionName(messageAttribute) 31 | ) 32 | 33 | if (mapType.valueTypes.doDiffer || mapType.keyTypes.doDiffer) { 34 | val initializer = CodeBlock.builder() 35 | .add("lazy·{\n") 36 | .add(JsToCommonMapMapper.mapMap("newMap", mapVariable, protoMessage, messageAttribute, mapType)) 37 | .add("\n}\n") 38 | 39 | builder.delegate(initializer.build()) 40 | } else { 41 | builder.initializer(mapVariable) 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/size_computation.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import cocoapods.Protobuf.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 5 | 6 | actual fun computeTagSize(fieldNumber: Int): Int = GPBComputeTagSize(fieldNumber).toInt() 7 | 8 | actual fun computeRawVarint32Size(value: Int): Int = GPBComputeRawVarint32Size(value).toInt() 9 | 10 | actual fun computeDoubleSize(fieldNumber: Int, value: Double): Int = GPBComputeDoubleSize(fieldNumber, value).toInt() 11 | actual fun computeDoubleSizeNoTag(value: Double): Int = GPBComputeDoubleSizeNoTag(value).toInt() 12 | 13 | actual fun computeFloatSize(fieldNumber: Int, value: Float): Int = GPBComputeFloatSize(fieldNumber, value).toInt() 14 | actual fun computeFloatSizeNoTag(value: Float): Int = GPBComputeFloatSizeNoTag(value).toInt() 15 | 16 | actual fun computeInt32Size(fieldNumber: Int, value: Int): Int = GPBComputeInt32Size(fieldNumber, value).toInt() 17 | actual fun computeInt32SizeNoTag(value: Int): Int = GPBComputeInt32SizeNoTag(value).toInt() 18 | 19 | actual fun computeInt64Size(fieldNumber: Int, value: Long): Int = GPBComputeInt64Size(fieldNumber, value).toInt() 20 | actual fun computeInt64SizeNoTag(value: Long): Int = GPBComputeInt64SizeNoTag(value).toInt() 21 | 22 | actual fun computeBoolSize(fieldNumber: Int, value: Boolean): Int = GPBComputeBoolSize(fieldNumber, value).toInt() 23 | actual fun computeBoolSizeNoTag(value: Boolean): Int = GPBComputeBoolSizeNoTag(value).toInt() 24 | 25 | actual fun computeStringSize(fieldNumber: Int, value: String): Int = GPBComputeStringSize(fieldNumber, value).toInt() 26 | actual fun computeStringSizeNoTag(value: String): Int = GPBComputeStringSizeNoTag(value).toInt() 27 | 28 | actual fun computeEnumSize(fieldNumber: Int, value: Int): Int = GPBComputeEnumSize(fieldNumber, value).toInt() 29 | actual fun computeEnumSizeNoTag(value: Int): Int = GPBComputeEnumSizeNoTag(value).toInt() 30 | 31 | actual fun computeMessageSize(fieldNumber: Int, value: KMMessage?): Int = 32 | if (value != null) { 33 | computeTagSize(fieldNumber) + computeMessageSizeNoTag(value) 34 | } else 0 35 | 36 | actual fun computeMessageSizeNoTag(value: KMMessage?): Int = if (value != null) GPBComputeRawVarint32Size( 37 | value.requiredSize 38 | ).toInt() + value.requiredSize else 0 -------------------------------------------------------------------------------- /plugin/src/main/antlr/io/github/timortel/kotlin_multiplatform_grpc_plugin/anltr/Proto3.g4: -------------------------------------------------------------------------------- 1 | grammar Proto3; 2 | 3 | @header { package io.github.timortel.kotlin_multiplatform_grpc_plugin.anltr; } 4 | 5 | file : syntax_def (WS? (option | message | service | proto_package | proto_import | COMMENT_OR_WS | proto_enum))* WS?; 6 | 7 | proto_package : 'package' COMMENT_OR_WS? pkgName=EXPRESSION_NAME COMMENT_OR_WS? ';'; 8 | 9 | proto_import : 'import' (WS? 'public')? COMMENT_OR_WS? VALUE_STRING COMMENT_OR_WS? ';'; 10 | 11 | syntax_def : 'syntax' COMMENT_OR_WS? '=' COMMENT_OR_WS? VALUE_STRING COMMENT_OR_WS? ';'; 12 | 13 | option : 'option' COMMENT_OR_WS? optionName=EXPRESSION_NAME COMMENT_OR_WS? '=' COMMENT_OR_WS? (optionValueString=VALUE_STRING | optionValueExpression=EXPRESSION_NAME) COMMENT_OR_WS? ';'; 14 | 15 | message : 'message' COMMENT_OR_WS? messageName=EXPRESSION_NAME COMMENT_OR_WS? '{' (COMMENT_OR_WS? (message_attribute | one_of | proto_enum | message | map))* COMMENT_OR_WS? '}'; 16 | 17 | message_attribute : repeated='repeated'? COMMENT_OR_WS? type=EXPRESSION_NAME COMMENT_OR_WS? name=EXPRESSION_NAME COMMENT_OR_WS? '=' COMMENT_OR_WS? num=NUM COMMENT_OR_WS? ';'; 18 | 19 | one_of : 'oneof' WS? one_of_name=EXPRESSION_NAME WS? '{' (COMMENT_OR_WS? message_attribute)* COMMENT_OR_WS? '}'; 20 | 21 | service : 'service' COMMENT_OR_WS? serviceName=EXPRESSION_NAME COMMENT_OR_WS? '{' (COMMENT_OR_WS? rpc)* COMMENT_OR_WS? '}'; 22 | 23 | rpc : 'rpc' COMMENT_OR_WS? rpcName=EXPRESSION_NAME COMMENT_OR_WS? '(' COMMENT_OR_WS? request=EXPRESSION_NAME COMMENT_OR_WS? ')' COMMENT_OR_WS? 'returns' COMMENT_OR_WS? '(' COMMENT_OR_WS? stream='stream'? COMMENT_OR_WS? response=EXPRESSION_NAME COMMENT_OR_WS? ')' COMMENT_OR_WS? ';'; 24 | 25 | proto_enum : 'enum' WS? enumName=EXPRESSION_NAME WS? '{' WS? (COMMENT_OR_WS? enum_field)+ COMMENT_OR_WS? '}'; 26 | 27 | enum_field : name=EXPRESSION_NAME WS? '=' WS? num=NUM WS? ';'; 28 | 29 | map : 'map' WS? '<' WS? key_type=EXPRESSION_NAME WS? ',' WS? value_type=EXPRESSION_NAME WS? '>' WS? name=EXPRESSION_NAME WS? '=' WS? num=NUM COMMENT_OR_WS? ';'; 30 | 31 | COMMENT_OR_WS : (COMMENT | LINE_COMMENT | WS) -> skip; 32 | 33 | COMMENT : '/*' .*? '*/' -> skip; 34 | 35 | LINE_COMMENT: '//' ~[\r\n]* -> skip; 36 | 37 | NUM : [0-9]+; 38 | EXPRESSION_NAME : ([a-zA-Z0-9_.])+; 39 | 40 | WS_NO_NEWLINE : (' ' | '\t')+ -> skip; 41 | WS : (' ' | '\t' | '\n')+ -> skip; 42 | VALUE_STRING : '"' EXPRESSION_NAME? '"'; -------------------------------------------------------------------------------- /plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.8.10" 3 | id("java-gradle-plugin") 4 | id("maven-publish") 5 | id("com.gradle.plugin-publish") version "0.18.0" 6 | antlr 7 | } 8 | 9 | group = "io.github.timortel" 10 | version = "0.3.1" 11 | 12 | java { 13 | withSourcesJar() 14 | withJavadocJar() 15 | } 16 | 17 | pluginBundle { 18 | website = "https://github.com/TimOrtel/GRPC-Kotlin-Multiplatform" 19 | vcsUrl = "https://github.com/TimOrtel/GRPC-Kotlin-Multiplatform.git" 20 | tags = listOf("grpc", "protobuf", "kotlin-multiplatform", "kotlin", "multiplatform") 21 | } 22 | 23 | gradlePlugin { 24 | plugins { 25 | create("kotlin-multiplatform-grpc-plugin") { 26 | id = "io.github.timortel.kotlin-multiplatform-grpc-plugin" 27 | displayName = "GRPC Kotlin Multiplatform Plugin" 28 | description = "Plugin that generates Kotlin multiplatform wrapper classes for GRPC" 29 | 30 | implementationClass = 31 | "io.github.timortel.kotlin_multiplatform_grpc_plugin.GrpcMultiplatformPlugin" 32 | } 33 | } 34 | } 35 | 36 | publishing { 37 | repositories { 38 | mavenLocal() 39 | } 40 | 41 | repositories { 42 | maven { 43 | setUrl("https://maven.pkg.github.com/oianmol/gRPC-KMP") 44 | } 45 | } 46 | 47 | publications { 48 | create("maven") { 49 | from(project.components["java"]) 50 | groupId = project.group as String 51 | version = project.version as String 52 | 53 | artifactId = "kotlin-multiplatform-grpc-plugin" 54 | } 55 | } 56 | } 57 | 58 | repositories { 59 | mavenCentral() 60 | } 61 | 62 | dependencies { 63 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") 64 | antlr("org.antlr:antlr4:4.11.1") 65 | implementation("org.antlr:antlr4:4.10.1") 66 | 67 | implementation("com.squareup:kotlinpoet:1.12.0") 68 | compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0") 69 | } 70 | 71 | tasks.generateGrammarSource { 72 | arguments = arguments + listOf("-visitor") 73 | } 74 | 75 | tasks.withType().all { 76 | dependsOn("generateGrammarSource") 77 | 78 | kotlinOptions { 79 | freeCompilerArgs = 80 | listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.ExperimentalStdlibApi") 81 | } 82 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/jsMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/rpc/rpc_implementation.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.rpc 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMCode 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMStatus 5 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMStatusException 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.cancel 8 | import kotlinx.coroutines.channels.awaitClose 9 | import kotlinx.coroutines.flow.* 10 | import kotlinx.coroutines.launch 11 | import kotlin.coroutines.coroutineContext 12 | import kotlin.coroutines.resume 13 | import kotlin.coroutines.resumeWithException 14 | import kotlin.coroutines.suspendCoroutine 15 | 16 | suspend fun simpleCallImplementation( 17 | performCall: (callback: (error: dynamic, response: JS_RESPONSE) -> Unit) -> Unit 18 | ): JS_RESPONSE { 19 | return suspendCoroutine { continuation -> 20 | performCall { error, response -> 21 | if (error == null) { 22 | continuation.resume(response) 23 | } else { 24 | continuation.resumeWithException( 25 | KMStatusException( 26 | KMStatus( 27 | KMCode.getCodeForValue(error.code as Int), 28 | (error.message as String?).orEmpty() 29 | ), null 30 | ) 31 | ) 32 | } 33 | } 34 | } 35 | } 36 | 37 | fun serverSideStreamingCallImplementation(performCall: () -> dynamic): Flow { 38 | return callbackFlow { 39 | val stream = performCall() 40 | stream.on("data") { data -> 41 | trySend(data as JS_RESPONSE) 42 | } 43 | 44 | stream.on("end") { 45 | close() 46 | } 47 | 48 | stream.on("status") { status -> 49 | //If the status is not ok, we throw an error 50 | if (status.code as Int != 0) { 51 | close(KMStatusException(KMStatus(KMCode.getCodeForValue(status.code as Int), status.details as String), null)) 52 | } 53 | } 54 | 55 | stream.on("error") { 56 | close(KMStatusException(KMStatus(KMCode.UNKNOWN, "Unknown streaming error"), null)) 57 | } 58 | 59 | awaitClose { 60 | stream.cancel() as Unit 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/message_writer.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 4 | 5 | fun writeKMMessage( 6 | stream: CodedOutputStream, 7 | fieldNumber: Int, 8 | msg: KMMessage, 9 | requiredSize: UInt, 10 | writeMessage: (KMMessage, CodedOutputStream) -> Unit 11 | ) { 12 | stream.writeUInt32NoTag(wireFormatMakeTag(fieldNumber, WireFormat.LENGTH_DELIMITED).toUInt()) 13 | stream.writeUInt32NoTag(requiredSize) 14 | 15 | writeMessage(msg, stream) 16 | } 17 | 18 | fun writeMessageList( 19 | stream: CodedOutputStream, 20 | fieldNumber: Int, 21 | values: List, 22 | requiredSize: (KMMessage) -> UInt, 23 | writeMessage: (KMMessage, CodedOutputStream) -> Unit 24 | ) { 25 | values.forEach { writeKMMessage(stream, fieldNumber, it, requiredSize(it), writeMessage) } 26 | } 27 | 28 | fun writeMap( 29 | stream: CodedOutputStream, 30 | fieldNumber: Int, 31 | map: Map, 32 | getKeySize: (fieldNumber: Int, key: K) -> Int, 33 | getValueSize: (fieldNumber: Int, value: V) -> Int, 34 | writeKey: CodedOutputStream.(fieldNumber: Int, K) -> Unit, 35 | writeValue: CodedOutputStream.(fieldNumber: Int, V) -> Unit 36 | ) { 37 | val tag = wireFormatMakeTag(fieldNumber, WireFormat.LENGTH_DELIMITED) 38 | map.forEach { (key, value) -> 39 | //Write tag 40 | stream.writeInt32NoTag(tag) 41 | //Write the size of the message 42 | val msgSize = getKeySize(kMapKeyFieldNumber, key) + getValueSize(kMapValueFieldNumber, value) 43 | stream.writeInt32NoTag(msgSize) 44 | //Write fields 45 | writeKey(stream, kMapKeyFieldNumber, key) 46 | writeValue(stream, kMapValueFieldNumber, value) 47 | } 48 | } 49 | 50 | fun writeArray( 51 | stream: CodedOutputStream, 52 | fieldNumber: Int, 53 | values: Collection, 54 | tag: UInt, 55 | computeSizeNoTag: (T) -> ULong, 56 | writeNoTag: (T) -> Unit, 57 | writeTag: (Int, T) -> Unit 58 | ) { 59 | if (tag != 0u) { 60 | if (values.isEmpty()) return 61 | 62 | val dataSize = values.sumOf(computeSizeNoTag) 63 | stream.writeRawVarint32(tag.toInt()) 64 | stream.writeRawVarint32(dataSize.toInt()) 65 | 66 | values.forEach(writeNoTag) 67 | } else { 68 | values.forEach { writeTag(fieldNumber, it) } 69 | } 70 | } -------------------------------------------------------------------------------- /grpc-mp-test/test-android-protos/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.google.protobuf.gradle.* 2 | 3 | plugins { 4 | id("com.android.library") 5 | kotlin("android") 6 | 7 | id("com.google.protobuf") 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | api("io.grpc:grpc-stub:${Versions.JVM_GRPC_VERSION}") 16 | api("io.grpc:grpc-protobuf-lite:${Versions.JVM_GRPC_VERSION}") 17 | api("io.grpc:grpc-kotlin-stub:${Versions.JVM_GRPC_KOTLIN_VERSION}") 18 | api("com.google.protobuf:protobuf-kotlin-lite:${Versions.JVM_PROTOBUF_VERSION}") 19 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.COROUTINES_VERSION}") 20 | } 21 | 22 | protobuf { 23 | protoc { 24 | artifact = "com.google.protobuf:protoc:${Versions.JVM_PROTOBUF_VERSION}" 25 | } 26 | 27 | plugins { 28 | id("java") { 29 | artifact = "io.grpc:protoc-gen-grpc-java:${Versions.JVM_GRPC_VERSION}" 30 | } 31 | 32 | id("grpc") { 33 | artifact = "io.grpc:protoc-gen-grpc-java:${Versions.JVM_GRPC_VERSION}" 34 | } 35 | id("grpckt") { 36 | artifact = "io.grpc:protoc-gen-grpc-kotlin:${Versions.JVM_GRPC_KOTLIN_VERSION}:jdk7@jar" 37 | } 38 | } 39 | generateProtoTasks { 40 | all().forEach { 41 | it.plugins { 42 | id("java") { 43 | option("lite") 44 | } 45 | id("grpc") { 46 | option("lite") 47 | } 48 | id("grpckt") { 49 | option("lite") 50 | } 51 | } 52 | 53 | it.builtins { 54 | id("kotlin") { 55 | option("lite") 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | android { 63 | compileSdk = 31 64 | 65 | defaultConfig { 66 | minSdk = 21 67 | targetSdk = 31 68 | } 69 | 70 | compileOptions { 71 | sourceCompatibility = JavaVersion.VERSION_1_8 72 | targetCompatibility = JavaVersion.VERSION_1_8 73 | } 74 | 75 | sourceSets { 76 | named("main") { 77 | manifest.srcFile("src/main/AndroidManifest.xml") 78 | res.srcDirs("src/main/res") 79 | 80 | val protoSrcs = listOf( 81 | "../src/commonMain/proto" 82 | ) 83 | withGroovyBuilder { 84 | "proto" { 85 | "srcDir"(protoSrcs) 86 | } 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/repeated/RepeatedMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated 2 | 3 | import com.squareup.kotlinpoet.* 4 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 8 | 9 | /** 10 | * generates methods for repeated fields. 11 | */ 12 | abstract class RepeatedMessageMethodGenerator(private val isActual: Boolean) { 13 | 14 | protected abstract val attrs: List 15 | 16 | fun generateFunctions(builder: TypeSpec.Builder, message: ProtoMessage, messageAttribute: ProtoMessageAttribute) { 17 | val type = getType(messageAttribute) 18 | 19 | val listProperty = PropertySpec 20 | .builder( 21 | Const.Message.Attribute.Repeated.listPropertyName(messageAttribute), 22 | LIST.parameterizedBy(type) 23 | ) 24 | .addModifiers(attrs) 25 | .apply { modifyListProperty(this, message, messageAttribute) } 26 | .build() 27 | 28 | val countProperty = PropertySpec 29 | .builder(Const.Message.Attribute.Repeated.countPropertyName(messageAttribute), Int::class) 30 | .addModifiers(attrs) 31 | .apply { modifyCountProperty(this, message, messageAttribute) } 32 | .build() 33 | 34 | builder.addProperty(listProperty) 35 | builder.addProperty(countProperty) 36 | } 37 | 38 | /** 39 | * @return the type of the list property 40 | */ 41 | protected abstract fun getType(messageAttribute: ProtoMessageAttribute): TypeName 42 | 43 | protected abstract fun modifyListProperty( 44 | builder: PropertySpec.Builder, 45 | message: ProtoMessage, 46 | attr: ProtoMessageAttribute 47 | ) 48 | 49 | protected open fun modifyCountProperty( 50 | builder: PropertySpec.Builder, 51 | message: ProtoMessage, 52 | attr: ProtoMessageAttribute 53 | ) { 54 | if (isActual) { 55 | builder.initializer("%N.size", Const.Message.Attribute.Repeated.listPropertyName(attr)) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/size_computation.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.DataType 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 5 | 6 | expect fun computeTagSize(fieldNumber: Int): Int 7 | 8 | fun computeWireFormatTagSize(fieldNumber: Int, dataType: DataType): Int { 9 | //https://github.com/protocolbuffers/protobuf/blob/main/objectivec/GPBCodedOutputStream.m#L1076 10 | val result = computeTagSize(fieldNumber) 11 | return if (dataType == DataType.GROUP) result * 2 else result 12 | } 13 | 14 | expect fun computeRawVarint32Size(value: Int): Int 15 | 16 | expect fun computeDoubleSize(fieldNumber: Int, value: Double): Int 17 | expect fun computeDoubleSizeNoTag(value: Double): Int 18 | 19 | expect fun computeFloatSize(fieldNumber: Int, value: Float): Int 20 | expect fun computeFloatSizeNoTag(value: Float): Int 21 | 22 | expect fun computeInt32Size(fieldNumber: Int, value: Int): Int 23 | expect fun computeInt32SizeNoTag(value: Int): Int 24 | 25 | expect fun computeInt64Size(fieldNumber: Int, value: Long): Int 26 | expect fun computeInt64SizeNoTag(value: Long): Int 27 | 28 | expect fun computeBoolSize(fieldNumber: Int, value: Boolean): Int 29 | expect fun computeBoolSizeNoTag(value: Boolean): Int 30 | 31 | expect fun computeStringSize(fieldNumber: Int, value: String): Int 32 | expect fun computeStringSizeNoTag(value: String): Int 33 | 34 | expect fun computeEnumSize(fieldNumber: Int, value: Int): Int 35 | expect fun computeEnumSizeNoTag(value: Int): Int 36 | 37 | expect fun computeMessageSize(fieldNumber: Int, value: KMMessage?): Int 38 | expect fun computeMessageSizeNoTag(value: KMMessage?): Int 39 | 40 | fun computeMapSize( 41 | fieldNumber: Int, 42 | map: Map, 43 | calculateKeySize: (fieldNumber: Int, K) -> Int, 44 | calculateValueSize: (fieldNumber: Int, V) -> Int 45 | ): Int { 46 | val mapSize = map.entries.sumOf { (key, value) -> 47 | val msgSize = 48 | calculateKeySize(kMapKeyFieldNumber, key) + calculateValueSize(kMapValueFieldNumber, value) 49 | computeRawVarint32Size(msgSize) + msgSize 50 | } 51 | 52 | //https://github.com/protocolbuffers/protobuf/blob/520c601c99012101c816b6ccc89e8d6fc28fdbb8/objectivec/GPBDictionary.m#L343 53 | //GPBDataTypeMessage is used in the original source 54 | val tagSize = computeWireFormatTagSize(fieldNumber, DataType.MESSAGE) 55 | return mapSize + tagSize * map.size 56 | } -------------------------------------------------------------------------------- /grpc-mp-test/test-jvm-protos/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.google.protobuf.gradle.* 2 | 3 | plugins { 4 | kotlin("jvm") 5 | 6 | id("java") 7 | id("com.google.protobuf") 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | dependencies { 15 | api(kotlin("stdlib")) 16 | 17 | api("com.google.protobuf:protobuf-kotlin:${Versions.JVM_PROTOBUF_VERSION}") 18 | api("com.google.protobuf:protobuf-java-util:${Versions.JVM_PROTOBUF_VERSION}") 19 | api("io.grpc:grpc-protobuf:${Versions.JVM_GRPC_VERSION}") 20 | api("io.grpc:grpc-stub:${Versions.JVM_GRPC_VERSION}") 21 | api("io.grpc:grpc-kotlin-stub:${Versions.JVM_GRPC_KOTLIN_VERSION}") 22 | 23 | compileOnly("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.COROUTINES_VERSION}") 24 | } 25 | 26 | sourceSets { 27 | main { 28 | proto { 29 | srcDirs("../src/commonMain/proto") 30 | } 31 | kotlin.srcDir(buildDir.resolve("generated/source/proto/main/grpc")) 32 | kotlin.srcDir(buildDir.resolve("generated/source/proto/main/grpckt")) 33 | kotlin.srcDir(buildDir.resolve("generated/source/proto/main/java")) 34 | kotlin.srcDir(buildDir.resolve("generated/source/proto/main/kotlin")) 35 | } 36 | } 37 | 38 | 39 | protobuf { 40 | protoc { 41 | artifact = "com.google.protobuf:protoc:${Versions.JVM_PROTOBUF_VERSION}" 42 | } 43 | 44 | plugins { 45 | id("grpc") { 46 | artifact = "io.grpc:protoc-gen-grpc-java:${Versions.JVM_GRPC_VERSION}" 47 | } 48 | id("grpckt") { 49 | artifact = "io.grpc:protoc-gen-grpc-kotlin:${Versions.JVM_GRPC_KOTLIN_VERSION}:jdk7@jar" 50 | } 51 | } 52 | generateProtoTasks { 53 | ofSourceSet("main").forEach { 54 | it.plugins { 55 | id("grpc") { 56 | option("lite") 57 | } 58 | id("grpckt") { 59 | option("lite") 60 | } 61 | } 62 | 63 | it.builtins { 64 | id("kotlin") { 65 | option("lite") 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | tasks.withType().all { 73 | kotlinOptions { 74 | freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") 75 | } 76 | } 77 | 78 | tasks.withType { 79 | kotlinOptions.jvmTarget = "1.8" 80 | } 81 | java { 82 | sourceCompatibility = JavaVersion.VERSION_1_8 83 | targetCompatibility = JavaVersion.VERSION_1_8 84 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/size_computation.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("ActualSizeComputation") 2 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 3 | 4 | import com.google.protobuf.CodedOutputStream 5 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 6 | 7 | actual fun computeTagSize(fieldNumber: Int): Int = CodedOutputStream.computeTagSize(fieldNumber) 8 | 9 | actual fun computeRawVarint32Size(value: Int): Int = CodedOutputStream.computeUInt32SizeNoTag(value) 10 | 11 | 12 | actual fun computeDoubleSize(fieldNumber: Int, value: Double): Int = 13 | CodedOutputStream.computeDoubleSize(fieldNumber, value) 14 | 15 | actual fun computeDoubleSizeNoTag(value: Double): Int = CodedOutputStream.computeDoubleSizeNoTag(value) 16 | 17 | actual fun computeFloatSize(fieldNumber: Int, value: Float): Int = 18 | CodedOutputStream.computeFloatSize(fieldNumber, value) 19 | 20 | actual fun computeFloatSizeNoTag(value: Float): Int = CodedOutputStream.computeFloatSizeNoTag(value) 21 | 22 | actual fun computeInt32Size(fieldNumber: Int, value: Int): Int = CodedOutputStream.computeInt32Size(fieldNumber, value) 23 | actual fun computeInt32SizeNoTag(value: Int): Int = CodedOutputStream.computeInt32SizeNoTag(value) 24 | 25 | actual fun computeInt64Size(fieldNumber: Int, value: Long): Int = CodedOutputStream.computeInt64Size(fieldNumber, value) 26 | actual fun computeInt64SizeNoTag(value: Long): Int = CodedOutputStream.computeInt64SizeNoTag(value) 27 | 28 | actual fun computeBoolSize(fieldNumber: Int, value: Boolean): Int = 29 | CodedOutputStream.computeBoolSize(fieldNumber, value) 30 | 31 | actual fun computeBoolSizeNoTag(value: Boolean): Int = CodedOutputStream.computeBoolSizeNoTag(value) 32 | 33 | actual fun computeStringSize(fieldNumber: Int, value: String): Int = 34 | CodedOutputStream.computeStringSize(fieldNumber, value) 35 | 36 | actual fun computeStringSizeNoTag(value: String): Int = CodedOutputStream.computeStringSizeNoTag(value) 37 | 38 | actual fun computeEnumSize(fieldNumber: Int, value: Int): Int = CodedOutputStream.computeEnumSize(fieldNumber, value) 39 | actual fun computeEnumSizeNoTag(value: Int): Int = CodedOutputStream.computeEnumSizeNoTag(value) 40 | 41 | actual fun computeMessageSize(fieldNumber: Int, value: KMMessage?): Int = if (value != null) { 42 | computeTagSize(fieldNumber) + computeMessageSizeNoTag(value) 43 | } else 0 44 | 45 | actual fun computeMessageSizeNoTag(value: KMMessage?): Int = 46 | if (value != null) CodedOutputStream.computeUInt32SizeNoTag( 47 | value.requiredSize 48 | ) + value.requiredSize else 0 -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/scalar/IosJvmScalarMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.ProtoType 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 8 | 9 | object IosJvmScalarMessageMethodGenerator : ScalarMessageMethodGenerator(true) { 10 | 11 | override val attrs: List = listOf(KModifier.ACTUAL) 12 | 13 | override fun getTypeForAttribute(protoMessageAttribute: ProtoMessageAttribute): TypeName = 14 | protoMessageAttribute.commonType 15 | 16 | override fun modifyProperty(builder: PropertySpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) { 17 | when (attr.types.protoType) { 18 | ProtoType.MESSAGE -> builder.initializer("%N ?: %T()", attr.name, attr.commonType) 19 | else -> builder.initializer(attr.name) 20 | } 21 | } 22 | 23 | override fun generateProperties( 24 | builder: TypeSpec.Builder, 25 | protoMessage: ProtoMessage, 26 | messageAttribute: ProtoMessageAttribute, 27 | type: TypeName 28 | ) { 29 | super.generateProperties(builder, protoMessage, messageAttribute, type) 30 | 31 | //Additionally generate a property that tells if a message is set. 32 | if (messageAttribute.types.protoType == ProtoType.MESSAGE) { 33 | builder.addProperty( 34 | PropertySpec 35 | .builder( 36 | Const.Message.Attribute.Scalar.IosJvm.isMessageSetFunctionName(protoMessage, messageAttribute), 37 | BOOLEAN, 38 | KModifier.PRIVATE 39 | ) 40 | .initializer("%N != null", messageAttribute.name) 41 | .build() 42 | ) 43 | } 44 | } 45 | 46 | override fun modifyHasFunction(builder: FunSpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) { 47 | builder.apply { 48 | addStatement("return %N", Const.Message.Attribute.Scalar.IosJvm.isMessageSetFunctionName(message, attr)) 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /grpc-mp-test/src/serializationTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/SerializationTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.* 4 | import kotlin.test.Test 5 | import kotlin.test.assertEquals 6 | 7 | abstract class SerializationTest { 8 | 9 | abstract fun serialize(message: KMLongMessage): KMLongMessage 10 | 11 | @Test 12 | fun testSerializeLong() { 13 | val message = kmLongMessage { 14 | field1 = 12L 15 | } 16 | 17 | val serializedMessage = serialize(message) 18 | 19 | assertEquals(message, serializedMessage) 20 | } 21 | 22 | abstract fun serialize(message: KMRepeatedLongMessage): KMRepeatedLongMessage 23 | 24 | @Test 25 | fun testSerializeRepeatedLong() { 26 | val message = kmRepeatedLongMessage { 27 | field1List += listOf(12L, 13L, 25L) 28 | } 29 | 30 | val serializedMessage = serialize(message) 31 | 32 | assertEquals(message, serializedMessage) 33 | } 34 | 35 | abstract fun serialize(message: KMScalarTypes): KMScalarTypes 36 | 37 | @Test 38 | fun testScalarSerialization() { 39 | val msg: KMScalarTypes = createScalarMessage() 40 | val reconstructed = serialize(msg) 41 | 42 | assertEquals(msg, reconstructed) 43 | } 44 | 45 | abstract fun serialize(message: KMMessageWithSubMessage): KMMessageWithSubMessage 46 | 47 | @Test 48 | fun testSerializeMessageWithMessage() { 49 | val msg = kmMessageWithSubMessage { 50 | field1 = kmSimpleMessage { field1 = "Foo" } 51 | } 52 | 53 | val reconstructed = serialize(msg) 54 | 55 | assertEquals(msg, reconstructed) 56 | } 57 | 58 | abstract fun serialize(message: KMOneOfMessage): KMOneOfMessage 59 | 60 | @Test 61 | fun testSerializeOneOfScalarNumeric() { 62 | val msg = kmOneOfMessage { 63 | oneOf1 = KMOneOfMessage.OneOf1.Field1(23) 64 | } 65 | 66 | assertEquals(msg, serialize(msg)) 67 | } 68 | 69 | @Test 70 | fun testSerializeOneOfMessage() { 71 | val msg = kmOneOfMessage { 72 | oneOf1 = KMOneOfMessage.OneOf1.Field3(kmLongMessage { field1 = 15323L }) 73 | } 74 | 75 | assertEquals(msg, serialize(msg)) 76 | } 77 | 78 | abstract fun serialize(message: KMMessageWithEverything): KMMessageWithEverything 79 | 80 | @Test 81 | fun testSerialization() { 82 | val msg = createMessageWithAllTypes() 83 | 84 | val reconstructed = serialize(msg) 85 | 86 | assertEquals(msg, reconstructed) 87 | } 88 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/oneof/JsOneOfMethodAndClassGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.FunSpec 5 | import com.squareup.kotlinpoet.KModifier 6 | import com.squareup.kotlinpoet.PropertySpec 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.ProtoType 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoOneOf 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 11 | 12 | object JsOneOfMethodAndClassGenerator : OneOfMethodAndClassGenerator(true) { 13 | 14 | override val attrs: List = listOf(KModifier.ACTUAL) 15 | 16 | override fun modifyOneOfProperty( 17 | builder: PropertySpec.Builder, 18 | message: ProtoMessage, 19 | oneOf: ProtoOneOf 20 | ) { 21 | builder.getter( 22 | FunSpec 23 | .getterBuilder() 24 | .apply { 25 | addCode("return when(jsImpl.%N()) {\n", Const.Message.OneOf.JS.getCaseFunctionName(oneOf)) 26 | oneOf.attributes.forEach { attr -> 27 | addCode( 28 | "%L -> %T(", attr.protoId, 29 | Const.Message.OneOf.childClassName(message, oneOf, attr) 30 | ) 31 | 32 | if (attr.types.protoType == ProtoType.MESSAGE) { 33 | addCode("%M(", Const.Message.CommonFunction.JS.commonFunction(attr)) 34 | } 35 | 36 | addCode( 37 | "%N.%N()", 38 | Const.Message.Constructor.JS.PARAM_IMPL, 39 | Const.Message.Attribute.Scalar.JS.getFunction(message, attr) 40 | ) 41 | 42 | if (attr.types.protoType == ProtoType.MESSAGE) { 43 | addCode(")") 44 | } 45 | 46 | addCode(")\n") 47 | } 48 | addCode("0 -> %T\n", Const.Message.OneOf.notSetClassName(message, oneOf)) 49 | addCode("else -> %T\n", Const.Message.OneOf.unknownClassName(message, oneOf)) 50 | addCode("}") 51 | } 52 | .build() 53 | ) 54 | } 55 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/message_tree/PacketTreeBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.message_tree 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.anltr.Proto3Lexer 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.anltr.Proto3Parser 5 | import org.antlr.v4.runtime.CharStreams 6 | import org.antlr.v4.runtime.CommonTokenStream 7 | import java.io.File 8 | 9 | object PacketTreeBuilder { 10 | 11 | /** 12 | * @return the root package node 13 | */ 14 | fun buildPacketTree(protoFiles: List): PackageNode { 15 | val nodes = protoFiles.map { protoFile -> 16 | val lexer = Proto3Lexer(CharStreams.fromStream(protoFile.inputStream())) 17 | 18 | val parser = Proto3Parser(CommonTokenStream(lexer)) 19 | 20 | val file = parser.file() 21 | val messageNodes = Proto3MessageTreeBuilder().visit(file) 22 | val fullPackage = file.proto_package().firstOrNull()?.pkgName?.text ?: "" 23 | 24 | var remainingPackageString = fullPackage 25 | var child: PackageNode? = null 26 | while (remainingPackageString.isNotEmpty()) { 27 | val packageString = remainingPackageString.substringAfterLast('.') 28 | remainingPackageString = remainingPackageString.substringBeforeLast('.', missingDelimiterValue = "") 29 | 30 | val messages = if (child == null) messageNodes else emptyList() 31 | 32 | child = PackageNode(packageString, messages, if (child == null) emptyList() else listOf(child)) 33 | } 34 | 35 | child ?: PackageNode("", messageNodes, emptyList()) 36 | } 37 | 38 | //messages without a package 39 | val topLevelMessages = nodes.filter { it.packageName.isEmpty() }.map { it.nodes }.flatten() 40 | val topLevelEnums = nodes.filter { it.packageName.isEmpty() }.map { it.nodes } 41 | 42 | //Merge the nodes 43 | val mergedNodes = mergeTree(nodes.filter { it.packageName.isNotEmpty() }) 44 | 45 | return PackageNode("", topLevelMessages, mergedNodes) 46 | } 47 | 48 | private fun mergeTree(nodesInSameLevel: List): List { 49 | return nodesInSameLevel.groupBy { it.packageName } 50 | .map { (packageName, nodes) -> 51 | val combinedMessages = nodes.map { it.nodes }.flatten() 52 | val combined = nodes.map { it.children }.flatten() 53 | 54 | val merged = mergeTree(combined) 55 | PackageNode(packageName, combinedMessages, merged) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/scalar/ScalarMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 6 | 7 | /** 8 | * Generates methods for scalar proto fields. Scalar means the simple, non-repeated message field. 9 | */ 10 | abstract class ScalarMessageMethodGenerator(private val isActual: Boolean) { 11 | 12 | protected abstract val attrs: List 13 | 14 | fun generateProperties( 15 | builder: TypeSpec.Builder, 16 | protoMessage: ProtoMessage, 17 | messageAttribute: ProtoMessageAttribute 18 | ) { 19 | val type = getTypeForAttribute(messageAttribute) 20 | 21 | //No property needed for one of attributes 22 | if (!messageAttribute.isOneOfAttribute) { 23 | generateProperties(builder, protoMessage, messageAttribute, type) 24 | } 25 | } 26 | 27 | open fun generateProperties( 28 | builder: TypeSpec.Builder, 29 | protoMessage: ProtoMessage, 30 | messageAttribute: ProtoMessageAttribute, 31 | type: TypeName 32 | ) { 33 | builder.addProperty( 34 | PropertySpec 35 | .builder(messageAttribute.name, type) 36 | .addModifiers(attrs) 37 | .apply { 38 | modifyProperty(this, protoMessage, messageAttribute) 39 | } 40 | .build() 41 | ) 42 | 43 | if (messageAttribute.types.isNullable) { 44 | //add an extra has function 45 | builder.addFunction( 46 | FunSpec 47 | .builder("has${messageAttribute.capitalizedName}") 48 | .addModifiers(attrs) 49 | .returns(Boolean::class) 50 | .apply { modifyHasFunction(this, protoMessage, messageAttribute) } 51 | .build() 52 | ) 53 | } 54 | } 55 | 56 | protected abstract fun getTypeForAttribute(protoMessageAttribute: ProtoMessageAttribute): TypeName 57 | 58 | protected abstract fun modifyProperty( 59 | builder: PropertySpec.Builder, 60 | message: ProtoMessage, 61 | attr: ProtoMessageAttribute 62 | ) 63 | 64 | protected abstract fun modifyHasFunction( 65 | builder: FunSpec.Builder, 66 | message: ProtoMessage, 67 | attr: ProtoMessageAttribute 68 | ) 69 | } -------------------------------------------------------------------------------- /example/jvm/envoy-custom.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /tmp/admin_access.log 3 | address: 4 | socket_address: { address: 0.0.0.0, port_value: 9901 } 5 | 6 | static_resources: 7 | listeners: 8 | - name: listener_0 9 | address: 10 | socket_address: { address: 0.0.0.0, port_value: 8082 } 11 | filter_chains: 12 | - filters: 13 | - name: envoy.filters.network.http_connection_manager 14 | typed_config: 15 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 16 | codec_type: auto 17 | stat_prefix: ingress_http 18 | route_config: 19 | name: local_route 20 | virtual_hosts: 21 | - name: local_service 22 | domains: ["*"] 23 | routes: 24 | - match: { prefix: "/" } 25 | route: 26 | cluster: echo_service 27 | timeout: 0s 28 | max_stream_duration: 29 | grpc_timeout_header_max: 0s 30 | cors: 31 | allow_origin_string_match: 32 | - prefix: "*" 33 | allow_methods: GET, PUT, DELETE, POST, OPTIONS 34 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout 35 | max_age: "1728000" 36 | expose_headers: custom-header-1,grpc-status,grpc-message 37 | http_filters: 38 | - name: envoy.filters.http.grpc_web 39 | typed_config: 40 | "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb 41 | - name: envoy.filters.http.cors 42 | typed_config: 43 | "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors 44 | - name: envoy.filters.http.router 45 | typed_config: 46 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 47 | clusters: 48 | - name: echo_service 49 | connect_timeout: 0.25s 50 | type: logical_dns 51 | http2_protocol_options: {} 52 | lb_policy: round_robin 53 | load_assignment: 54 | cluster_name: cluster_0 55 | endpoints: 56 | - lb_endpoints: 57 | - endpoint: 58 | address: 59 | socket_address: 60 | address: 127.0.0.1 61 | port_value: 17600 -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/message_reader.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.DataType 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 5 | 6 | fun readKMMessage( 7 | wrapper: CodedInputStream, 8 | messageFactory: (CodedInputStream) -> M 9 | ): M = recursiveRead(wrapper) { messageFactory(wrapper) } 10 | 11 | /* 12 | Adapted version of https://github.com/protocolbuffers/protobuf/blob/520c601c99012101c816b6ccc89e8d6fc28fdbb8/objectivec/GPBDictionary.m#L455 13 | */ 14 | fun readMapEntry( 15 | inputStream: CodedInputStream, 16 | map: MutableMap, 17 | keyDataType: DataType, 18 | valueDataType: DataType, 19 | defaultKey: K?, 20 | defaultValue: V?, 21 | readKey: CodedInputStream.() -> K, 22 | readValue: CodedInputStream.() -> V 23 | ) { 24 | recursiveRead(inputStream) { 25 | val keyTag = wireFormatMakeTag(kMapKeyFieldNumber, wireFormatForType(keyDataType, false)) 26 | val valueTag = 27 | wireFormatMakeTag(kMapValueFieldNumber, wireFormatForType(valueDataType, false)) 28 | 29 | var key: K? = defaultKey 30 | var value: V? = defaultValue 31 | 32 | var hitError = false 33 | 34 | while (true) { 35 | when (val tag = inputStream.readTag()) { 36 | 0 -> break 37 | keyTag -> key = inputStream.readKey() 38 | valueTag -> value = inputStream.readValue() 39 | else -> { 40 | //Unknown 41 | if (!inputStream.skipField(tag)) { 42 | hitError = true 43 | break 44 | } 45 | } 46 | } 47 | } 48 | 49 | 50 | if (!hitError && key != null && value != null) { 51 | map[key] = value 52 | } 53 | } 54 | } 55 | 56 | private fun recursiveRead(stream: CodedInputStream, block: () -> T): T { 57 | checkRecursionLimit(stream) 58 | val length: Int = stream.readInt32() 59 | val oldLimit = stream.pushLimit(length) 60 | stream.recursionDepth++ 61 | val r = block() 62 | stream.checkLastTagWas(0) 63 | stream.recursionDepth-- 64 | stream.popLimit(oldLimit) 65 | return r 66 | 67 | //checkRecursionLimit(wrapper) 68 | //val length: int32_t = wrapper.stream.readInt32() 69 | //val oldLimit = wrapper.stream.pushLimit(length.toULong()) 70 | //wrapper.recursionDepth++ 71 | //val r = block() 72 | //wrapper.stream.checkLastTagWas(0) 73 | //wrapper.recursionDepth-- 74 | //wrapper.stream.popLimit(oldLimit) 75 | //return r 76 | } 77 | 78 | private fun checkRecursionLimit(wrapper: CodedInputStream) { 79 | if (wrapper.recursionDepth >= 100) { 80 | throw RuntimeException("Recursion depth exceeded.") 81 | } 82 | } -------------------------------------------------------------------------------- /grpc-mp-test/src/commonTest/kotlin/io/github/timortel/kotlin_multiplatform_grpc_plugin/test/RpcTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMChannel 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages.* 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.collect 7 | import kotlinx.coroutines.flow.toCollection 8 | import kotlinx.coroutines.flow.toList 9 | import kotlinx.coroutines.test.runTest 10 | import kotlin.test.Test 11 | import kotlin.test.assertEquals 12 | 13 | class RpcTest { 14 | 15 | private val channel = KMChannel.Builder 16 | .forAddress("localhost", 17888) 17 | .usePlaintext() 18 | .build() 19 | 20 | private val stub = KMTestServiceStub(channel) 21 | 22 | @Test 23 | fun testEmpty() { 24 | runTest { 25 | val message = kmEmptyMessage { } 26 | val response = stub 27 | .emptyRpc(message) 28 | 29 | assertEquals(message, response) 30 | } 31 | } 32 | 33 | @Test 34 | fun testSimple() { 35 | runTest { 36 | val message = kmSimpleMessage { field1 = "Test" } 37 | val response = stub 38 | .simpleRpc(message) 39 | 40 | assertEquals(message, response) 41 | } 42 | } 43 | 44 | @Test 45 | fun testScalar() { 46 | runTest { 47 | val message = createScalarMessage() 48 | val response = stub 49 | .scalarRpc(message) 50 | 51 | assertEquals(message, response) 52 | } 53 | } 54 | 55 | @Test 56 | fun testEverything() { 57 | runTest { 58 | val message = createMessageWithAllTypes() 59 | val response = stub 60 | .everythingRpc(message) 61 | 62 | assertEquals(message, response) 63 | } 64 | } 65 | 66 | @Test 67 | fun testStreamEmpty() { 68 | runTest { 69 | val message = kmEmptyMessage { } 70 | val flow: Flow = stub 71 | .emptyStream(message) 72 | 73 | assertEquals(listOf(message, message, message), flow.toList()) 74 | } 75 | } 76 | 77 | @Test 78 | fun testStreamSimple() { 79 | runTest { 80 | val message = kmSimpleMessage { field1 = "Streaming test" } 81 | val flow: Flow = stub 82 | .simpleStreamingRpc(message) 83 | 84 | assertEquals(listOf(message, message, message), flow.toList()) 85 | } 86 | } 87 | 88 | @Test 89 | fun testStreamEverything() { 90 | runTest { 91 | val message = createMessageWithAllTypes() 92 | val flow: Flow = stub 93 | .everythingStreamingRpc(message) 94 | 95 | assertEquals(listOf(message, message, message), flow.toList()) 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto_file/JsProtoFileWriter.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.* 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.JsMapMessageMethodGenerator 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.MapMessageMethodGenerator 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof.JsOneOfMethodAndClassGenerator 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof.OneOfMethodAndClassGenerator 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated.JsRepeatedMessageMethodGenerator 11 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated.RepeatedMessageMethodGenerator 12 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar.JsScalarMessageMethodGenerator 13 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar.ScalarMessageMethodGenerator 14 | 15 | class JsProtoFileWriter(private val protoFile: ProtoFile) : ProtoFileWriter(protoFile, true), DefaultChildClassName { 16 | override val scalarMessageMethodGenerator: ScalarMessageMethodGenerator = JsScalarMessageMethodGenerator 17 | 18 | override val repeatedMessageMethodGenerator: RepeatedMessageMethodGenerator = JsRepeatedMessageMethodGenerator 19 | 20 | override val oneOfMethodAndClassGenerator: OneOfMethodAndClassGenerator = JsOneOfMethodAndClassGenerator 21 | 22 | override val mapMessageMethodGenerator: MapMessageMethodGenerator = JsMapMessageMethodGenerator 23 | 24 | override fun applyToClass(builder: TypeSpec.Builder, message: ProtoMessage, messageClassName: ClassName) { 25 | val paramName = Const.Message.Constructor.JS.PARAM_IMPL 26 | 27 | builder.addProperty( 28 | PropertySpec 29 | .builder(paramName, message.jsType, KModifier.PUBLIC, KModifier.OVERRIDE) 30 | .initializer(paramName) 31 | .build() 32 | ) 33 | 34 | builder.primaryConstructor( 35 | FunSpec 36 | .constructorBuilder() 37 | .addParameter(paramName, message.jsType) 38 | .build() 39 | ) 40 | } 41 | 42 | override fun getChildClassName(parentClass: ClassName?, childName: String): ClassName = 43 | getChildClassName(parentClass, childName, protoFile.pkg) 44 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /example/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /grpc-mp-test/src/commonMain/proto/basicMessages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.test.basic_messages; 4 | 5 | option java_multiple_files = true; 6 | 7 | message EmptyMessage { 8 | 9 | } 10 | 11 | message SimpleMessage { 12 | string field1 = 1; 13 | } 14 | 15 | message ScalarTypes { 16 | string field1 = 1; 17 | bool field2 = 2; 18 | int32 field3 = 3; 19 | int64 field4 = 4; 20 | float field5 = 5; 21 | double field6 = 6; 22 | } 23 | 24 | message SimpleRepeatedMessage { 25 | repeated string field1 = 1; 26 | } 27 | 28 | message ComplexRepeatedMessage { 29 | repeated string field1 = 1; 30 | repeated bool field2 = 2; 31 | repeated int32 field3 = 3; 32 | repeated int64 field4 = 4; 33 | repeated float field5 = 5; 34 | repeated double field6 = 6; 35 | } 36 | 37 | message MessageWithSubMessage { 38 | SimpleMessage field1 = 1; 39 | } 40 | 41 | message MessageWithRepeatedSubMessage { 42 | repeated SimpleMessage field1 = 1; 43 | } 44 | 45 | enum SimpleEnum { 46 | ZERO = 0; 47 | ONE = 1; 48 | TWO = 2; 49 | } 50 | 51 | message MessageWithEnum { 52 | SimpleEnum field1 = 1; 53 | } 54 | 55 | message MessageWithRepeatedEnum { 56 | repeated SimpleEnum field1 = 1; 57 | } 58 | 59 | message MessageWithNestedMessage { 60 | NestedMessage field1 = 1; 61 | 62 | message NestedMessage { 63 | int32 field1 = 1; 64 | } 65 | } 66 | 67 | message MessageWithMap { 68 | map field1 = 1; 69 | } 70 | 71 | message MessageWithMessageMap { 72 | map field1 = 1; 73 | } 74 | 75 | message MessageWithEnumMap { 76 | map field1 = 1; 77 | } 78 | 79 | message MessageWithEverything { 80 | string field1 = 1; 81 | bool field2 = 2; 82 | int32 field3 = 3; 83 | int64 field4 = 4; 84 | float field5 = 5; 85 | double field6 = 6; 86 | SimpleEnum field7 = 7; 87 | SimpleMessage field8 = 8; 88 | 89 | repeated string field9 = 9; 90 | repeated bool field10 = 10; 91 | repeated int32 field11 = 11; 92 | repeated int64 field12 = 12; 93 | repeated float field13 = 13; 94 | repeated double field14 = 14; 95 | repeated SimpleEnum field15 = 15; 96 | 97 | map field16 = 16; 98 | map field17 = 17; 99 | map field18 = 18; 100 | } 101 | 102 | message LongMessage { 103 | int64 field1 = 1; 104 | } 105 | 106 | message RepeatedLongMessage { 107 | repeated int64 field1 = 1; 108 | } 109 | 110 | message OneOfMessage { 111 | oneof oneOf1 { 112 | int32 field1 = 1; 113 | string field2 = 2; 114 | LongMessage field3 = 3; 115 | } 116 | } 117 | 118 | service TestService { 119 | rpc emptyRpc (EmptyMessage) returns (EmptyMessage); 120 | 121 | rpc simpleRpc (SimpleMessage) returns (SimpleMessage); 122 | rpc scalarRpc (ScalarTypes) returns (ScalarTypes); 123 | rpc everythingRpc (MessageWithEverything) returns (MessageWithEverything); 124 | 125 | rpc emptyStream (EmptyMessage) returns (stream EmptyMessage); 126 | rpc simpleStreamingRpc (SimpleMessage) returns (stream SimpleMessage); 127 | rpc everythingStreamingRpc (MessageWithEverything) returns (stream MessageWithEverything); 128 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto_file/IOSProtoFileWriter.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.* 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.* 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.IosJvmMapMessageMethodGenerator 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.MapMessageMethodGenerator 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof.IosJvmOneOfMethodAndClassGenerator 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof.OneOfMethodAndClassGenerator 11 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated.IosJvmRepeatedMessageMethodGenerator 12 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated.RepeatedMessageMethodGenerator 13 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar.IosJvmScalarMessageMethodGenerator 14 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar.ScalarMessageMethodGenerator 15 | 16 | class IOSProtoFileWriter(protoFile: ProtoFile) : IosJvmProtoFileWriteBase(protoFile) { 17 | 18 | override val scalarMessageMethodGenerator: ScalarMessageMethodGenerator 19 | get() = IosJvmScalarMessageMethodGenerator 20 | override val repeatedMessageMethodGenerator: RepeatedMessageMethodGenerator 21 | get() = IosJvmRepeatedMessageMethodGenerator 22 | override val oneOfMethodAndClassGenerator: OneOfMethodAndClassGenerator 23 | get() = IosJvmOneOfMethodAndClassGenerator 24 | override val mapMessageMethodGenerator: MapMessageMethodGenerator 25 | get() = IosJvmMapMessageMethodGenerator 26 | 27 | 28 | override val serializeFunctionCode: CodeBlock 29 | get() = CodeBlock.builder() 30 | .addStatement("val data = %T().apply { setLength(requiredSize.toULong()) }", NSMutableData) 31 | .addStatement( 32 | "val stream = %T(%T(data))", 33 | CodedOutputStream, 34 | ClassName("cocoapods.Protobuf", "GPBCodedOutputStream") 35 | ) 36 | .addStatement("serialize(stream)") 37 | .addStatement("return data") 38 | .build() 39 | 40 | override val serializedDataType: ClassName = NSData 41 | 42 | override val deserializeFunctionCode: CodeBlock 43 | get() = CodeBlock 44 | .builder() 45 | .addStatement( 46 | "val stream = %T(%T(data))", 47 | CodedInputStream, 48 | GPBCodedInputStream 49 | ) 50 | .addStatement( 51 | "return %N(stream)", 52 | Const.Message.Companion.IOS.WrapperDeserializationFunction.NAME 53 | ) 54 | .build() 55 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/map/mapper/MapMapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.Types 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.MapType 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 8 | 9 | /** 10 | * Can map a map from common to native and backwards 11 | */ 12 | abstract class MapMapper { 13 | 14 | fun mapMap( 15 | builderVariable: String, 16 | mapVariable: CodeBlock, 17 | message: ProtoMessage, 18 | attribute: ProtoMessageAttribute, 19 | mapType: MapType 20 | ): CodeBlock { 21 | val builder = CodeBlock.builder() 22 | 23 | //Here we have to handle the case where we can directly put the map in or where we have to modify the map 24 | if (!mapType.keyTypes.doDiffer && !mapType.valueTypes.doDiffer) { 25 | //Case where nothing differs, and we can directly put in the map 26 | builder.add( 27 | handleCreatedMap( 28 | builderVariable, 29 | mapVariable, 30 | message, 31 | attribute, 32 | mapType 33 | ) 34 | ) 35 | } else { 36 | builder.beginControlFlow("run") 37 | //Something differs, we have to copy and map the map 38 | builder.add("val newMap = ") 39 | builder.add(mapVariable) 40 | builder.add(".entries.associate·{·(k, v) ->\n") 41 | 42 | if (mapType.keyTypes.doDiffer) { 43 | builder.add(mapVariable("k", message, attribute, mapType.keyTypes)) 44 | } else { 45 | builder.add("k") 46 | } 47 | 48 | builder.add(" to ") 49 | 50 | if (mapType.valueTypes.doDiffer) { 51 | builder.add(mapVariable("v", message, attribute, mapType.valueTypes)) 52 | } else { 53 | builder.add("v") 54 | } 55 | 56 | builder.add("\n") 57 | 58 | builder.add("}\n") 59 | 60 | builder.add(handleCreatedMap(builderVariable, CodeBlock.of("newMap"), message, attribute, mapType)) 61 | builder.endControlFlow() 62 | } 63 | 64 | return builder.build() 65 | } 66 | 67 | /** 68 | * Generate the code that puts the map into the builder map 69 | * 70 | * @param mapToPutVariable the variable of type map that is put int 71 | */ 72 | protected abstract fun handleCreatedMap( 73 | builderVariable: String, 74 | mapToPutVariable: CodeBlock, 75 | message: ProtoMessage, 76 | attribute: ProtoMessageAttribute, 77 | mapType: MapType 78 | ): CodeBlock 79 | 80 | /** 81 | * @return a code block that transforms the variable into the correct type 82 | */ 83 | protected abstract fun mapVariable( 84 | variableName: String, 85 | message: ProtoMessage, 86 | attribute: ProtoMessageAttribute, 87 | types: Types 88 | ): CodeBlock 89 | 90 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/service/ActualServiceWriter.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.service 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoService 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.kmChannel 8 | 9 | abstract class ActualServiceWriter : ServiceWriter(true) { 10 | 11 | abstract val callOptionsType: TypeName 12 | 13 | /** 14 | * Create empty call options for your platform, e.g. CallOptions.empty() or CallOptions() 15 | */ 16 | abstract val createEmptyCallOptionsCode: CodeBlock 17 | 18 | override fun applyToClass( 19 | builder: TypeSpec.Builder, 20 | protoFile: ProtoFile, 21 | service: ProtoService, 22 | serviceName: ClassName 23 | ) { 24 | builder.apply { 25 | primaryConstructor( 26 | FunSpec 27 | .constructorBuilder() 28 | .addParameter(Const.Service.CHANNEL_PROPERTY_NAME, kmChannel) 29 | .addParameter(Const.Service.CALL_OPTIONS_PROPERTY_NAME, callOptionsType) 30 | .build() 31 | ) 32 | 33 | addProperty( 34 | PropertySpec.builder( 35 | Const.Service.CHANNEL_PROPERTY_NAME, 36 | kmChannel, 37 | KModifier.OVERRIDE 38 | ) 39 | .initializer(Const.Service.CHANNEL_PROPERTY_NAME) 40 | .build() 41 | ) 42 | 43 | addProperty( 44 | PropertySpec 45 | .builder( 46 | Const.Service.CALL_OPTIONS_PROPERTY_NAME, 47 | callOptionsType, 48 | KModifier.OVERRIDE 49 | ) 50 | .initializer(Const.Service.CALL_OPTIONS_PROPERTY_NAME) 51 | .build() 52 | ) 53 | 54 | addFunction( 55 | FunSpec 56 | .builder("build") 57 | .addModifiers(KModifier.OVERRIDE) 58 | .addParameter(Const.Service.CHANNEL_PROPERTY_NAME, kmChannel) 59 | .addParameter(Const.Service.CALL_OPTIONS_PROPERTY_NAME, callOptionsType) 60 | .returns(serviceName) 61 | .addStatement( 62 | "return %T(%N, %N)", 63 | serviceName, 64 | Const.Service.CHANNEL_PROPERTY_NAME, 65 | Const.Service.CALL_OPTIONS_PROPERTY_NAME 66 | ) 67 | .build() 68 | ) 69 | } 70 | } 71 | 72 | override fun applyToChannelConstructor(builder: FunSpec.Builder, protoFile: ProtoFile, service: ProtoService) { 73 | //Fill the call options with the default call options 74 | builder.apply { 75 | callThisConstructor( 76 | CodeBlock.of("%N", Const.Service.Constructor.CHANNEL_PARAMETER_NAME), 77 | createEmptyCallOptionsCode 78 | ) 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/content/ProtoMessageAttribute.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.MemberName 6 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 7 | import com.squareup.kotlinpoet.asTypeName 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.ProtoType 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.Types 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 11 | import java.util.* 12 | 13 | /** 14 | * @property hasDefaultValue if the value is always set or if it can be null 15 | * @property protoId the unique integer index for this attribute 16 | */ 17 | class ProtoMessageAttribute( 18 | val name: String, 19 | val commonType: ClassName, 20 | val types: Types, 21 | val attributeType: AttributeType, 22 | val protoId: Int, 23 | val isOneOfAttribute: Boolean 24 | ) { 25 | val capitalizedName = name.capitalize(Locale.ROOT) 26 | 27 | /** 28 | * The default value for this attribute 29 | * @param useEmptyMessage if an empty message should be used, or null 30 | */ 31 | fun commonDefaultValue(mutable: Boolean, useEmptyMessage: Boolean): CodeBlock = when (attributeType) { 32 | is Scalar -> when (types.protoType) { 33 | ProtoType.DOUBLE -> CodeBlock.of("0.0") 34 | ProtoType.FLOAT -> CodeBlock.of("0f") 35 | ProtoType.INT_32 -> CodeBlock.of("0") 36 | ProtoType.INT_64 -> CodeBlock.of("0L") 37 | ProtoType.BOOL -> CodeBlock.of("false") 38 | ProtoType.STRING -> CodeBlock.of("\"\"") 39 | ProtoType.MESSAGE -> if (useEmptyMessage) { 40 | CodeBlock.of("%T()", types.commonType) 41 | } else CodeBlock.of("null") 42 | ProtoType.ENUM -> { 43 | CodeBlock.of( 44 | "%T.%N(0)", 45 | types.commonType, 46 | Const.Enum.getEnumForNumFunctionName 47 | ) 48 | } 49 | 50 | ProtoType.MAP -> throw IllegalStateException() 51 | } 52 | 53 | is Repeated -> CodeBlock.of( 54 | "%M()", 55 | MemberName("kotlin.collections", if (mutable) "mutableListOf" else "emptyList") 56 | ) 57 | 58 | is MapType -> CodeBlock.of( 59 | "%M()", 60 | MemberName("kotlin.collections", if (mutable) "mutableMapOf" else "emptyMap") 61 | ) 62 | } 63 | } 64 | 65 | sealed class AttributeType(val isEnum: Boolean) 66 | 67 | /** 68 | * @property inOneOf if this attribute is in a one of 69 | */ 70 | class Scalar(val inOneOf: Boolean, isEnum: Boolean) : AttributeType(isEnum) 71 | 72 | class Repeated(isEnum: Boolean) : AttributeType(isEnum) 73 | 74 | class MapType(val keyTypes: Types, val valueTypes: Types) : AttributeType(false) { 75 | val commonMapType = Map::class.asTypeName().parameterizedBy(keyTypes.commonType, valueTypes.commonType) 76 | val commonMutableMapType = 77 | ClassName("kotlin.collections", "MutableMap").parameterizedBy(keyTypes.commonType, valueTypes.commonType) 78 | 79 | val jsMapType = ClassName("kotlin.collections", "MutableMap").parameterizedBy(keyTypes.jsType, valueTypes.jsType) 80 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/scalar/JsScalarMessageMethodGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 7 | 8 | object JsScalarMessageMethodGenerator : ScalarMessageMethodGenerator(true) { 9 | 10 | override val attrs: List = listOf(KModifier.ACTUAL) 11 | 12 | override fun getTypeForAttribute(protoMessageAttribute: ProtoMessageAttribute): TypeName = 13 | protoMessageAttribute.commonType 14 | 15 | override fun modifyProperty(builder: PropertySpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) { 16 | builder.getter(FunSpec.getterBuilder().apply { 17 | if (attr.types.isEnum) { 18 | addCode( 19 | "return %T.%N(jsImpl.%N())", 20 | attr.commonType, 21 | Const.Enum.getEnumForNumFunctionName, 22 | Const.Message.Attribute.Scalar.JS.getFunction(message, attr).simpleName 23 | ) 24 | } else { 25 | val getter = 26 | CodeBlock.of( 27 | "jsImpl.%N()", 28 | Const.Message.Attribute.Scalar.JS.getFunction(message, attr).simpleName 29 | ) 30 | 31 | addCode("return ") 32 | if (attr.types.doDiffer) { 33 | // if (attr.types.hasDefaultValue) { 34 | addCode("%M(", Const.Message.CommonFunction.JS.commonFunction(attr)) 35 | addCode(getter) 36 | addCode(")") 37 | // } else { 38 | // addCode(getter) 39 | // addCode( 40 | // ".let·{ if (it == null) null else %M(it) }", 41 | // Const.Message.CommonFunction.JS.commonFunction(attr) 42 | // ) 43 | // } 44 | } else { 45 | addCode(getter) 46 | } 47 | } 48 | }.build()) 49 | } 50 | 51 | // override fun modifySetter(builder: FunSpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) { 52 | // val value = 53 | // if (attr.types.isEnum) "value.value" 54 | // else if (attr.types.doDiffer) { 55 | // if (attr.types.hasDefaultValue) { 56 | // "value.jsImpl" 57 | // } else "value?.jsImpl ?: undefined" 58 | // } else { 59 | // if (attr.types.hasDefaultValue) { 60 | // "value" 61 | // } else { 62 | // "value ?: undefined" 63 | // } 64 | // } 65 | // 66 | // builder.addStatement("jsImpl.%N($value)", Const.Message.Attribute.Scalar.JS.setFunction(message, attr).simpleName) 67 | // } 68 | 69 | override fun modifyHasFunction(builder: FunSpec.Builder, message: ProtoMessage, attr: ProtoMessageAttribute) { 70 | builder.addStatement( 71 | "return jsImpl.%N()", 72 | Const.Message.Attribute.Scalar.JS.getHasFunction(message, attr).simpleName 73 | ) 74 | } 75 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/androidJvmCommon/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/CodedInputStream.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import com.google.protobuf.CodedInputStream 4 | import com.google.protobuf.InvalidProtocolBufferException 5 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.DataType 6 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 7 | import kotlin.jvm.Throws 8 | 9 | actual class CodedInputStream(private val impl: CodedInputStream, actual var recursionDepth: Int = 0) { 10 | 11 | actual val bytesUntilLimit: Int 12 | get() = impl.bytesUntilLimit 13 | 14 | actual val isAtEnd: Boolean 15 | get() = impl.isAtEnd 16 | 17 | actual fun readTag(): Int = impl.readTag() 18 | 19 | actual fun getLastTag(): Int = impl.lastTag 20 | 21 | actual fun skipField(tag: Int): Boolean = impl.skipField(tag) 22 | 23 | actual fun skipMessage() = impl.skipMessage() 24 | 25 | actual fun readDouble(): Double = impl.readDouble() 26 | 27 | actual fun readFloat(): Float = impl.readFloat() 28 | 29 | actual fun readUInt64(): ULong = impl.readUInt64().toULong() 30 | 31 | actual fun readInt64(): Long = impl.readInt64() 32 | 33 | actual fun readInt32(): Int = impl.readInt32() 34 | 35 | actual fun readFixed32(): Int = impl.readFixed32() 36 | 37 | actual fun readBool(): Boolean = impl.readBool() 38 | 39 | actual fun readString(): String = impl.readString() 40 | 41 | actual fun readBytes(): ByteArray = impl.readBytes().toByteArray() 42 | 43 | actual fun readByteArray(): ByteArray = impl.readByteArray() 44 | 45 | actual fun readUInt32(): UInt = impl.readUInt32().toUInt() 46 | 47 | actual fun readEnum(): Int = impl.readEnum() 48 | 49 | actual fun readSFixed32(): Int = impl.readSFixed32() 50 | 51 | actual fun readSFixed64(): Long = impl.readSFixed64() 52 | 53 | actual fun readSInt32(): Int = impl.readSFixed32() 54 | 55 | actual fun readSInt64(): Long = impl.readSInt64() 56 | 57 | actual fun readRawVarint32(): Int = impl.readRawVarint32() 58 | 59 | actual fun readRawVarint64(): Long = impl.readRawVarint64() 60 | 61 | actual fun readRawByte(): Byte = impl.readRawByte() 62 | 63 | actual fun pushLimit(newLimit: Int): Int = impl.pushLimit(newLimit) 64 | 65 | actual fun popLimit(oldLimit: Int) = impl.popLimit(oldLimit) 66 | 67 | actual fun readKMMessage(messageFactory: (io.github.timortel.kotlin_multiplatform_grpc_lib.io.CodedInputStream) -> M): M { 68 | return readKMMessage(this, messageFactory) 69 | } 70 | 71 | @Throws(ParseException::class) 72 | actual fun checkLastTagWas(value: Int) { 73 | try { 74 | impl.checkLastTagWas(value) 75 | } catch (_: InvalidProtocolBufferException) { 76 | throw ParseException() 77 | } 78 | } 79 | 80 | actual fun setSizeLimit(newLimit: Int) = impl.setSizeLimit(newLimit) 81 | 82 | actual fun readMapEntry( 83 | map: MutableMap, 84 | keyDataType: DataType, 85 | valueDataType: DataType, 86 | defaultKey: K?, 87 | defaultValue: V?, 88 | readKey: io.github.timortel.kotlin_multiplatform_grpc_lib.io.CodedInputStream.() -> K, 89 | readValue: io.github.timortel.kotlin_multiplatform_grpc_lib.io.CodedInputStream.() -> V 90 | ) = readMapEntry( 91 | this, 92 | map, 93 | keyDataType, 94 | valueDataType, 95 | defaultKey, 96 | defaultValue, 97 | readKey, 98 | readValue 99 | ) 100 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/service/IOSServiceWriter.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.service 2 | 3 | import com.squareup.kotlinpoet.* 4 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.* 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoRpc 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoService 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 10 | 11 | object IOSServiceWriter : ActualServiceWriter() { 12 | 13 | override val classAndFunctionModifiers: List = listOf(KModifier.ACTUAL) 14 | override val channelConstructorModifiers: List = listOf(KModifier.ACTUAL) 15 | override val primaryConstructorModifiers: List = listOf(KModifier.PRIVATE, KModifier.ACTUAL) 16 | 17 | private val GRPC_MUTABLE_CALL_OPTIONS = ClassName("cocoapods.GRPCClient", "GRPCMutableCallOptions") 18 | 19 | override val callOptionsType: TypeName = ClassName("cocoapods.GRPCClient", "GRPCCallOptions") 20 | override val createEmptyCallOptionsCode: CodeBlock = 21 | CodeBlock.of("%T()", GRPC_MUTABLE_CALL_OPTIONS) 22 | 23 | override fun applyToClass( 24 | builder: TypeSpec.Builder, 25 | protoFile: ProtoFile, 26 | service: ProtoService, 27 | serviceName: ClassName 28 | ) { 29 | super.applyToClass(builder, protoFile, service, serviceName) 30 | 31 | overrideWithDeadlineAfter(builder, serviceName) 32 | } 33 | 34 | override fun applyToRpcFunction( 35 | builder: FunSpec.Builder, 36 | protoFile: ProtoFile, 37 | service: ProtoService, 38 | rpc: ProtoRpc 39 | ) { 40 | val impl = when (rpc.method) { 41 | ProtoRpc.Method.UNARY -> iosUnaryCallImplementation 42 | ProtoRpc.Method.SERVER_STREAMING -> iosServerSideStreamingCallImplementation 43 | } 44 | 45 | builder.apply { 46 | addStatement( 47 | "val callOptions = %N.mutableCopy() as %T", 48 | Const.Service.CALL_OPTIONS_PROPERTY_NAME, 49 | GRPC_MUTABLE_CALL_OPTIONS 50 | ) 51 | addStatement("callOptions.setInitialMetadata(%N.metadataMap.toMap())", Const.Service.RpcCall.PARAM_METADATA) 52 | } 53 | 54 | builder.addStatement( 55 | "return %M(%N, callOptions, %S, %N, %T.Companion)", 56 | impl, 57 | Const.Service.CHANNEL_PROPERTY_NAME, 58 | "/${protoFile.pkg}.${service.serviceName}/${rpc.rpcName}", 59 | Const.Service.RpcCall.PARAM_REQUEST, 60 | rpc.response.iosType 61 | ) 62 | } 63 | 64 | override fun applyToMetadataParameter(builder: ParameterSpec.Builder, service: ProtoService) { 65 | } 66 | 67 | override fun specifyInheritance( 68 | builder: TypeSpec.Builder, 69 | serviceClass: ClassName, 70 | protoFile: ProtoFile, 71 | service: ProtoService 72 | ) { 73 | builder.superclass(kmStub.parameterizedBy(serviceClass)) 74 | builder.addSuperinterface( 75 | ClassName( 76 | "io.github.timortel.kotlin_multiplatform_grpc_lib.stub", 77 | "IOSKMStub" 78 | ).parameterizedBy(serviceClass) 79 | ) 80 | } 81 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/proto_file/CommonProtoFileWriter.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.CommonMapMessageMethodGenerator 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.MapMessageMethodGenerator 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof.CommonOneOfMethodAndClassGenerator 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof.OneOfMethodAndClassGenerator 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated.CommonRepeatedMessageMethodGenerator 10 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.repeated.RepeatedMessageMethodGenerator 11 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar.CommonScalarMessageMethodGenerator 12 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.scalar.ScalarMessageMethodGenerator 13 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoFile 14 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 15 | import java.io.File 16 | 17 | /** 18 | * File writer that writes Kotlin Multiplatform Proto Files. These are platform independent. 19 | */ 20 | class CommonProtoFileWriter(private val protoFile: ProtoFile) : ProtoFileWriter(protoFile, false), 21 | DefaultChildClassName { 22 | 23 | override val scalarMessageMethodGenerator: ScalarMessageMethodGenerator = CommonScalarMessageMethodGenerator 24 | 25 | override val repeatedMessageMethodGenerator: RepeatedMessageMethodGenerator = CommonRepeatedMessageMethodGenerator 26 | 27 | override val oneOfMethodAndClassGenerator: OneOfMethodAndClassGenerator = CommonOneOfMethodAndClassGenerator 28 | 29 | override val mapMessageMethodGenerator: MapMessageMethodGenerator = CommonMapMessageMethodGenerator 30 | 31 | override fun writeFile(outputDir: File) { 32 | super.writeFile(outputDir) 33 | 34 | //Top level enums are only written in the common module 35 | protoFile.enums.forEach { topLevelEnum -> 36 | FileSpec 37 | .builder(protoFile.pkg, Const.Enum.commonEnumName(topLevelEnum)) 38 | .apply { 39 | addProtoEnum(this::addType, EnumType.TOP_LEVEL, topLevelEnum) { childName -> 40 | ClassName( 41 | protoFile.pkg, 42 | childName 43 | ) 44 | } 45 | } 46 | .build() 47 | .writeTo(outputDir) 48 | } 49 | } 50 | 51 | override fun applyToClass(builder: TypeSpec.Builder, message: ProtoMessage, messageClassName: ClassName) = Unit 52 | 53 | override fun getChildClassName(parentClass: ClassName?, childName: String): ClassName = 54 | getChildClassName(parentClass, childName, protoFile.pkg) 55 | 56 | override fun applyToEqualsFunction( 57 | builder: FunSpec.Builder, 58 | message: ProtoMessage, 59 | thisClassName: ClassName 60 | ) = Unit 61 | 62 | override fun applyToHashCodeFunction(builder: FunSpec.Builder, message: ProtoMessage) = Unit 63 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/io/CodedInputStream.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.io 2 | 3 | import cocoapods.Protobuf.GPBCodedInputStream 4 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.DataType 5 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 6 | import kotlinx.cinterop.addressOf 7 | import kotlinx.cinterop.usePinned 8 | import kotlinx.cinterop.memScoped 9 | import kotlinx.cinterop.allocArrayOf 10 | import kotlinx.cinterop.addressOf 11 | import kotlinx.cinterop.usePinned 12 | import platform.Foundation.NSData 13 | import platform.Foundation.create 14 | import platform.posix.memcpy 15 | 16 | actual class CodedInputStream(private val impl: GPBCodedInputStream, actual var recursionDepth: Int = 0) { 17 | 18 | actual val bytesUntilLimit: Int 19 | get() = TODO() 20 | actual val isAtEnd: Boolean 21 | get() = impl.isAtEnd() 22 | 23 | actual fun readTag(): Int = impl.readTag() 24 | 25 | @Throws(ParseException::class) 26 | actual fun checkLastTagWas(value: Int) { 27 | try { 28 | impl.checkLastTagWas(value) 29 | } catch (_: Exception) { 30 | throw ParseException() 31 | } 32 | } 33 | 34 | actual fun getLastTag(): Int = throw NotImplementedError("Not available on iOS") 35 | 36 | actual fun skipField(tag: Int): Boolean = impl.skipField(tag) 37 | 38 | actual fun skipMessage() = impl.skipMessage() 39 | 40 | actual fun readDouble(): Double = impl.readDouble() 41 | 42 | actual fun readFloat(): Float = impl.readFloat() 43 | 44 | actual fun readUInt64(): ULong = impl.readUInt64() 45 | 46 | actual fun readInt64(): Long = impl.readInt64() 47 | 48 | actual fun readInt32(): Int = impl.readInt32() 49 | 50 | actual fun readFixed32(): Int = impl.readFixed32().toInt() 51 | 52 | actual fun readBool(): Boolean = impl.readBool() 53 | 54 | actual fun readString(): String = impl.readString() 55 | 56 | actual fun readKMMessage(messageFactory: (CodedInputStream) -> M): M = readKMMessage(this, messageFactory) 57 | 58 | actual fun readMapEntry( 59 | map: MutableMap, 60 | keyDataType: DataType, 61 | valueDataType: DataType, 62 | defaultKey: K?, 63 | defaultValue: V?, 64 | readKey: CodedInputStream.() -> K, 65 | readValue: CodedInputStream.() -> V 66 | ) = readMapEntry(this, map, keyDataType, valueDataType, defaultKey, defaultValue, readKey, readValue) 67 | 68 | actual fun readBytes(): ByteArray { 69 | val data = impl.readBytes() 70 | return ByteArray(data.length.toInt()).apply { 71 | usePinned { 72 | memcpy(it.addressOf(0), data.bytes, data.length) 73 | } 74 | } 75 | } 76 | 77 | actual fun readByteArray(): ByteArray = readBytes() 78 | 79 | actual fun readUInt32(): UInt = impl.readUInt32() 80 | 81 | actual fun readEnum(): Int = impl.readEnum() 82 | 83 | actual fun readSFixed32(): Int = impl.readSFixed32() 84 | 85 | actual fun readSFixed64(): Long = impl.readSFixed64() 86 | 87 | actual fun readSInt32(): Int = impl.readSInt32() 88 | 89 | actual fun readSInt64(): Long = impl.readSInt64() 90 | 91 | actual fun readRawVarint32(): Int = impl.readInt32() 92 | 93 | actual fun readRawVarint64(): Long = impl.readInt64() 94 | 95 | actual fun readRawByte(): Byte = throw NotImplementedError("Not available on ios") 96 | 97 | actual fun pushLimit(newLimit: Int): Int = impl.pushLimit(newLimit.toULong()).toInt() 98 | 99 | actual fun popLimit(oldLimit: Int) = impl.popLimit(oldLimit.toULong()) 100 | 101 | actual fun setSizeLimit(newLimit: Int): Int = TODO() 102 | 103 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/oneof/IosJvmOneOfMethodAndClassGenerator.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.oneof 2 | 3 | import com.squareup.kotlinpoet.* 4 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.CodedOutputStream 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoOneOf 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.proto_file.IosJvmProtoFileWriteBase 9 | 10 | object IosJvmOneOfMethodAndClassGenerator : OneOfMethodAndClassGenerator(true) { 11 | override val attrs: List = listOf(KModifier.ACTUAL) 12 | 13 | override fun modifyOneOfProperty(builder: PropertySpec.Builder, message: ProtoMessage, oneOf: ProtoOneOf) { 14 | builder.initializer(Const.Message.OneOf.propertyName(message, oneOf)) 15 | } 16 | 17 | override fun modifyParentClass(builder: TypeSpec.Builder, message: ProtoMessage, oneOf: ProtoOneOf) { 18 | builder.addProperty(Const.Message.OneOf.IosJvm.REQUIRED_SIZE_PROPERTY_NAME, INT, KModifier.ABSTRACT) 19 | addSerializeFunction(builder, listOf(KModifier.ABSTRACT)) { 20 | 21 | } 22 | } 23 | 24 | override fun modifyChildClass( 25 | builder: TypeSpec.Builder, 26 | message: ProtoMessage, 27 | oneOf: ProtoOneOf, 28 | childClassType: ChildClassType 29 | ) { 30 | builder.addProperty( 31 | PropertySpec 32 | .builder( 33 | Const.Message.OneOf.IosJvm.REQUIRED_SIZE_PROPERTY_NAME, 34 | INT, 35 | KModifier.OVERRIDE 36 | ) 37 | .initializer( 38 | when (childClassType) { 39 | is ChildClassType.Normal -> IosJvmProtoFileWriteBase.getCodeForRequiredSizeForScalarAttributeC( 40 | childClassType.attr 41 | ) 42 | 43 | ChildClassType.NotSet -> CodeBlock.of("0") 44 | /* 45 | If KM-GRPC wants to conform to proto 3.5, unknown fields must be retained. 46 | */ 47 | ChildClassType.Unknown -> CodeBlock.of("0") 48 | } 49 | ) 50 | .build() 51 | ) 52 | 53 | addSerializeFunction(builder, listOf(KModifier.OVERRIDE)) { 54 | when (childClassType) { 55 | is ChildClassType.Normal -> addCode( 56 | IosJvmProtoFileWriteBase.getWriteScalarFieldCode( 57 | message, 58 | childClassType.attr, 59 | Const.Message.OneOf.IosJvm.SERIALIZE_FUNCTION_STREAM_PARAM_NAME, 60 | performIsMessageSetCheck = false 61 | ) 62 | ) 63 | ChildClassType.Unknown, ChildClassType.NotSet -> { 64 | } 65 | } 66 | 67 | } 68 | } 69 | 70 | private fun addSerializeFunction( 71 | builder: TypeSpec.Builder, 72 | modifiers: List, 73 | modify: FunSpec.Builder.() -> Unit 74 | ) { 75 | builder.addFunction( 76 | FunSpec 77 | .builder(Const.Message.OneOf.IosJvm.SERIALIZE_FUNCTION_NAME) 78 | .addModifiers(modifiers) 79 | .addParameter(Const.Message.OneOf.IosJvm.SERIALIZE_FUNCTION_STREAM_PARAM_NAME, CodedOutputStream) 80 | .apply(modify) 81 | .build() 82 | ) 83 | } 84 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/GrpcMultiplatformPlugin.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin 2 | 3 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.GenerateMultiplatformSourcesTask 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 7 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 8 | import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper 9 | import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType 10 | import org.jetbrains.kotlin.gradle.plugin.KotlinTarget 11 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 12 | import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosArm32Variant 13 | import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosArm64Variant 14 | import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosSimulatorArm64Variant 15 | import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosX64Variant 16 | import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget 17 | import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget 18 | import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile 19 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 20 | import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile 21 | import org.jetbrains.kotlin.gradle.utils.`is` 22 | 23 | class GrpcMultiplatformPlugin : Plugin { 24 | override fun apply(project: Project) { 25 | val grpcMultiplatformExtension = 26 | project.extensions.create("grpcKotlinMultiplatform", GrpcMultiplatformExtension::class.java) 27 | 28 | project.tasks.register("generateMPProtos", GenerateMultiplatformSourcesTask::class.java) 29 | 30 | project.plugins.withType(KotlinMultiplatformPluginWrapper::class.java) { 31 | 32 | project.afterEvaluate { 33 | val generateMpProtosTask = project.tasks.withType(GenerateMultiplatformSourcesTask::class.java) 34 | 35 | val targetSourceMap = grpcMultiplatformExtension.targetSourcesMap.getOrElse(emptyMap()) 36 | 37 | targetSourceMap[GrpcMultiplatformExtension.OutputTarget.COMMON].orEmpty().forEach { 38 | it.kotlin.srcDir(GenerateMultiplatformSourcesTask.getCommonOutputFolder(project)) 39 | } 40 | 41 | targetSourceMap[GrpcMultiplatformExtension.OutputTarget.JVM].orEmpty().forEach { 42 | it.kotlin.srcDir(GenerateMultiplatformSourcesTask.getJVMOutputFolder(project)) 43 | } 44 | 45 | targetSourceMap[GrpcMultiplatformExtension.OutputTarget.JS].orEmpty().forEach { 46 | it.kotlin.srcDir(GenerateMultiplatformSourcesTask.getJSOutputFolder(project)) 47 | } 48 | targetSourceMap[GrpcMultiplatformExtension.OutputTarget.IOS].orEmpty().forEach { 49 | it.kotlin.srcDir(GenerateMultiplatformSourcesTask.getIOSOutputFolder(project)) 50 | } 51 | 52 | //JVM 53 | project.tasks.withType(KotlinCompile::class.java).all { kotlinCompile -> 54 | generateMpProtosTask.forEach { generateProtoTask -> 55 | kotlinCompile.dependsOn(generateProtoTask) 56 | } 57 | } 58 | 59 | //JS 60 | project.tasks.withType(Kotlin2JsCompile::class.java).all { kotlinCompile -> 61 | generateMpProtosTask.forEach { generateProtoTask -> 62 | kotlinCompile.dependsOn(generateProtoTask) 63 | } 64 | } 65 | 66 | //IOS 67 | project.tasks.withType(KotlinNativeCompile::class.java).all { kotlinCompile -> 68 | generateMpProtosTask.forEach { generateProtoTask -> 69 | kotlinCompile.dependsOn(generateProtoTask) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /plugin/src/main/java/io/github/timortel/kotlin_multiplatform_grpc_plugin/generate_mulitplatform_sources/generators/dsl/JsDslBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.dsl 2 | 3 | import com.squareup.kotlinpoet.CodeBlock 4 | import com.squareup.kotlinpoet.FunSpec 5 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.Const 6 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper.CommonToJsMapMapper 7 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.generators.map.mapper.MapMapper 8 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessage 9 | import io.github.timortel.kotlin_multiplatform_grpc_plugin.generate_mulitplatform_sources.content.ProtoMessageAttribute 10 | 11 | object JsDslBuilder : SubDslBuilder(true) { 12 | 13 | override val mapMapper: MapMapper = CommonToJsMapMapper 14 | 15 | override fun initializeBuilder(builder: FunSpec.Builder, message: ProtoMessage): String { 16 | builder.addStatement("val jsImpl = %T()", message.jsType) 17 | return "jsImpl" 18 | } 19 | 20 | override fun returnPlatformType(builder: FunSpec.Builder, message: ProtoMessage, builderVariableName: String) { 21 | builder.addStatement("return %T(jsImpl)", message.commonType) 22 | } 23 | 24 | override fun setEnumValue( 25 | builder: FunSpec.Builder, 26 | variableName: String, 27 | message: ProtoMessage, 28 | attribute: ProtoMessageAttribute, 29 | builderVariable: String 30 | ) { 31 | val f = Const.Message.Attribute.Scalar.JS.setFunction(message, attribute).simpleName 32 | val v = CodeBlock.of("%N?.value ?: 0", variableName) 33 | 34 | builder.addCode("%N.%N(", builderVariable, f) 35 | builder.addCode(v) 36 | builder.addCode(")\n") 37 | } 38 | 39 | override fun setScalarValue( 40 | builder: FunSpec.Builder, 41 | variableName: String, 42 | message: ProtoMessage, 43 | attribute: ProtoMessageAttribute, 44 | builderVariable: String 45 | ) { 46 | val f = Const.Message.Attribute.Scalar.JS.setFunction(message, attribute).simpleName 47 | val v = if (attribute.types.doDiffer) { 48 | CodeBlock.of( 49 | "%N.%N", 50 | variableName, 51 | Const.Message.Constructor.JS.PARAM_IMPL 52 | ) 53 | } else { 54 | CodeBlock.of("%N", variableName) 55 | } 56 | 57 | builder.addCode("%N.%N(", builderVariable, f) 58 | builder.addCode(v) 59 | builder.addCode(")\n") 60 | } 61 | 62 | override fun addAllValues( 63 | builder: FunSpec.Builder, 64 | message: ProtoMessage, 65 | attribute: ProtoMessageAttribute, 66 | builderVariable: String 67 | ) { 68 | builder.addCode( 69 | "%N.%N(", 70 | builderVariable, 71 | Const.Message.Attribute.Repeated.JS.setListFunctionName(attribute) 72 | ) 73 | 74 | if (attribute.types.doDiffer) { 75 | builder.addCode( 76 | "%N.map·{ it.%N }.toTypedArray()", 77 | Const.DSL.Attribute.Repeated.propertyName(attribute), 78 | Const.Message.Constructor.JS.PARAM_IMPL 79 | ) 80 | } else { 81 | builder.addCode("%N.toTypedArray()", Const.DSL.Attribute.Repeated.propertyName(attribute)) 82 | } 83 | 84 | builder.addCode(")\n") 85 | } 86 | 87 | override fun addAllEnumValues( 88 | builder: FunSpec.Builder, 89 | message: ProtoMessage, 90 | attribute: ProtoMessageAttribute, 91 | builderVariable: String 92 | ) { 93 | builder.addCode( 94 | "%N.%N(", 95 | builderVariable, 96 | Const.Message.Attribute.Repeated.JS.setListFunctionName(attribute) 97 | ) 98 | 99 | builder.addCode( 100 | "%N.map·{ it.%N }.toTypedArray())\n", 101 | Const.DSL.Attribute.Repeated.propertyName(attribute), 102 | "value" 103 | ) 104 | } 105 | } -------------------------------------------------------------------------------- /grpc-multiplatform-lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | kotlin("multiplatform") 4 | kotlin("native.cocoapods") 5 | id("maven-publish") 6 | } 7 | 8 | group = "io.github.timortel" 9 | version = "0.3.1" 10 | 11 | repositories { 12 | mavenCentral() 13 | google() 14 | } 15 | 16 | kotlin { 17 | android("android") { 18 | publishLibraryVariants("release") 19 | } 20 | js(IR) { 21 | browser() 22 | nodejs() 23 | } 24 | jvm("jvm") 25 | iosX64() 26 | iosArm64() 27 | iosSimulatorArm64() 28 | 29 | cocoapods { 30 | version = "1.0" 31 | summary = "GRPC Kotlin Multiplatform Implementation" 32 | homepage = "https://github.com/TimOrtel/GRPC-Kotlin-Multiplatform" 33 | 34 | 35 | framework { 36 | baseName = "GRPCKotlinMultiplatform" 37 | } 38 | 39 | ios.deploymentTarget = "14.1" 40 | 41 | pod("gRPC-ProtoRPC", version = "~> 1.49.0", moduleName = "GRPCClient") 42 | pod("Protobuf", version = "~> 3.21.6", moduleName = "Protobuf") 43 | //pod("gRPC-Core") 44 | } 45 | 46 | sourceSets { 47 | val commonMain by getting { 48 | dependencies { 49 | implementation(kotlin("stdlib-common")) 50 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") 51 | } 52 | } 53 | val commonTest by getting { 54 | dependencies { 55 | implementation(kotlin("test-common")) 56 | implementation(kotlin("test-annotations-common")) 57 | } 58 | } 59 | 60 | val GRPC = "1.50.0" 61 | val GRPC_KOTLIN = "1.3.0" 62 | val PROTOBUF = "3.21.6" 63 | 64 | val iosJvmCommon by creating { 65 | dependsOn(commonMain) 66 | } 67 | 68 | val androidJvmCommon by creating { 69 | dependsOn(iosJvmCommon) 70 | dependencies { 71 | implementation("com.google.protobuf:protobuf-kotlin:${PROTOBUF}") 72 | } 73 | } 74 | 75 | 76 | val jvmMain by getting { 77 | dependsOn(androidJvmCommon) 78 | dependencies { 79 | implementation("io.grpc:grpc-protobuf:${GRPC}") 80 | implementation("io.grpc:grpc-stub:${GRPC}") 81 | implementation("io.grpc:grpc-kotlin-stub:${GRPC_KOTLIN}") 82 | } 83 | } 84 | 85 | val androidMain by getting { 86 | dependsOn(androidJvmCommon) 87 | dependencies { 88 | implementation("io.grpc:grpc-stub:${GRPC}") 89 | implementation("io.grpc:grpc-protobuf:${GRPC}") 90 | implementation("io.grpc:grpc-kotlin-stub:${GRPC_KOTLIN}") 91 | } 92 | } 93 | 94 | val jsMain by getting { 95 | dependencies { 96 | api(npm("google-protobuf", "^3.19.1")) 97 | api(npm("grpc-web", "^1.3.0")) 98 | api(npm("protobufjs", "^6.11.2")) 99 | } 100 | } 101 | 102 | val iosX64Main by getting 103 | val iosArm64Main by getting 104 | 105 | val iosSimulatorArm64Main by getting 106 | val iosMain by creating { 107 | dependsOn(commonMain) 108 | dependsOn(iosJvmCommon) 109 | iosX64Main.dependsOn(this) 110 | iosArm64Main.dependsOn(this) 111 | iosSimulatorArm64Main.dependsOn(this) 112 | } 113 | } 114 | } 115 | 116 | publishing { 117 | repositories { 118 | mavenLocal() 119 | } 120 | } 121 | 122 | android { 123 | compileSdk = 31 124 | 125 | defaultConfig { 126 | minSdk = 21 127 | targetSdk = 31 128 | } 129 | 130 | compileOptions { 131 | sourceCompatibility = JavaVersion.VERSION_1_8 132 | targetCompatibility = JavaVersion.VERSION_1_8 133 | } 134 | 135 | sourceSets { 136 | named("main") { 137 | manifest.srcFile("src/androidMain/AndroidManifest.xml") 138 | res.srcDirs("src/androidMain/res") 139 | } 140 | } 141 | } 142 | 143 | kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) { 144 | binaries.all { 145 | binaryOptions["memoryModel"] = "experimental" 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /grpc-multiplatform-lib/src/iosMain/kotlin/io/github/timortel/kotlin_multiplatform_grpc_lib/rpc/rpc_implementation.kt: -------------------------------------------------------------------------------- 1 | package io.github.timortel.kotlin_multiplatform_grpc_lib.rpc 2 | 3 | import cocoapods.GRPCClient.GRPCCall2 4 | import cocoapods.GRPCClient.GRPCCallOptions 5 | import cocoapods.GRPCClient.GRPCMutableCallOptions 6 | import cocoapods.GRPCClient.GRPCResponseHandlerProtocol 7 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMChannel 8 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMCode 9 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMStatus 10 | import io.github.timortel.kotlin_multiplatform_grpc_lib.KMStatusException 11 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.KMMessage 12 | import io.github.timortel.kotlin_multiplatform_grpc_lib.message.MessageDeserializer 13 | import kotlinx.coroutines.* 14 | import kotlinx.coroutines.channels.awaitClose 15 | import kotlinx.coroutines.flow.* 16 | import platform.Foundation.NSData 17 | import platform.Foundation.NSError 18 | import platform.darwin.* 19 | import kotlin.coroutines.coroutineContext 20 | import kotlin.coroutines.resume 21 | import kotlin.coroutines.resumeWithException 22 | import kotlin.coroutines.suspendCoroutine 23 | 24 | @Throws(KMStatusException::class, CancellationException::class) 25 | suspend fun unaryCallImplementation( 26 | channel: KMChannel, 27 | callOptions: GRPCCallOptions, 28 | path: String, 29 | request: REQ, 30 | responseDeserializer: MessageDeserializer 31 | ): RES { 32 | val data = request.serialize() 33 | 34 | return suspendCoroutine { continuation -> 35 | val handler = CallHandler( 36 | onReceive = { data: Any -> 37 | val response = responseDeserializer.deserialize(data as NSData) 38 | continuation.resume(response) 39 | }, 40 | onError = { error: NSError -> 41 | val exception = 42 | KMStatusException( 43 | KMStatus(KMCode.getCodeForValue(error.code.toInt()), error.description ?: "No description"), 44 | null 45 | ) 46 | 47 | continuation.resumeWithException(exception) 48 | }, 49 | onDone = {} 50 | ) 51 | 52 | val call = GRPCCall2( 53 | requestOptions = channel.buildRequestOptions(path), 54 | responseHandler = handler, 55 | callOptions = channel.applyToCallOptions(callOptions) 56 | ) 57 | 58 | call.start() 59 | call.writeData(data) 60 | 61 | call.finish() 62 | } 63 | } 64 | 65 | fun serverSideStreamingCallImplementation( 66 | channel: KMChannel, 67 | callOptions: GRPCCallOptions, 68 | path: String, 69 | request: REQ, 70 | responseDeserializer: MessageDeserializer 71 | ): Flow { 72 | return callbackFlow { 73 | val handler = CallHandler( 74 | onReceive = { data -> 75 | val msg = responseDeserializer.deserialize(data as NSData) 76 | 77 | trySend(msg) 78 | }, 79 | onError = { error -> 80 | val exception = KMStatusException( 81 | KMStatus(KMCode.getCodeForValue(error.code.toInt()), error.description ?: "No description"), 82 | null 83 | ) 84 | 85 | close(exception) 86 | }, 87 | onDone = { 88 | close() 89 | } 90 | ) 91 | 92 | val call = GRPCCall2(channel.buildRequestOptions(path), handler, channel.applyToCallOptions(callOptions)) 93 | 94 | call.start() 95 | call.writeData(request.serialize()) 96 | call.finish() 97 | 98 | awaitClose { 99 | call.cancel() 100 | } 101 | } 102 | } 103 | 104 | private class CallHandler( 105 | private val onReceive: (data: Any) -> Unit, 106 | private val onError: (error: NSError) -> Unit, 107 | private val onDone: () -> Unit 108 | ) : 109 | NSObject(), GRPCResponseHandlerProtocol { 110 | 111 | override fun dispatchQueue() = null 112 | 113 | override fun didReceiveData(data: Any) = onReceive(data) 114 | 115 | override fun didCloseWithTrailingMetadata(trailingMetadata: Map?, error: NSError?) { 116 | if (error != null) { 117 | onError(error) 118 | } 119 | onDone() 120 | } 121 | } 122 | --------------------------------------------------------------------------------