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.
/*
* 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 org.gradle.api.Project
import org.gradle.api.artifacts.*
import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_TEST_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import java.io.File
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import javax.xml.parsers.DocumentBuilderFactory
internal sealed class MetadataDependencyResolution(
val dependency: ResolvedDependency,
val projectDependency: ProjectDependency?
) {
override fun toString(): String {
val verb = when (this) {
is KeepOriginalDependency -> "keep"
is ExcludeAsUnrequested -> "exclude"
is ChooseVisibleSourceSets -> "choose"
}
return "$verb, dependency = $dependency"
}
class KeepOriginalDependency(
dependency: ResolvedDependency,
projectDependency: ProjectDependency?
) : MetadataDependencyResolution(dependency, projectDependency)
class ExcludeAsUnrequested(
dependency: ResolvedDependency,
projectDependency: ProjectDependency?
) : MetadataDependencyResolution(dependency, projectDependency)
abstract class ChooseVisibleSourceSets(
dependency: ResolvedDependency,
projectDependency: ProjectDependency?,
val projectStructureMetadata: KotlinProjectStructureMetadata,
val allVisibleSourceSetNames: Set,
val visibleSourceSetNamesExcludingDependsOn: Set,
val visibleTransitiveDependencies: Set
) : MetadataDependencyResolution(dependency, projectDependency) {
/** Returns the mapping of source set names to files which should be used as the [dependency] parts representing the source sets.
* If any temporary files need to be created, their paths are built from the [baseDir].
* If [doProcessFiles] is true, these temporary files are actually re-created during the call,
* otherwise only their paths are returned, while the files might be missing.
*/
abstract fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map
override fun toString(): String =
super.toString() + ", sourceSets = " + allVisibleSourceSetNames.joinToString(", ", "[", "]") {
(if (it in visibleSourceSetNamesExcludingDependsOn) "*" else "") + it
}
}
}
private typealias ModuleId = Pair // group ID, artifact ID
private val ResolvedDependency.moduleId: ModuleId
get() = moduleGroup to moduleName
private val Dependency.moduleId: ModuleId
get() = group to name
internal class GranularMetadataTransformation(
val project: Project,
val kotlinSourceSet: KotlinSourceSet,
/** A list of scopes that the dependencies from [kotlinSourceSet] are treated as requested dependencies. */
val sourceSetRequestedScopes: List,
/** A configuration that holds the dependencies of the appropriate scope for all Kotlin source sets in the project */
val allSourceSetsConfiguration: Configuration,
val parentTransformations: Lazy>
) {
val metadataDependencyResolutions: Iterable by lazy { doTransform() }
// Keep parents of each dependency, too. We need a dependency's parent when it's an MPP's metadata module dependency:
// in this case, the parent is the MPP's root module.
private data class ResolvedDependencyWithParent(
val dependency: ResolvedDependency,
val parent: ResolvedDependency?
)
private fun collectProjectDependencies(
requestedDependencies: Iterable,
resolvedDependencies: Iterable
): Map {
val result = mutableMapOf()
val resolvedDependenciesMap: Map = resolvedDependencies.associateBy { it.moduleId }
fun visitProjectDependency(projectDependency: ProjectDependency) {
val moduleId = projectDependency.group to projectDependency.name
if (moduleId in result) return
result[moduleId] = projectDependency
val resolvedDependency = resolvedDependenciesMap[moduleId] ?: return
projectDependency.dependencyProject.configurations.getByName(resolvedDependency.configuration)
.allDependencies
.withType(ProjectDependency::class.java)
.forEach(::visitProjectDependency)
}
requestedDependencies.forEach(::visitProjectDependency)
return result
}
private val requestedDependencies: Iterable by lazy {
fun collectScopedDependenciesFromSourceSet(sourceSet: KotlinSourceSet): Set =
sourceSetRequestedScopes.flatMapTo(mutableSetOf()) { scope ->
project.sourceSetDependencyConfigurationByScope(sourceSet, scope).allDependencies
}
val ownDependencies = collectScopedDependenciesFromSourceSet(kotlinSourceSet)
val parentDependencies = parentTransformations.value.flatMapTo(mutableSetOf()) { it.requestedDependencies }
ownDependencies + parentDependencies
}
private val resolvedConfiguration: LenientConfiguration by lazy {
/** If [kotlinSourceSet] is not a published source set, its dependencies are not included in [allSourceSetsConfiguration].
* In that case, to resolve the dependencies of the source set in a way that is consistent with the published source sets,
* we need to create a new configuration with the dependencies from both [allSourceSetsConfiguration] and the
* input configuration(s) of the source set. */
var modifiedConfiguration: Configuration? = null
val originalDependencies = allSourceSetsConfiguration.allDependencies
requestedDependencies.forEach { dependency ->
if (dependency !in originalDependencies) {
modifiedConfiguration = (modifiedConfiguration ?: allSourceSetsConfiguration.copyRecursive()).apply {
dependencies.add(dependency)
}
}
}
(modifiedConfiguration ?: allSourceSetsConfiguration).resolvedConfiguration.lenientConfiguration
}
private fun doTransform(): Iterable {
val result = mutableListOf()
val parentResolutions =
parentTransformations.value.flatMap { it.metadataDependencyResolutions }.groupBy { it.dependency.moduleId }
val allRequestedDependencies = requestedDependencies
val allModuleDependencies = resolvedConfiguration.allModuleDependencies
val knownProjectDependencies = collectProjectDependencies(
allRequestedDependencies.filterIsInstance(),
allModuleDependencies
)
val resolvedDependencyQueue: Queue = ArrayDeque().apply {
val requestedModules: Set = allRequestedDependencies.mapTo(mutableSetOf()) { it.moduleId }
addAll(
resolvedConfiguration.firstLevelModuleDependencies
.filter { it.moduleId in requestedModules }
.map { ResolvedDependencyWithParent(it, null) }
)
}
val visitedDependencies = mutableSetOf()
while (resolvedDependencyQueue.isNotEmpty()) {
val (resolvedDependency, parent: ResolvedDependency?) = resolvedDependencyQueue.poll()
val projectDependency: ProjectDependency? = knownProjectDependencies[resolvedDependency.moduleId]
visitedDependencies.add(resolvedDependency)
val dependencyResult = processDependency(
resolvedDependency,
parentResolutions[resolvedDependency.moduleId].orEmpty(),
parent,
projectDependency
)
result.add(dependencyResult)
val transitiveDependenciesToVisit = when (dependencyResult) {
is MetadataDependencyResolution.KeepOriginalDependency -> resolvedDependency.children
is MetadataDependencyResolution.ChooseVisibleSourceSets -> dependencyResult.visibleTransitiveDependencies
is MetadataDependencyResolution.ExcludeAsUnrequested -> error("a visited dependency is erroneously considered unrequested")
}
resolvedDependencyQueue.addAll(
transitiveDependenciesToVisit.filter { it !in visitedDependencies }
.map { ResolvedDependencyWithParent(it, resolvedDependency) }
)
}
allModuleDependencies.forEach { resolvedDependency ->
if (resolvedDependency !in visitedDependencies) {
result.add(
MetadataDependencyResolution.ExcludeAsUnrequested(
resolvedDependency,
knownProjectDependencies[resolvedDependency.moduleGroup to resolvedDependency.moduleName]
)
)
}
}
return result
}
/**
* If the [module] is an MPP metadata module, we extract [KotlinProjectStructureMetadata] and do the following:
*
* * get the [KotlinProjectStructureMetadata] from the dependency (either deserialize from the artifact or build from the project)
*
* * determine the set *S* of source sets that should be seen in the [kotlinSourceSet] by finding which variants the [parent]
* dependency got resolved for the compilations where [kotlinSourceSet] participates:
*
* * transform the single Kotlin metadata artifact into a set of Kotlin metadata artifacts for the particular source sets in
* *S* and add the results as [MetadataDependencyResolution.ChooseVisibleSourceSets]
*
* * based on the project structure metadata, determine which of the module's dependencies are requested by the
* source sets in *S*, then consider only these transitive dependencies, ignore the others;
*/
private fun processDependency(
module: ResolvedDependency,
parentResolutionsForModule: Iterable,
parent: ResolvedDependency?,
projectDependency: ProjectDependency?
): MetadataDependencyResolution {
val mppDependencyMetadataExtractor = when {
projectDependency != null -> ProjectMppDependencyMetadataExtractor(project, module, projectDependency.dependencyProject)
parent != null -> JarArtifactMppDependencyMetadataExtractor(project, module)
else -> null
}
val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata()
?: return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency)
val allVisibleSourceSets =
SourceSetVisibilityProvider(project).getVisibleSourceSetNames(
kotlinSourceSet,
sourceSetRequestedScopes,
parent ?: module,
projectStructureMetadata,
projectDependency?.dependencyProject
)
val sourceSetsVisibleInParents = parentResolutionsForModule
.filterIsInstance()
.flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames }
// Keep only the transitive dependencies requested by the visible source sets:
// Visit the transitive dependencies visible by parents, too (i.e. allVisibleSourceSets), as this source set might get a more
// concrete view on them:
val requestedTransitiveDependencies: Set =
mutableSetOf().apply {
projectStructureMetadata.sourceSetModuleDependencies.forEach { (sourceSetName, moduleDependencies) ->
if (sourceSetName in allVisibleSourceSets) {
addAll(moduleDependencies.map { ModuleId(it.groupId, it.moduleId) })
}
}
}
val transitiveDependenciesToVisit = module.children.filterTo(mutableSetOf()) {
(it.moduleId) in requestedTransitiveDependencies
}
val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in sourceSetsVisibleInParents }
return object : MetadataDependencyResolution.ChooseVisibleSourceSets(
module,
projectDependency,
projectStructureMetadata,
allVisibleSourceSets,
visibleSourceSetsExcludingDependsOn,
transitiveDependenciesToVisit
) {
override fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map =
mppDependencyMetadataExtractor.getVisibleSourceSetsMetadata(visibleSourceSetsExcludingDependsOn, baseDir, doProcessFiles)
}
}
}
private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedDependency) {
abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata?
abstract fun getVisibleSourceSetsMetadata(
visibleSourceSetNames: Set,
baseDir: File,
doProcessFiles: Boolean
): Map
}
private class ProjectMppDependencyMetadataExtractor(
project: Project,
dependency: ResolvedDependency,
private val dependencyProject: Project
) : MppDependencyMetadataExtractor(project, dependency) {
override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? =
buildKotlinProjectStructureMetadata(dependencyProject)
override fun getVisibleSourceSetsMetadata(
visibleSourceSetNames: Set,
baseDir: File,
doProcessFiles: Boolean
): Map =
dependencyProject.multiplatformExtension.targets.getByName(KotlinMultiplatformPlugin.METADATA_TARGET_NAME).compilations
.filter { it.defaultSourceSet.name in visibleSourceSetNames }
.associate { it.defaultSourceSet.name to it.output.classesDirs }
}
private class JarArtifactMppDependencyMetadataExtractor(
project: Project,
dependency: ResolvedDependency
) : MppDependencyMetadataExtractor(project, dependency) {
private val artifact: ResolvedArtifact?
get() = dependency.moduleArtifacts.singleOrNull { it.extension == "jar" }
override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? {
val artifactFile = artifact?.file ?: return null
return ZipFile(artifactFile).use { zip ->
val metadata = zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME")
?: return null
val metadataXmlDocument = zip.getInputStream(metadata).use { inputStream ->
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
}
parseKotlinSourceSetMetadataFromXml(metadataXmlDocument)
}
}
override fun getVisibleSourceSetsMetadata(
visibleSourceSetNames: Set,
baseDir: File,
doProcessFiles: Boolean
): Map {
val jarArtifact = artifact ?: return emptyMap()
val artifactFile = jarArtifact.file
val moduleId = jarArtifact.moduleVersion.id
return extractSourceSetMetadataFromJar(moduleId, visibleSourceSetNames, artifactFile, baseDir, doProcessFiles)
}
private fun extractSourceSetMetadataFromJar(
module: ModuleVersionIdentifier,
chooseSourceSetsByNames: Set,
artifactJar: File,
baseDir: File,
doProcessFiles: Boolean
): Map {
val moduleString = "${module.group}-${module.name}-${module.version}"
val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } }
val resultFiles = mutableMapOf()
ZipFile(artifactJar).use { zip ->
val entriesBySourceSet = zip.entries().asSequence()
.groupBy { it.name.substringBefore("/") }
.filterKeys { it in chooseSourceSetsByNames }
entriesBySourceSet.forEach { (sourceSetName, entries) ->
// TODO: once IJ supports non-JAR metadata dependencies, extraact to a directory, not a JAR
// Also, if both IJ and the CLI compiler can read metadata from a path inside a JAR, then no operations will be needed
val extractToJarFile = transformedModuleRoot.resolve("$moduleString-$sourceSetName.jar")
resultFiles[sourceSetName] = project.files(extractToJarFile)
if (doProcessFiles) {
ZipOutputStream(extractToJarFile.outputStream()).use { resultZipOutput ->
for (entry in entries) {
if (entry.isDirectory)
continue
// Drop the source set name from the entry path
val resultEntry = ZipEntry(entry.name.substringAfter("/"))
zip.getInputStream(entry).use { inputStream ->
resultZipOutput.putNextEntry(resultEntry)
inputStream.copyTo(resultZipOutput)
resultZipOutput.closeEntry()
}
}
}
}
}
}
return resultFiles
}
}