[Gradle] Implement KotlinExtensionPoint to allow extensible designs inside the KotlinGradlePlugin

KT-61634
This commit is contained in:
Sebastian Sellmair
2023-09-01 14:13:04 +02:00
committed by Space Team
parent e78331cd5c
commit 30665c3e02
7 changed files with 190 additions and 0 deletions
@@ -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 {
}
@@ -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<MyKotlinGradlePluginExtension>()
* }
* }
* ```
*
* #### Registering an Extension Point
* Extension Points are scoped by [Project] and should be registered as early as possible
* ```kotlin
* class MyPlugin: Plugin<Project> {
* 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<T> {
/**
* @return all currently registered extension points.
* The returned list is *not* live and just represents the current snapshot.
*/
operator fun get(project: Project): List<T>
/**
* @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)
}
@@ -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 <T> KotlinExtensionPoint(): KotlinExtensionPoint<T> {
@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<T> : KotlinExtensionPoint<T> {
private val Project.registeredExtensions by storedProjectProperty<MutableList<T>> { mutableListOf() }
override fun get(project: Project): List<T> {
return project.registeredExtensions.toList()
}
override fun register(project: Project, extension: T) {
project.registeredExtensions.add(extension)
}
fun set(project: Project, extensions: List<T>) {
project.registeredExtensions.clear()
project.registeredExtensions.addAll(extensions)
}
}
@@ -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.
}
@@ -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<String>()
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<Int>()
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])
}
}
@@ -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 <T> KotlinExtensionPoint<T>.set(project: Project, extensions: List<T>) {
(this as KotlinExtensionPointInternal<T>)
set(project, extensions)
}