diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt index 86be17a0110..511cc80b59f 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt @@ -7,6 +7,8 @@ package org.jetbrains.kotlin.idea.configuration import com.google.common.graph.GraphBuilder import com.google.common.graph.Graphs +import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.ProjectKeys import com.intellij.openapi.externalSystem.model.project.* @@ -73,6 +75,10 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp initializeModuleData(gradleModule, moduleDataNode, projectDataNode, resolverCtx) } + override fun getExtraCommandLineArgs(): List { + return if (!androidPluginPresent) listOf("-Pkotlin.include.android.dependencies=true") else emptyList() + } + override fun populateModuleExtraModels(gradleModule: IdeaModule, ideModule: DataNode) { if (ExternalSystemApiUtil.find(ideModule, BuildScriptClasspathData.KEY) == null) { val buildScriptClasspathData = buildClasspathData(gradleModule, resolverCtx) @@ -228,6 +234,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp val proxyObjectCloningCache = WeakHashMap() private var nativeDebugAdvertised = false + private val androidPluginPresent = PluginManager.getPlugin(PluginId.findId("org.jetbrains.android"))?.isEnabled ?: false fun initializeModuleData( gradleModule: IdeaModule, @@ -279,7 +286,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp val sourceSetToCompilationData = LinkedHashMap>() for (target in mppModel.targets) { - if (target.platform == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(target)) continue if (target.name == KotlinTarget.METADATA_TARGET_NAME) continue val targetData = KotlinTargetData(target.name).also { it.archiveFile = target.jar?.archiveFile @@ -353,7 +360,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp val ignoreCommonSourceSets by lazy { externalProject.notImportedCommonSourceSets() } for (sourceSet in mppModel.sourceSets.values) { - if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(sourceSet)) continue if (sourceSet.actualPlatforms.supports(KotlinPlatform.COMMON) && ignoreCommonSourceSets) continue val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) val existingSourceSetDataNode = sourceSetMap[moduleId]?.first @@ -451,7 +458,8 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp .toMap() if (resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) == null) return processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet -> - if (dataNode == null || sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) return@processSourceSets + if (dataNode == null || delegateToAndroidPlugin(sourceSet)) return@processSourceSets + createContentRootData( sourceSet.sourceDirs, sourceSet.sourceType, @@ -561,7 +569,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp } val closedSourceSetGraph = Graphs.transitiveClosure(sourceSetGraph) for (sourceSet in closedSourceSetGraph.nodes()) { - val isAndroid = sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID + val isAndroid = delegateToAndroidPlugin(sourceSet) val fromDataNode = if (isAndroid) { ideModule } else { @@ -581,7 +589,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp sourceSetInfo.addSourceSets(dependeeSourceSets, selfName, gradleModule, resolverCtx) } } - if (sourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(sourceSet)) continue for (dependeeSourceSet in dependeeSourceSets) { val toDataNode = getSiblingKotlinModuleData(dependeeSourceSet, gradleModule, ideModule, resolverCtx) ?: continue addDependency(fromDataNode, toDataNode, dependeeSourceSet.isTestModule) @@ -750,7 +758,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp } } for (target in mppModel.targets) { - if (target.platform == KotlinPlatform.ANDROID) continue + if (delegateToAndroidPlugin(target)) continue for (compilation in target.compilations) { val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) val moduleDataNode = sourceSetsMap[moduleId] ?: continue @@ -971,6 +979,12 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp "true", ignoreCase = true ) ?: false + + private fun delegateToAndroidPlugin(kotlinTarget: KotlinTarget): Boolean = + androidPluginPresent && kotlinTarget.platform == KotlinPlatform.ANDROID + + private fun delegateToAndroidPlugin(kotlinSourceSet: KotlinSourceSet): Boolean = + androidPluginPresent && kotlinSourceSet.actualPlatforms.platforms.singleOrNull() == KotlinPlatform.ANDROID } } diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index d0e45a23445..da0a83492c6 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -20,6 +20,7 @@ import org.gradle.api.provider.Provider import org.gradle.api.tasks.Exec import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel.Companion.NO_KOTLIN_NATIVE_HOME +import org.jetbrains.plugins.gradle.DefaultExternalDependencyId import org.jetbrains.plugins.gradle.model.* import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder import org.jetbrains.plugins.gradle.tooling.ModelBuilderService @@ -144,12 +145,13 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { @Suppress("UNCHECKED_CAST") val sourceSets = (getSourceSets(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + val androidDeps = buildAndroidDeps(kotlinExt.javaClass.classLoader, project) // Some performance optimisation: do not build metadata dependencies if source set is not common val doBuildMetadataDependencies = project.properties["build_metadata_dependencies_for_actualised_source_sets"]?.toString()?.toBoolean() ?: DEFAULT_BUILD_METADATA_DEPENDENCIES_FOR_ACTUALISED_SOURCE_SETS - val allSourceSetsProtos = sourceSets.mapNotNull { buildSourceSet(it, dependencyResolver, project, dependencyMapper) } + val allSourceSetsProtos = sourceSets.mapNotNull { buildSourceSet(it, dependencyResolver, project, dependencyMapper, androidDeps) } val allSourceSets = if (doBuildMetadataDependencies) { allSourceSetsProtos.map { proto -> proto.buildKotlinSourceSetImpl(true) } } else { @@ -173,11 +175,28 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } + private fun buildAndroidDeps(classLoader: ClassLoader, project: Project): Map>? { + val includeAndroidDeps = project.properties["kotlin.include.android.dependencies"]?.toString()?.toBoolean() == true + if (includeAndroidDeps) { + try { + val resolverClass = classLoader.loadClass("org.jetbrains.kotlin.gradle.targets.android.internal.AndroidDependencyResolver") + val getAndroidSourceSetDependencies = resolverClass.getMethodOrNull("getAndroidSourceSetDependencies", Project::class.java) + val resolver = resolverClass.getField("INSTANCE").get(null) + @Suppress("UNCHECKED_CAST") + return getAndroidSourceSetDependencies?.let { it(resolver, project) } as Map>? + } catch (e: Exception) { + logger.info("Unexpected exception", e) + } + } + return null + } + private fun buildSourceSet( gradleSourceSet: Named, dependencyResolver: DependencyResolver, project: Project, - dependencyMapper: KotlinDependencyMapper + dependencyMapper: KotlinDependencyMapper, + androidDeps: Map>? ): KotlinSourceSetProto? { val sourceSetClass = gradleSourceSet.javaClass val getLanguageSettings = sourceSetClass.getMethodOrNull("getLanguageSettings") ?: return null @@ -192,7 +211,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val dependsOnSourceSets = (getDependsOn(gradleSourceSet) as? Set)?.mapTo(LinkedHashSet()) { it.name } ?: emptySet() val sourceSetDependenciesBuilder: () -> Array = { - buildSourceSetDependencies(gradleSourceSet, dependencyResolver, project).map { dependencyMapper.getId(it) }.distinct() + buildSourceSetDependencies(gradleSourceSet, dependencyResolver, project, androidDeps).map { dependencyMapper.getId(it) }.distinct() .toTypedArray() } return KotlinSourceSetProto( @@ -598,7 +617,8 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { private fun buildSourceSetDependencies( gradleSourceSet: Named, dependencyResolver: DependencyResolver, - project: Project + project: Project, + androidDeps: Map>? ): List { return ArrayList().apply { val transformationBuilder = MetadataDependencyTransformationBuilder(gradleSourceSet) @@ -614,9 +634,33 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { this += buildDependencies( gradleSourceSet, dependencyResolver, "getRuntimeOnlyMetadataConfigurationName", "RUNTIME", project, transformationBuilder ) + this += buildAndroidSourceSetDependencies(androidDeps, gradleSourceSet) } } + private fun buildAndroidSourceSetDependencies( + androidDeps: Map>?, + gradleSourceSet: Named + ): Collection { + return androidDeps?.get(gradleSourceSet.name)?.map { it -> + @Suppress("UNCHECKED_CAST") + val collection = it["getCollection"] as Set? + if (collection == null) { + DefaultExternalLibraryDependency().apply { + (id as? DefaultExternalDependencyId)?.apply { + name = it["getName"] as String? + group = it["getGroup"] as String? + version = it["getVersion"] as String? + } + file = it["getJar"] as File + source = it["getSource"] as File? + } + } else { + DefaultFileCollectionDependency(collection) + } + } ?: emptyList() + } + @Suppress("UNCHECKED_CAST") private fun safelyGetArguments(compileKotlinTask: Task, accessor: Method?) = try { accessor?.invoke(compileKotlinTask) as? List diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index d8fa203821f..a32324669d8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -9,15 +9,16 @@ import groovy.lang.Closure import org.gradle.api.InvalidUserCodeException import org.gradle.api.NamedDomainObjectCollection import org.gradle.util.ConfigureUtil -import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTests +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainerWithPresets import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast open class KotlinMultiplatformExtension : KotlinProjectExtension(), KotlinTargetContainerWithPresetFunctions, - KotlinTargetContainerWithNativeShortcuts -{ + KotlinTargetContainerWithNativeShortcuts { override lateinit var presets: NamedDomainObjectCollection> internal set diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 10928594e69..88ab3e00a2a 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -1,7 +1,6 @@ package org.jetbrains.kotlin.gradle.plugin -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.BasePlugin +import com.android.build.gradle.* import com.android.build.gradle.api.AndroidSourceSet import com.android.build.gradle.api.BaseVariant import com.android.builder.model.SourceProvider @@ -805,7 +804,6 @@ class KotlinConfigurationTools internal constructor( abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTools: KotlinConfigurationTools) { protected val logger = Logging.getLogger(this.javaClass) - abstract fun forEachVariant(project: Project, action: (BaseVariant) -> Unit): Unit abstract fun getResDirectories(variantData: BaseVariant): FileCollection abstract fun getFlavorNames(variant: BaseVariant): List abstract fun getBuildTypeName(variant: BaseVariant): String @@ -879,7 +877,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool androidPluginIds.joinToString("\n\t") { "* $it" }) } - forEachVariant(project) { variant -> + project.forEachVariant { variant -> val variantName = getVariantName(variant) // Create the compilation and configure it first, then add to the compilations container. As this code is executed @@ -899,7 +897,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool } project.whenEvaluated { - forEachVariant(project) { variant -> + forEachVariant { variant -> val compilation = kotlinAndroidTarget.compilations.getByName(getVariantName(variant)) postprocessVariant(variant, compilation, project, ext, plugin) @@ -966,7 +964,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool if (kotlinAndroidTarget.disambiguationClassifier != null) { val sourceSetToVariants = mutableMapOf>().apply { - forEachVariant(project) { variant -> + project.forEachVariant { variant -> for (sourceSet in getSourceProviders(variant)) { val androidSourceSet = sourceSet as? AndroidSourceSet ?: continue getOrPut(androidSourceSet) { mutableListOf() }.add(variant) @@ -1242,4 +1240,22 @@ internal fun compareVersionNumbers(v1: String?, v2: String?): Int { } return 0 } -} \ No newline at end of file +} + +internal fun Project.forEachVariant(action: (BaseVariant) -> Unit) { + val androidExtension = this.extensions.getByName("android") + when (androidExtension) { + is AppExtension -> androidExtension.applicationVariants.all(action) + is LibraryExtension -> { + androidExtension.libraryVariants.all(action) + if (androidExtension is FeatureExtension) { + androidExtension.featureVariants.all(action) + } + } + is TestExtension -> androidExtension.applicationVariants.all(action) + } + if (androidExtension is TestedExtension) { + androidExtension.testVariants.all(action) + androidExtension.unitTestVariants.all(action) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt index e36c8ccfcef..b014b3d8381 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt @@ -6,7 +6,8 @@ @file:Suppress("PackageDirectoryMismatch") // Old package for compatibility package org.jetbrains.kotlin.gradle.plugin -import com.android.build.gradle.* +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.BasePlugin import com.android.build.gradle.api.* import com.android.build.gradle.tasks.MergeResources import com.android.builder.model.SourceProvider @@ -29,24 +30,6 @@ class Android25ProjectHandler( kotlinConfigurationTools: KotlinConfigurationTools ) : AbstractAndroidProjectHandler(kotlinConfigurationTools) { - override fun forEachVariant(project: Project, action: (BaseVariant) -> Unit) { - val androidExtension = project.extensions.getByName("android") - when (androidExtension) { - is AppExtension -> androidExtension.applicationVariants.all(action) - is LibraryExtension -> { - androidExtension.libraryVariants.all(action) - if (androidExtension is FeatureExtension) { - androidExtension.featureVariants.all(action) - } - } - is TestExtension -> androidExtension.applicationVariants.all(action) - } - if (androidExtension is TestedExtension) { - androidExtension.testVariants.all(action) - androidExtension.unitTestVariants.all(action) - } - } - override fun wireKotlinTasks( project: Project, compilation: KotlinJvmAndroidCompilation, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt index 1aa6f617d50..d7c80ae6f4c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt @@ -64,7 +64,7 @@ open class KotlinAndroidTarget( private fun checkPublishLibraryVariantsExist() { fun AbstractAndroidProjectHandler.getLibraryVariantNames() = mutableSetOf().apply { - forEachVariant(project) { + project.forEachVariant { if (getLibraryOutputTask(it) != null) add(getVariantName(it)) } @@ -94,7 +94,7 @@ open class KotlinAndroidTarget( private fun AbstractAndroidProjectHandler.doCreateComponents(): Set { val publishableVariants = mutableListOf() - .apply { forEachVariant(project) { add(it) } } + .apply { project.forEachVariant { add(it) } } .toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { } .filter { getLibraryOutputTask(it) != null && publishLibraryVariants?.contains(getVariantName(it)) ?: true } 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 new file mode 100644 index 00000000000..9f6e65c6ebb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidDependencyResolver.kt @@ -0,0 +1,173 @@ +/* + * 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.targets.android.internal + +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.api.AndroidSourceSet +import com.android.build.gradle.internal.LoggerWrapper +import com.android.build.gradle.internal.publishing.AndroidArtifacts +import com.android.sdklib.IAndroidTarget +import com.android.sdklib.repository.AndroidSdkHandler +import com.android.sdklib.repository.LoggerProgressIndicatorWrapper +import com.intellij.openapi.util.Version +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.ResolvedComponentResult +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import org.jetbrains.kotlin.gradle.plugin.forEachVariant +import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName +import java.io.File +import kotlin.reflect.full.findParameterByName + +data class AndroidDependency( + val name: String? = null, + val jar: File? = null, + val source: File? = null, + val group: String? = null, + val version: String? = null, + val collection: Set? = null +) + +@Suppress("unused") +object AndroidDependencyResolver { + private fun getAndroidSdkJar(project: Project): AndroidDependency? { + val androidExtension = project.extensions.findByName("android") as BaseExtension? ?: return null + val sdkLocation = getClassOrNull("com.android.build.gradle.internal.SdkLocator")?.let { sdkLocatorClass -> + val sdkLocation = + sdkLocatorClass.getMethodOrNull("getSdkLocation", File::class.java)?.invoke(sdkLocatorClass, project.rootDir) + sdkLocation?.javaClass?.getMethodOrNull("getDirectory")?.invoke(sdkLocation) as? File + } ?: return null + val sdkHandler = AndroidSdkHandler.getInstance(sdkLocation) + val logger = LoggerProgressIndicatorWrapper(LoggerWrapper(project.logger)) + val androidTarget = + sdkHandler.getAndroidTargetManager(logger).getTargetFromHashString(androidExtension.compileSdkVersion, logger) ?: return null + return AndroidDependency( + androidTarget.fullName, + File(androidTarget.getPath(IAndroidTarget.ANDROID_JAR)), + File(androidTarget.getPath(IAndroidTarget.SOURCES)) + ) + } + + private fun getClassOrNull(s: String) = + try { + Class.forName(s) + } catch (e: Exception) { + null + } + + private fun Class<*>.getMethodOrNull(name: String, vararg parameterTypes: Class<*>) = + try { + getMethod(name, *parameterTypes) + } catch (e: Exception) { + null + } + + private fun isAndroidPluginCompatible(): Boolean { + val version = getClassOrNull("com.android.Version")?.let { + try { + it.getField("ANDROID_GRADLE_PLUGIN_VERSION").get(null) as String + } catch (e: Exception) { + return false + } + } ?: return false + return Version.parseVersion(version)?.isOrGreaterThan(3, 6) ?: false + } + + fun getAndroidSourceSetDependencies(project: Project): Map?> { + if (!isAndroidPluginCompatible()) return emptyMap() + + data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration) + + val androidSdkJar = getAndroidSdkJar(project) ?: return emptyMap() + val sourceSet2Impl = HashMap() + val allImplConfigs = HashSet() + + project.forEachVariant { variant -> + val compileConfig = variant.compileConfiguration + 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) + } + } + + return sourceSet2Impl.mapValues { entry -> + val dependencies = findDependencies(allImplConfigs, entry.value.implConfig) + + val selfResolved = dependencies.filterIsInstance().map { AndroidDependency(collection = it.resolve()) } + val resolvedExternal = dependencies.filterIsInstance() + .flatMap { collectDependencies(it.module, entry.value.compileConfig) } + + val result = (selfResolved + resolvedExternal + androidSdkJar).toMutableList() + + if (entry.key == "androidMain") { + // this is a terrible hack, but looks like the only way, other than proper support via light-classes + val task = project.tasks.findByName("processDebugResources") + getClassOrNull("com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask")?.let { linkAppClass -> + @Suppress("UNCHECKED_CAST") + val rClassOutputJar = + linkAppClass.getMethodOrNull("getRClassOutputJar")?.invoke(task) as Provider? + rClassOutputJar?.orNull?.asFile?.let { result += AndroidDependency("R.jar", it) } + } + } + + result + }.toMap() + } + + @Suppress("UnstableApiUsage") + private fun collectDependencies( + module: ModuleIdentifier, + compileClasspathConf: Configuration + ): List { + val viewConfig: (ArtifactView.ViewConfiguration) -> Unit = { config -> + config.attributes { it.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.JAR.type) } + config.isLenient = true + } + + val resolvedArtifacts = compileClasspathConf.incoming.artifactView(viewConfig).artifacts.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 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] } + } + + @Suppress("UnstableApiUsage") + private tailrec fun doCollectDependencies( + modules: List, + resolutionResults: Map, + result: MutableSet + ) { + if (modules.isEmpty()) return + val newModules = modules.filter { result.add(it) }.mapNotNull { resolutionResults[it] }.flatMap { it.dependencies } + .mapNotNull { (it.requested as? ModuleComponentSelector)?.moduleIdentifier } + doCollectDependencies(newModules, resolutionResults, result) + } + + private fun findDependencies(implConfigs: Set, conf: Configuration): Set { + return HashSet().also { doFindDependencies(implConfigs, listOf(conf), it) } + } + + private tailrec fun doFindDependencies( + implConfigs: Set, configs: List, 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) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt index 955e8ff5dbc..8df1cdb4010 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/internal/AndroidSubplugin.kt @@ -208,7 +208,7 @@ class AndroidSubplugin : KotlinGradleSubplugin { val resDirectoriesForAllVariants = mutableListOf() - androidProjectHandler.forEachVariant(project) { variant -> + project.forEachVariant { variant -> if (getTestedVariantData(variant) != null) return@forEachVariant resDirectoriesForAllVariants += androidProjectHandler.getResDirectories(variant) }