diff --git a/startup/launcher/README.md b/startup/launcher/README.md new file mode 100644 index 00000000000..82206d17dcd --- /dev/null +++ b/startup/launcher/README.md @@ -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" + ] +} +``` diff --git a/startup/launcher/build.gradle b/startup/launcher/build.gradle index f04028c0d4d..e80e77abe88 100644 --- a/startup/launcher/build.gradle +++ b/startup/launcher/build.gradle @@ -4,7 +4,7 @@ plugins { id "application" } -apply from: "$mppJavaProjectPresetPath" +apply from: "$mppJsAndJavaProjectPresetPath" kotlin { sourceSets { @@ -13,7 +13,7 @@ kotlin { api internalProject("micro_utils.startup.plugin") } } - jvmTest { + commonTest { dependencies { implementation libs.kt.coroutines.test } diff --git a/startup/launcher/src/jvmTest/kotlin/StartupLaunchingTests.kt b/startup/launcher/src/commonTest/kotlin/StartupLaunchingTests.kt similarity index 84% rename from startup/launcher/src/jvmTest/kotlin/StartupLaunchingTests.kt rename to startup/launcher/src/commonTest/kotlin/StartupLaunchingTests.kt index f911e0d5234..6eecbdb16e6 100644 --- a/startup/launcher/src/jvmTest/kotlin/StartupLaunchingTests.kt +++ b/startup/launcher/src/commonTest/kotlin/StartupLaunchingTests.kt @@ -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.HelloWorldPlugin import dev.inmo.micro_utils.startup.launcher.defaultJson @@ -6,16 +5,15 @@ import dev.inmo.micro_utils.startup.launcher.start import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.jsonObject -import org.koin.core.context.GlobalContext import kotlin.test.BeforeTest import kotlin.test.Test class StartupLaunchingTests { @BeforeTest fun resetGlobalKoinContext() { - kotlin.runCatching { GlobalContext.stopKoin() } + runCatching { stopKoin() } } - @Test(timeout = 60000L) + @Test fun CheckThatEmptyPluginsListLeadsToEndOfMain() { val emptyJson = defaultJson.encodeToJsonElement( Config.serializer(), @@ -29,7 +27,7 @@ class StartupLaunchingTests { job.join() } } - @Test(timeout = 60000L) + @Test fun CheckThatHelloWorldPluginsListLeadsToEndOfMain() { val emptyJson = defaultJson.encodeToJsonElement( Config.serializer(), diff --git a/startup/launcher/src/commonTest/kotlin/StopKoin.kt b/startup/launcher/src/commonTest/kotlin/StopKoin.kt new file mode 100644 index 00000000000..e385ce93df5 --- /dev/null +++ b/startup/launcher/src/commonTest/kotlin/StopKoin.kt @@ -0,0 +1 @@ +expect fun stopKoin() diff --git a/startup/launcher/src/jsMain/kotlin/CreateStartupPluginAndRegister.kt b/startup/launcher/src/jsMain/kotlin/CreateStartupPluginAndRegister.kt new file mode 100644 index 00000000000..f1a743db4e7 --- /dev/null +++ b/startup/launcher/src/jsMain/kotlin/CreateStartupPluginAndRegister.kt @@ -0,0 +1,14 @@ +package dev.inmo.micro_utils.startup.launcher + +import dev.inmo.micro_utils.startup.plugin.StartPlugin +import dev.inmo.micro_utils.startup.plugin.StartPluginSerializer + +/** + * Creates [T] using [block], register it in [StartPluginSerializer] using its [StartPluginSerializer.registerPlugin] + * and returns created by [block] plugin + */ +inline fun createStartupPluginAndRegister(name: String, block: () -> T): T { + val plugin = block() + StartPluginSerializer.registerPlugin(name, plugin) + return plugin +} diff --git a/startup/launcher/src/jsMain/kotlin/PluginsStarter.kt b/startup/launcher/src/jsMain/kotlin/PluginsStarter.kt new file mode 100644 index 00000000000..37bb2e6f1a8 --- /dev/null +++ b/startup/launcher/src/jsMain/kotlin/PluginsStarter.kt @@ -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) + } +} diff --git a/startup/launcher/src/jsTest/kotlin/stopKoin.kt b/startup/launcher/src/jsTest/kotlin/stopKoin.kt new file mode 100644 index 00000000000..2fb40b1ea64 --- /dev/null +++ b/startup/launcher/src/jsTest/kotlin/stopKoin.kt @@ -0,0 +1,3 @@ +import org.koin.core.context.GlobalContext + +actual fun stopKoin() = GlobalContext.stopKoin() diff --git a/startup/launcher/src/jvmMain/kotlin/Main.kt b/startup/launcher/src/jvmMain/kotlin/Main.kt index 23bb111cefa..3d1f475f366 100644 --- a/startup/launcher/src/jvmMain/kotlin/Main.kt +++ b/startup/launcher/src/jvmMain/kotlin/Main.kt @@ -26,7 +26,7 @@ import java.io.File */ suspend fun main(args: Array) { - KSLog.default = KSLog("ServerLauncher") + KSLog.default = KSLog("Launcher") val (configPath) = args val file = File(configPath) KSLog.i("Start read config from ${file.absolutePath}") diff --git a/startup/launcher/src/jvmTest/kotlin/stopKoin.kt b/startup/launcher/src/jvmTest/kotlin/stopKoin.kt new file mode 100644 index 00000000000..2fb40b1ea64 --- /dev/null +++ b/startup/launcher/src/jvmTest/kotlin/stopKoin.kt @@ -0,0 +1,3 @@ +import org.koin.core.context.GlobalContext + +actual fun stopKoin() = GlobalContext.stopKoin() diff --git a/startup/plugin/build.gradle b/startup/plugin/build.gradle index 044c982f351..64b4acd8103 100644 --- a/startup/plugin/build.gradle +++ b/startup/plugin/build.gradle @@ -16,5 +16,10 @@ kotlin { api project(":micro_utils.coroutines") } } + jsMain { + dependencies { + api libs.uuid + } + } } } diff --git a/startup/plugin/src/jsMain/kotlin/StartPluginSerializer.kt b/startup/plugin/src/jsMain/kotlin/StartPluginSerializer.kt index d12c722b8ce..b8eda302903 100644 --- a/startup/plugin/src/jsMain/kotlin/StartPluginSerializer.kt +++ b/startup/plugin/src/jsMain/kotlin/StartPluginSerializer.kt @@ -1,20 +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 { - override val descriptor: SerialDescriptor - get() = TODO("Not yet implemented") + private val registeredPlugins = mutableMapOf() + private val registeredPluginsByPlugin = mutableMapOf() + override val descriptor: SerialDescriptor = String.serializer().descriptor override fun deserialize(decoder: Decoder): StartPlugin { - TODO("Not yet implemented") + val name = decoder.decodeString() + return registeredPlugins[name] ?: error("Unable to find startup plugin for $name") } override fun serialize(encoder: Encoder, value: StartPlugin) { - TODO("Not yet implemented") + 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 + } }