From 30665c3e0207f10e30fdae5b8b85741bdebf26fa Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Fri, 1 Sep 2023 14:13:04 +0200 Subject: [PATCH] [Gradle] Implement KotlinExtensionPoint to allow extensible designs inside the KotlinGradlePlugin KT-61634 --- .../api/kotlin-gradle-plugin-api.api | 5 ++ .../gradle/plugin/KotlinExtensionPoint.kt | 62 +++++++++++++++++++ .../plugin/mpp/KotlinExtensionPoints.kt | 0 .../plugin/KotlinExtensionPointInternal.kt | 42 +++++++++++++ .../plugin/registerKotlinPluginExtensions.kt | 15 +++++ .../unitTests/KotlinExtensionPointTest.kt | 46 ++++++++++++++ .../kotlin/gradle/util/extensionPointUtils.kt | 20 ++++++ 7 files changed, 190 insertions(+) create mode 100644 libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPoint.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinExtensionPoints.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPointInternal.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinExtensionPointTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/extensionPointUtils.kt diff --git a/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api b/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api index 0b668e6cce9..0661a26d16e 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api +++ b/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api @@ -896,6 +896,11 @@ public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinExecuti public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinExecution$ExecutionSource { } +public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPoint { + public abstract fun get (Lorg/gradle/api/Project;)Ljava/util/List; + public abstract fun register (Lorg/gradle/api/Project;Ljava/lang/Object;)V +} + public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinGradleSubplugin { } diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPoint.kt b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPoint.kt new file mode 100644 index 00000000000..d548040c27c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPoint.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin + +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + +/** + * ### Extension Point for the Kotlin Gradle Plugin + * + * Note: Not stable for implementation: Custom implementations of this interface will not be supported by the Kotlin Gradle Plugin! + * + * #### Motivation + * This [KotlinExtensionPoint] will be used to 'generically' extend parts of the Kotlin Gradle Plugin (internally and externally). + * General Kotlin Gradle Plugin code will call into this 'semantically' structured extension points to load logic + * Such extension points are to be used for public as well as for internal extension points. + * + * ### Usage + * #### Declaring an Extension Point (internal in Kotlin Gradle Plugin) + * Extension Points are to be declared on companion objects on the corresponding interface + * ```kotlin + * interface MyKotlinGradlePluginExtension { + * fun foo(project: Project) + * + * companion object { + * val extensionPoint = KotlinExtensionPoint() + * } + * } + * ``` + * + * #### Registering an Extension Point + * Extension Points are scoped by [Project] and should be registered as early as possible + * ```kotlin + * class MyPlugin: Plugin { + * fun apply(project: Project) { + * MyKotlinGradlePluginExtension.extensionPoint.register(project, MyKotlinGradlePluginExtensionImpl()) + * } + * } + * ``` + * + * #### Kotlin Gradle Plugin Entry Point: + * Extensions registered by the Kotlin Gradle Plugin directly will use the + * [org.jetbrains.kotlin.gradle.plugin.registerKotlinPluginExtensions] entry point. + */ +@ExperimentalKotlinGradlePluginApi +interface KotlinExtensionPoint { + /** + * @return all currently registered extension points. + * The returned list is *not* live and just represents the current snapshot. + */ + operator fun get(project: Project): List + + /** + * @param project The current [project] to register an extension. The extension will only be visible to this particular [project] + * @param extension The implementation of the extension to register. If registered twice, it will be returned twice when + * the extensions are queried (no de-duplication) + */ + fun register(project: Project, extension: T) +} diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinExtensionPoints.kt b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinExtensionPoints.kt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPointInternal.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPointInternal.kt new file mode 100644 index 00000000000..228d19fd82e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinExtensionPointInternal.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin + +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.utils.storedProjectProperty +import org.jetbrains.kotlin.tooling.core.UnsafeApi + +/** + * Creates a new [KotlinExtensionPoint] + * See [KotlinExtensionPoint] documentation for the intended usage! + */ +internal fun KotlinExtensionPoint(): KotlinExtensionPoint { + @OptIn(UnsafeApi::class) + return KotlinExtensionPointInternal() +} + +/** + * Only intended implementation of [KotlinExtensionPoint]! + * Visible for tests to allow test utils to overwrite certain extension points + */ +@UnsafeApi("Visible for tests only") +internal class KotlinExtensionPointInternal : KotlinExtensionPoint { + + private val Project.registeredExtensions by storedProjectProperty> { mutableListOf() } + + override fun get(project: Project): List { + return project.registeredExtensions.toList() + } + + override fun register(project: Project, extension: T) { + project.registeredExtensions.add(extension) + } + + fun set(project: Project, extensions: List) { + project.registeredExtensions.clear() + project.registeredExtensions.addAll(extensions) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt new file mode 100644 index 00000000000..e9554da2428 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.plugin + +import org.gradle.api.Project + +/** + * Active Extensions (using the [KotlinExtensionPoint] infrastructure) will be registered here by the Kotlin Gradle Plugin. + */ +internal fun Project.registerKotlinPluginExtensions() { + // No extensions available yet. +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinExtensionPointTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinExtensionPointTest.kt new file mode 100644 index 00000000000..c0a5a9e124c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinExtensionPointTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("FunctionName") + +package org.jetbrains.kotlin.gradle.unitTests + +import org.jetbrains.kotlin.gradle.util.buildProject +import org.jetbrains.kotlin.gradle.util.set +import org.jetbrains.kotlin.gradle.plugin.KotlinExtensionPoint +import kotlin.test.Test +import kotlin.test.assertEquals + +class KotlinExtensionPointTest { + + @Test + fun `test - simple extension point`() { + val extensionPoint = KotlinExtensionPoint() + val projectA = buildProject() + val projectB = buildProject() + + extensionPoint.register(projectA, "a") + extensionPoint.register(projectB, "b") + + assertEquals(listOf("a"), extensionPoint[projectA]) + assertEquals(listOf("b"), extensionPoint[projectB]) + + extensionPoint.register(projectA, "a2") + assertEquals(listOf("a", "a2"), extensionPoint[projectA]) + } + + @Test + fun `test - overwrite`() { + val extensionPoint = KotlinExtensionPoint() + val project = buildProject() + + extensionPoint.register(project, 0) + extensionPoint.register(project, 1) + assertEquals(listOf(0, 1), extensionPoint[project]) + + extensionPoint.set(project, listOf(2, 3)) + assertEquals(listOf(2, 3), extensionPoint[project]) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/extensionPointUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/extensionPointUtils.kt new file mode 100644 index 00000000000..a466205187f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/util/extensionPointUtils.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.util + +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.plugin.KotlinExtensionPoint +import org.jetbrains.kotlin.gradle.plugin.KotlinExtensionPointInternal +import org.jetbrains.kotlin.tooling.core.UnsafeApi + +/** + * Completely overwrites the currently registered extensions on this [KotlinExtensionPoint] in this project. + */ +@OptIn(UnsafeApi::class) +fun KotlinExtensionPoint.set(project: Project, extensions: List) { + (this as KotlinExtensionPointInternal) + set(project, extensions) +}