Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2019 JetBrains s.r.o. 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.mpp
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.stream.JsonWriter
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
import org.jetbrains.kotlin.gradle.plugin.KotlinProjectSetupCoroutine
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.await
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.targets.metadata.dependsOnClosureWithInterCompilationDependencies
import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations
import org.jetbrains.kotlin.gradle.targets.metadata.isNativeSourceSet
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerCompositeMetadataJarBundling.cinteropMetadataDirectoryPath
import org.jetbrains.kotlin.gradle.utils.*
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import java.io.Serializable
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory
// FIXME support module classifiers for PM2.0 or drop this class in favor of KotlinModuleIdentifier
open class ModuleDependencyIdentifier(
@get:Input
open val groupId: String?,
@get:Input
open val moduleId: String,
) : Serializable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ModuleDependencyIdentifier) return false
if (groupId != other.groupId) return false
if (moduleId != other.moduleId) return false
return true
}
override fun hashCode(): Int {
var result = groupId?.hashCode() ?: 0
result = 31 * result + moduleId.hashCode()
return result
}
operator fun component1(): String? = groupId
operator fun component2(): String = moduleId
override fun toString(): String {
return "${groupId}-${moduleId}"
}
}
sealed class SourceSetMetadataLayout(
@get:Input
val name: String,
@get:Internal
val archiveExtension: String,
) : Serializable {
object METADATA : SourceSetMetadataLayout("metadata", "jar")
object KLIB : SourceSetMetadataLayout("klib", "klib")
override fun toString(): String = name
companion object {
private val values get() = listOf(METADATA, KLIB)
fun byName(name: String): SourceSetMetadataLayout? = values.firstOrNull { it.name == name }
fun chooseForProducingProject() =
/** A producing project will now only generate Granular source sets metadata as a KLIB */
KLIB
}
}
/**
* Represents the structure of "shared" source sets within a Kotlin Project.
* Note: This entity is designed to only list "shared" source sets.
* No "platform" source sets will be listed.
*/
data class KotlinProjectStructureMetadata(
@Input
val sourceSetNamesByVariantName: Map>,
@Input
val sourceSetsDependsOnRelation: Map>,
@Nested
val sourceSetBinaryLayout: Map,
@Internal
val sourceSetModuleDependencies: Map>,
@Input
val sourceSetCInteropMetadataDirectory: Map,
@Input
val hostSpecificSourceSets: Set,
@get:Input
val isPublishedAsRoot: Boolean,
@get:Input
val sourceSetNames: Set,
@Input
val formatVersion: String = FORMAT_VERSION_0_3_3,
) : Serializable {
@Suppress("UNUSED") // Gradle input
@get:Input
internal val sourceSetModuleDependenciesInput: Map>>
get() = sourceSetModuleDependencies.mapValues { (_, ids) -> ids.map { (group, module) -> group.orEmpty() to module }.toSet() }
companion object {
internal const val FORMAT_VERSION_0_1 = "0.1"
// + binaryFormat (klib, metadata/jar)
internal const val FORMAT_VERSION_0_2 = "0.2"
// + 'hostSpecific' flag for source sets
internal const val FORMAT_VERSION_0_3 = "0.3"
// + 'isPublishedInRootModule' top-level flag
internal const val FORMAT_VERSION_0_3_1 = "0.3.1"
// + 'sourceSetCInteropMetadataDirectory' map
internal const val FORMAT_VERSION_0_3_2 = "0.3.2"
// + 'sourceSetsNames'
internal const val FORMAT_VERSION_0_3_3 = "0.3.3"
}
}
internal val KotlinMultiplatformExtension.kotlinProjectStructureMetadata: KotlinProjectStructureMetadata by extrasStoredProperty {
buildKotlinProjectStructureMetadata(this)
}
/**
* Return the name of the variant, taking into account that external targets might pass `.*-published` whereas
* internally maintained targets will pass the name of the original dependency configuration
*
* ### Example
* external target: `apiElements-published` -> `apiElements`
* non-external target: `apiElements` -> `apiElements`
*
*/
private val KotlinUsageContext.variantName get() = kotlinVariantNameFromPublishedVariantName(name)
private fun buildKotlinProjectStructureMetadata(extension: KotlinMultiplatformExtension): KotlinProjectStructureMetadata {
val project = extension.project
require(project.state.executed) { "Cannot build 'KotlinProjectStructureMetadata' during project configuration phase" }
val sourceSetsWithMetadataCompilations = extension.targets
.getByName(KotlinMetadataTarget.METADATA_TARGET_NAME)
.compilations.associateBy { it.defaultSourceSet }
val publishedVariantsNamesWithCompilation = project.future { getPublishedPlatformCompilations(project).mapKeys { it.key.variantName } }
.getOrThrow()
return KotlinProjectStructureMetadata(
sourceSetNamesByVariantName = publishedVariantsNamesWithCompilation.mapValues { (_, compilation) ->
compilation.allKotlinSourceSets.filter { it in sourceSetsWithMetadataCompilations }.map { it.name }.toSet()
},
sourceSetsDependsOnRelation = sourceSetsWithMetadataCompilations.keys.associate { sourceSet ->
sourceSet.name to sourceSet.dependsOn.filter { it in sourceSetsWithMetadataCompilations }.map { it.name }.toSet()
},
sourceSetModuleDependencies = project.sourceSetModuleDependencies(sourceSetsWithMetadataCompilations),
sourceSetCInteropMetadataDirectory = sourceSetsWithMetadataCompilations.keys
.filter { it.isNativeSourceSet.getOrThrow() }
.associate { sourceSet -> sourceSet.name to cinteropMetadataDirectoryPath(sourceSet.name) },
hostSpecificSourceSets = project.future { getHostSpecificSourceSets(project) }.getOrThrow()
.filter { it in sourceSetsWithMetadataCompilations }.map { it.name }
.toSet(),
sourceSetBinaryLayout = sourceSetsWithMetadataCompilations.keys.associate { sourceSet ->
sourceSet.name to SourceSetMetadataLayout.chooseForProducingProject()
},
isPublishedAsRoot = true,
sourceSetNames = sourceSetsWithMetadataCompilations.keys.map { it.name }.toSet(),
)
}
private fun Project.sourceSetModuleDependencies(
sourceSetsWithMetadataCompilations: Map>,
): Map> {
/**
* When PI is enabled, calling [ModuleIds.fromDependency] is not PI friendly
* So Sources Set Dependencies will be populated in the [GenerateProjectStructureMetadata] task.
* */
if (kotlinPropertiesProvider.kotlinKmpProjectIsolationEnabled) return emptyMap()
return sourceSetsWithMetadataCompilations.keys.associate { sourceSet ->
/**
* Currently, Kotlin/Native dependencies must include the implementation dependencies, too. These dependencies must also be
* published as API dependencies of the metadata module to get into the resolution result, see
* [KotlinMetadataTargetConfigurator.exportDependenciesForPublishing].
*/
val isNativeSharedSourceSet = sourceSet.isNativeSourceSet.getOrThrow()
val scopes = listOfNotNull(
KotlinDependencyScope.API_SCOPE,
KotlinDependencyScope.IMPLEMENTATION_SCOPE.takeIf { isNativeSharedSourceSet }
)
val sourceSetsToIncludeDependencies =
if (isNativeSharedSourceSet)
dependsOnClosureWithInterCompilationDependencies(sourceSet).plus(sourceSet)
else listOf(sourceSet)
val sourceSetExportedDependencies = scopes.flatMap { scope ->
sourceSetsToIncludeDependencies.flatMap { hierarchySourceSet ->
configurations.sourceSetDependencyConfigurationByScope(hierarchySourceSet, scope).allDependencies.toList()
}
}
sourceSet.name to sourceSetExportedDependencies.map { ModuleIds.fromDependency(it) }.toSet()
}
}
internal fun KotlinProjectStructureMetadata.serialize(
serializer: Serializer,
node: Serializer.(name: String, Serializer.() -> Unit) -> Unit,
multiNodes: Serializer.(name: String, Serializer.() -> Unit) -> Unit,
multiNodesItem: Serializer.(name: String, Serializer.() -> Unit) -> Unit,
value: Serializer.(key: String, value: String) -> Unit,
multiValue: Serializer.(name: String, values: List) -> Unit,
) = with(serializer) {
node(ROOT_NODE_NAME) {
value(FORMAT_VERSION_NODE_NAME, formatVersion)
value(PUBLISHED_AS_ROOT_NAME, isPublishedAsRoot.toString())
multiNodes(VARIANTS_NODE_NAME) {
sourceSetNamesByVariantName.forEach { (variantName, sourceSets) ->
multiNodesItem(VARIANT_NODE_NAME) {
value(NAME_NODE_NAME, variantName)
multiValue(SOURCE_SET_NODE_NAME, sourceSets.toList())
}
}
}
multiNodes(SOURCE_SETS_NODE_NAME) {
for (sourceSet in sourceSetNames) {
multiNodesItem(SOURCE_SET_NODE_NAME) {
value(NAME_NODE_NAME, sourceSet)
multiValue(DEPENDS_ON_NODE_NAME, sourceSetsDependsOnRelation[sourceSet].orEmpty().toList())
multiValue(MODULE_DEPENDENCY_NODE_NAME, sourceSetModuleDependencies[sourceSet].orEmpty().map { moduleDependency ->
moduleDependency.groupId + ":" + moduleDependency.moduleId
})
sourceSetCInteropMetadataDirectory[sourceSet]?.let { cinteropMetadataDirectory ->
value(SOURCE_SET_CINTEROP_METADATA_NODE_NAME, cinteropMetadataDirectory)
}
sourceSetBinaryLayout[sourceSet]?.let { binaryLayout ->
value(BINARY_LAYOUT_NODE_NAME, binaryLayout.name)
}
if (sourceSet in hostSpecificSourceSets) {
value(HOST_SPECIFIC_NODE_NAME, "true")
}
}
}
}
}
}
internal fun KotlinProjectStructureMetadata.toXmlDocument(): Document {
return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().apply {
val node: Node.(String, Node.() -> Unit) -> Unit = { name, content -> appendChild(createElement(name).apply(content)) }
val textNode: Node.(String, String) -> Unit =
{ name, value -> appendChild(createElement(name).apply { appendChild(createTextNode(value)) }) }
serialize(this as Node, node, node, node, textNode, { name, values -> for (v in values) textNode(name, v) })
}
}
internal fun KotlinProjectStructureMetadata.toJson(): String {
val gson = GsonBuilder().setPrettyPrinting().create()
val stringWriter = StringWriter()
with(gson.newJsonWriter(stringWriter)) {
val obj: JsonWriter.(String, JsonWriter.() -> Unit) -> Unit =
{ name, content -> if (name.isNotEmpty()) name(name); beginObject(); content(); endObject() }
val property: JsonWriter.(String, String) -> Unit = { name, value -> name(name); value(value) }
val array: JsonWriter.(String, JsonWriter.() -> Unit) -> Unit =
{ name, contents -> name(name); beginArray(); contents(); endArray() }
beginObject()
serialize(this, obj, array, { _, fn -> obj("", fn) }, property, { key, values -> array(key) { values.forEach { value(it) } } })
endObject()
}
return stringWriter.toString()
}
private val NodeList.elements: Iterable get() = (0 until length).map { [email protected] (it) }.filterIsInstance()
internal fun parseKotlinSourceSetMetadataFromJson(string: String): KotlinProjectStructureMetadata {
@Suppress("DEPRECATION") // The replacement doesn't compile against old dependencies such as AS 4.0
val json = JsonParser().parse(string).asJsonObject
val valueNamed: JsonObject.(String) -> String? = { name -> get(name)?.asString }
val multiObjects: JsonObject.(String?) -> Iterable = { name -> get(name).asJsonArray.map { it.asJsonObject } }
val multiValues: JsonObject.(String?) -> Iterable = { name -> get(name).asJsonArray.map { it.asString } }
return parseKotlinSourceSetMetadata({ json.get(ROOT_NODE_NAME).asJsonObject }, valueNamed, multiObjects, multiValues)
}
internal fun parseKotlinSourceSetMetadataFromXml(document: Document): KotlinProjectStructureMetadata {
val nodeNamed: Element.(String) -> Element? = { name -> getElementsByTagName(name).elements.singleOrNull() }
val valueNamed: Element.(String) -> String? =
{ name -> getElementsByTagName(name).run { if (length > 0) item(0).textContent else null } }
val multiObjects: Element.(String) -> Iterable = { name -> nodeNamed(name)?.childNodes?.elements ?: emptyList() }
val multiValues: Element.(String) -> Iterable = { name -> getElementsByTagName(name).elements.map { it.textContent } }
return parseKotlinSourceSetMetadata(
{ document.getElementsByTagName(ROOT_NODE_NAME).elements.single() },
valueNamed,
multiObjects,
multiValues
)
}
internal fun parseKotlinSourceSetMetadata(
getRoot: () -> ParsingContext,
valueNamed: ParsingContext.(key: String) -> String?,
multiObjects: ParsingContext.(named: String) -> Iterable,
multiValues: ParsingContext.(named: String) -> Iterable,
): KotlinProjectStructureMetadata {
val projectStructureNode = getRoot()
val formatVersion = checkNotNull(projectStructureNode.valueNamed(FORMAT_VERSION_NODE_NAME))
val variantsNode = projectStructureNode.multiObjects(VARIANTS_NODE_NAME)
val isPublishedAsRoot = projectStructureNode.valueNamed(PUBLISHED_AS_ROOT_NAME)?.toBoolean() ?: false
val sourceSetsByVariant = mutableMapOf>()
variantsNode.forEach { variantNode ->
val variantName = requireNotNull(variantNode.valueNamed(NAME_NODE_NAME))
val sourceSets = variantNode.multiValues(SOURCE_SET_NODE_NAME).toSet()
sourceSetsByVariant[variantName] = sourceSets
}
val sourceSetDependsOnRelation = mutableMapOf>()
val sourceSetModuleDependencies = mutableMapOf>()
val sourceSetBinaryLayout = mutableMapOf()
val sourceSetCInteropMetadataDirectory = mutableMapOf()
val hostSpecificSourceSets = mutableSetOf()
val sourceSetNames = mutableSetOf()
val sourceSetsNode = projectStructureNode.multiObjects(SOURCE_SETS_NODE_NAME)
sourceSetsNode.forEach { sourceSetNode ->
val sourceSetName = checkNotNull(sourceSetNode.valueNamed(NAME_NODE_NAME))
sourceSetNames.add(sourceSetName)
val dependsOn = sourceSetNode.multiValues(DEPENDS_ON_NODE_NAME).toSet()
val moduleDependencies = sourceSetNode.multiValues(MODULE_DEPENDENCY_NODE_NAME).mapTo(mutableSetOf()) {
val (groupId, moduleId) = it.split(":")
ModuleDependencyIdentifier(groupId, moduleId)
}
sourceSetNode.valueNamed(SOURCE_SET_CINTEROP_METADATA_NODE_NAME)?.let { cinteropMetadataDirectory ->
sourceSetCInteropMetadataDirectory[sourceSetName] = cinteropMetadataDirectory
}
sourceSetNode.valueNamed(HOST_SPECIFIC_NODE_NAME)
?.let { if (it.toBoolean()) hostSpecificSourceSets.add(sourceSetName) }
sourceSetNode.valueNamed(BINARY_LAYOUT_NODE_NAME)
?.let { SourceSetMetadataLayout.byName(it) }
?.let { sourceSetBinaryLayout[sourceSetName] = it }
sourceSetDependsOnRelation[sourceSetName] = dependsOn
sourceSetModuleDependencies[sourceSetName] = moduleDependencies
}
return KotlinProjectStructureMetadata(
sourceSetNamesByVariantName = sourceSetsByVariant,
sourceSetsDependsOnRelation = sourceSetDependsOnRelation,
sourceSetBinaryLayout = sourceSetBinaryLayout,
sourceSetModuleDependencies = sourceSetModuleDependencies,
sourceSetCInteropMetadataDirectory = sourceSetCInteropMetadataDirectory,
hostSpecificSourceSets = hostSpecificSourceSets,
isPublishedAsRoot = isPublishedAsRoot,
sourceSetNames = sourceSetNames,
formatVersion = formatVersion
)
}
internal val GlobalProjectStructureMetadataStorageSetupAction = KotlinProjectSetupCoroutine {
// Run in AfterEvaluate stage to avoid issues with Precompiled Script Plugins
// When Gradle runs `:generatePrecompiledScriptPluginAccessors` it creates dummy project and
// applies plugins from *.gradle.kts file to and generates accessors from it.
// These dummy projects never gets evaluated and should not expose any Project Structure Metadata.
// Putting registerProjectStructureMetadata in AfterEvaluate stage prevents PSM registration in dummy projects.
KotlinPluginLifecycle.Stage.AfterEvaluateBuildscript.await()
GlobalProjectStructureMetadataStorage.registerProjectStructureMetadata(project) {
multiplatformExtension.kotlinProjectStructureMetadata
}
}
internal object GlobalProjectStructureMetadataStorage {
private const val propertyPrefix = "kotlin.projectStructureMetadata.build"
fun propertyName(buildName: String, projectPath: String) = "$propertyPrefix.$buildName.path.$projectPath"
fun registerProjectStructureMetadata(project: Project, metadataProvider: () -> KotlinProjectStructureMetadata) {
project.compositeBuildRootProject {
(it as ExtensionAware).extensions.extraProperties.set(
propertyName(project.currentBuildId().buildPathCompat, project.path),
{ metadataProvider().toJson() }
)
}
}
fun getProjectStructureMetadataProvidersFromAllGradleBuilds(project: Project): Map> {
return project.compositeBuildRootProject.extensions.extraProperties.properties
.filterKeys { it.startsWith(propertyPrefix) }
.entries
.associate { (propertyName, propertyValue) ->
Pair(
propertyName.toProjectPathWithBuildName(),
lazy { propertyValue?.getProjectStructureMetadataOrNull() }
)
}
}
private fun Any.getProjectStructureMetadataOrNull(): KotlinProjectStructureMetadata? {
val jsonStringProvider = this as? Function0<*> ?: return null
val jsonString = jsonStringProvider.invoke() as? String ?: return null
return parseKotlinSourceSetMetadataFromJson(jsonString)
}
private fun String.toProjectPathWithBuildName(): ProjectPathWithBuildPath {
val (buildPath, projectPath) = removePrefix("$propertyPrefix.").split(".path.")
return ProjectPathWithBuildPath(
projectPath = projectPath,
buildPath = buildPath
)
}
}
private const val ROOT_NODE_NAME = "projectStructure"
private const val PUBLISHED_AS_ROOT_NAME = "isPublishedAsRoot"
private const val FORMAT_VERSION_NODE_NAME = "formatVersion"
private const val VARIANTS_NODE_NAME = "variants"
private const val VARIANT_NODE_NAME = "variant"
private const val NAME_NODE_NAME = "name"
private const val SOURCE_SETS_NODE_NAME = "sourceSets"
private const val SOURCE_SET_NODE_NAME = "sourceSet"
private const val SOURCE_SET_CINTEROP_METADATA_NODE_NAME = "sourceSetCInteropMetadataDirectory"
private const val DEPENDS_ON_NODE_NAME = "dependsOn"
private const val MODULE_DEPENDENCY_NODE_NAME = "moduleDependency"
private const val BINARY_LAYOUT_NODE_NAME = "binaryLayout"
private const val HOST_SPECIFIC_NODE_NAME = "hostSpecific"