Skip to content

Commit

Permalink
Added Wasm support (No Wasi for kRPC)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr3zee committed Sep 11, 2024
1 parent 81bed3c commit 5a50657
Show file tree
Hide file tree
Showing 27 changed files with 384 additions and 70 deletions.
8 changes: 7 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@

import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import util.kotlinVersionParsed
import util.libs

plugins {
alias(libs.plugins.serialization) apply false
alias(libs.plugins.kotlinx.rpc) apply false
alias(libs.plugins.atomicfu) apply false
alias(libs.plugins.conventions.kover)
alias(libs.plugins.conventions.gradle.doctor)
alias(libs.plugins.binary.compatibility.validator)

if (libs.versions.atomicfu.get() >= "0.24.0") {
alias(libs.plugins.atomicfu.new)
} else {
alias(libs.plugins.atomicfu.old)
}
}

// useful for dependencies introspection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.isVararg
import org.jetbrains.kotlin.ir.util.properties
import org.jetbrains.kotlin.platform.isWasm
import org.jetbrains.kotlin.platform.konan.isNative
import org.jetbrains.kotlin.types.Variance

Expand Down Expand Up @@ -131,6 +132,10 @@ internal class RPCIrContext(
return pluginContext.platform.isNative()
}

fun isWasmTarget(): Boolean {
return pluginContext.platform.isWasm()
}

val functions = Functions()

inner class Functions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1209,10 +1209,10 @@ internal class RPCStubGenerator(
}
}

// Associated object annotation works on JS and Native platforms.
// Associated object annotation works on JS, WASM, and Native platforms.
// See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/find-associated-object.html
private fun addAssociatedObjectAnnotationIfPossible() {
if (ctx.isJsTarget() || ctx.isNativeTarget()) {
if (ctx.isJsTarget() || ctx.isNativeTarget() || ctx.isWasmTarget()) {
addAssociatedObjectAnnotation()
}
}
Expand Down
17 changes: 16 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

import util.applyAtomicfuPlugin

/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
alias(libs.plugins.conventions.kmp)
alias(libs.plugins.atomicfu)
alias(libs.plugins.kotlinx.rpc)
alias(libs.plugins.serialization)
}

applyAtomicfuPlugin()

