[Gradle] Implement generic 'StoredProperty' mechanism
This will allow extending objects of 'Project' or 'HasMutableExtras' with generic, stored instances KT-61634
This commit is contained in:
committed by
Space Team
parent
1fd9706f47
commit
e78331cd5c
+9
-4
@@ -57,6 +57,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
|
||||
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
|
||||
import org.jetbrains.kotlin.gradle.utils.loadProperty
|
||||
import org.jetbrains.kotlin.gradle.utils.localProperties
|
||||
import org.jetbrains.kotlin.gradle.utils.storedProjectProperty
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.target.presetName
|
||||
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
|
||||
@@ -606,7 +607,8 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
||||
val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT = property("kotlin.mpp.hierarchicalStructureSupport")
|
||||
val KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN = property("kotlin.mpp.androidGradlePluginCompatibility.nowarn")
|
||||
val KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION = property("kotlin.mpp.androidSourceSetLayoutVersion")
|
||||
val KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_ANDROID_STYLE_NO_WARN = property("kotlin.mpp.androidSourceSetLayoutV2AndroidStyleDirs.nowarn")
|
||||
val KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_ANDROID_STYLE_NO_WARN =
|
||||
property("kotlin.mpp.androidSourceSetLayoutV2AndroidStyleDirs.nowarn")
|
||||
val KOTLIN_MPP_IMPORT_ENABLE_KGP_DEPENDENCY_RESOLUTION = property("kotlin.mpp.import.enableKgpDependencyResolution")
|
||||
val KOTLIN_MPP_IMPORT_ENABLE_SLOW_SOURCES_JAR_RESOLVER = property("kotlin.mpp.import.enableSlowSourcesJarResolver")
|
||||
val KOTLIN_MPP_ENABLE_INTRANSITIVE_METADATA_CONFIGURATION = property("kotlin.mpp.enableIntransitiveMetadataConfiguration")
|
||||
@@ -624,7 +626,8 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
||||
val KOTLIN_OPTIONS_SUPPRESS_FREEARGS_MODIFICATION_WARNING = property("kotlin.options.suppressFreeCompilerArgsModificationWarning")
|
||||
val KOTLIN_NATIVE_USE_XCODE_MESSAGE_STYLE = property("kotlin.native.useXcodeMessageStyle")
|
||||
val KOTLIN_COMPILER_USE_PRECISE_COMPILATION_RESULTS_BACKUP = property("kotlin.compiler.preciseCompilationResultsBackup")
|
||||
val KOTLIN_COMPILER_KEEP_INCREMENTAL_COMPILATION_CACHES_IN_MEMORY = property("kotlin.compiler.keepIncrementalCompilationCachesInMemory")
|
||||
val KOTLIN_COMPILER_KEEP_INCREMENTAL_COMPILATION_CACHES_IN_MEMORY =
|
||||
property("kotlin.compiler.keepIncrementalCompilationCachesInMemory")
|
||||
val KOTLIN_SUPPRESS_EXPERIMENTAL_IC_OPTIMIZATIONS_WARNING = property("kotlin.compiler.suppressExperimentalICOptimizationsWarning")
|
||||
val KOTLIN_RUN_COMPILER_VIA_BUILD_TOOLS_API = property("kotlin.compiler.runViaBuildToolsApi")
|
||||
val KOTLIN_MPP_ALLOW_LEGACY_DEPENDENCIES = property("kotlin.mpp.allow.legacy.dependencies")
|
||||
@@ -634,14 +637,16 @@ internal class PropertiesProvider private constructor(private val project: Proje
|
||||
val KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS = property("kotlin.native.ignoreDisabledTargets")
|
||||
val KOTLIN_NATIVE_SUPPRESS_EXPERIMENTAL_ARTIFACTS_DSL_WARNING = property("kotlin.native.suppressExperimentalArtifactsDslWarning")
|
||||
val KONAN_DATA_DIR = property("konan.data.dir")
|
||||
val KOTLIN_SUPPRESS_BUILD_TOOLS_API_VERSION_CONSISTENCY_CHECKS = property("kotlin.internal.suppress.buildToolsApiVersionConsistencyChecks")
|
||||
val KOTLIN_SUPPRESS_BUILD_TOOLS_API_VERSION_CONSISTENCY_CHECKS =
|
||||
property("kotlin.internal.suppress.buildToolsApiVersionConsistencyChecks")
|
||||
|
||||
/**
|
||||
* Internal properties: builds get big non-suppressible warning when such properties are used
|
||||
* See [org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.InternalGradlePropertiesUsageChecker]
|
||||
**/
|
||||
val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.hierarchicalStructureByDefault")
|
||||
val KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS = property("$KOTLIN_INTERNAL_NAMESPACE.mpp.createDefaultMultiplatformPublications")
|
||||
val KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS =
|
||||
property("$KOTLIN_INTERNAL_NAMESPACE.mpp.createDefaultMultiplatformPublications")
|
||||
val KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING = property("$KOTLIN_INTERNAL_NAMESPACE.diagnostics.useParsableFormatting")
|
||||
val KOTLIN_INTERNAL_DIAGNOSTICS_SHOW_STACKTRACE = property("$KOTLIN_INTERNAL_NAMESPACE.diagnostics.showStacktrace")
|
||||
val KOTLIN_SUPPRESS_GRADLE_PLUGIN_ERRORS = property("$KOTLIN_INTERNAL_NAMESPACE.suppressGradlePluginErrors")
|
||||
|
||||
+2
-5
@@ -23,7 +23,6 @@ import org.jetbrains.kotlin.gradle.utils.getOrCreate
|
||||
import org.jetbrains.kotlin.gradle.utils.listProperty
|
||||
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
|
||||
import org.jetbrains.kotlin.gradle.utils.markResolvable
|
||||
import org.jetbrains.kotlin.tooling.core.extrasLazyProperty
|
||||
|
||||
/**
|
||||
* @see resolvableMetadataConfiguration
|
||||
@@ -36,9 +35,7 @@ internal val InternalKotlinSourceSet.resolvableMetadataConfigurationName: String
|
||||
* These dependencies are set up to resolve Kotlin Metadata (without transformation) and will resolve
|
||||
* consistently across the whole project.
|
||||
*/
|
||||
internal val InternalKotlinSourceSet.resolvableMetadataConfiguration: Configuration by extrasLazyProperty(
|
||||
"resolvableMetadataConfiguration"
|
||||
) {
|
||||
internal val InternalKotlinSourceSet.resolvableMetadataConfiguration: Configuration by storedExtrasProperty {
|
||||
assert(resolvableMetadataConfigurationName !in project.configurations.names)
|
||||
val configuration = project.configurations.maybeCreate(resolvableMetadataConfigurationName)
|
||||
configuration.markResolvable()
|
||||
@@ -79,7 +76,7 @@ Dependencies will be coming from extending the newer 'resolvableMetadataConfigur
|
||||
|
||||
the intransitiveMetadataConfigurationName will not extend this mechanism, since it only
|
||||
relies on dependencies being added explicitly by the Kotlin Gradle Plugin
|
||||
*/
|
||||
*/
|
||||
private fun InternalKotlinSourceSet.configureMetadataDependenciesConfigurations(resolvableMetadataConfiguration: Configuration) {
|
||||
@Suppress("DEPRECATION")
|
||||
listOf(
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.plugin.extraProperties
|
||||
import org.jetbrains.kotlin.tooling.core.HasMutableExtras
|
||||
import org.jetbrains.kotlin.tooling.core.extrasKeyOf
|
||||
import org.jetbrains.kotlin.tooling.core.getOrPut
|
||||
import java.util.*
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* ### Generic mechanism of attaching 'data' to [Project]
|
||||
* #### e.g. attaching a simple property to a [Project]
|
||||
* ```kotlin
|
||||
* class Foo(val projectName: String)
|
||||
*
|
||||
* val Project.myFoo by storedProjectProperty {
|
||||
* Foo(project.name)
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* _Usage in Project 'a'_
|
||||
*
|
||||
* ```kotlin
|
||||
* class MyPlugin : Plugin<Project> {
|
||||
* fun apply(project: Project) {
|
||||
* // prints 'Foo("a")'
|
||||
* println(project.myFoo)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* _Usage in Project 'b'_
|
||||
*
|
||||
* ```kotlin
|
||||
* class MyPlugin : Plugin<Project> {
|
||||
* fun apply(project: Project) {
|
||||
* // prints 'Foo("b")'
|
||||
* println(project.myFoo)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ### Note:
|
||||
* The key used for storing the property to the [Project] is the instance of the returned [ReadOnlyProperty],
|
||||
* *not* the type, or any String based key
|
||||
*
|
||||
*/
|
||||
internal fun <T : Any> storedProjectProperty(initializer: Project.() -> T): ReadOnlyProperty<Project, T> = StoredLazyProperty(
|
||||
storage = { storedPropertyStorage },
|
||||
initializer = initializer
|
||||
)
|
||||
|
||||
/**
|
||||
* Same as [storedProjectProperty], but will allow storing the property on any object implementing [HasMutableExtras]
|
||||
*/
|
||||
internal fun <R : HasMutableExtras, T : Any> storedExtrasProperty(initializer: R.() -> T): ReadOnlyProperty<R, T> = StoredLazyProperty(
|
||||
storage = { storedPropertyStorage },
|
||||
initializer = initializer
|
||||
)
|
||||
|
||||
|
||||
private interface StoredProperty<out T>
|
||||
|
||||
private class StoredLazyProperty<in T, out V>(
|
||||
private val storage: T.() -> StoredPropertyStorage,
|
||||
private val initializer: T.() -> V,
|
||||
) : ReadOnlyProperty<T, V>, StoredProperty<V> {
|
||||
override fun getValue(thisRef: T, property: KProperty<*>): V {
|
||||
return thisRef.storage().getOrPut(this) { thisRef.initializer() }
|
||||
}
|
||||
}
|
||||
|
||||
private class StoredPropertyStorage {
|
||||
/**
|
||||
* The stored value of the [StoredProperty] will be kept as long as the reference to the [StoredProperty] is alive.
|
||||
* e.g. a top level delegate like
|
||||
* ```kotlin
|
||||
* val Project.myProperty by storedProjectProperty { 42 }
|
||||
* ```
|
||||
*
|
||||
* will be effectively kept forever as the [StoredProperty] is referenced globally from the static scope
|
||||
* of the containing Source File.
|
||||
*
|
||||
* However:
|
||||
*
|
||||
* ```kotlin
|
||||
* class MyClass {
|
||||
* val Project.myProperty by storedProjectProperty { 42 }
|
||||
* }
|
||||
*
|
||||
* val myInstance = MyClass()
|
||||
* ```
|
||||
*
|
||||
* Such code would keep the property referenced from `myInstance`. Once this `myInstance` is collected,
|
||||
* there is no need for keeping the attached/stored property. `myInstance` getting collected leads to `myPropert$storedProjectProperty`
|
||||
* getting collected and the associated value will be released
|
||||
*/
|
||||
private val values = WeakHashMap<StoredProperty<*>, Any?>()
|
||||
fun <T> getOrPut(key: StoredLazyProperty<*, T>, create: () -> T): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return values.getOrPut(key) { create() } as T
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
operator fun <T> get(key: StoredProperty<T>): T? {
|
||||
return values[key] as T?
|
||||
}
|
||||
}
|
||||
|
||||
private val Project.storedPropertyStorage
|
||||
get() = extraProperties.getOrPut(StoredPropertyStorage::class.java.name) { StoredPropertyStorage() }
|
||||
|
||||
private val storedPropertyStorageExtrasKey = extrasKeyOf<StoredPropertyStorage>()
|
||||
|
||||
private val HasMutableExtras.storedPropertyStorage
|
||||
get() = extras.getOrPut(storedPropertyStorageExtrasKey) { StoredPropertyStorage() }
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.util.buildProject
|
||||
import org.jetbrains.kotlin.gradle.utils.storedExtrasProperty
|
||||
import org.jetbrains.kotlin.gradle.utils.storedProjectProperty
|
||||
import org.jetbrains.kotlin.tooling.core.HasMutableExtras
|
||||
import org.jetbrains.kotlin.tooling.core.MutableExtras
|
||||
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertSame
|
||||
|
||||
class StoredPropertyTest {
|
||||
|
||||
private data class Value(val id: Any)
|
||||
|
||||
private val Project.projectNameProperty by storedProjectProperty { Value(project.name) }
|
||||
|
||||
private val atomic = AtomicInteger(0)
|
||||
private val Project.counterProperty by storedProjectProperty { Value(atomic.getAndIncrement()) }
|
||||
|
||||
private class Scope(val id: Any) {
|
||||
val Project.myProperty by storedProjectProperty { Value(id) }
|
||||
}
|
||||
|
||||
private class TestEntity(val id: Any) : HasMutableExtras {
|
||||
override val extras: MutableExtras = mutableExtrasOf()
|
||||
}
|
||||
|
||||
private val TestEntity.myProperty by storedExtrasProperty { Value(id) }
|
||||
|
||||
@Test
|
||||
fun `test - projectNameProperty`() {
|
||||
val projectA = buildProject({ withName("a") })
|
||||
val projectB = buildProject({ withName("b") })
|
||||
|
||||
assertEquals(Value("a"), projectA.projectNameProperty)
|
||||
assertEquals(Value("b"), projectB.projectNameProperty)
|
||||
assertSame(projectA.projectNameProperty, projectA.projectNameProperty)
|
||||
assertSame(projectB.projectNameProperty, projectB.projectNameProperty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - counterProperty`() {
|
||||
val projectA = buildProject()
|
||||
val projectB = buildProject()
|
||||
|
||||
assertEquals(Value(0), projectA.counterProperty)
|
||||
assertEquals(Value(0), projectA.counterProperty)
|
||||
assertEquals(Value(1), projectB.counterProperty)
|
||||
assertEquals(Value(1), projectB.counterProperty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - Scope`() {
|
||||
val project = buildProject()
|
||||
val scopeA = Scope("a")
|
||||
val scopeB = Scope("b")
|
||||
|
||||
with(scopeA) {
|
||||
assertEquals(Value("a"), project.myProperty)
|
||||
}
|
||||
|
||||
with(scopeB) {
|
||||
assertEquals(Value("b"), project.myProperty)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - TestEntity - myProperty`() {
|
||||
val entityA = TestEntity("a")
|
||||
val entityB = TestEntity("b")
|
||||
|
||||
assertEquals(Value("a"), entityA.myProperty)
|
||||
assertEquals(Value("b"), entityB.myProperty)
|
||||
|
||||
assertSame(entityA.myProperty, entityA.myProperty)
|
||||
assertSame(entityB.myProperty, entityB.myProperty)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user