From e71460ba4680aae2ced431edfc38a63a99dbb441 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Thu, 28 Aug 2025 14:39:54 +0200 Subject: [PATCH 1/5] added `descriptors` option for libraries.json files. This can contain anything a normal descriptor file can, like introduce dependencies, run init blocks etc. This allows libraries to adapt how they behave in notebooks without having to depend on the jupyter api --- .../kotlinx/jupyter/api/libraries/Scanning.kt | 5 ++++ .../plugin/KotlinJupyterPluginExtension.kt | 8 ++++++ .../plugin/tasks/JupyterApiResourcesTask.kt | 18 ++++++++++--- .../jupyter/api/plugin/util/LibrariesUtil.kt | 1 + .../jupyter/libraries/LibrariesScanner.kt | 27 ++++++++++++++++--- .../kotlinx/jupyter/libraries/Parsing.kt | 13 ++++++++- 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/Scanning.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/Scanning.kt index 9d16d2385..9cbf898ff 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/Scanning.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/libraries/Scanning.kt @@ -1,6 +1,7 @@ package org.jetbrains.kotlinx.jupyter.api.libraries import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject import org.jetbrains.kotlinx.jupyter.api.TypeName /** @@ -48,9 +49,13 @@ data class LibrariesProducerDeclaration( /** * Serialized form of this class instance is a correct content of * [KOTLIN_JUPYTER_LIBRARIES_FILE_NAME] file, and vice versa. + * + * @param descriptors List of library descriptor JSON objects adhering to + * `org.jetbrains.kotlinx.jupyter.libraries.LibraryDescriptor`. */ @Serializable data class LibrariesScanResult( val definitions: List = emptyList(), val producers: List = emptyList(), + val descriptors: List = emptyList(), ) diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt index b3cfdb800..349446004 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt @@ -15,11 +15,13 @@ class KotlinJupyterPluginExtension( private val libraryProducers: MutableSet = mutableSetOf() private val libraryDefinitions: MutableSet = mutableSetOf() + private val libraryDescriptors: MutableSet = mutableSetOf() internal val libraryFqns get() = LibrariesScanResult( definitions = libraryDefinitions, producers = libraryProducers, + descriptors = libraryDescriptors, ) internal fun addDependenciesIfNeeded() { @@ -42,6 +44,7 @@ class KotlinJupyterPluginExtension( /** * Add adding library integrations by specifying their fully qualified names */ + @Suppress("unused") fun integrations(action: IntegrationsSpec.() -> Unit) { IntegrationsSpec().apply(action) } @@ -56,6 +59,11 @@ class KotlinJupyterPluginExtension( fun definition(className: String) { libraryDefinitions.add(FQNAware(className)) } + + @Suppress("unused") + fun descriptor(descriptor: String) { + libraryDescriptors.add(descriptor) + } } companion object { diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt index 878a569d1..f3b43497b 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt @@ -30,6 +30,13 @@ open class JupyterApiResourcesTask : DefaultTask() { @Input var libraryDefinitions: List = emptyList() + /** + * List of JSON library descriptors in the + * [library descriptor format](https://github.com/Kotlin/kotlin-jupyter/blob/master/docs/libraries.md#creating-a-library-descriptor). + */ + @Input + var libraryDescriptors: List = emptyList() + @OutputDirectory val outputDir: File = project.getBuildDirectory().resolve("jupyterProcessedResources") @@ -42,6 +49,7 @@ open class JupyterApiResourcesTask : DefaultTask() { LibrariesScanResult( definitions = libraryDefinitions.map { FQNAware(it) }.toSet(), producers = libraryProducers.map { FQNAware(it) }.toSet(), + descriptors = libraryDescriptors.toSet(), ) val resultObject = @@ -70,14 +78,16 @@ open class JupyterApiResourcesTask : DefaultTask() { } return LibrariesScanResult( - fqns("definitions"), - fqns("producers"), + definitions = fqns("definitions"), + producers = fqns("producers"), + descriptors = emptySet(), ) } operator fun LibrariesScanResult.plus(other: LibrariesScanResult): LibrariesScanResult = LibrariesScanResult( - definitions + other.definitions, - producers + other.producers, + definitions = definitions + other.definitions, + producers = producers + other.producers, + descriptors = descriptors + other.descriptors, ) } diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/util/LibrariesUtil.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/util/LibrariesUtil.kt index 84bcae237..7b753750e 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/util/LibrariesUtil.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/util/LibrariesUtil.kt @@ -7,6 +7,7 @@ data class FQNAware( class LibrariesScanResult( val definitions: Set = emptySet(), val producers: Set = emptySet(), + val descriptors: Set = emptySet(), ) val emptyScanResult = LibrariesScanResult() diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt index fb2f3d13e..903ebd33d 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt @@ -1,6 +1,7 @@ package org.jetbrains.kotlinx.jupyter.libraries import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost import org.jetbrains.kotlinx.jupyter.api.LibraryLoader import org.jetbrains.kotlinx.jupyter.api.Notebook @@ -27,6 +28,7 @@ class LibrariesScanner( private val processedFQNs = mutableSetOf() private val discardedFQNs = mutableSetOf() + private val processedDescriptors = mutableSetOf() private fun > Iterable.filterNamesToLoad( host: KotlinKernelHost, @@ -46,10 +48,14 @@ class LibrariesScanner( discardedFQNs.add(typeName) false } + null -> typeName !in discardedFQNs && processedFQNs.add(typeName) } } + private fun Iterable.filterDescriptorsToLoad() = + filter { processedDescriptors.add(it) } + fun addLibrariesFromClassLoader( classLoader: ClassLoader, host: KotlinKernelHost, @@ -77,23 +83,28 @@ class LibrariesScanner( } } + private val jsonParser = Json { ignoreUnknownKeys = true } + private fun scanForLibraries( classLoader: ClassLoader, host: KotlinKernelHost, integrationTypeNameRules: List> = listOf(), ): LibrariesScanResult { val results = - classLoader.getResources("$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME").toList().map { url -> - val contents = url.readText() - Json.decodeFromString(contents) - } + classLoader.getResources("$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME").toList() + .map { url -> + val contents = url.readText() + jsonParser.decodeFromString(contents) + } val definitions = mutableListOf() val producers = mutableListOf() + val descriptors = mutableListOf() for (result in results) { definitions.addAll(result.definitions) producers.addAll(result.producers) + descriptors.addAll(result.descriptors) } fun > Iterable.filterNames() = filterNamesToLoad(host, integrationTypeNameRules) @@ -101,6 +112,7 @@ class LibrariesScanner( return LibrariesScanResult( definitions.filterNames(), producers.filterNames(), + descriptors.filterDescriptorsToLoad(), ) } @@ -140,6 +152,13 @@ class LibrariesScanner( } } } + + scanResult.descriptors.forEach { + val descriptor = parseLibraryDescriptor(it) + val definition = descriptor.convertToDefinition(arguments = emptyList()) + definitions.add(definition) + } + return definitions } diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt index 4253c2655..bbd6a3b18 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt @@ -2,16 +2,27 @@ package org.jetbrains.kotlinx.jupyter.libraries import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromJsonElement import org.jetbrains.kotlinx.jupyter.api.exceptions.ReplException import org.jetbrains.kotlinx.jupyter.protocol.api.KernelLoggerFactory +private val jsonParser = Json { ignoreUnknownKeys = true } + fun parseLibraryDescriptor(json: String): LibraryDescriptor = try { - Json.decodeFromString(json) + jsonParser.decodeFromString(json) } catch (e: SerializationException) { throw ReplException("Error during library deserialization. Library descriptor text:\n$json", e) } +fun parseLibraryDescriptor(jsonObject: JsonObject): LibraryDescriptor = + try { + jsonParser.decodeFromJsonElement(jsonObject) + } catch (e: SerializationException) { + throw ReplException("Error during library deserialization. Library descriptor text:\n$jsonObject", e) + } + fun parseLibraryDescriptors( loggerFactory: KernelLoggerFactory, libJsons: Map, From a3a7fedae77d7f65815d96038a91aa6e935a3541 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Thu, 28 Aug 2025 14:49:47 +0200 Subject: [PATCH 2/5] updated README for `descriptors` option for libraries.json files. --- docs/libraries.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/libraries.md b/docs/libraries.md index f10da335b..3c267510d 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -933,13 +933,20 @@ resources. This file should contain FQNs of all integration classes in the JSON { "fqn": "org.jetbrains.kotlinx.jupyter.example.GettingStartedIntegration" } - ] + ], + "descriptors": [] } ``` Classes derived from the `LibraryDefinition` interface should be added to the `definitions` array. Classes derived from the `LibraryDefinitionProducer` interface should be added to the `producers` array. +Additionally, you're allowed to add full custom library descriptors to the `descriptors` array. +This is an advanced option that allows you to load additional dependencies or add additional imports using the +[library descriptor API](#creating-a-library-descriptor). +This can be particularly useful for libraries that cannot depend on the jupyter-api artifact but still want to modify +the kernel behavior when loaded. + For more information, see: * [Libraries repository](https://github.com/Kotlin/kotlin-jupyter-libraries) From b8c9f41e2b1097f3004ca492076fc3b1380c436e Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Fri, 29 Aug 2025 12:34:00 +0200 Subject: [PATCH 3/5] fixes for `descriptors` option for libraries.json files based on feedback --- docs/libraries.md | 19 ++++++++++++++ .../jupyter/api/plugin/ApiGradlePlugin.kt | 8 ------ .../plugin/KotlinJupyterPluginExtension.kt | 3 ++- .../plugin/tasks/JupyterApiResourcesTask.kt | 26 +------------------ .../jupyter/libraries/LibrariesScanner.kt | 17 +++++++----- .../kotlinx/jupyter/libraries/Parsing.kt | 20 +++++++++----- 6 files changed, 45 insertions(+), 48 deletions(-) diff --git a/docs/libraries.md b/docs/libraries.md index 3c267510d..d484caaa9 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -947,6 +947,25 @@ This is an advanced option that allows you to load additional dependencies or ad This can be particularly useful for libraries that cannot depend on the jupyter-api artifact but still want to modify the kernel behavior when loaded. +For example: + +```json +{ + "definitions": [], + "producers": [], + "descriptors": [ + { + "dependencies": [ + "my.library:my-library-jupyter-integration:1.0.0" + ], + "init": [ + "DISPLAY(\"Implicitly loading Jupyter integration for MyLibrary...\")" + ] + } + ] +} +``` + For more information, see: * [Libraries repository](https://github.com/Kotlin/kotlin-jupyter-libraries) diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/ApiGradlePlugin.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/ApiGradlePlugin.kt index 7cd6ab250..09781e5c5 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/ApiGradlePlugin.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/ApiGradlePlugin.kt @@ -12,7 +12,6 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask import org.jetbrains.kotlinx.jupyter.api.plugin.util.addMavenCentralIfDoesNotExist -import org.jetbrains.kotlinx.jupyter.api.plugin.util.getBuildDirectory import org.jetbrains.kotlinx.jupyter.api.plugin.util.whenAdded class ApiGradlePlugin : Plugin { @@ -20,9 +19,6 @@ class ApiGradlePlugin : Plugin { with(target) { val pluginExtension = KotlinJupyterPluginExtension(target) extensions.add(KotlinJupyterPluginExtension.NAME, pluginExtension) - - val jupyterBuildPath = getBuildDirectory().resolve(FQNS_PATH) - jupyterBuildPath.mkdirs() pluginExtension.addDependenciesIfNeeded() repositories { @@ -63,8 +59,4 @@ class ApiGradlePlugin : Plugin { } } } - - companion object { - const val FQNS_PATH = "generated/jupyter/fqns" - } } diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt index 349446004..add4fd07c 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt @@ -1,6 +1,7 @@ package org.jetbrains.kotlinx.jupyter.api.plugin import org.gradle.api.Project +import org.intellij.lang.annotations.Language import org.jetbrains.kotlinx.jupyter.api.plugin.util.FQNAware import org.jetbrains.kotlinx.jupyter.api.plugin.util.LibrariesScanResult import org.jetbrains.kotlinx.jupyter.api.plugin.util.configureDependency @@ -61,7 +62,7 @@ class KotlinJupyterPluginExtension( } @Suppress("unused") - fun descriptor(descriptor: String) { + fun descriptor(@Language("JSON") descriptor: String) { libraryDescriptors.add(descriptor) } } diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt index f3b43497b..c6fdbebbb 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/JupyterApiResourcesTask.kt @@ -5,7 +5,6 @@ import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction -import org.jetbrains.kotlinx.jupyter.api.plugin.ApiGradlePlugin import org.jetbrains.kotlinx.jupyter.api.plugin.KotlinJupyterPluginExtension import org.jetbrains.kotlinx.jupyter.api.plugin.util.FQNAware import org.jetbrains.kotlinx.jupyter.api.plugin.util.LibrariesScanResult @@ -52,10 +51,7 @@ open class JupyterApiResourcesTask : DefaultTask() { descriptors = libraryDescriptors.toSet(), ) - val resultObject = - jupyterExtensionScanResult + - taskScanResult + - getScanResultFromAnnotations() + val resultObject = jupyterExtensionScanResult + taskScanResult val json = Gson().toJson(resultObject) val jupyterDir = outputDir.resolve("META-INF/kotlin-jupyter-libraries") @@ -64,26 +60,6 @@ open class JupyterApiResourcesTask : DefaultTask() { libFile.writeText(json) } - private fun getScanResultFromAnnotations(): LibrariesScanResult { - val path = project.getBuildDirectory().resolve(ApiGradlePlugin.FQNS_PATH) - - fun fqns(name: String): Set { - val file = path.resolve(name) - if (!file.exists()) return emptySet() - return file - .readLines() - .filter { it.isNotBlank() } - .map { FQNAware(it) } - .toSet() - } - - return LibrariesScanResult( - definitions = fqns("definitions"), - producers = fqns("producers"), - descriptors = emptySet(), - ) - } - operator fun LibrariesScanResult.plus(other: LibrariesScanResult): LibrariesScanResult = LibrariesScanResult( definitions = definitions + other.definitions, diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt index 903ebd33d..fdd9b49ac 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/LibrariesScanner.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlinx.jupyter.api.libraries.LibrariesInstantiable import org.jetbrains.kotlinx.jupyter.api.libraries.LibrariesProducerDeclaration import org.jetbrains.kotlinx.jupyter.api.libraries.LibrariesScanResult import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinition +import org.jetbrains.kotlinx.jupyter.api.libraries.Variable import org.jetbrains.kotlinx.jupyter.config.errorForUser import org.jetbrains.kotlinx.jupyter.protocol.api.KernelLoggerFactory import org.jetbrains.kotlinx.jupyter.protocol.api.getLogger @@ -25,10 +26,11 @@ class LibrariesScanner( loggerFactory: KernelLoggerFactory, ) : LibraryLoader { private val logger = loggerFactory.getLogger(this::class) + private val jsonParser = Json { ignoreUnknownKeys = true } private val processedFQNs = mutableSetOf() private val discardedFQNs = mutableSetOf() - private val processedDescriptors = mutableSetOf() + private val processedDescriptorHashes = mutableSetOf() private fun > Iterable.filterNamesToLoad( host: KotlinKernelHost, @@ -53,8 +55,8 @@ class LibrariesScanner( } } - private fun Iterable.filterDescriptorsToLoad() = - filter { processedDescriptors.add(it) } + /** Makes sure each unique descriptor is only loaded once by caching their hashes in [processedDescriptorHashes]. */ + private fun Iterable.filterDescriptorsToLoad() = filter { processedDescriptorHashes.add(it.hashCode()) } fun addLibrariesFromClassLoader( classLoader: ClassLoader, @@ -83,15 +85,15 @@ class LibrariesScanner( } } - private val jsonParser = Json { ignoreUnknownKeys = true } - private fun scanForLibraries( classLoader: ClassLoader, host: KotlinKernelHost, integrationTypeNameRules: List> = listOf(), ): LibrariesScanResult { val results = - classLoader.getResources("$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME").toList() + classLoader + .getResources("$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME") + .toList() .map { url -> val contents = url.readText() jsonParser.decodeFromString(contents) @@ -155,7 +157,8 @@ class LibrariesScanner( scanResult.descriptors.forEach { val descriptor = parseLibraryDescriptor(it) - val definition = descriptor.convertToDefinition(arguments = emptyList()) + val arguments = libraryOptions.map { (name, value) -> Variable(name, value) } + val definition = descriptor.convertToDefinition(arguments) definitions.add(definition) } diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt index bbd6a3b18..74440daf8 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/libraries/Parsing.kt @@ -9,18 +9,24 @@ import org.jetbrains.kotlinx.jupyter.protocol.api.KernelLoggerFactory private val jsonParser = Json { ignoreUnknownKeys = true } -fun parseLibraryDescriptor(json: String): LibraryDescriptor = +private inline fun parseCatching( + descriptor: T, + parse: (T) -> LibraryDescriptor, +): LibraryDescriptor = try { - jsonParser.decodeFromString(json) + parse(descriptor) } catch (e: SerializationException) { - throw ReplException("Error during library deserialization. Library descriptor text:\n$json", e) + throw ReplException("Error during library deserialization. Library descriptor text:\n$descriptor", e) + } + +fun parseLibraryDescriptor(json: String): LibraryDescriptor = + parseCatching(json) { + jsonParser.decodeFromString(it) } fun parseLibraryDescriptor(jsonObject: JsonObject): LibraryDescriptor = - try { - jsonParser.decodeFromJsonElement(jsonObject) - } catch (e: SerializationException) { - throw ReplException("Error during library deserialization. Library descriptor text:\n$jsonObject", e) + parseCatching(jsonObject) { + jsonParser.decodeFromJsonElement(it) } fun parseLibraryDescriptors( From 2d40bc215db0720923486ec1720a71824c96e22e Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Fri, 29 Aug 2025 13:52:02 +0200 Subject: [PATCH 4/5] linting --- .../jupyter/api/plugin/KotlinJupyterPluginExtension.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt index add4fd07c..a95d1674c 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/KotlinJupyterPluginExtension.kt @@ -62,7 +62,9 @@ class KotlinJupyterPluginExtension( } @Suppress("unused") - fun descriptor(@Language("JSON") descriptor: String) { + fun descriptor( + @Language("JSON") descriptor: String, + ) { libraryDescriptors.add(descriptor) } } From 42ed64d3b9adf85b784c83c040c918bc72cd9bc3 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Fri, 29 Aug 2025 14:25:54 +0200 Subject: [PATCH 5/5] removed testIncorrectDescriptors() test as unsupported keys are now ignored for future compatibility --- .../test/repl/CustomLibraryResolverTests.kt | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/CustomLibraryResolverTests.kt b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/CustomLibraryResolverTests.kt index 990b5feb3..47077f240 100644 --- a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/CustomLibraryResolverTests.kt +++ b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/CustomLibraryResolverTests.kt @@ -385,36 +385,6 @@ class CustomLibraryResolverTests : AbstractReplTest() { assertEquals(expected, repl.executedCodes) } - @Test - fun testIncorrectDescriptors() { - val ex1 = - assertThrows { - parseLibraryDescriptor( - """ - { - "imports": [] - """.trimIndent(), - ) - } - assertTrue(ex1.cause is SerializationException) - - val ex2 = - assertThrows { - parseLibraryDescriptor( - """ - { - "imports2": [] - } - """.trimIndent(), - ) - } - assertTrue(ex2.cause is SerializationException) - - assertDoesNotThrow { - parseLibraryDescriptor("{}") - } - } - @Test fun testLibraryWithResourcesDescriptorParsing() { val descriptor = parseLibraryDescriptor(File("src/test/testData/lib-with-resources.json").readText())