From ac8d135a2187da83123daf48462d10929656f7d6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Karpukhin Date: Fri, 19 Feb 2021 21:45:27 +0100 Subject: [PATCH] AndroidDependencyResolver: 1. Correctly process project dependencies 2. Don't include variant-specific dependencies into more generic sourcesets --- .../internal/AndroidDependencyResolver.kt | 122 +++++++++++++++--- 1 file changed, 104 insertions(+), 18 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt index d4cfcf4c1ea..5f1121a356b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt @@ -16,9 +16,18 @@ import org.gradle.api.Project import org.gradle.api.artifacts.* import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.artifacts.component.ModuleComponentSelector +import org.gradle.api.artifacts.result.ResolvedArtifactResult import org.gradle.api.artifacts.result.ResolvedComponentResult +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.AttributesSchema +import org.gradle.api.capabilities.Capability import org.gradle.api.file.FileSystemLocation +import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal +import org.gradle.api.internal.artifacts.dependencies.ProjectDependencyInternal +import org.gradle.api.internal.attributes.AttributeContainerInternal +import org.gradle.api.internal.attributes.AttributesSchemaInternal import org.gradle.api.provider.Provider +import org.gradle.internal.component.model.AttributeConfigurationSelector import org.jetbrains.kotlin.gradle.plugin.forEachVariant import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import java.io.File @@ -95,12 +104,12 @@ object AndroidDependencyResolver { return versions[0].toInt() >= 4 || versions[0].toInt() >= 3 && versions[1].toInt() >= 6 } + private data class SourceSetConfigs(val implConfig: Configuration, val compileConfigs: MutableList = ArrayList()) + fun getAndroidSourceSetDependencies(project: Project): Map?> { val androidPluginVersions = getAndroidPluginVersions() ?: return emptyMap() if (!isAndroidPluginCompatible(androidPluginVersions)) return emptyMap() - data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration) - val androidSdkJar = getAndroidSdkJar(project, androidPluginVersions) ?: return emptyMap() val sourceSet2Impl = HashMap() val allImplConfigs = HashSet() @@ -110,19 +119,24 @@ object AndroidDependencyResolver { variant.sourceSets.filterIsInstance(AndroidSourceSet::class.java).map { val implConfig = project.configurations.getByName(it.implementationConfigurationName) allImplConfigs.add(implConfig) - sourceSet2Impl[lowerCamelCaseName("android", it.name)] = SourceSetConfigs(implConfig, compileConfig) + val sourceSetConfig = + sourceSet2Impl.computeIfAbsent(lowerCamelCaseName("android", it.name)) { SourceSetConfigs(implConfig) } + // The same sourceset can be included into multiple variants (e.g. androidMain) + sourceSetConfig.compileConfigs.add(compileConfig) } } + val attributesSchema = project.dependencies.attributesSchema + return sourceSet2Impl.mapValues { entry -> - val dependencies = findDependencies(allImplConfigs, entry.value.implConfig) + val dependencies = findDependencies(allImplConfigs, entry.value, attributesSchema) val selfResolved = dependencies.filterIsInstance().map { dependency -> val collection = dependency.resolve().takeIf(Collection::isNotEmpty) AndroidDependency(dependency.name, group = dependency.group, collection = collection) } - val resolvedExternal = dependencies.filterIsInstance() - .flatMap { collectDependencies(it.module, entry.value.compileConfig) } + val resolvedExternal = + collectDependencies(dependencies.filterIsInstance(), entry.value.compileConfigs) val result = (selfResolved + resolvedExternal + androidSdkJar).toMutableList() @@ -145,9 +159,9 @@ object AndroidDependencyResolver { @Suppress("UnstableApiUsage") private fun collectDependencies( - module: ModuleIdentifier, - compileClasspathConf: Configuration - ): List { + dependencies: List, + compileClasspathConfigs: List + ): Set { val processedJarArtifactType = try { AndroidArtifacts.ArtifactType.valueOf("PROCESSED_JAR") } catch (e: IllegalArgumentException) { @@ -158,19 +172,34 @@ object AndroidDependencyResolver { config.isLenient = true } - val resolvedArtifacts = compileClasspathConf.incoming.artifactView(viewConfig).artifacts.mapNotNull { + val allResults = LinkedHashSet() + val allComponents = LinkedHashSet() + + compileClasspathConfigs.forEach { config -> + // If the current sourceset's implementation configuration is included into several compile classpath configuration's, + // we'll take only the results which are the same across all of them. The dependencies which resolve differently will appear + // in the more specific sourcesets. + allResults.addOrRetainAll(config.incoming.artifactView(viewConfig).artifacts.artifacts) { it.id } + allComponents.addOrRetainAll(config.incoming.resolutionResult.allComponents) { it.id } + } + + val resolvedArtifacts = allResults.mapNotNull { val componentIdentifier = it.id.componentIdentifier as? ModuleComponentIdentifier ?: return@mapNotNull null val id = componentIdentifier.moduleIdentifier ?: return@mapNotNull null id to AndroidDependency(id.name, it.file, null, id.group, componentIdentifier.version) }.toMap() - val resolutionResults = compileClasspathConf.incoming.resolutionResult.allComponents.mapNotNull { + val resolutionResults = allComponents.mapNotNull { val id = it.moduleVersion?.module ?: return@mapNotNull null id to it }.toMap() - val deps = HashSet().also { doCollectDependencies(listOf(module), resolutionResults, it) } - return deps.mapNotNull { resolvedArtifacts[it] } + return HashSet().also { result -> + dependencies.flatMapTo(result) { dependency -> + val deps = HashSet().also { doCollectDependencies(listOf(dependency.module), resolutionResults, it) } + deps.mapNotNull { resolvedArtifacts[it] } + } + } } @Suppress("UnstableApiUsage") @@ -185,16 +214,73 @@ object AndroidDependencyResolver { doCollectDependencies(newModules, resolutionResults, result) } - private fun findDependencies(implConfigs: Set, conf: Configuration): Set { - return HashSet().also { doFindDependencies(implConfigs, listOf(conf), it) } + private fun findDependencies( + implConfigs: Set, + sourceSetConfigs: SourceSetConfigs, + attributesSchema: AttributesSchema + ): Set { + // Using the attributes of the first configuration. If there are multiple configurations, then differences in resolve would be + // discarded later anyway, so they won't make any difference + val attributes = sourceSetConfigs.compileConfigs.first().attributes + + return HashSet().also { + doFindDependencies(implConfigs, listOf(sourceSetConfigs.implConfig), attributes, attributesSchema, it) + } } private tailrec fun doFindDependencies( - implConfigs: Set, configs: List, result: MutableSet, + implConfigs: Set, + configs: List, + attributes: AttributeContainer, + attributesSchema: AttributesSchema, + result: MutableSet, visited: MutableSet = HashSet() ) { if (configs.isEmpty()) return - result.addAll(configs.flatMap { it.dependencies }) - doFindDependencies(implConfigs, configs.flatMap { it.extendsFrom }.filter { it !in implConfigs && visited.add(it) }, result) + + val allDependencies = configs.flatMap { it.dependencies } + val allExtendsFrom = configs.flatMap { it.extendsFrom }.toMutableList() + + result.addAll(allDependencies.filter { it !is ProjectDependency }) + + val projectDependencies = allDependencies.filterIsInstance() + allExtendsFrom.addAll(selectConfigurations(projectDependencies, attributes, attributesSchema)) + + doFindDependencies( + implConfigs, + allExtendsFrom.filter { it !in implConfigs && visited.add(it) }, + attributes, + attributesSchema, + result + ) + } + + private fun selectConfigurations( + projectDependencies: List, + attributes: AttributeContainer, + attributesSchema: AttributesSchema + ): List { + return projectDependencies.mapNotNull { projectDependency -> + val rootComponentMetaData = (projectDependency.findProjectConfiguration() as? ConfigurationInternal)?.toRootComponentMetaData() + ?: return@mapNotNull null + + val matching = AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching( + (attributes as? AttributeContainerInternal)?.asImmutable() ?: return@mapNotNull null, + emptyList(), + rootComponentMetaData, + (attributesSchema as? AttributesSchemaInternal) ?: return@mapNotNull null, + emptyList() + ) + projectDependency.dependencyProject.configurations.findByName(matching.name) + } + } + + private fun MutableSet.addOrRetainAll(c: Collection, selector: (E) -> K) { + if (isEmpty()) { + addAll(c) + } else { + val selectedSet = c.map(selector).toSet() + retainAll { selectedSet.contains(selector(it)) } + } } } \ No newline at end of file