Skip to content

Commit

Permalink
Wasm Support (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr3zee authored Sep 23, 2024
1 parent faf307f commit f633ff4
Show file tree
Hide file tree
Showing 38 changed files with 507 additions and 124 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.platform.TargetPlatform

interface VersionSpecificApi {
fun isJs(platform: TargetPlatform?): Boolean
fun isWasm(platform: TargetPlatform?): Boolean

fun referenceClass(context: IrPluginContext, packageName: String, name: String): IrClassSymbol?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ internal class RPCIrContext(
return pluginContext.platform.isNative()
}

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

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
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.isJs
import org.jetbrains.kotlin.platform.isWasm

object VersionSpecificApiImpl : VersionSpecificApi {
override fun isJs(platform: TargetPlatform?): Boolean {
return platform.isJs()
}

override fun isWasm(platform: TargetPlatform?): Boolean {
return platform.isWasm()
}

override var IrFieldBuilder.isFinalVS: Boolean
get() = isFinal
set(value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ object VersionSpecificApiImpl : VersionSpecificApi {
override fun isJs(platform: TargetPlatform?): Boolean {
return platform.isJs()
}

override fun isWasm(platform: TargetPlatform?): Boolean = false

override var IrFieldBuilder.isFinalVS: Boolean
get() = undefinedAPI()
set(_) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ object VersionSpecificApiImpl : VersionSpecificApi {
override fun isJs(platform: TargetPlatform?): Boolean {
return platform.isJs()
}

override fun isWasm(platform: TargetPlatform?): Boolean = false

override var IrFieldBuilder.isFinalVS: Boolean
get() = undefinedAPI()
set(_) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ object VersionSpecificApiImpl : VersionSpecificApi {
override fun isJs(platform: TargetPlatform?): Boolean {
return platform.isJs()
}

override fun isWasm(platform: TargetPlatform?): Boolean = false

override var IrFieldBuilder.isFinalVS: Boolean
get() = undefinedAPI()
set(_) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ object VersionSpecificApiImpl : VersionSpecificApi {
return platform.isJs()
}

override fun isWasm(platform: TargetPlatform?): Boolean = false

override var IrFieldBuilder.isFinalVS: Boolean
get() = modality == Modality.FINAL
set(value) {
Expand Down
15 changes: 14 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
/*
* 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)
}

applyAtomicfuPlugin()

kotlin {
sourceSets {
commonMain {
Expand All @@ -24,5 +31,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)
)
}
58 changes: 33 additions & 25 deletions gradle-conventions-settings/src/main/kotlin/util/JsTarget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,53 @@

package util

import org.gradle.api.Project
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.getting
import org.gradle.kotlin.dsl.invoke
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
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() {
if (!js) {
return
}

kotlin {
js(IR) {
configureJsAndWasmJsTasks()
}

sourceSets {
jsTest {
dependencies {
implementation(npm("puppeteer", "*"))
}
val jsTest by getting {
puppeteer()
}
}
}
}

private fun Project.configureJsTasks() {
kotlin {
js(IR) {
nodejs {
testTask {
useMocha {
timeout = "10000"
}
}
}
fun KotlinSourceSet.puppeteer() {
dependencies {
implementation(npm("puppeteer", "*"))
}
}

browser {
testTask {
useKarma {
useChromeHeadless()
useConfigDirectory(File(project.rootProject.projectDir, "karma"))
}
}
fun KotlinJsTargetDsl.configureJsAndWasmJsTasks() {
nodejs {
testTask {
useMocha {
timeout = "10000"
}
}
}

binaries.library()
(this as KotlinJsIrTarget).whenBrowserConfigured {
testTask {
useKarma {
useChromeHeadless()
useConfigDirectory(File(project.rootProject.projectDir, "karma"))
}
}
}
}
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
Loading

0 comments on commit f633ff4

Please sign in to comment.