Build PM2.0 in o.j.k.multiplatform (+ dependency resolution API)
This commit is contained in:
committed by
TeamCityServer
parent
994f940a31
commit
6aee396e4b
+218
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.mpp
|
||||||
|
|
||||||
|
import org.gradle.api.Named
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.Dependency
|
||||||
|
import org.gradle.api.artifacts.ProjectDependency
|
||||||
|
import org.gradle.api.attributes.Attribute
|
||||||
|
import org.gradle.api.attributes.AttributeContainer
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.currentBuildId
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.matchesModule
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.sources.getVisibleSourceSetsFromAssociateCompilations
|
||||||
|
import org.jetbrains.kotlin.project.model.*
|
||||||
|
|
||||||
|
class ProjectStructureMetadataModuleBuilder {
|
||||||
|
private val modulesCache = mutableMapOf<String, KotlinModule>()
|
||||||
|
|
||||||
|
private fun buildModuleFromProjectStructureMetadata(
|
||||||
|
moduleName: String,
|
||||||
|
moduleOrigin: ModuleOrigin,
|
||||||
|
metadata: KotlinProjectStructureMetadata
|
||||||
|
): KotlinModule =
|
||||||
|
BasicKotlinModule(moduleName, moduleOrigin).apply {
|
||||||
|
metadata.sourceSetNamesByVariantName.keys.forEach { variantName ->
|
||||||
|
fragments.add(BasicKotlinModuleVariant(this@apply, variantName))
|
||||||
|
}
|
||||||
|
metadata.sourceSetNamesByVariantName.forEach { (variantName, sourceSets) ->
|
||||||
|
val variant = fragmentByName(variantName)
|
||||||
|
sourceSets.forEach { sourceSetName ->
|
||||||
|
if (fragments.none { it.fragmentName == sourceSetName })
|
||||||
|
fragments.add(BasicKotlinModuleFragment(this@apply, sourceSetName))
|
||||||
|
val fragment = fragmentByName(sourceSetName)
|
||||||
|
variant.directRefinesDependencies.add(fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata.sourceSetsDependsOnRelation.forEach { (depending, dependencies) ->
|
||||||
|
val dependingFragment = fragmentByName(depending)
|
||||||
|
dependencies.forEach { dependency ->
|
||||||
|
if (fragments.none { it.fragmentName == dependency })
|
||||||
|
fragments.add(BasicKotlinModuleFragment(this@apply, dependency))
|
||||||
|
val fragment = fragmentByName(dependency)
|
||||||
|
dependingFragment.directRefinesDependencies.add(fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getModule(name: String, moduleOrigin: ModuleOrigin, projectStructureMetadata: KotlinProjectStructureMetadata): KotlinModule {
|
||||||
|
return modulesCache.getOrPut(name) {
|
||||||
|
buildModuleFromProjectStructureMetadata(
|
||||||
|
name,
|
||||||
|
moduleOrigin,
|
||||||
|
projectStructureMetadata
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class GradleProjectModuleBuilder(private val addInferredSourceSetVisibilityAsExplicit: Boolean) {
|
||||||
|
fun buildModuleFromProject(project: Project): KotlinModule? {
|
||||||
|
val extension = project.multiplatformExtensionOrNull
|
||||||
|
?: project.kotlinExtension
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val targets = when (extension) {
|
||||||
|
is KotlinMultiplatformExtension -> extension.targets.filter { it.name != "metadata" }
|
||||||
|
is KotlinSingleTargetExtension -> listOf(extension.target)
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasicKotlinModule(project.path, LocalBuild(project.currentBuildId().name)).apply {
|
||||||
|
val variantToCompilation = mutableMapOf<KotlinModuleFragment, KotlinCompilation<*>>()
|
||||||
|
|
||||||
|
targets.forEach { target ->
|
||||||
|
val publishedVariantsByCompilation = (target as? AbstractKotlinTarget)?.kotlinComponents.orEmpty()
|
||||||
|
.flatMap { component -> (component as? KotlinVariant)?.usages.orEmpty() }
|
||||||
|
.groupBy { it.compilation }
|
||||||
|
|
||||||
|
target.compilations.forEach { compilation ->
|
||||||
|
val names =
|
||||||
|
publishedVariantsByCompilation[compilation].orEmpty()
|
||||||
|
.filter { it.includeIntoProjectStructureMetadata }.map { it.name }
|
||||||
|
|
||||||
|
names.forEach { variantName ->
|
||||||
|
val variant = BasicKotlinModuleVariant(this@apply, variantName)
|
||||||
|
variantToCompilation[variant] = compilation
|
||||||
|
fragments.add(variant)
|
||||||
|
|
||||||
|
// TODO The attributes from the compile dependencies configuration might differ from exposed attributes
|
||||||
|
val compileDependenciesConfiguration =
|
||||||
|
project.configurations.getByName(compilation.compileDependencyConfigurationName)
|
||||||
|
compileDependenciesConfiguration.attributes.keySet().forEach { key ->
|
||||||
|
variant.variantAttributes[KotlinAttributeKey(key.name)] =
|
||||||
|
attributeString(compileDependenciesConfiguration.attributes, key)
|
||||||
|
}
|
||||||
|
variant.isExported = compilation.isMain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extension.sourceSets.forEach { sourceSet ->
|
||||||
|
val existingVariant = fragments.filterIsInstance<BasicKotlinModuleVariant>().find { it.fragmentName == sourceSet.name }
|
||||||
|
val fragment = existingVariant ?: BasicKotlinModuleFragment(this@apply, sourceSet.name).also { fragments.add(it) }
|
||||||
|
fragment.kotlinSourceRoots = sourceSet.kotlin.sourceDirectories.toList()
|
||||||
|
|
||||||
|
// FIXME: Kotlin/Native implementation-effective-api dependencies are missing here. Introduce dependency scopes
|
||||||
|
project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies.forEach {
|
||||||
|
val moduleDependency = it.toModuleDependency(project)
|
||||||
|
fragment.declaredModuleDependencies.add(moduleDependency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once all fragments are created, add dependencies between them
|
||||||
|
|
||||||
|
fragments.forEach { fragment ->
|
||||||
|
val sourceSet = extension.sourceSets.findByName(fragment.fragmentName)
|
||||||
|
?: variantToCompilation.getValue(fragment).defaultSourceSet
|
||||||
|
sourceSet.dependsOn.forEach { dependency ->
|
||||||
|
val dependencyFragment = fragmentByName(dependency.name)
|
||||||
|
fragment.directRefinesDependencies.add(dependencyFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targets.forEach { target ->
|
||||||
|
target.compilations.forEach { compilation ->
|
||||||
|
val fragment = fragmentByName(compilation.defaultSourceSetName)
|
||||||
|
compilation.associateWith.forEach { associate ->
|
||||||
|
val dependencyFragment = fragmentByName(associate.defaultSourceSetName)
|
||||||
|
fragment.declaredContainingModuleFragmentDependencies.add(dependencyFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (addInferredSourceSetVisibilityAsExplicit) {
|
||||||
|
extension.sourceSets.forEach { sourceSet ->
|
||||||
|
val fragment = fragmentByName(sourceSet.name)
|
||||||
|
getVisibleSourceSetsFromAssociateCompilations(project, sourceSet).forEach { dependency ->
|
||||||
|
val dependencyFragment = fragmentByName(dependency.name)
|
||||||
|
fragment.declaredContainingModuleFragmentDependencies.add(dependencyFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun <T : Any> attributeString(container: AttributeContainer, attributeKey: Attribute<T>): String {
|
||||||
|
val value = container.getAttribute(attributeKey)
|
||||||
|
return when (value) {
|
||||||
|
is Named -> value.name
|
||||||
|
else -> value.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Dependency.toModuleDependency(
|
||||||
|
project: Project
|
||||||
|
) = when (val dependency = this) {
|
||||||
|
is ProjectDependency ->
|
||||||
|
LocalModuleDependency(LocalBuild(project.currentBuildId().name), dependency.dependencyProject.path)
|
||||||
|
else ->
|
||||||
|
ExternalModuleDependency(ExternalOrigin(listOfNotNull(dependency.group, dependency.name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BasicKotlinModule.fragmentByName(name: String) =
|
||||||
|
fragments.single { it.fragmentName == name }
|
||||||
|
|
||||||
|
class GradleModuleVariantResolver(val project: Project) : ModuleVariantResolver {
|
||||||
|
private val resolvedVariantProvider = ResolvedMppVariantsProvider.get(project)
|
||||||
|
|
||||||
|
override fun getChosenVariant(requestingVariant: KotlinModuleVariant, dependencyModule: KotlinModule): VariantResolution {
|
||||||
|
// TODO maybe improve this behavior? Currently it contradicts dependency resolution in that it may return a chosen variant for an
|
||||||
|
// unrequested dependency. This workaround is needed for synthetic modules which were not produced from module metadata, so maybe
|
||||||
|
// those modules should be marked somehow
|
||||||
|
dependencyModule.variants.singleOrNull()
|
||||||
|
?.let { return VariantResolution.fromMatchingVariants(requestingVariant, dependencyModule, listOf(it)) }
|
||||||
|
|
||||||
|
val module = requestingVariant.containingModule
|
||||||
|
|
||||||
|
// This implementation can only resolve variants for the current project's KotlinModule
|
||||||
|
require(module.moduleName == project.path)
|
||||||
|
require(module.moduleOrigin.let { it is LocalBuild && it.buildId == project.currentBuildId().name })
|
||||||
|
|
||||||
|
val targets =
|
||||||
|
project.multiplatformExtensionOrNull?.targets ?: listOf((project.kotlinExtension as KotlinSingleTargetExtension).target)
|
||||||
|
|
||||||
|
val compilation =
|
||||||
|
targets.filterIsInstance<AbstractKotlinTarget>()
|
||||||
|
.flatMap { it.kotlinComponents.filterIsInstance<KotlinVariant>() }
|
||||||
|
.flatMap { it.usages }
|
||||||
|
.firstOrNull { it.name == requestingVariant.fragmentName }
|
||||||
|
?.compilation
|
||||||
|
?: return VariantResolution.NotRequested(requestingVariant, dependencyModule)
|
||||||
|
|
||||||
|
val compileClasspath = project.configurations.getByName(compilation.compileDependencyConfigurationName)
|
||||||
|
|
||||||
|
// TODO optimize O(n) search, store the mapping
|
||||||
|
val component = compileClasspath.incoming.resolutionResult.allComponents.find { it.id.matchesModule(dependencyModule) }
|
||||||
|
?: return VariantResolution.NotRequested(requestingVariant, dependencyModule)
|
||||||
|
|
||||||
|
val dependencyModuleId = ModuleIds.fromComponent(project, component)
|
||||||
|
// FIXME check composite builds, it's likely that resolvedVariantProvider fails on them?
|
||||||
|
val variantName = resolvedVariantProvider.getResolvedVariantName(dependencyModuleId, compileClasspath)
|
||||||
|
|
||||||
|
val resultVariant = dependencyModule.variants.singleOrNull { it.fragmentName == variantName }
|
||||||
|
|
||||||
|
return if (resultVariant == null)
|
||||||
|
VariantResolution.NoVariantMatch(requestingVariant, dependencyModule)
|
||||||
|
else
|
||||||
|
VariantResolution.VariantMatch(requestingVariant, dependencyModule, resultVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
-16
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
|||||||
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||||
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
|
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
|
||||||
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
|
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
|
||||||
|
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -224,23 +225,12 @@ internal class GranularMetadataTransformation(
|
|||||||
parentResolutionsForModule: Iterable<MetadataDependencyResolution>,
|
parentResolutionsForModule: Iterable<MetadataDependencyResolution>,
|
||||||
parent: ResolvedComponentResult?
|
parent: ResolvedComponentResult?
|
||||||
): MetadataDependencyResolution {
|
): MetadataDependencyResolution {
|
||||||
val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project)
|
val mppDependencyMetadataExtractor = getMetadataExtractor(
|
||||||
val metadataArtifact = resolvedMppVariantsProvider.getPlatformArtifactByPlatformModule(
|
project,
|
||||||
ModuleIds.fromComponent(project, module),
|
module,
|
||||||
configurationToResolve
|
configurationToResolve,
|
||||||
|
resolveViaAvailableAt = false // we will process this dependency later in the queue
|
||||||
)
|
)
|
||||||
val moduleId = module.id
|
|
||||||
val mppDependencyMetadataExtractor: MppDependencyMetadataExtractor? = when {
|
|
||||||
moduleId is ProjectComponentIdentifier -> when {
|
|
||||||
moduleId.build.isCurrentBuild ->
|
|
||||||
ProjectMppDependencyMetadataExtractor(project, module, project.project(moduleId.projectPath))
|
|
||||||
metadataArtifact != null ->
|
|
||||||
IncludedBuildMetadataExtractor(project, module, metadataArtifact)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
metadataArtifact != null -> JarArtifactMppDependencyMetadataExtractor(project, module, metadataArtifact)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
val resolvedToProject: Project? = (mppDependencyMetadataExtractor as? ProjectMppDependencyMetadataExtractor)?.dependencyProject
|
val resolvedToProject: Project? = (mppDependencyMetadataExtractor as? ProjectMppDependencyMetadataExtractor)?.dependencyProject
|
||||||
|
|
||||||
@@ -468,6 +458,46 @@ private open class JarArtifactMppDependencyMetadataExtractor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getMetadataExtractor(
|
||||||
|
project: Project,
|
||||||
|
module: ResolvedComponentResult,
|
||||||
|
configuration: Configuration,
|
||||||
|
resolveViaAvailableAt: Boolean
|
||||||
|
): MppDependencyMetadataExtractor? {
|
||||||
|
val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project)
|
||||||
|
val moduleIdentifier = ModuleIds.fromComponent(project, module)
|
||||||
|
val metadataArtifact = resolvedMppVariantsProvider.getResolvedArtifactByPlatformModule(
|
||||||
|
moduleIdentifier,
|
||||||
|
configuration
|
||||||
|
) ?: if (resolveViaAvailableAt) resolvedMppVariantsProvider.getHostSpecificMetadataArtifactByRootModule(
|
||||||
|
moduleIdentifier,
|
||||||
|
configuration
|
||||||
|
) else null
|
||||||
|
|
||||||
|
val moduleId = module.id
|
||||||
|
return when {
|
||||||
|
moduleId is ProjectComponentIdentifier -> when {
|
||||||
|
moduleId.build.isCurrentBuild ->
|
||||||
|
ProjectMppDependencyMetadataExtractor(project, module, project.project(moduleId.projectPath))
|
||||||
|
metadataArtifact != null ->
|
||||||
|
IncludedBuildMetadataExtractor(project, module, metadataArtifact)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
metadataArtifact != null -> JarArtifactMppDependencyMetadataExtractor(project, module, metadataArtifact)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getProjectStructureMetadata(
|
||||||
|
project: Project,
|
||||||
|
module: ResolvedComponentResult,
|
||||||
|
configuration: Configuration
|
||||||
|
): KotlinProjectStructureMetadata? {
|
||||||
|
// FIXME test dependencies won't work
|
||||||
|
val extractor = getMetadataExtractor(project, module, configuration, resolveViaAvailableAt = true)
|
||||||
|
return extractor?.getProjectStructureMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
// This class is needed to encapsulate how we extract the files and point to them in a way that doesn't capture the Gradle project state
|
// This class is needed to encapsulate how we extract the files and point to them in a way that doesn't capture the Gradle project state
|
||||||
internal abstract class ExtractableMetadataFiles {
|
internal abstract class ExtractableMetadataFiles {
|
||||||
abstract fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean): Map<String, FileCollection>
|
abstract fun getMetadataFilesPerSourceSet(doProcessFiles: Boolean): Map<String, FileCollection>
|
||||||
|
|||||||
+4
-3
@@ -45,13 +45,14 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
|
|||||||
* module of a multiplatform project, not one of its platform-specific modules, as seen in the given [configuration].
|
* module of a multiplatform project, not one of its platform-specific modules, as seen in the given [configuration].
|
||||||
* If the [configuration] requests platform artifacts and not the common code metadata, then this function will resolve its
|
* If the [configuration] requests platform artifacts and not the common code metadata, then this function will resolve its
|
||||||
* dependencies to metadata separately. */
|
* dependencies to metadata separately. */
|
||||||
fun getMetadataArtifactByRootModule(rootModuleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? {
|
fun getHostSpecificMetadataArtifactByRootModule(rootModuleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? {
|
||||||
val rootModuleEntry = getEntryForModule(rootModuleIdentifier)
|
val rootModuleEntry = getEntryForModule(rootModuleIdentifier)
|
||||||
|
|
||||||
val platformModuleEntry = rootModuleEntry.run {
|
val platformModuleEntry = rootModuleEntry.run {
|
||||||
if (configuration !in chosenPlatformModuleByConfiguration) {
|
if (configuration !in chosenPlatformModuleByConfiguration) {
|
||||||
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA)
|
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA)
|
||||||
}
|
}
|
||||||
|
// At this point the map should contain the result if calculation above succeeded. If not, put null to avoid recalculation.
|
||||||
chosenPlatformModuleByConfiguration.getOrPut(configuration) { null }
|
chosenPlatformModuleByConfiguration.getOrPut(configuration) { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the artifact of a particular MPP platform-specific [moduleIdentifier] as resolved in the [configuration]. */
|
/** Gets the artifact of a particular MPP platform-specific [moduleIdentifier] as resolved in the [configuration]. */
|
||||||
fun getPlatformArtifactByPlatformModule(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? =
|
fun getResolvedArtifactByPlatformModule(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? =
|
||||||
getEntryForModule(moduleIdentifier).run {
|
getEntryForModule(moduleIdentifier).run {
|
||||||
if (configuration !in resolvedArtifactByConfiguration) {
|
if (configuration !in resolvedArtifactByConfiguration) {
|
||||||
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NORMAL)
|
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NORMAL)
|
||||||
@@ -170,7 +171,7 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
|
|||||||
moduleEntry.resolvedMetadataArtifactByConfiguration[configuration] = null
|
moduleEntry.resolvedMetadataArtifactByConfiguration[configuration] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// We found the host-specific artifact for a platform variant of some MPP:
|
// We found a requested artifact of the MPP; it is one of: platform artifact, root metadata, host-specific metadata
|
||||||
artifact != null -> {
|
artifact != null -> {
|
||||||
val resolvedArtifactMap = when (artifactResolutionMode) {
|
val resolvedArtifactMap = when (artifactResolutionMode) {
|
||||||
ArtifactResolutionMode.NORMAL -> moduleEntry.resolvedArtifactByConfiguration
|
ArtifactResolutionMode.NORMAL -> moduleEntry.resolvedArtifactByConfiguration
|
||||||
|
|||||||
+1
-1
@@ -111,7 +111,7 @@ internal class SourceSetVisibilityProvider(
|
|||||||
|
|
||||||
someVariantByHostSpecificSourceSet.entries.mapNotNull { (sourceSetName, variantName) ->
|
someVariantByHostSpecificSourceSet.entries.mapNotNull { (sourceSetName, variantName) ->
|
||||||
val configuration = firstConfigurationByVariant.getValue(variantName)
|
val configuration = firstConfigurationByVariant.getValue(variantName)
|
||||||
resolvedVariantsProvider.getMetadataArtifactByRootModule(mppModuleIdentifier, configuration)
|
resolvedVariantsProvider.getHostSpecificMetadataArtifactByRootModule(mppModuleIdentifier, configuration)
|
||||||
?.let { sourceSetName to it }
|
?.let { sourceSetName to it }
|
||||||
}.toMap()
|
}.toMap()
|
||||||
}
|
}
|
||||||
|
|||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.mpp.pm20
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.Configuration
|
||||||
|
import org.gradle.api.artifacts.component.ComponentIdentifier
|
||||||
|
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||||
|
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
|
||||||
|
import org.gradle.api.artifacts.result.ResolvedComponentResult
|
||||||
|
import org.gradle.api.artifacts.result.ResolvedDependencyResult
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.GradleModuleVariantResolver
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.ProjectStructureMetadataModuleBuilder
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.GradleProjectModuleBuilder
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.getProjectStructureMetadata
|
||||||
|
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
|
||||||
|
import org.jetbrains.kotlin.project.model.*
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
|
||||||
|
class GradleModuleDependencyResolver(
|
||||||
|
private val project: Project,
|
||||||
|
private val projectStructureMetadataModuleBuilder: ProjectStructureMetadataModuleBuilder,
|
||||||
|
private val projectModuleBuilder: GradleProjectModuleBuilder
|
||||||
|
) : ModuleDependencyResolver {
|
||||||
|
|
||||||
|
// FIXME this wont work for tests, as their dependencies are currently not in the all compile dependencies configuration
|
||||||
|
private val configurationToResolve: Configuration
|
||||||
|
get() = project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)
|
||||||
|
|
||||||
|
override fun resolveDependency(moduleDependency: ModuleDependency): KotlinModule? {
|
||||||
|
val allComponents = configurationToResolve.incoming.resolutionResult.allComponents
|
||||||
|
// TODO: optimize O(n) search, store the resolved components with dependency keys
|
||||||
|
val component = allComponents.find { it.id.matchesModuleDependency(moduleDependency) }
|
||||||
|
val id = component?.id
|
||||||
|
return when {
|
||||||
|
id is ProjectComponentIdentifier && id.build.isCurrentBuild ->
|
||||||
|
projectModuleBuilder.buildModuleFromProject(project.project(id.projectPath))
|
||||||
|
id is ModuleComponentIdentifier -> {
|
||||||
|
val metadata = getProjectStructureMetadata(project, component, configurationToResolve) ?: return null
|
||||||
|
projectStructureMetadataModuleBuilder.getModule(id.displayName, id.toModuleOrigin(), metadata)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GradleProjectDependencyDiscovery(
|
||||||
|
private val project: Project,
|
||||||
|
private val variantDependencyDiscovery: VariantDependencyDiscovery,
|
||||||
|
private val fragmentDependenciesDiscovery: FragmentDependenciesDiscovery
|
||||||
|
) : DependencyDiscovery {
|
||||||
|
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<ModuleDependency> {
|
||||||
|
require(fragment.containingModule.representsProject(project))
|
||||||
|
return when (fragment) {
|
||||||
|
is KotlinModuleVariant -> variantDependencyDiscovery.discoverDependencies(fragment)
|
||||||
|
else -> fragmentDependenciesDiscovery.discoverDependencies(fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FragmentDependenciesDiscovery(
|
||||||
|
private val project: Project,
|
||||||
|
private val expansion: InternalDependencyExpansion,
|
||||||
|
private val moduleResolver: ModuleDependencyResolver,
|
||||||
|
private val fragmentResolver: KotlinModuleFragmentResolver
|
||||||
|
) : DependencyDiscovery {
|
||||||
|
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<ModuleDependency> {
|
||||||
|
require(fragment.containingModule.representsProject(project))
|
||||||
|
|
||||||
|
val requested = expansion.expandInternalFragmentDependencies(fragment).visibleFragments().plus(fragment.refinesClosure)
|
||||||
|
.flatMap { it.declaredModuleDependencies }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
// FIXME: test dependencies won't work here
|
||||||
|
val allComponents =
|
||||||
|
project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME).incoming.resolutionResult.allComponents
|
||||||
|
|
||||||
|
val componentByModuleDependency = allComponents.associateBy { it.toModuleDependency() }
|
||||||
|
|
||||||
|
val resolvedComponentQueue = ArrayDeque<ResolvedComponentResult>().apply {
|
||||||
|
addAll(componentByModuleDependency.filterKeys { it in requested }.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
val visited = mutableSetOf<ResolvedComponentResult>()
|
||||||
|
|
||||||
|
while (resolvedComponentQueue.isNotEmpty()) {
|
||||||
|
val component = resolvedComponentQueue.removeFirst()
|
||||||
|
visited.add(component)
|
||||||
|
|
||||||
|
val module = moduleResolver.resolveDependency(component.toModuleDependency())
|
||||||
|
?: buildStubModule(component, component.variants.singleOrNull()?.displayName ?: "default")
|
||||||
|
|
||||||
|
val visibleFragments = fragmentResolver.getChosenFragments(fragment, module)
|
||||||
|
val newTransitiveDeps =
|
||||||
|
visibleFragments.chosenFragments.flatMap { it.declaredModuleDependencies }.mapNotNull { dependency ->
|
||||||
|
componentByModuleDependency[dependency].takeIf { component -> component !in visited }
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedComponentQueue.addAll(newTransitiveDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
return visited.map { it.toModuleDependency() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// refactor extract to a separate class
|
||||||
|
// TODO think about multi-variant stub modules for non-Kotlin modules which got more than one chosen variant
|
||||||
|
private fun buildStubModule(resolvedComponentResult: ResolvedComponentResult, singleVariantName: String): KotlinModule {
|
||||||
|
val moduleDependency = resolvedComponentResult.toModuleDependency()
|
||||||
|
val moduleName = when (val id = resolvedComponentResult.id) {
|
||||||
|
is ProjectComponentIdentifier -> id.projectPath
|
||||||
|
else -> id.displayName
|
||||||
|
}
|
||||||
|
return BasicKotlinModule(moduleName, moduleDependency.moduleOrigin).apply {
|
||||||
|
BasicKotlinModuleVariant(this@apply, singleVariantName).apply {
|
||||||
|
fragments.add(this)
|
||||||
|
this.declaredModuleDependencies.addAll(
|
||||||
|
resolvedComponentResult.dependencies
|
||||||
|
.filterIsInstance<ResolvedDependencyResult>()
|
||||||
|
.map { it.selected.toModuleDependency() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VariantDependencyDiscovery(
|
||||||
|
val project: Project
|
||||||
|
) : DependencyDiscovery {
|
||||||
|
|
||||||
|
private val kotlinExtension
|
||||||
|
get() = project.kotlinExtension
|
||||||
|
|
||||||
|
private fun resolvedComponentResults(fragment: KotlinModuleFragment): Iterable<ResolvedComponentResult> {
|
||||||
|
val compilation = kotlinExtension.targets.asSequence()
|
||||||
|
.flatMap { it.compilations.asSequence() }
|
||||||
|
.find { it.defaultSourceSetName == fragment.fragmentName }
|
||||||
|
requireNotNull(compilation)
|
||||||
|
|
||||||
|
val configuration = project.configurations.getByName(compilation.compileDependencyConfigurationName)
|
||||||
|
return configuration.incoming.resolutionResult.allComponents
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun discoverDependencies(fragment: KotlinModuleFragment): Iterable<ModuleDependency> {
|
||||||
|
require(fragment is KotlinModuleVariant)
|
||||||
|
require(fragment.containingModule.representsProject(project))
|
||||||
|
|
||||||
|
val components = resolvedComponentResults(fragment)
|
||||||
|
return components.mapNotNull { component ->
|
||||||
|
when (val id = component.id) {
|
||||||
|
is ProjectComponentIdentifier -> LocalModuleDependency(LocalBuild(id.build.name), id.projectPath)
|
||||||
|
is ModuleComponentIdentifier -> ExternalModuleDependency(id.toModuleOrigin())
|
||||||
|
else -> null // TODO check that no other options are possible, throw errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ModuleComponentIdentifier.toModuleOrigin(): ExternalOrigin =
|
||||||
|
ExternalOrigin(listOf(moduleIdentifier.group, moduleIdentifier.name))
|
||||||
|
|
||||||
|
internal fun ComponentIdentifier.matchesModule(module: KotlinModule): Boolean {
|
||||||
|
return when (val moduleSource = module.moduleOrigin) {
|
||||||
|
is LocalBuild -> {
|
||||||
|
val projectId = this as? ProjectComponentIdentifier
|
||||||
|
projectId?.build?.name == moduleSource.buildId && projectId.projectPath == module.moduleName
|
||||||
|
}
|
||||||
|
is ExternalOrigin -> {
|
||||||
|
val moduleId = this as? ModuleComponentIdentifier
|
||||||
|
moduleId?.toModuleOrigin() == moduleSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ResolvedComponentResult.toModuleDependency(): ModuleDependency = when (val id = id) {
|
||||||
|
is ProjectComponentIdentifier -> LocalModuleDependency(LocalBuild(id.build.name), id.projectPath)
|
||||||
|
is ModuleComponentIdentifier -> ExternalModuleDependency(id.toModuleOrigin())
|
||||||
|
else -> ExternalModuleDependency(ExternalOrigin(listOf(moduleVersion?.group.orEmpty(), moduleVersion?.name.orEmpty())))
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComponentIdentifier.matchesModuleDependency(moduleDependency: ModuleDependency) =
|
||||||
|
when (moduleDependency) {
|
||||||
|
is LocalModuleDependency -> {
|
||||||
|
val projectId = this as? ProjectComponentIdentifier
|
||||||
|
projectId?.build?.name == moduleDependency.moduleOrigin.buildId && projectId.projectPath == moduleDependency.moduleName
|
||||||
|
}
|
||||||
|
is ExternalModuleDependency -> {
|
||||||
|
val moduleId = this as? ModuleComponentIdentifier
|
||||||
|
moduleId?.toModuleOrigin() == moduleDependency.moduleOrigin
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.mpp.pm20
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.component.BuildIdentifier
|
||||||
|
import org.gradle.api.internal.project.ProjectInternal
|
||||||
|
import org.gradle.internal.build.BuildState
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||||
|
import org.jetbrains.kotlin.project.model.KotlinModule
|
||||||
|
import org.jetbrains.kotlin.project.model.LocalBuild
|
||||||
|
|
||||||
|
val KotlinProjectExtension.targets: Iterable<KotlinTarget>
|
||||||
|
get() = when (this) {
|
||||||
|
is KotlinSingleTargetExtension -> listOf(this.target)
|
||||||
|
is KotlinMultiplatformExtension -> targets
|
||||||
|
else -> error("Unexpected 'kotlin' extension $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KotlinModule.representsProject(project: Project): Boolean =
|
||||||
|
moduleOrigin.let { it is LocalBuild && it.buildId == project.currentBuildId().name } && moduleName == project.path
|
||||||
|
|
||||||
|
// FIXME internal API?
|
||||||
|
fun Project.currentBuildId(): BuildIdentifier =
|
||||||
|
(project as ProjectInternal).services.get(BuildState::class.java).buildIdentifier
|
||||||
Reference in New Issue
Block a user