diff --git a/gradle.properties b/gradle.properties index 534440918..9402b6a1c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ shadowJarVersion=7.0.0 kotlinxSerializationVersion=1.1.0 ktlintGradleVersion=10.0.0 ktlintVersion=0.40.0 -publishPluginVersion=0.0.27-dev +publishPluginVersion=0.0.29-dev junitVersion=5.7.1 slf4jVersion=1.7.30 logbackVersion=1.2.3 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf51..05679dc3c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/build.gradle.kts b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/build.gradle.kts index 15009b45f..b502aefab 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/build.gradle.kts +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/build.gradle.kts @@ -48,14 +48,21 @@ val saveVersion by tasks.registering { } } -tasks.processResources { - dependsOn(saveVersion) -} +tasks { + processResources { + dependsOn(saveVersion) + } + + test { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } + } -tasks.test { - useJUnitPlatform() - testLogging { - events("passed", "skipped", "failed") + register("sourceJar") { + archiveClassifier.set("sources") + from(sourceSets.named("main").get().allSource) } } @@ -90,6 +97,12 @@ pluginBundle { } publishing { + publications { + withType { + artifact(tasks["sourceJar"]) + } + } + repositories { (rootProject.findProperty("localPublicationsRepo") as? java.nio.file.Path)?.let { maven { 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 c2adfd80e..f82504b41 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 @@ -2,8 +2,10 @@ package org.jetbrains.kotlinx.jupyter.api.plugin import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.tasks.Copy import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.invoke +import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.repositories import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -11,6 +13,7 @@ import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin import org.jetbrains.kotlin.gradle.plugin.KaptExtension import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlinx.jupyter.api.plugin.tasks.JupyterApiResourcesTask +import org.jetbrains.kotlinx.jupyter.api.plugin.tasks.whenAdded class ApiGradlePlugin : Plugin { override fun apply(target: Project): Unit = with(target) { @@ -41,37 +44,50 @@ class ApiGradlePlugin : Plugin { } val resourcesTaskName = "processJupyterApiResources" - fun registerResourceTask() { - register(resourcesTaskName) { - val kaptKotlinTask = findByName("kaptKotlin") - if (kaptKotlinTask != null) { - dependsOn(kaptKotlinTask) - kaptKotlinTask.dependsOn(cleanJupyterTask) - kaptKotlinTask.outputs.dir(jupyterBuildPath) - } + fun registerResourceTask(): JupyterApiResourcesTask { + findByName(resourcesTaskName) ?: register(resourcesTaskName) + return named(resourcesTaskName).get() + } + + fun dependOnProcessingTask(processTaskName: String) { + val jupyterTask = registerResourceTask() + tasks.named(processTaskName) { + dependsOn(resourcesTaskName) + from(jupyterTask.outputDir) } } + fun dependOnKapt(kaptTaskName: String) { + registerResourceTask() + tasks.whenAdded( + { it.name == kaptTaskName }, + { + tasks.named(resourcesTaskName) { + dependsOn(it) + it.dependsOn(cleanJupyterTask) + it.outputs.dir(jupyterBuildPath) + } + } + ) + } + // apply configuration to JVM-only project plugins.withId("org.jetbrains.kotlin.jvm") { - // Task should be registered after plugin is applied - registerResourceTask() - named("processResources") { - dependsOn(resourcesTaskName) - } + dependOnProcessingTask("processResources") + dependOnKapt("kaptKotlin") } // apply only to multiplatform plugin plugins.withId("org.jetbrains.kotlin.multiplatform") { - // Task should be registered after plugin is applied - registerResourceTask() extensions.findByType()?.apply { - val jvmTargetName = targets.filterIsInstance().firstOrNull()?.name - ?: error("Single JVM target not found in a multiplatform project") - named(jvmTargetName + "ProcessResources") { - dependsOn(resourcesTaskName) - } + targets.whenAdded( + { (it is KotlinJvmTarget) }, + { + dependOnProcessingTask(it.name + "ProcessResources") + } + ) } + dependOnKapt("kaptKotlinJvm") } } } 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 62fd06243..a910d9ea6 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 @@ -5,6 +5,7 @@ import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.findByType import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget +import org.jetbrains.kotlinx.jupyter.api.plugin.tasks.whenAdded import java.util.Locale private fun Project.configureDependency(scope: String, dependencyNotation: Any) { @@ -19,13 +20,17 @@ private fun Project.configureDependency(scope: String, dependencyNotation: Any) // apply only to multiplatform plugin plugins.withId("org.jetbrains.kotlin.multiplatform") { extensions.findByType()?.apply { - val jvmTargetName = targets.filterIsInstance().firstOrNull()?.name - ?: error("Single JVM target not found in a multiplatform project") - val configuration = project.configurations.findByName(jvmTargetName + scope.capitalize(Locale.ROOT)) - ?: error("$scope configuration is not resolved for a multiplatform project") - dependencies { - configuration.invoke(dependencyNotation) - } + targets.whenAdded( + { it is KotlinJvmTarget }, + { + val jvmTargetName = it.name + val configuration = project.configurations.findByName(jvmTargetName + scope.capitalize(Locale.ROOT)) + ?: error("$scope configuration is not resolved for a multiplatform project") + dependencies { + configuration.invoke(dependencyNotation) + } + } + ) } } } @@ -39,13 +44,14 @@ class KotlinJupyterPluginExtension( } fun addScannerDependency(version: String? = null) = with(project) { - val kaptConf = configurations.findByName("kapt") ?: return - val apiVersion = version ?: apiVersion() - val mavenCoordinates = "$GROUP_ID:kotlin-jupyter-api-annotations:$apiVersion" - dependencies { - kaptConf(mavenCoordinates) + configurations.whenAdded({ it.name == "kapt" }) { kaptConf -> + val apiVersion = version ?: apiVersion() + val mavenCoordinates = "$GROUP_ID:kotlin-jupyter-api-annotations:$apiVersion" + dependencies { + kaptConf(mavenCoordinates) + } + configureDependency("implementation", mavenCoordinates) } - configureDependency("implementation", mavenCoordinates) } internal fun addDependenciesIfNeeded() { 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 9a98f7ff5..117a437e2 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 @@ -4,12 +4,7 @@ import com.google.gson.Gson import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.SourceSet -import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.findByType -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlinx.jupyter.api.plugin.ApiGradlePlugin import java.io.File @@ -31,30 +26,7 @@ open class JupyterApiResourcesTask : DefaultTask() { var libraryDefinitions: List = emptyList() @OutputDirectory - val outputDir: File - - init { - val sourceSets = project.extensions.getByName("sourceSets") as SourceSetContainer - when { - project.plugins.findPlugin("org.jetbrains.kotlin.jvm") != null -> { - val mainSourceSet: SourceSet = sourceSets.named("main").get() - outputDir = mainSourceSet.output.resourcesDir?.resolve("META-INF/kotlin-jupyter-libraries") - ?: throw IllegalStateException("No resources dir for main source set") - } - project.plugins.findPlugin("org.jetbrains.kotlin.multiplatform") != null -> { - val mppExtension = project.extensions.findByType() - ?: error("Kotlin MPP extension not found") - val jvmTargetName = mppExtension.targets.filterIsInstance().firstOrNull()?.name - ?: error("Single JVM target not found in a multiplatform project") - // TODO properly resolve resource directory - outputDir = project.buildDir.resolve("processedResources/$jvmTargetName/main") - .resolve("META-INF/kotlin-jupyter-libraries") - } - else -> { - error("Kotlin plugin not found in the project") - } - } - } + val outputDir: File = project.buildDir.resolve("jupyterProcessedResources") @TaskAction fun createDescriptions() { @@ -64,7 +36,9 @@ open class JupyterApiResourcesTask : DefaultTask() { ) + getScanResultFromAnnotations() val json = Gson().toJson(resultObject) - val libFile = outputDir.resolve("libraries.json") + val jupyterDir = outputDir.resolve("META-INF/kotlin-jupyter-libraries") + val libFile = jupyterDir.resolve("libraries.json") + libFile.parentFile.mkdirs() libFile.writeText(json) } diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/domainCollectionUtil.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/domainCollectionUtil.kt new file mode 100644 index 000000000..bc3002300 --- /dev/null +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/tasks/domainCollectionUtil.kt @@ -0,0 +1,15 @@ +package org.jetbrains.kotlinx.jupyter.api.plugin.tasks + +import org.gradle.api.NamedDomainObjectCollection + +fun NamedDomainObjectCollection.whenAdded(condition: (T) -> Boolean, action: (T) -> Unit) { + val element = find(condition) + if (element != null) { + action(element) + return + } + whenObjectAdded { + val addedElement = this + if (condition(addedElement)) action(addedElement) + } +} diff --git a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/test/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/test/ResourcesTaskTests.kt b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/test/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/test/ResourcesTaskTests.kt index 5deccdbda..0ae3772da 100644 --- a/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/test/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/test/ResourcesTaskTests.kt +++ b/jupyter-lib/kotlin-jupyter-api-gradle-plugin/src/test/kotlin/org/jetbrains/kotlinx/jupyter/api/plugin/test/ResourcesTaskTests.kt @@ -23,7 +23,7 @@ class ResourcesTaskTests { val buildFile = projectDir.resolve("build.gradle") val taskSetupIndented = taskSetup.prependIndent(" ".repeat(2)) val buildFileText = """ - $PLUGINS_BLOCK + ${pluginsBlock()} tasks { $JUPYTER_RESOURCES_TASK_NAME { @@ -34,12 +34,15 @@ class ResourcesTaskTests { buildFile.writeText(buildFileText) } - private fun runResourcesTask(args: Array? = null): BuildResult { + private fun runResourcesTask(args: Array? = null, type: String = ""): BuildResult { val arguments = args?.toMutableList() ?: mutableListOf( "-Pkotlin.jupyter.add.api=false", - "-Pkotlin.jupyter.add.scanner=false" + "-Pkotlin.jupyter.add.scanner=false", + "--stacktrace", + "--info" ) - arguments.add(0, RESOURCES_TASK_NAME) + val taskName = RESOURCES_TASK_NAME.withPrefix(type) + arguments.add(0, taskName) return GradleRunner.create() .withProjectDir(projectDir) @@ -49,8 +52,8 @@ class ResourcesTaskTests { .build() } - private fun assertLibrariesJsonContents(expected: LibrariesScanResult) { - val librariesJsonText = projectDir.resolve(BUILD_LIBRARIES_JSON_PATH).readText() + private fun assertLibrariesJsonContents(expected: LibrariesScanResult, type: String = "") { + val librariesJsonText = projectDir.resolve(buildLibrariesJsonPath(type)).readText() val libraryInfo = Json.decodeFromString(librariesJsonText) assertEquals(expected, libraryInfo) @@ -111,7 +114,7 @@ class ResourcesTaskTests { val apiAnnotations = jarFile("api-annotations") buildFile.writeText( """ - $PLUGINS_BLOCK + ${pluginsBlock()} dependencies { implementation(files("$apiJar")) @@ -149,6 +152,89 @@ class ResourcesTaskTests { ) } + @Test + fun `check annotations in MPP`() { + val version = ClassLoader.getSystemClassLoader().getResource("VERSION")?.readText().orEmpty() + + val propertiesFile = projectDir.resolve("properties.gradle") + propertiesFile.writeText( + """ + kapt.verbose=true + """.trimIndent() + ) + + val buildFile = projectDir.resolve("build.gradle.kts") + + fun jarFile(name: String): String { + return File("../$name/build/libs/$name-$version.jar").canonicalFile.absolutePath.replace("\\", "/") + } + + val apiJar = jarFile("api") + val apiAnnotations = jarFile("api-annotations") + buildFile.writeText( + """ + plugins { + kotlin("multiplatform") version "$KOTLIN_VERSION" + id("org.jetbrains.kotlin.jupyter.api") + } + + kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "11" + } + } + js(LEGACY) { + binaries.executable() + browser() + } + sourceSets { + val commonMain by getting + val commonTest by getting + val jvmMain by getting { + dependencies { + implementation(files("$apiJar")) + implementation(files("$apiAnnotations")) + } + dependencies.add("kapt", files("$apiAnnotations")) + } + val jvmTest by getting + val jsMain by getting + val jsTest by getting + } + } + """.trimIndent() + ) + + val srcDir = projectDir.resolve("src/jvmMain/kotlin") + srcDir.mkdirs() + + val integrationKt = srcDir.resolve("pack").resolve("Integration.kt") + integrationKt.parentFile.mkdirs() + integrationKt.writeText( + """ + package pack + + import org.jetbrains.kotlinx.jupyter.api.annotations.JupyterLibrary + import org.jetbrains.kotlinx.jupyter.api.* + import org.jetbrains.kotlinx.jupyter.api.libraries.* + + @JupyterLibrary + class Integration : JupyterIntegration({ + import("org.my.lib.*") + }) + """.trimIndent() + ) + runResourcesTask(type = "jvm") + + assertLibrariesJsonContents( + LibrariesScanResult( + producers = listOf("pack.Integration").map(::LibrariesProducerDeclaration) + ), + "jvm" + ) + } + @Test fun `check extension`() { val version = "0.8.3.202" @@ -157,7 +243,7 @@ class ResourcesTaskTests { buildFile.writeText( """ - $PLUGINS_BLOCK + ${pluginsBlock()} kotlinJupyter { addApiDependency("$version") @@ -195,17 +281,27 @@ class ResourcesTaskTests { } companion object { + private const val KOTLIN_VERSION = "1.5.20" private const val RESOURCES_TASK_NAME = "processResources" private const val JUPYTER_RESOURCES_TASK_NAME = "processJupyterApiResources" - private const val MAIN_SOURCE_SET_BUILD_RESOURCES_PATH = "build/resources/main" - private const val BUILD_LIBRARIES_JSON_PATH = "$MAIN_SOURCE_SET_BUILD_RESOURCES_PATH/$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME" + private fun mainSourceSetBuildResourcesPath(type: String = ""): String { + return if (type.isEmpty()) { + "build/resources/main" + } else { + "build/processedResources/$type/main" + } + } + private fun buildLibrariesJsonPath(type: String = "") = "${mainSourceSetBuildResourcesPath(type)}/$KOTLIN_JUPYTER_RESOURCES_PATH/$KOTLIN_JUPYTER_LIBRARIES_FILE_NAME" - private val PLUGINS_BLOCK = """ + private fun pluginsBlock(ktPluginId: String = "jvm") = """ plugins { - id 'org.jetbrains.kotlin.jvm' version '1.4.20' + id 'org.jetbrains.kotlin.$ktPluginId' version '$KOTLIN_VERSION' id 'org.jetbrains.kotlin.jupyter.api' } """.trimIndent() + + private fun String.withPrefix(prefix: String) = + if (prefix.isEmpty()) this else prefix + capitalize() } }