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.sources.KotlinDependencyScope
|
||||
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.InputStream
|
||||
import java.util.*
|
||||
@@ -224,23 +225,12 @@ internal class GranularMetadataTransformation(
|
||||
parentResolutionsForModule: Iterable<MetadataDependencyResolution>,
|
||||
parent: ResolvedComponentResult?
|
||||
): MetadataDependencyResolution {
|
||||
val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project)
|
||||
val metadataArtifact = resolvedMppVariantsProvider.getPlatformArtifactByPlatformModule(
|
||||
ModuleIds.fromComponent(project, module),
|
||||
configurationToResolve
|
||||
val mppDependencyMetadataExtractor = getMetadataExtractor(
|
||||
project,
|
||||
module,
|
||||
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
|
||||
|
||||
@@ -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
|
||||
internal abstract class ExtractableMetadataFiles {
|
||||
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].
|
||||
* If the [configuration] requests platform artifacts and not the common code metadata, then this function will resolve its
|
||||
* dependencies to metadata separately. */
|
||||
fun getMetadataArtifactByRootModule(rootModuleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? {
|
||||
fun getHostSpecificMetadataArtifactByRootModule(rootModuleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? {
|
||||
val rootModuleEntry = getEntryForModule(rootModuleIdentifier)
|
||||
|
||||
val platformModuleEntry = rootModuleEntry.run {
|
||||
if (configuration !in chosenPlatformModuleByConfiguration) {
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -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]. */
|
||||
fun getPlatformArtifactByPlatformModule(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? =
|
||||
fun getResolvedArtifactByPlatformModule(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? =
|
||||
getEntryForModule(moduleIdentifier).run {
|
||||
if (configuration !in resolvedArtifactByConfiguration) {
|
||||
resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NORMAL)
|
||||
@@ -170,7 +171,7 @@ internal class ResolvedMppVariantsProvider private constructor(private val proje
|
||||
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 -> {
|
||||
val resolvedArtifactMap = when (artifactResolutionMode) {
|
||||
ArtifactResolutionMode.NORMAL -> moduleEntry.resolvedArtifactByConfiguration
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ internal class SourceSetVisibilityProvider(
|
||||
|
||||
someVariantByHostSpecificSourceSet.entries.mapNotNull { (sourceSetName, variantName) ->
|
||||
val configuration = firstConfigurationByVariant.getValue(variantName)
|
||||
resolvedVariantsProvider.getMetadataArtifactByRootModule(mppModuleIdentifier, configuration)
|
||||
resolvedVariantsProvider.getHostSpecificMetadataArtifactByRootModule(mppModuleIdentifier, configuration)
|
||||
?.let { sourceSetName to it }
|
||||
}.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