Merge branch 'slava/mpp-import-experiments'

# Conflicts:
#	idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt
This commit is contained in:
Vyacheslav Karpukhin
2020-02-18 14:14:23 +01:00
8 changed files with 274 additions and 43 deletions
@@ -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<KotlinTargetPreset<*>>
internal set
@@ -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<String>
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<AndroidSourceSet, MutableList<BaseVariant>>().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
}
}
}
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)
}
}
@@ -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,
@@ -64,7 +64,7 @@ open class KotlinAndroidTarget(
private fun checkPublishLibraryVariantsExist() {
fun AbstractAndroidProjectHandler.getLibraryVariantNames() =
mutableSetOf<String>().apply {
forEachVariant(project) {
project.forEachVariant {
if (getLibraryOutputTask(it) != null)
add(getVariantName(it))
}
@@ -94,7 +94,7 @@ open class KotlinAndroidTarget(
private fun AbstractAndroidProjectHandler.doCreateComponents(): Set<KotlinTargetComponent> {
val publishableVariants = mutableListOf<BaseVariant>()
.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 }
@@ -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<File>? = 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<String, List<AndroidDependency>?> {
if (!isAndroidPluginCompatible()) return emptyMap()
data class SourceSetConfigs(val implConfig: Configuration, val compileConfig: Configuration)
val androidSdkJar = getAndroidSdkJar(project) ?: return emptyMap()
val sourceSet2Impl = HashMap<String, SourceSetConfigs>()
val allImplConfigs = HashSet<Configuration>()
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<SelfResolvingDependency>().map { AndroidDependency(collection = it.resolve()) }
val resolvedExternal = dependencies.filterIsInstance<ExternalModuleDependency>()
.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<FileSystemLocation>?
rClassOutputJar?.orNull?.asFile?.let { result += AndroidDependency("R.jar", it) }
}
}
result
}.toMap()
}
@Suppress("UnstableApiUsage")
private fun collectDependencies(
module: ModuleIdentifier,
compileClasspathConf: Configuration
): List<AndroidDependency> {
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<ModuleIdentifier>().also { doCollectDependencies(listOf(module), resolutionResults, it) }
return deps.mapNotNull { resolvedArtifacts[it] }
}
@Suppress("UnstableApiUsage")
private tailrec fun doCollectDependencies(
modules: List<ModuleIdentifier>,
resolutionResults: Map<ModuleIdentifier, ResolvedComponentResult>,
result: MutableSet<ModuleIdentifier>
) {
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<Configuration>, conf: Configuration): Set<Dependency> {
return HashSet<Dependency>().also { doFindDependencies(implConfigs, listOf(conf), it) }
}
private tailrec fun doFindDependencies(
implConfigs: Set<Configuration>, configs: List<Configuration>, result: MutableSet<Dependency>,
visited: MutableSet<Configuration> = 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)
}
}
@@ -208,7 +208,7 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val resDirectoriesForAllVariants = mutableListOf<FileCollection>()
androidProjectHandler.forEachVariant(project) { variant ->
project.forEachVariant { variant ->
if (getTestedVariantData(variant) != null) return@forEachVariant
resDirectoriesForAllVariants += androidProjectHandler.getResDirectories(variant)
}