├── 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 |
--------------------------------------------------------------------------------