Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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

/**
Expand Down Expand Up @@ -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<LibrariesDefinitionDeclaration> = emptyList(),
val producers: List<LibrariesProducerDeclaration> = emptyList(),
val descriptors: List<JsonObject> = emptyList(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ class KotlinJupyterPluginExtension(

private val libraryProducers: MutableSet<FQNAware> = mutableSetOf()
private val libraryDefinitions: MutableSet<FQNAware> = mutableSetOf()
private val libraryDescriptors: MutableSet<String> = mutableSetOf()

internal val libraryFqns get() =
LibrariesScanResult(
definitions = libraryDefinitions,
producers = libraryProducers,
descriptors = libraryDescriptors,
)

internal fun addDependenciesIfNeeded() {
Expand All @@ -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)
}
Expand All @@ -56,6 +59,11 @@ class KotlinJupyterPluginExtension(
fun definition(className: String) {
libraryDefinitions.add(FQNAware(className))
}

@Suppress("unused")
fun descriptor(descriptor: String) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't gradle have a better API for representing JSON's? BTW, probably use parameter lang injection here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image not sure! There are many Json wrappers on the classpath, but most are internally shadowed

libraryDescriptors.add(descriptor)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ open class JupyterApiResourcesTask : DefaultTask() {
@Input
var libraryDefinitions: List<String> = 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<String> = emptyList()

@OutputDirectory
val outputDir: File = project.getBuildDirectory().resolve("jupyterProcessedResources")

Expand All @@ -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 =
Expand Down Expand Up @@ -70,14 +78,16 @@ open class JupyterApiResourcesTask : DefaultTask() {
}

return LibrariesScanResult(
fqns("definitions"),
fqns("producers"),
definitions = fqns("definitions"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better remove everything related to FQNS_PATH: we don't scan annotations anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so removing getScanResultFromAnnotations() altogether?

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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ data class FQNAware(
class LibrariesScanResult(
val definitions: Set<FQNAware> = emptySet(),
val producers: Set<FQNAware> = emptySet(),
val descriptors: Set<String> = emptySet(),
)

val emptyScanResult = LibrariesScanResult()
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -27,6 +28,7 @@ class LibrariesScanner(

private val processedFQNs = mutableSetOf<TypeName>()
private val discardedFQNs = mutableSetOf<TypeName>()
private val processedDescriptors = mutableSetOf<JsonObject>()

private fun <I : LibrariesInstantiable<*>> Iterable<I>.filterNamesToLoad(
host: KotlinKernelHost,
Expand All @@ -46,10 +48,14 @@ class LibrariesScanner(
discardedFQNs.add(typeName)
false
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant newline

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ktlint adds it because of the {} before it ;P

null -> typeName !in discardedFQNs && processedFQNs.add(typeName)
}
}

private fun Iterable<JsonObject>.filterDescriptorsToLoad() =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comment is welcomed here, would be nice to see a description of the trouble you experienced

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, I can probably make this a bit more efficient by only storing the hash instead of the entire string

filter { processedDescriptors.add(it) }

fun addLibrariesFromClassLoader(
classLoader: ClassLoader,
host: KotlinKernelHost,
Expand Down Expand Up @@ -77,30 +83,36 @@ class LibrariesScanner(
}
}

private val jsonParser = Json { ignoreUnknownKeys = true }

private fun scanForLibraries(
classLoader: ClassLoader,
host: KotlinKernelHost,
integrationTypeNameRules: List<AcceptanceRule<TypeName>> = listOf(),
): LibrariesScanResult {
val results =
classLoader.getResources("$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME").toList().map { url ->
val contents = url.readText()
Json.decodeFromString<LibrariesScanResult>(contents)
}
classLoader.getResources("$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME").toList()
.map { url ->
val contents = url.readText()
jsonParser.decodeFromString<LibrariesScanResult>(contents)
}

val definitions = mutableListOf<LibrariesDefinitionDeclaration>()
val producers = mutableListOf<LibrariesProducerDeclaration>()
val descriptors = mutableListOf<JsonObject>()

for (result in results) {
definitions.addAll(result.definitions)
producers.addAll(result.producers)
descriptors.addAll(result.descriptors)
}

fun <I : LibrariesInstantiable<*>> Iterable<I>.filterNames() = filterNamesToLoad(host, integrationTypeNameRules)

return LibrariesScanResult(
definitions.filterNames(),
producers.filterNames(),
descriptors.filterDescriptorsToLoad(),
)
}

Expand Down Expand Up @@ -140,6 +152,13 @@ class LibrariesScanner(
}
}
}

scanResult.descriptors.forEach {
val descriptor = parseLibraryDescriptor(it)
val definition = descriptor.convertToDefinition(arguments = emptyList())
definitions.add(definition)
}

return definitions
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String>,
Expand Down