kotlin {
sourceSets {
commonMain {
Expand All @@ -24,5 +33,11 @@ kotlin {
implementation(libs.kotlin.js.wrappers)
}
}

wasmJsMain {
dependencies {
implementation(libs.kotlinx.browser)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

@file:Suppress("detekt.MatchingDeclarationName")

package kotlinx.rpc.internal

import kotlinx.rpc.internal.utils.InternalRPCApi
import kotlin.reflect.KClass

@InternalRPCApi
public actual val KClass<*>.qualifiedClassNameOrNull: String?
get() = toString()

@InternalRPCApi
public actual val KClass<*>.typeName: String?
get() = qualifiedClassNameOrNull
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

@file:Suppress("detekt.MatchingDeclarationName")

package kotlinx.rpc.internal

import kotlinx.rpc.RPC
import kotlinx.rpc.internal.utils.InternalRPCApi
import kotlin.reflect.AssociatedObjectKey
import kotlin.reflect.ExperimentalAssociatedObjects
import kotlin.reflect.KClass
import kotlin.reflect.findAssociatedObject

@InternalRPCApi
@AssociatedObjectKey
@OptIn(ExperimentalAssociatedObjects::class)
@Target(AnnotationTarget.CLASS)
public annotation class WithRPCStubObject(
@Suppress("unused")
val stub: KClass<out RPCStubObject<out RPC>>
)

@OptIn(ExperimentalAssociatedObjects::class)
@InternalRPCApi
public actual fun <R : Any> findRPCStubProvider(kClass: KClass<*>, resultKClass: KClass<R>): R {
val associatedObject = kClass.findAssociatedObject<WithRPCStubObject>()
?: internalError("Unable to find $kClass associated object")

if (resultKClass.isInstance(associatedObject)) {
@Suppress("UNCHECKED_CAST")
return associatedObject as R
}

internalError(
"Located associated object is not of desired type $resultKClass, " +
"instead found $associatedObject of class " +
(associatedObject::class.qualifiedClassNameOrNull ?: associatedObject::class)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

@file:Suppress("detekt.MatchingDeclarationName")

package kotlinx.rpc.internal

import kotlinx.rpc.internal.utils.InternalRPCApi
import kotlin.reflect.KClass

@InternalRPCApi
public actual val KClass<*>.qualifiedClassNameOrNull: String?
get() = toString()

@InternalRPCApi
public actual val KClass<*>.typeName: String?
get() = qualifiedClassNameOrNull
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

@file:Suppress("detekt.MatchingDeclarationName")

package kotlinx.rpc.internal

import kotlinx.rpc.RPC
import kotlinx.rpc.internal.utils.InternalRPCApi
import kotlin.reflect.AssociatedObjectKey
import kotlin.reflect.ExperimentalAssociatedObjects
import kotlin.reflect.KClass
import kotlin.reflect.findAssociatedObject

@InternalRPCApi
@AssociatedObjectKey
@OptIn(ExperimentalAssociatedObjects::class)
@Target(AnnotationTarget.CLASS)
public annotation class WithRPCStubObject(
@Suppress("unused")
val stub: KClass<out RPCStubObject<out RPC>>
)

@OptIn(ExperimentalAssociatedObjects::class)
@InternalRPCApi
public actual fun <R : Any> findRPCStubProvider(kClass: KClass<*>, resultKClass: KClass<R>): R {
val associatedObject = kClass.findAssociatedObject<WithRPCStubObject>()
?: internalError("Unable to find $kClass associated object")

if (resultKClass.isInstance(associatedObject)) {
@Suppress("UNCHECKED_CAST")
return associatedObject as R
}

internalError(
"Located associated object is not of desired type $resultKClass, " +
"instead found $associatedObject of class " +
(associatedObject::class.qualifiedClassNameOrNull ?: associatedObject::class)
)
}
54 changes: 43 additions & 11 deletions gradle-conventions-settings/src/main/kotlin/util/JsTarget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,50 @@

package util

import org.gradle.api.Project
import org.gradle.api.Action
import org.gradle.kotlin.dsl.invoke
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import java.io.File

fun Project.configureJs() {
configureJsTasks()
fun ProjectKotlinConfig.configureJsAndWasmJs() {
configureJsAndWasmJsTasks()

kotlin {
sourceSets {
jsTest {
dependencies {
implementation(npm("puppeteer", "*"))
if (js) {
jsTest {
puppeteer()
}
}

if (wasmJs) {
wasmJsMain {
dependencies {
implementation(libs.kotlinx.browser)
}
}

wasmJsTest {
puppeteer()
}
}
}
}
}

private fun Project.configureJsTasks() {
private fun KotlinSourceSet.puppeteer() {
dependencies {
implementation(npm("puppeteer", "*"))
}
}

private fun ProjectKotlinConfig.configureJsAndWasmJsTasks() {
kotlin {
js(IR) {
jsAnsWasmJs(this@configureJsAndWasmJsTasks) {
nodejs {
testTask {
useMocha {
Expand All @@ -33,16 +56,25 @@ private fun Project.configureJsTasks() {
}
}

browser {
(this as KotlinJsIrTarget).whenBrowserConfigured {
testTask {
useKarma {
useChromeHeadless()
useConfigDirectory(File(project.rootProject.projectDir, "karma"))
}
}
}

binaries.library()
}
}
}

@OptIn(ExperimentalWasmDsl::class)
private fun KotlinMultiplatformExtension.jsAnsWasmJs(
config: ProjectKotlinConfig,
configure: Action<KotlinJsTargetDsl>,
) {
listOfNotNull(
if (config.js) js(IR) else null,
if (config.wasmJs) wasmJs() else null,
).forEach { configure(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.gradle.api.Project
import kotlin.reflect.KProperty

class OptionalProperty(private val target: Project) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Boolean {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return getValue("kotlinx.rpc.${property.name}")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package util

import org.gradle.api.Project
import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.provideDelegate

class ProjectKotlinConfig(
project: Project,
val kotlinVersion: KotlinVersion,
val jvm: Boolean = true,
val js: Boolean = true,
val wasmJs: Boolean = true,
val wasmWasi: Boolean = true,
val native: Boolean = true,
) : Project by project

fun Project.withKotlinConfig(configure: ProjectKotlinConfig.() -> Unit) {
val kotlinVersion: KotlinVersion by extra
val excludeJvm: Boolean by optionalProperty()
val excludeJs: Boolean by optionalProperty()
val excludeWasmJs: Boolean by optionalProperty()
val excludeWasmWasi: Boolean by optionalProperty()
val excludeNative: Boolean by optionalProperty()

ProjectKotlinConfig(
project = project,
kotlinVersion = kotlinVersion,
jvm = !excludeJvm,
js = !excludeJs,
wasmJs = !excludeWasmJs,
wasmWasi = !excludeWasmWasi,
native = !excludeNative,
).configure()
}
Loading

0 comments on commit 5a50657

Please sign in to comment.