From 2478a57646fadc0edd3b4687c917c635cf04dd12 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Mon, 2 Mar 2020 19:35:09 +0300 Subject: [PATCH] Rework source sets metadata consumption * Add `ResolvedMppVariantsProvider` that provides variant names, platform artifacts, and metadata artifacts for MPP dependencies * Rework SourceSetVisibilityProvider.kt so that it uses the `ResolvedMppVariantsProvider` to determine which source sets should be read from which metadata artifacts * Rework `GranularMetadataTransformation` and its consumers so that: * it uses the visibility result and extracts the source set metadata from the host-specific artifacts whenever `SourceSetsVisibilityProvider` yields host-specific artifacts in the visibility results * it uses the new Gradle API for resolutionResults / artifactView * Rework module IDs in the code base, unify the logic of their construction, also fix possible false-positive visibility in `applyToConfiguration` that used a module ID that mismatched the published module ID. --- .../mpp/GranularMetadataTransformation.kt | 249 +++++++++--------- .../mpp/KotlinProjectStructureMetadata.kt | 9 +- .../kotlin/gradle/plugin/mpp/ModuleIds.kt | 59 +++++ .../plugin/mpp/ResolvedMppVariantsProvider.kt | 218 +++++++++++++++ .../plugin/mpp/SourceSetVisibilityProvider.kt | 124 ++++++--- .../plugin/sources/DefaultKotlinSourceSet.kt | 7 +- .../KotlinMetadataTargetConfigurator.kt | 48 ++-- 7 files changed, 525 insertions(+), 189 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ResolvedMppVariantsProvider.kt 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 66a2a02e36c..63d9221bfce 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 @@ -7,6 +7,9 @@ package org.jetbrains.kotlin.gradle.plugin.mpp import org.gradle.api.Project import org.gradle.api.artifacts.* +import org.gradle.api.artifacts.component.* +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.file.FileCollection import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet @@ -20,8 +23,8 @@ import java.util.zip.ZipOutputStream import javax.xml.parsers.DocumentBuilderFactory internal sealed class MetadataDependencyResolution( - val dependency: ResolvedDependency, - val projectDependency: ProjectDependency? + val dependency: ResolvedComponentResult, + val projectDependency: Project? ) { override fun toString(): String { val verb = when (this) { @@ -33,22 +36,22 @@ internal sealed class MetadataDependencyResolution( } class KeepOriginalDependency( - dependency: ResolvedDependency, - projectDependency: ProjectDependency? + dependency: ResolvedComponentResult, + projectDependency: Project? ) : MetadataDependencyResolution(dependency, projectDependency) class ExcludeAsUnrequested( - dependency: ResolvedDependency, - projectDependency: ProjectDependency? + dependency: ResolvedComponentResult, + projectDependency: Project? ) : MetadataDependencyResolution(dependency, projectDependency) abstract class ChooseVisibleSourceSets( - dependency: ResolvedDependency, - projectDependency: ProjectDependency?, + dependency: ResolvedComponentResult, + projectDependency: Project?, val projectStructureMetadata: KotlinProjectStructureMetadata, val allVisibleSourceSetNames: Set, val visibleSourceSetNamesExcludingDependsOn: Set, - val visibleTransitiveDependencies: 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]. @@ -64,64 +67,28 @@ internal sealed class MetadataDependencyResolution( } } -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, + private 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> + private val allSourceSetsConfiguration: Configuration, + private 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? + val dependency: ResolvedComponentResult, + val parent: ResolvedComponentResult? ) - 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 + project.sourceSetDependencyConfigurationByScope(sourceSet, scope).incoming.dependencies } val ownDependencies = collectScopedDependenciesFromSourceSet(kotlinSourceSet) @@ -130,7 +97,7 @@ internal class GranularMetadataTransformation( ownDependencies + parentDependencies } - private val resolvedConfiguration: LenientConfiguration by lazy { + private val configurationToResolve: Configuration 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 @@ -147,70 +114,73 @@ internal class GranularMetadataTransformation( } } - (modifiedConfiguration ?: allSourceSetsConfiguration).resolvedConfiguration.lenientConfiguration + modifiedConfiguration ?: allSourceSetsConfiguration } private fun doTransform(): Iterable { val result = mutableListOf() val parentResolutions = - parentTransformations.value.flatMap { it.metadataDependencyResolutions }.groupBy { it.dependency.moduleId } + parentTransformations.value.flatMap { it.metadataDependencyResolutions }.groupBy { + ModuleIds.fromComponent(project, it.dependency) + } val allRequestedDependencies = requestedDependencies - val allModuleDependencies = resolvedConfiguration.allModuleDependencies - - val knownProjectDependencies = collectProjectDependencies( - allRequestedDependencies.filterIsInstance(), - allModuleDependencies - ) + val resolutionResult = configurationToResolve.incoming.resolutionResult + val allModuleDependencies = + configurationToResolve.incoming.resolutionResult.allDependencies.filterIsInstance() val resolvedDependencyQueue: Queue = ArrayDeque().apply { - val requestedModules: Set = allRequestedDependencies.mapTo(mutableSetOf()) { it.moduleId } + val requestedModules: Set = allRequestedDependencies.mapTo(mutableSetOf()) { + ModuleIds.fromDependency(it) + } addAll( - resolvedConfiguration.firstLevelModuleDependencies - .filter { it.moduleId in requestedModules } - .map { ResolvedDependencyWithParent(it, null) } + resolutionResult.root.dependencies + .filter { ModuleIds.fromComponentSelector(project, it.requested) in requestedModules } + .filterIsInstance() + .map { ResolvedDependencyWithParent(it.selected, null) } ) } - val visitedDependencies = mutableSetOf() + val visitedDependencies = mutableSetOf() while (resolvedDependencyQueue.isNotEmpty()) { - val (resolvedDependency, parent: ResolvedDependency?) = resolvedDependencyQueue.poll() + val (resolvedDependency: ResolvedComponentResult, parent: ResolvedComponentResult?) = resolvedDependencyQueue.poll() - val projectDependency: ProjectDependency? = knownProjectDependencies[resolvedDependency.moduleId] + val resolvedToProject: Project? = (resolvedDependency.id as? ProjectComponentIdentifier)?.projectPath?.let(project::project) visitedDependencies.add(resolvedDependency) val dependencyResult = processDependency( resolvedDependency, - parentResolutions[resolvedDependency.moduleId].orEmpty(), + parentResolutions[ModuleIds.fromComponent(project, resolvedDependency)].orEmpty(), parent, - projectDependency + resolvedToProject ) result.add(dependencyResult) val transitiveDependenciesToVisit = when (dependencyResult) { - is MetadataDependencyResolution.KeepOriginalDependency -> resolvedDependency.children + is MetadataDependencyResolution.KeepOriginalDependency -> + resolvedDependency.dependencies.filterIsInstance() 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) } + transitiveDependenciesToVisit.filter { it.selected !in visitedDependencies } + .map { ResolvedDependencyWithParent(it.selected, resolvedDependency) } ) } allModuleDependencies.forEach { resolvedDependency -> - if (resolvedDependency !in visitedDependencies) { + if (resolvedDependency.selected !in visitedDependencies) { result.add( MetadataDependencyResolution.ExcludeAsUnrequested( - resolvedDependency, - knownProjectDependencies[resolvedDependency.moduleGroup to resolvedDependency.moduleName] + resolvedDependency.selected, + (resolvedDependency.selected.id as? ProjectComponentIdentifier)?.let { project.project(it.projectPath) } ) ) } @@ -234,30 +204,43 @@ internal class GranularMetadataTransformation( * source sets in *S*, then consider only these transitive dependencies, ignore the others; */ private fun processDependency( - module: ResolvedDependency, + module: ResolvedComponentResult, parentResolutionsForModule: Iterable, - parent: ResolvedDependency?, - projectDependency: ProjectDependency? + parent: ResolvedComponentResult?, + resolvedToProject: Project? ): MetadataDependencyResolution { + val resolvedMppVariantsProvider = ResolvedMppVariantsProvider.get(project) - val mppDependencyMetadataExtractor = when { - projectDependency != null -> ProjectMppDependencyMetadataExtractor(project, module, projectDependency.dependencyProject) - parent != null -> JarArtifactMppDependencyMetadataExtractor(project, module) - else -> null + val mppDependencyMetadataExtractor = if (resolvedToProject != null) { + ProjectMppDependencyMetadataExtractor(project, module, resolvedToProject) + } else { + val metadataArtifact = resolvedMppVariantsProvider.getPlatformArtifactByPlatformModule( + ModuleIds.fromComponent(project, module), + configurationToResolve + ) + if (metadataArtifact != null) { + JarArtifactMppDependencyMetadataExtractor(project, module, metadataArtifact) + } else null } val projectStructureMetadata = mppDependencyMetadataExtractor?.getProjectStructureMetadata() - ?: return MetadataDependencyResolution.KeepOriginalDependency(module, projectDependency) + ?: return MetadataDependencyResolution.KeepOriginalDependency(module, resolvedToProject) - val allVisibleSourceSets = - SourceSetVisibilityProvider(project).getVisibleSourceSetNames( + val sourceSetVisibility = + SourceSetVisibilityProvider(project).getVisibleSourceSets( kotlinSourceSet, sourceSetRequestedScopes, - parent ?: module, + parent, module, projectStructureMetadata, - projectDependency?.dependencyProject + resolvedToProject ) + if (mppDependencyMetadataExtractor is JarArtifactMppDependencyMetadataExtractor) { + mppDependencyMetadataExtractor.metadataArtifactBySourceSet.putAll(sourceSetVisibility.hostSpecificMetadataArtifactBySourceSet) + } + + val allVisibleSourceSets = sourceSetVisibility.visibleSourceSetNames + val sourceSetsVisibleInParents = parentResolutionsForModule .filterIsInstance() .flatMapTo(mutableSetOf()) { it.allVisibleSourceSetNames } @@ -265,36 +248,49 @@ internal class GranularMetadataTransformation( // 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 { + val requestedTransitiveDependencies: Set = + mutableSetOf().apply { projectStructureMetadata.sourceSetModuleDependencies.forEach { (sourceSetName, moduleDependencies) -> if (sourceSetName in allVisibleSourceSets) { - addAll(moduleDependencies.map { ModuleId(it.groupId, it.moduleId) }) + addAll(moduleDependencies.map { ModuleDependencyIdentifier(it.groupId, it.moduleId) }) } } } - val transitiveDependenciesToVisit = module.children.filterTo(mutableSetOf()) { - (it.moduleId) in requestedTransitiveDependencies - } + val transitiveDependenciesToVisit = module.dependencies + .filterIsInstance() + .filterTo(mutableSetOf()) { ModuleIds.fromComponent(project, it.selected) 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) - } + return ChooseVisibleSourceSetsImpl( + module, resolvedToProject, projectStructureMetadata, allVisibleSourceSets, visibleSourceSetsExcludingDependsOn, + transitiveDependenciesToVisit, mppDependencyMetadataExtractor + ) + } + + private class ChooseVisibleSourceSetsImpl( + dependency: ResolvedComponentResult, + projectDependency: Project?, + projectStructureMetadata: KotlinProjectStructureMetadata, + allVisibleSourceSetNames: Set, + visibleSourceSetNamesExcludingDependsOn: Set, + visibleTransitiveDependencies: Set, + private val metadataExtractor: MppDependencyMetadataExtractor + ) : MetadataDependencyResolution.ChooseVisibleSourceSets( + dependency, + projectDependency, + projectStructureMetadata, + allVisibleSourceSetNames, + visibleSourceSetNamesExcludingDependsOn, + visibleTransitiveDependencies + ) { + override fun getMetadataFilesBySourceSet(baseDir: File, doProcessFiles: Boolean): Map = + metadataExtractor.getVisibleSourceSetsMetadata(visibleSourceSetNamesExcludingDependsOn, baseDir, doProcessFiles) } } -private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedDependency) { +private abstract class MppDependencyMetadataExtractor(val project: Project, val dependency: ResolvedComponentResult) { abstract fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? abstract fun getVisibleSourceSetsMetadata( visibleSourceSetNames: Set, @@ -305,7 +301,7 @@ private abstract class MppDependencyMetadataExtractor(val project: Project, val private class ProjectMppDependencyMetadataExtractor( project: Project, - dependency: ResolvedDependency, + dependency: ResolvedComponentResult, private val dependencyProject: Project ) : MppDependencyMetadataExtractor(project, dependency) { override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? = @@ -323,16 +319,14 @@ private class ProjectMppDependencyMetadataExtractor( private class JarArtifactMppDependencyMetadataExtractor( project: Project, - dependency: ResolvedDependency + dependency: ResolvedComponentResult, + val primaryArtifact: File ) : MppDependencyMetadataExtractor(project, dependency) { - private val artifact: ResolvedArtifact? - get() = dependency.moduleArtifacts.singleOrNull { it.extension == "jar" } + val metadataArtifactBySourceSet: MutableMap = mutableMapOf() override fun getProjectStructureMetadata(): KotlinProjectStructureMetadata? { - val artifactFile = artifact?.file ?: return null - - return ZipFile(artifactFile).use { zip -> + return ZipFile(primaryArtifact).use { zip -> val metadata = zip.getEntry("META-INF/$MULTIPLATFORM_PROJECT_METADATA_FILE_NAME") ?: return null @@ -349,21 +343,24 @@ private class JarArtifactMppDependencyMetadataExtractor( baseDir: File, doProcessFiles: Boolean ): Map { - val jarArtifact = artifact ?: return emptyMap() - val artifactFile = jarArtifact.file - val moduleId = jarArtifact.moduleVersion.id + val primaryArtifact = primaryArtifact + val moduleId = ModuleIds.fromComponent(project, dependency) - return extractSourceSetMetadataFromJar(moduleId, visibleSourceSetNames, artifactFile, baseDir, doProcessFiles) + return extractSourceSetMetadataFromArtifacts( + moduleId, + baseDir, + doProcessFiles, + visibleSourceSetNames.associate { it to (metadataArtifactBySourceSet[it] ?: primaryArtifact) } + ) } - private fun extractSourceSetMetadataFromJar( - module: ModuleVersionIdentifier, - chooseSourceSetsByNames: Set, - artifactJar: File, + private fun extractSourceSetMetadataFromArtifacts( + module: ModuleDependencyIdentifier, baseDir: File, - doProcessFiles: Boolean + doProcessFiles: Boolean, + artifactBySourceSet: Map ): Map { - val moduleString = "${module.group}-${module.name}-${module.version}" + val moduleString = "${module.groupId}-${module.moduleId}" val transformedModuleRoot = run { baseDir.resolve(moduleString).also { it.mkdirs() } } val resultFiles = mutableMapOf() @@ -372,12 +369,10 @@ private class JarArtifactMppDependencyMetadataExtractor( "can't extract metadata from a module without project structure metadata" } - ZipFile(artifactJar).use { zip -> - val entriesBySourceSet = zip.entries().asSequence() - .groupBy { it.name.substringBefore("/") } - .filterKeys { it in chooseSourceSetsByNames } + artifactBySourceSet.forEach { (sourceSetName, artifact) -> + ZipFile(artifact).use { zip -> + val entries = zip.entries().asSequence().filter { it.name.startsWith("$sourceSetName/") } - entriesBySourceSet.forEach { (sourceSetName, entries) -> // TODO: once IJ supports non-JAR metadata dependencies, extract 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 extension = diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt index 0b3459f6c9d..56bcaacbbab 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinProjectStructureMetadata.kt @@ -10,7 +10,6 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull -import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope @@ -22,7 +21,7 @@ import org.w3c.dom.NodeList import javax.xml.parsers.DocumentBuilderFactory data class ModuleDependencyIdentifier( - val groupId: String, + val groupId: String?, val moduleId: String ) @@ -68,7 +67,7 @@ data class KotlinProjectStructureMetadata( @Suppress("UNUSED") // Gradle input @get:Input internal val sourceSetModuleDependenciesInput: Map>> - get() = sourceSetModuleDependencies.mapValues { (_, ids) -> ids.map { (group, module) -> group to module }.toSet() } + get() = sourceSetModuleDependencies.mapValues { (_, ids) -> ids.map { (group, module) -> group.orEmpty() to module }.toSet() } companion object { internal const val FORMAT_VERSION_0_1 = "0.1" @@ -111,9 +110,7 @@ internal fun buildKotlinProjectStructureMetadata(project: Project): KotlinProjec }.distinct() else -> project.configurations.getByName(sourceSet.apiConfigurationName).allDependencies } - sourceSet.name to sourceSetExportedDependencies.map { - ModuleDependencyIdentifier(it.group.orEmpty(), it.name) - }.toSet() + sourceSet.name to sourceSetExportedDependencies.map { ModuleIds.fromDependency(it) }.toSet() }, hostSpecificSourceSets = getHostSpecificSourceSets(project).map { it.name }.toSet(), sourceSetBinaryLayout = sourceSetsWithMetadataCompilations.keys.associate { sourceSet -> diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt new file mode 100644 index 00000000000..48d6fc9f468 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ModuleIds.kt @@ -0,0 +1,59 @@ +/* + * 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.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.artifacts.component.* +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull + +internal object ModuleIds { + fun fromDependency(dependency: Dependency) = when (dependency) { + is ProjectDependency -> idOfRootModule(dependency.dependencyProject) + else -> ModuleDependencyIdentifier(dependency.group, dependency.name) + } + + fun fromComponentSelector( + thisProject: Project, + componentSelector: ComponentSelector + ): ModuleDependencyIdentifier = when (componentSelector) { + is ProjectComponentSelector -> idOfRootModuleByProjectPath(thisProject, componentSelector.projectPath) + is ModuleComponentSelector -> ModuleDependencyIdentifier(componentSelector.group, componentSelector.module) + else -> idFromName(componentSelector.displayName) + } + + fun fromComponentId( + thisProject: Project, + componentIdentifier: ComponentIdentifier + ): ModuleDependencyIdentifier = + when (componentIdentifier) { + is ProjectComponentIdentifier -> idOfRootModuleByProjectPath(thisProject, componentIdentifier.projectPath) + is ModuleComponentIdentifier -> ModuleDependencyIdentifier(componentIdentifier.group, componentIdentifier.module) + else -> idFromName(componentIdentifier.displayName) + } + + fun fromComponent(thisProject: Project, component: ResolvedComponentResult) = + fromComponentId(thisProject, component.id) + + private fun idOfRootModule(project: Project): ModuleDependencyIdentifier = + if (project.multiplatformExtensionOrNull != null) { + val rootPublication = project.multiplatformExtension.rootSoftwareComponent.publicationDelegate + val group = rootPublication?.groupId ?: project.group.toString() + val name = rootPublication?.artifactId ?: project.name + ModuleDependencyIdentifier(group, name) + } else { + ModuleDependencyIdentifier(project.group.toString(), project.name) + } + + private fun idFromName(name: String) = + ModuleDependencyIdentifier(null, name) + + private fun idOfRootModuleByProjectPath(thisProject: Project, projectPath: String): ModuleDependencyIdentifier = + idOfRootModule(thisProject.project(projectPath)) +} \ No newline at end of file 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 new file mode 100644 index 00000000000..7d267dc507c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ResolvedMppVariantsProvider.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.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.component.ComponentIdentifier +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.artifacts.result.ResolvedArtifactResult +import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.artifacts.result.ResolvedDependencyResult +import org.gradle.api.artifacts.result.ResolvedVariantResult +import org.gradle.api.attributes.Usage +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.usageByName +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast +import java.io.File + +internal class ResolvedMppVariantsProvider private constructor(private val project: Project) { + companion object { + + fun get(project: Project): ResolvedMppVariantsProvider = with(project.extensions.extraProperties) { + val propertyName = "kotlin.mpp.internal.resolvedModuleVariantsProvider" + if (!has(propertyName)) { + set(propertyName, ResolvedMppVariantsProvider(project)) + } + @Suppress("UNCHECKED_CAST") + get(propertyName) as ResolvedMppVariantsProvider + } + } + + /** Gets the name of the variant that the module specified by the [moduleIdentifier] resolved to in the given [configuration]. + * The [moduleIdentifier] may be either the root module or a platform-specific module, the result is the same for the two cases. */ + fun getResolvedVariantName(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): String? = + getEntryForModule(moduleIdentifier).run { + if (configuration !in resolvedVariantsByConfiguration) { + resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NONE) + } + val variants = resolvedVariantsByConfiguration.getOrPut(configuration) { null } + variants?.singleOrNull()?.displayName + } + + /** Gets the artifact that contains the common code metadata for the given [rootModuleIdentifier], which can only denote the root + * 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? { + val rootModuleEntry = getEntryForModule(rootModuleIdentifier) + + val platformModuleEntry = rootModuleEntry.run { + if (configuration !in chosenPlatformModuleByConfiguration) { + resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA) + } + chosenPlatformModuleByConfiguration.getOrPut(configuration) { null } + } + + return platformModuleEntry?.run { + // The condition might be true if the configuration has only been resolved with resolution mode NORMAL + if (configuration !in resolvedMetadataArtifactByConfiguration) { + resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.METADATA) + } + resolvedMetadataArtifactByConfiguration.getOrPut(configuration) { null } + } + } + + /** Gets the artifact of a particular MPP platform-specific [moduleIdentifier] as resolved in the [configuration]. */ + fun getPlatformArtifactByPlatformModule(moduleIdentifier: ModuleDependencyIdentifier, configuration: Configuration): File? = + getEntryForModule(moduleIdentifier).run { + if (configuration !in resolvedArtifactByConfiguration) { + resolveConfigurationAndSaveVariants(configuration, artifactResolutionMode = ArtifactResolutionMode.NORMAL) + } + resolvedArtifactByConfiguration.getOrPut(configuration) { null } + } + + private fun getEntryForModule(moduleIdentifier: ModuleDependencyIdentifier) = + entriesCache.getOrPut(moduleIdentifier, { ModuleEntry(moduleIdentifier) }) + + private val entriesCache: MutableMap = mutableMapOf() + + private val mppComponentIdsByConfiguration: MutableMap> = mutableMapOf() + + private enum class ArtifactResolutionMode { + NONE, NORMAL, METADATA + } + + private fun resolveConfigurationAndSaveVariants( + configuration: Configuration, + artifactResolutionMode: ArtifactResolutionMode + ) { + val mppComponentIds: Set = mppComponentIdsByConfiguration.getOrPut(configuration) { + resolveMppComponents(configuration) + } + + if (artifactResolutionMode != ArtifactResolutionMode.NONE) { + val artifacts = resolveArtifacts(artifactResolutionMode, configuration, mppComponentIds) + matchMppComponentsWithResolvedArtifacts(mppComponentIds, artifacts, configuration, artifactResolutionMode) + } + } + + private fun resolveMppComponents(configuration: Configuration): MutableSet { + val result = mutableListOf() + + configuration.incoming.resolutionResult.allComponents { component -> + val moduleId = ModuleIds.fromComponent(project, component) + val variants = component.variantsCompatible + + val isMpp = variants.any { variant -> variant.attributes.keySet().any { it.name == KotlinPlatformType.attribute.name } } + if (isMpp) { + result.add(component) + val moduleEntry = getEntryForModule(moduleId) + moduleEntry.resolvedVariantsByConfiguration[configuration] = variants + + moduleEntry.dependenciesByConfiguration[configuration] = component.dependencies + .filterIsInstance() + .map { dependency -> ModuleIds.fromComponent(project, dependency.selected) } + + if (component.id is ProjectComponentIdentifier) { + // Then the platform variant chosen for this module is definitely inside the module itself: + moduleEntry.chosenPlatformModuleByConfiguration[configuration] = moduleEntry + } + } + } + + return result.mapTo(mutableSetOf()) { it.id } + } + + private fun resolveArtifacts( + artifactResolutionMode: ArtifactResolutionMode, + configuration: Configuration, + mppComponentIds: Set + ): Map { + val artifactsConfiguration = + if ( + artifactResolutionMode == ArtifactResolutionMode.NORMAL || + configuration.attributes.getAttribute(Usage.USAGE_ATTRIBUTE)?.name == KotlinUsages.KOTLIN_METADATA + ) { + configuration + } else { + configuration.copyRecursive().apply { + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) + } + } + + return artifactsConfiguration.incoming.artifactView { view -> + view.componentFilter { it in mppComponentIds } + view.attributes { attrs -> attrs.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_METADATA)) } + }.artifacts.associateBy { it.id.componentIdentifier } + } + + private fun matchMppComponentsWithResolvedArtifacts( + mppComponentIds: Set, + artifacts: Map, + configuration: Configuration, + artifactResolutionMode: ArtifactResolutionMode + ) { + val mppModuleIds = mppComponentIds.mapTo(mutableSetOf()) { ModuleIds.fromComponentId(project, it) } + + mppComponentIds.forEach { componentId -> + val moduleEntry = getEntryForModule(ModuleIds.fromComponentId(project, componentId)) + val artifact = artifacts[componentId] + when { + // With project dependencies, we don't need the host-specific metadata artifacts, as we have the compilation outputs: + componentId is ProjectComponentIdentifier -> { + moduleEntry.resolvedMetadataArtifactByConfiguration[configuration] = null + } + + // We found the host-specific artifact for a platform variant of some MPP: + artifact != null -> { + val resolvedArtifactMap = when (artifactResolutionMode) { + ArtifactResolutionMode.NORMAL -> moduleEntry.resolvedArtifactByConfiguration + ArtifactResolutionMode.METADATA -> moduleEntry.resolvedMetadataArtifactByConfiguration + else -> error("unexpected $artifactResolutionMode") + } + resolvedArtifactMap[configuration] = artifact.file + } + + // Otherwise, this may be a root module of some MPP that resolved to a variant in another module. Take a note of that. + else -> { + // TODO: there's an assumption that resolving a root MPP module to a host-specific metadata artifact and to a platform + // artifact will choose variants that are published within the same Maven module; change this code if that's not + // true anymore. + val singleDependencyId = moduleEntry.dependenciesByConfiguration.getValue(configuration).singleOrNull() + if (singleDependencyId != null && singleDependencyId in mppModuleIds) { + moduleEntry.chosenPlatformModuleByConfiguration[configuration] = + getEntryForModule(singleDependencyId) + } + } + } + } + } + + /** + * Stores resolution results of the module denoted by [moduleIdentifier] in different configurations of the project. + * The [moduleIdentifier] may point to a root module of a multiplatform project (then it has meaningful + * [chosenPlatformModuleByConfiguration]) or to a platform module. + */ + private class ModuleEntry( + @Suppress("unused") // simplify debugging + val moduleIdentifier: ModuleDependencyIdentifier + ) { + val dependenciesByConfiguration: MutableMap> = HashMap() + val resolvedVariantsByConfiguration: MutableMap?> = HashMap() + val resolvedArtifactByConfiguration: MutableMap = HashMap() + val resolvedMetadataArtifactByConfiguration: MutableMap = HashMap() + val chosenPlatformModuleByConfiguration: MutableMap = HashMap() + } +} + +private val ResolvedComponentResult.variantsCompatible: List + get() = if (isGradleVersionAtLeast(5, 2)) { + variants + } else { + @Suppress("DEPRECATION") + listOf(variant) + } \ No newline at end of file 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 deff43a9ac9..18dd74ebcc0 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 @@ -7,76 +7,135 @@ package org.jetbrains.kotlin.gradle.plugin.mpp import org.gradle.api.Project import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ResolvedDependency +import org.gradle.api.artifacts.result.ResolvedComponentResult import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope import org.jetbrains.kotlin.gradle.targets.metadata.getPublishedPlatformCompilations +import java.io.File + +internal data class SourceSetVisibilityResult( + /** + * Names of source sets that the consumer sees from the requested dependency. + */ + val visibleSourceSetNames: Set, + + /** + * For some of the [visibleSourceSetNames], additional artifacts may be present that + * the consumer should read the compiled source set metadata from. + */ + val hostSpecificMetadataArtifactBySourceSet: Map +) internal class SourceSetVisibilityProvider( private val project: Project ) { + private val resolvedVariantsProvider = ResolvedMppVariantsProvider.get(project) + /** - * Determine which source sets of the [resolvedMppDependency] are visible in the [visibleFrom] source set. + * Determine which source sets of the [resolvedRootMppDependency] are visible in the [visibleFrom] source set. * * This requires resolving dependencies of the compilations which [visibleFrom] takes part in, in order to find which variants the - * [resolvedMppDependency] got resolved to for those compilations. The [resolvedMppDependency] should therefore be the dependency + * [resolvedRootMppDependency] got resolved to for those compilations. The [resolvedRootMppDependency] should therefore be the dependency * on the 'root' module of the MPP (such as 'com.example:lib-foo', not 'com.example:lib-foo-metadata'). * * Once the variants are known, they are checked against the [dependencyProjectStructureMetadata], and the * source sets of the dependency are determined that are compiled for all those variants and thus should be visible here. * - * If the [resolvedMppDependency] is a project dependency, its project should be passed as [resolvedToOtherProject], as + * If the [resolvedRootMppDependency] is a project dependency, its project should be passed as [resolvedToOtherProject], as * the Gradle API for dependency variants behaves differently for project dependencies and published ones. */ - fun getVisibleSourceSetNames( + fun getVisibleSourceSets( visibleFrom: KotlinSourceSet, dependencyScopes: Iterable, - resolvedMppDependency: ResolvedDependency, + resolvedRootMppDependency: ResolvedComponentResult?, + resolvedMetadataDependency: ResolvedComponentResult, dependencyProjectStructureMetadata: KotlinProjectStructureMetadata, resolvedToOtherProject: Project? - ): Set { + ): SourceSetVisibilityResult { val compilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(visibleFrom) - var visiblePlatformVariantNames: Set = + val mppModuleIdentifier = ModuleIds.fromComponent(project, resolvedRootMppDependency ?: resolvedMetadataDependency) + + /** + * When Gradle resolves a project dependency, as opposed to an external dependency, the variant name it returns in the resolution + * results is the name of the configuration that is chosen during variant-aware resolution, not the actual name of the variant that + * is written to the Gradle module metadata and the Kotlin project structure metadata. To fix this, get the published Kotlin + * variants of the dependency project and find among them the one that owns the configuration by the given name. + */ + val projectPublishedCompilations = resolvedToOtherProject?.let(::getPublishedPlatformCompilations)?.keys + fun platformVariantName(resolvedToVariantName: String): String = + projectPublishedCompilations?.first { it.dependencyConfigurationName == resolvedToVariantName }?.name ?: resolvedToVariantName + + val firstConfigurationByVariant = mutableMapOf() + + val visiblePlatformVariantNames: Set = compilations .filter { it.target.platformType != KotlinPlatformType.common } .flatMapTo(mutableSetOf()) { compilation -> - val configurations = dependencyScopes.mapNotNullTo(mutableSetOf()) { scope -> - project.resolvableConfigurationFromCompilationByScope(compilation, scope) - } - configurations.mapNotNull { configuration -> - // Resolve the configuration but don't trigger artifacts download, only download component metadata: - configuration.incoming.resolutionResult.allComponents - .find { - it.moduleVersion?.group == resolvedMppDependency.moduleGroup && - it.moduleVersion?.name == resolvedMppDependency.moduleName - } - ?.variant?.displayName - } + // To find out which variant the MPP dependency got resolved for each compilation, take the resolvable configurations + // that we have in the compilations: + dependencyScopes.mapNotNull { scope -> project.resolvableConfigurationFromCompilationByScope(compilation, scope) } + } + .mapNotNullTo(mutableSetOf()) { configuration -> + val resolvedVariant = resolvedVariantsProvider.getResolvedVariantName(mppModuleIdentifier, configuration) + ?.let { platformVariantName(it) } + ?: return@mapNotNullTo null + + firstConfigurationByVariant.putIfAbsent(resolvedVariant, configuration) + resolvedVariant } if (visiblePlatformVariantNames.isEmpty()) { - return emptySet() + return SourceSetVisibilityResult(emptySet(), emptyMap()) } - if (resolvedToOtherProject != null) { - val publishedVariants = getPublishedPlatformCompilations(resolvedToOtherProject).keys - - visiblePlatformVariantNames = visiblePlatformVariantNames.mapTo(mutableSetOf()) { configurationName -> - publishedVariants.first { it.dependencyConfigurationName == configurationName }.name - } - } - - return dependencyProjectStructureMetadata.sourceSetNamesByVariantName + val visibleSourceSetNames = dependencyProjectStructureMetadata.sourceSetNamesByVariantName .filterKeys { it in visiblePlatformVariantNames } .values.let { if (it.isEmpty()) emptySet() else it.reduce { acc, item -> acc intersect item } } + + val hostSpecificArtifactBySourceSet: Map = + if (resolvedToOtherProject != null) { + /** + * When a dependency resolves to a project, we don't need any artifacts from it, we can + * instead use the compilation outputs directly: + */ + emptyMap() + } else { + val hostSpecificSourceSets = visibleSourceSetNames.intersect(dependencyProjectStructureMetadata.hostSpecificSourceSets) + + /** + * As all of the variants normally contain the same metadata for each of the relevant host-specific source sets, + * any of the variants that we resolved can be used, so choose the first one that satisfies both: + * + * - it contains the host-specific source set, and + * - we have resolved it for some compilation + */ + val someVariantByHostSpecificSourceSet = + hostSpecificSourceSets.associate { sourceSetName -> + sourceSetName to dependencyProjectStructureMetadata.sourceSetNamesByVariantName + .filterKeys { it in firstConfigurationByVariant } + .filterValues { sourceSetName in it } + .keys.first() + } + + someVariantByHostSpecificSourceSet.mapValues { (_, variantName) -> + val configuration = firstConfigurationByVariant.getValue(variantName) + resolvedVariantsProvider.getMetadataArtifactByRootModule(mppModuleIdentifier, configuration) + ?: error("Couldn't resolve metadata artifact for $mppModuleIdentifier in $configuration") + } + } + + return SourceSetVisibilityResult( + visibleSourceSetNames, + hostSpecificArtifactBySourceSet + ) } } -internal fun Project.resolvableConfigurationFromCompilationByScope( +private fun Project.resolvableConfigurationFromCompilationByScope( compilation: KotlinCompilation<*>, scope: KotlinDependencyScope ): Configuration? { @@ -89,5 +148,4 @@ internal fun Project.resolvableConfigurationFromCompilationByScope( } return project.configurations.getByName(configurationName) -} - +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt index 5bd8ad28c74..aad4a0f5286 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt @@ -16,9 +16,8 @@ import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder -import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler +import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution import org.jetbrains.kotlin.gradle.utils.* import java.io.File @@ -156,7 +155,7 @@ class DefaultKotlinSourceSet( internal fun getDependenciesTransformation(scope: KotlinDependencyScope): Iterable { val metadataDependencyResolutionByModule = dependencyTransformations[scope]?.metadataDependencyResolutions - ?.associateBy { it.dependency.moduleGroup to it.dependency.moduleName } + ?.associateBy { ModuleIds.fromComponent(project, it.dependency) } ?: emptyMap() val baseDir = project.buildDir.resolve("tmp/kotlinMetadata/$name/${scope.scopeName}") @@ -169,7 +168,7 @@ class DefaultKotlinSourceSet( return metadataDependencyResolutionByModule.mapNotNull { (groupAndName, resolution) -> val (group, name) = groupAndName - val projectPath = resolution.projectDependency?.dependencyProject?.path + val projectPath = resolution.projectDependency?.path when (resolution) { is MetadataDependencyResolution.KeepOriginalDependency -> null diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt index 993f7f7e2a5..b8d21acc6d8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt @@ -31,7 +31,6 @@ import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.setArchiveAppendixCompatible import org.jetbrains.kotlin.gradle.utils.setArchiveClassifierCompatible import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics -import java.io.File import java.util.concurrent.Callable internal const val ALL_COMPILE_METADATA_CONFIGURATION_NAME = "allSourceSetsCompileDependenciesMetadata" @@ -374,17 +373,25 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : val (unrequested, requested) = metadataDependencyResolutions .partition { it is MetadataDependencyResolution.ExcludeAsUnrequested } - unrequested.forEach { configuration.exclude(mapOf("group" to it.dependency.moduleGroup, "module" to it.dependency.moduleName)) } + unrequested.forEach { + val (group, name) = it.projectDependency?.run { + /** Note: the project dependency notation here should be exactly this, group:name, + * not from [ModuleIds.fromProjectPathDependency], as `exclude` checks it against the project's group:name */ + ModuleDependencyIdentifier(group.toString(), name) + } ?: ModuleIds.fromComponent(project, it.dependency) + configuration.exclude(mapOf("group" to group, "module" to name)) + } - requested.forEach { - val notation = listOf(it.dependency.moduleGroup, it.dependency.moduleName, it.dependency.moduleVersion).joinToString(":") + requested.filter { it.projectDependency == null }.forEach { + val (group, name) = ModuleIds.fromComponent(project, it.dependency) + val notation = listOfNotNull(group.orEmpty(), name, it.dependency.moduleVersion?.version).joinToString(":") configuration.resolutionStrategy.force(notation) } } } private fun createTransformedMetadataClasspath( - fromFiles: Iterable, + fromFiles: Configuration, compilation: AbstractKotlinCompilation<*> ): FileCollection { val project = compilation.target.project @@ -400,17 +407,12 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : project.locateTask(transformGranularMetadataTaskName(hierarchySourceSet.name)) } - val allResolutionsByArtifactFile: Map> = - mutableMapOf>().apply { + val allResolutionsByModule: Map> = + mutableMapOf>().apply { transformationTaskHolders.forEach { val resolutions = it.get().metadataDependencyResolutions - resolutions.forEach { resolution -> - val artifacts = resolution.dependency.moduleArtifacts.map { it.file } - - artifacts.forEach { artifactFile -> - getOrPut(artifactFile) { mutableListOf() }.add(resolution) - } + getOrPut(ModuleIds.fromComponent(project, resolution.dependency)) { mutableListOf() }.add(resolution) } } } @@ -423,14 +425,22 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : dependencyCompilation.output.classesDirs.takeIf { hierarchySourceSet != sourceSet } } + val artifactView = fromFiles.incoming.artifactView { view -> + view.componentFilter { id -> + val moduleId = ModuleIds.fromComponentId(project, id) + allResolutionsByModule[moduleId].let { resolutions -> + resolutions == null || resolutions.any { it !is MetadataDependencyResolution.ExcludeAsUnrequested } + } + } + } + mutableSetOf().apply { - addAll(dependsOnCompilationOutputs) - - fromFiles.forEach { file -> - val resolutions = allResolutionsByArtifactFile[file] + artifactView.artifacts.forEach { artifact -> + val resolutions = + allResolutionsByModule[ModuleIds.fromComponentId(project, artifact.id.componentIdentifier)] if (resolutions == null) { - add(file) + add(artifact.file) } else { val chooseVisibleSourceSets = resolutions.filterIsInstance() @@ -439,7 +449,7 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) : // Wrap the list into a FileCollection, as some older Gradle version failed to resolve the classpath add(project.files(chooseVisibleSourceSets.map { transformedFilesByResolution.getValue(it) })) } else if (resolutions.any { it is MetadataDependencyResolution.KeepOriginalDependency }) { - add(file) + add(artifact.file) } // else: all dependency transformations exclude this dependency as unrequested; don't add any files } }