diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GradleProjectModuleBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GradleProjectModuleBuilder.kt new file mode 100644 index 00000000000..ae3b1e50648 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GradleProjectModuleBuilder.kt @@ -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() + + 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>() + + 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().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 attributeString(container: AttributeContainer, attributeKey: Attribute): 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() + .flatMap { it.kotlinComponents.filterIsInstance() } + .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) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt index d34557a0e11..3d6091b75fb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -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, 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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ResolvedMppVariantsProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ResolvedMppVariantsProvider.kt index 0e7238e20b1..5cba68c6828 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ResolvedMppVariantsProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ResolvedMppVariantsProvider.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt index 3df0be30d5f..48688576722 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/SourceSetVisibilityProvider.kt @@ -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() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt new file mode 100644 index 00000000000..9031d1607c9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/DependencyResolution.kt @@ -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 { + 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 { + 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().apply { + addAll(componentByModuleDependency.filterKeys { it in requested }.values) + } + + val visited = mutableSetOf() + + 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() + .map { it.selected.toModuleDependency() } + ) + } + } + } +} + +class VariantDependencyDiscovery( + val project: Project +) : DependencyDiscovery { + + private val kotlinExtension + get() = project.kotlinExtension + + private fun resolvedComponentResults(fragment: KotlinModuleFragment): Iterable { + 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 { + 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 + } + } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/Utils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/Utils.kt new file mode 100644 index 00000000000..49d58a155a9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/Utils.kt @@ -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 + 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 \ No newline at end of file