
org.jetbrains.kotlin.gradle.plugin.internal.KotlinSecondaryVariantsDataSharing.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2024 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.internal
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.attributes.Usage
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
import org.jetbrains.kotlin.gradle.utils.JsonUtils
import org.jetbrains.kotlin.gradle.utils.LazyResolvedConfiguration
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.utils.projectStoredProperty
import org.jetbrains.kotlin.gradle.utils.registerArtifact
internal val Project.kotlinSecondaryVariantsDataSharing: KotlinSecondaryVariantsDataSharing by projectStoredProperty {
KotlinSecondaryVariantsDataSharing(project)
}
/**
* Marker interface of classes that shares data between Gradle Projects using [KotlinSecondaryVariantsDataSharing]
* Implementations should be serializable via [JsonUtils]
*/
internal interface KotlinShareableDataAsSecondaryVariant
/**
* Service to share configuration state between Kotlin Projects as Configuration Secondary Variants
*/
internal class KotlinSecondaryVariantsDataSharing(
private val project: Project,
) {
fun shareDataFromProvider(
key: String,
outgoingConfiguration: Configuration,
dataProvider: Provider,
taskDependencies: List = emptyList(),
) {
val taskName = lowerCamelCaseName("export", key, "for", outgoingConfiguration.name)
val task = project.locateOrRegisterTask(taskName, configureTask = {
val fileName = "${key}_${outgoingConfiguration.name}.json"
@Suppress("UNCHECKED_CAST")
val taskOutputData = outputData as Property
taskOutputData.set(dataProvider)
outputFile.set(project.layout.buildDirectory.file("kotlin/kotlin-project-shared-data/$fileName"))
dependsOn(taskDependencies)
})
shareDataFromExistingTask(key, outgoingConfiguration, task.flatMap { it.outputFile })
}
private fun shareDataFromExistingTask(
key: String,
outgoingConfiguration: Configuration,
taskOutputProvider: Provider,
taskDependencies: List = emptyList(),
) {
if (outgoingConfiguration.outgoing.variants.names.contains(key)) {
project.logger.warn(
"KotlinSecondaryVariantsDataSharing can't create secondary variant with name $key "
+ "on configuration $outgoingConfiguration. Something can be declared twice or have clashing names. "
+ "Please report this to https://kotl.in/issue"
)
return
}
outgoingConfiguration.outgoing.variants.create(key) { variant ->
variant.registerArtifact(
artifactProvider = taskOutputProvider,
type = key
) {
builtBy(taskDependencies)
}
variant.attributes.configureAttributes(key)
}
}
fun consume(
key: String,
incomingConfiguration: Configuration,
clazz: Class,
componentFilter: ((ComponentIdentifier) -> Boolean)? = null,
): KotlinProjectSharedDataProvider {
val lazyResolvedConfiguration = LazyResolvedConfiguration(incomingConfiguration, configureArtifactView = {
attributes.configureAttributes(key)
if (componentFilter != null) this.componentFilter(componentFilter)
})
return KotlinProjectSharedDataProvider(key, lazyResolvedConfiguration, clazz)
}
private fun AttributeContainer.configureAttributes(key: String) {
val usageValue = project.objects.named(Usage::class.java, "kotlin-project-shared-data")
attributeProvider(Usage.USAGE_ATTRIBUTE, project.provider { usageValue })
attributeProvider(kotlinProjectSharedDataAttribute, project.provider { key })
}
}
private val kotlinProjectSharedDataAttribute = Attribute.of("org.jetbrains.kotlin.project-shared-data", String::class.java)
/**
* This class is Configuration Cache safe. It can be stored in a Task field.
*/
internal class KotlinProjectSharedDataProvider(
private val key: String,
private val lazyResolvedConfiguration: LazyResolvedConfiguration,
private val clazz: Class,
) {
val rootComponent: ResolvedComponentResult get() = lazyResolvedConfiguration.root
val allResolvedDependencies: Set get() = lazyResolvedConfiguration.allResolvedDependencies
val files: FileCollection = lazyResolvedConfiguration.files
fun getProjectDataFromDependencyOrNull(resolvedDependency: ResolvedDependencyResult): T? {
val artifact = lazyResolvedConfiguration.getArtifacts(resolvedDependency).singleOrNull() ?: return null
return artifact.parse()
}
private fun ResolvedArtifactResult.parse(): T? {
// In rare cases, for example when provided attributes and requested attributes didn't match at all.
// Gradle will resolve into that variant.
// It can happen in Android Gradle Plugin for example, as they have a lot of secondary variants with few attributes
val keyFromResolvedArtifact = variant.attributes.getAttribute(kotlinProjectSharedDataAttribute) ?: return null
if (key != keyFromResolvedArtifact) return null
// In some cases, for example CInterop transformations for IDE, actual artifact file may not exists
// this can happen because consuming task doesn't not depend on the resolved artifacts collection
// and producing task may not be invoked. So checking for file existence is required here.
if (!file.exists()) return null
val content = file.readText()
return runCatching { JsonUtils.gson.fromJson(content, clazz) }.getOrNull()
}
}
@DisableCachingByDefault(because = "Trivial operation")
internal abstract class ExportKotlinProjectDataTask : DefaultTask() {
@get:Nested
abstract val outputData: Property
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun action() {
val data = outputData.get()
val json = JsonUtils.gson.toJson(data)
outputFile.get().asFile.writeText(json)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy