Merge pull request #212 from InsanusMokrassar/0.16.2

0.16.2
This commit is contained in:
InsanusMokrassar 2022-12-16 13:59:47 +06:00 committed by GitHub
commit 96fdff6ffd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 273 additions and 14 deletions

View File

@ -1,5 +1,12 @@
# Changelog # Changelog
## 0.16.2
* `Versions`:
* `Compose`: `1.2.1` -> `1.2.2`
* `Startup`:
* Module become available on `JS` target
## 0.16.1 ## 0.16.1
* `Coroutines`: * `Coroutines`:

View File

@ -23,6 +23,7 @@ allprojects {
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"

View File

@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.16.1 version=0.16.2
android_code_version=169 android_code_version=170

View File

@ -6,7 +6,7 @@ kt-coroutines = "1.6.4"
kslog = "0.5.4" kslog = "0.5.4"
jb-compose = "1.2.1" jb-compose = "1.2.2"
jb-exposed = "0.41.1" jb-exposed = "0.41.1"
jb-dokka = "1.7.20" jb-dokka = "1.7.20"

View File

@ -0,0 +1,49 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js (IR) {
browser()
nodejs()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@ -0,0 +1,92 @@
# Startup Plugin Launcher
This module contains tools to start your plugin system.
## Config
Base config is pretty simple:
```json
{
"plugins": [
"dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin"
]
}
```
So, `"dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin"` is the fully qualified name of plugin you wish to be
included in the server.
> JS note: In JS there are no opportunity to determine object type by its full name. Because of it, in JS developers
> should prefer to use `Config` in their kotlin code directly instead of json config passing. More info see in [JS](#js)
> section
## JVM
For JVM target you may use main class by path: `dev.inmo.micro_utils.startup.launcher.MainKt`
It is expected, that you will pass the main ONE argument with path to the config json. Sample of launching:
```bash
./gradlew run --args="sample.config.json"
```
Content of `sample.config.json` described in [Config](#config) section.
You may build runnable app using:
```bash
./gradlew assembleDist
```
In that case in `build/distributions` folder you will be able to find zip and tar files with all required
tools for application running (via their `bin/app_name` binary). In that case yoy will not need to pass
`--args=...` and launch will look like `./bin/app_name sample.config.json`
## JS
In JS for starting of your plugins app, you should use `PluginsStarter` in your code:
```kotlin
PluginsStarter.startPlugins(
Config(HelloWorldPlugin)
)
```
`Config` here is deserialized variant from [Config](#config) section. As was said in [Config](#config) section, in JS
there is no way to find classes/objects by their full qualifiers. Because of it you should use some way to register your
plugins in `StartPluginSerializer` or use the code like in the snippet above: there plugins will be registered
automatically.
In case you wish to register your plugins manually and run server from config, you should use one of the ways to register
plugin on start.
Sample with `EagerInitialization`: [Kotlin JS doc about lazy initialization](https://kotlinlang.org/docs/js-ir-compiler.html#incremental-compilation-for-development-binaries),
[@EagerInitialization docs](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/-eager-initialization/):
```kotlin
@ExperimentalStdlibApi
@EagerInitialization
val plugin = createStartupPluginAndRegister("PluginNameToUseInConfig") {
// Your plugin creation. For example:
HelloWorldPlugin
}
```
So, in that case you will be able to load plugins list as `JsonObject` from anywhere and start plugins app with it:
```kotlin
PluginsStarter.startPlugins(
jsonObject
)
```
It will load `HelloWorldPlugin` if `jsonObject` have next content:
```json
{
"plugins": [
"PluginNameToUseInConfig"
]
}
```

View File

@ -4,7 +4,7 @@ plugins {
id "application" id "application"
} }
apply from: "$mppJavaProjectPresetPath" apply from: "$mppJsAndJavaProjectPresetPath"
kotlin { kotlin {
sourceSets { sourceSets {
@ -13,7 +13,7 @@ kotlin {
api internalProject("micro_utils.startup.plugin") api internalProject("micro_utils.startup.plugin")
} }
} }
jvmTest { commonTest {
dependencies { dependencies {
implementation libs.kt.coroutines.test implementation libs.kt.coroutines.test
} }

View File

@ -3,7 +3,7 @@ package dev.inmo.micro_utils.startup.launcher
import dev.inmo.kslog.common.i import dev.inmo.kslog.common.i
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import org.koin.core.KoinApplication import org.koin.core.KoinApplication
import org.koin.core.context.GlobalContext import org.koin.core.context.startKoin
import org.koin.dsl.module import org.koin.dsl.module
/** /**
@ -23,7 +23,7 @@ suspend fun start(rawConfig: JsonObject) {
} }
) )
logger.i("Modules loaded") logger.i("Modules loaded")
GlobalContext.startKoin(koinApp) startKoin(koinApp)
logger.i("Koin started") logger.i("Koin started")
startPlugin(koinApp.koin) startPlugin(koinApp.koin)
logger.i("App has been setup") logger.i("App has been setup")

View File

@ -1,4 +1,3 @@
import dev.inmo.micro_utils.coroutines.launchSynchronously
import dev.inmo.micro_utils.startup.launcher.Config import dev.inmo.micro_utils.startup.launcher.Config
import dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin import dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin
import dev.inmo.micro_utils.startup.launcher.defaultJson import dev.inmo.micro_utils.startup.launcher.defaultJson
@ -6,16 +5,16 @@ import dev.inmo.micro_utils.startup.launcher.start
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import org.koin.core.context.GlobalContext import org.koin.core.context.stopKoin
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.Test import kotlin.test.Test
class StartupLaunchingTests { class StartupLaunchingTests {
@BeforeTest @BeforeTest
fun resetGlobalKoinContext() { fun resetGlobalKoinContext() {
kotlin.runCatching { GlobalContext.stopKoin() } runCatching { stopKoin() }
} }
@Test(timeout = 60000L) @Test
fun CheckThatEmptyPluginsListLeadsToEndOfMain() { fun CheckThatEmptyPluginsListLeadsToEndOfMain() {
val emptyJson = defaultJson.encodeToJsonElement( val emptyJson = defaultJson.encodeToJsonElement(
Config.serializer(), Config.serializer(),
@ -29,7 +28,7 @@ class StartupLaunchingTests {
job.join() job.join()
} }
} }
@Test(timeout = 60000L) @Test
fun CheckThatHelloWorldPluginsListLeadsToEndOfMain() { fun CheckThatHelloWorldPluginsListLeadsToEndOfMain() {
val emptyJson = defaultJson.encodeToJsonElement( val emptyJson = defaultJson.encodeToJsonElement(
Config.serializer(), Config.serializer(),

View File

@ -0,0 +1,33 @@
package dev.inmo.micro_utils.startup.launcher
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.i
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
object PluginsStarter {
init {
KSLog.default = KSLog("Launcher")
}
/**
* It is expected that you have registered all the [dev.inmo.micro_utils.startup.plugin.StartPlugin]s of your JS
* app inside of [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer] using its
* [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer.registerPlugin] method
*/
suspend fun startPlugins(json: JsonObject) {
start(json)
}
/**
* Will convert [config] to [JsonObject] with auto registration of [dev.inmo.micro_utils.startup.plugin.StartPlugin]s
* in [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer]
*/
suspend fun startPlugins(config: Config) {
KSLog.i("Start convert config to JSON")
val json = defaultJson.encodeToJsonElement(Config.serializer(), config).jsonObject
KSLog.i("Config has been read")
start(json)
}
}

View File

@ -26,7 +26,7 @@ import java.io.File
*/ */
suspend fun main(args: Array<String>) { suspend fun main(args: Array<String>) {
KSLog.default = KSLog("ServerLauncher") KSLog.default = KSLog("Launcher")
val (configPath) = args val (configPath) = args
val file = File(configPath) val file = File(configPath)
KSLog.i("Start read config from ${file.absolutePath}") KSLog.i("Start read config from ${file.absolutePath}")

View File

@ -3,7 +3,7 @@ plugins {
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
} }
apply from: "$mppJavaProjectPresetPath" apply from: "$mppJsAndJavaProjectPresetPath"
kotlin { kotlin {
sourceSets { sourceSets {
@ -16,5 +16,10 @@ kotlin {
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")
} }
} }
jsMain {
dependencies {
api libs.uuid
}
}
} }
} }

View File

@ -0,0 +1,38 @@
package dev.inmo.micro_utils.startup.plugin
import com.benasher44.uuid.uuid4
import kotlin.reflect.KClass
/**
* Creates [T] using [block], register it in [StartPluginSerializer] using its [StartPluginSerializer.registerPlugin]
* and returns created by [block] plugin
*
* @param name Will be used as a key for registration in [StartPluginSerializer] and will be passed to the [block] as
* parameter
*/
inline fun <T : StartPlugin> createStartupPluginAndRegister(
name: String = uuid4().toString(),
block: (String) -> T
): T {
val plugin = block(name)
StartPluginSerializer.registerPlugin(name, plugin)
return plugin
}
/**
* Creates [T] using [block], register it in [StartPluginSerializer] using its [StartPluginSerializer.registerPlugin]
* and returns created by [block] plugin
*/
inline fun <T : StartPlugin> createStartupPluginAndRegister(
kClass: KClass<T>,
name: String = uuid4().toString(),
block: (String) -> T
): T = createStartupPluginAndRegister("${kClass.simpleName}_$name", block)
/**
* Creates [T] using [block], register it in [StartPluginSerializer] using its [StartPluginSerializer.registerPlugin]
* and returns created by [block] plugin
*/
inline fun <reified T : StartPlugin> createStartupPluginAndRegister(
block: (String) -> T
): T = createStartupPluginAndRegister(T::class, uuid4().toString(), block)

View File

@ -0,0 +1,35 @@
package dev.inmo.micro_utils.startup.plugin
import com.benasher44.uuid.uuid4
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
actual object StartPluginSerializer : KSerializer<StartPlugin> {
private val registeredPlugins = mutableMapOf<String, StartPlugin>()
private val registeredPluginsByPlugin = mutableMapOf<StartPlugin, String>()
override val descriptor: SerialDescriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): StartPlugin {
val name = decoder.decodeString()
return registeredPlugins[name] ?: error("Unable to find startup plugin for $name")
}
override fun serialize(encoder: Encoder, value: StartPlugin) {
val name = registeredPluginsByPlugin[value] ?: uuid4().toString().also {
registeredPlugins[it] = value
registeredPluginsByPlugin[value] = it
}
encoder.encodeString(name)
}
/**
* Register plugin inside of this [KSerializer]. Since plugin has been registered, you may use its [name] in any
* serialized [dev.inmo.micro_utils.startup.launcher.Config] to retrieve [plugin] you passed here
*/
fun registerPlugin(name: String, plugin: StartPlugin) {
registeredPlugins[name] = plugin
}
}