IDE: Propagate platform libraries to common modules for some native targets

Some native platforms (e.g. ios_arm64 and ios_x64) have almost
identical platform libraries. For such platforms we can workaround
a lack of the commonizer and resolve common sources against
libraries of one of these platforms. This patch enables this
workaround by propagating dependencies to common modules.

Issue #KT-33999 fixed
This commit is contained in:
Ilya Matveev
2019-09-19 13:44:36 +07:00
parent 6b10336497
commit 701e92ace2
5 changed files with 97 additions and 7 deletions
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.idea.configuration.GradlePropertiesFileFacade.Companion.KOTLIN_NOT_IMPORTED_COMMON_SOURCE_SETS_SETTING
import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling
import org.jetbrains.kotlin.konan.util.removeSuffixIfPresent
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
import org.jetbrains.plugins.gradle.model.*
import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData
@@ -465,6 +466,18 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
}
}
private data class CompilationWithDependencies(
val compilation: KotlinCompilation,
val substitutedDependencies: List<ExternalDependency>
) {
val konanTarget: String?
get() = compilation.konanTarget
val dependencyNames: Map<String, ExternalDependency> by lazy {
substitutedDependencies.associateBy { it.name.removeSuffixIfPresent(" [$konanTarget]") }
}
}
fun populateModuleDependencies(
gradleModule: IdeaModule,
ideProject: DataNode<ProjectData>,
@@ -475,6 +488,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
val sourceSetMap = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) ?: return
val artifactsMap = ideProject.getUserData(CONFIGURATION_ARTIFACTS) ?: return
val substitutor = KotlinNativeLibrariesDependencySubstitutor(mppModel, gradleModule, resolverCtx)
val sourceSetToCompilations = mutableMapOf<String, MutableList<CompilationWithDependencies>>()
val processedModuleIds = HashSet<String>()
processCompilations(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, compilation ->
if (processedModuleIds.add(getKotlinModuleId(gradleModule, compilation, resolverCtx))) {
@@ -490,6 +504,11 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
)
KotlinNativeLibrariesFixer.applyTo(dataNode, ideProject)
for (sourceSet in compilation.sourceSets) {
(sourceSet.dependsOnSourceSets + sourceSet.name).forEach {
sourceSetToCompilations
.getOrPut(it) { mutableListOf() }
.add(CompilationWithDependencies(compilation, substitutedDependencies))
}
if (sourceSet.fullName() == compilation.fullName()) continue
val targetDataNode = getSiblingKotlinModuleData(sourceSet, gradleModule, ideModule, resolverCtx) ?: continue
addDependency(dataNode, targetDataNode, sourceSet.isTestModule)
@@ -558,6 +577,13 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
val mergedDependencies = LinkedHashSet<KotlinDependency>().apply {
addAll(sourceSet.dependencies.mapNotNull { mppModel.dependencyMap[it] })
dependeeSourceSets.flatMapTo(this) { it.dependencies.mapNotNull { mppModel.dependencyMap[it] } }
if (sourceSet.actualPlatforms.getSinglePlatform() == KotlinPlatform.NATIVE) {
sourceSetToCompilations[sourceSet.name]
?.takeIf { it.size > 1 }
?.let { compilations ->
addAll(propagatedDependencies(compilations))
}
}
}
buildDependencies(
resolverCtx,
@@ -567,10 +593,65 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() {
preprocessDependencies(mergedDependencies),
ideProject
)
@Suppress("UNCHECKED_CAST")
KotlinNativeLibrariesFixer.applyTo(fromDataNode as DataNode<GradleSourceSetData>, ideProject)
}
}
}
// We can't really commonize native platform libraries yet.
// But APIs for different targets may be very similar.
// E.g. ios_arm64 and ios_x64 have almost identical platform libraries.
// We handle these special cases and resolve common sources for such
// targets against libraries of one of them. E.g. common sources for
// ios_x64 and ios_arm64 will be resolved against ios_arm64 libraries.
//
// Currently such special casing is available for Apple platforms
// (iOS, watchOS and tvOS) and native Android (ARM, X86).
// TODO: Do we need to support user's interop libraries too?
private fun propagatedDependencies(compilations: List<CompilationWithDependencies>): List<ExternalDependency> {
if (compilations.isEmpty()) {
return emptyList()
}
val copyFrom = when {
compilations.all { it.isAppleCompilation } ->
compilations.selectFirstAvailableTarget(
"watchos_arm64", "watchos_arm32", "watchos_x86",
"ios_arm64", "ios_arm32", "ios_x64",
"tvos_arm64", "tvos_x64"
)
compilations.all { it.konanTarget?.startsWith("android") == true } ->
compilations.selectFirstAvailableTarget(
"android_arm64", "android_arm32", "android_x64", "android_x86"
)
else -> return emptyList()
}
return copyFrom.dependencyNames.mapNotNull { (name, dependency) ->
when {
!name.startsWith(KOTLIN_NATIVE_LIBRARY_PREFIX) -> null // Support only default platform libs for now.
compilations.all { it.dependencyNames.containsKey(name) } -> dependency
else -> null
}
}
}
private val CompilationWithDependencies.isAppleCompilation: Boolean
get() = konanTarget?.let {
it.startsWith("ios") || it.startsWith("watchos") || it.startsWith("tvos")
} ?: false
private fun Iterable<CompilationWithDependencies>.selectFirstAvailableTarget(vararg targetsByPriority: String): CompilationWithDependencies {
for (target in targetsByPriority) {
val result = firstOrNull { it.konanTarget == target }
if (result != null) {
return result
}
}
return first()
}
private fun KotlinModule.toSourceSet(mppModel: KotlinMPPGradleModel) = when (this) {
is KotlinSourceSet -> this
is KotlinCompilation -> mppModel.sourceSets[fullName()]
@@ -8,10 +8,7 @@ package org.jetbrains.kotlin.idea.configuration
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.LibraryData
import com.intellij.openapi.externalSystem.model.project.LibraryDependencyData
import com.intellij.openapi.externalSystem.model.project.LibraryLevel
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.model.project.*
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.IdeUIModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
@@ -100,6 +100,7 @@ interface KotlinCompilation : KotlinModule {
val platform: KotlinPlatform
val kotlinTaskProperties: KotlinTaskProperties
val konanTarget: String?
companion object {
const val MAIN_COMPILATION_NAME = "main"
@@ -421,6 +421,14 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
buildCompilationDependencies(gradleCompilation, classifier, sourceSetMap, dependencyResolver, project, dependencyMapper)
val kotlinTaskProperties = getKotlinTaskProperties(compileKotlinTask)
// Get konanTarget (for native compilations only).
val konanTarget = compilationClass.getMethodOrNull("getKonanTarget")?.let { getKonanTarget ->
val konanTarget = getKonanTarget.invoke(gradleCompilation)
konanTarget.javaClass.getMethodOrNull("getName")?.let {
it.invoke(konanTarget) as? String
}
}
return KotlinCompilationImpl(
gradleCompilation.name,
kotlinSourceSets,
@@ -428,7 +436,8 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService {
output,
arguments,
dependencyClasspath.toTypedArray(),
kotlinTaskProperties
kotlinTaskProperties,
konanTarget
)
}
@@ -89,7 +89,8 @@ data class KotlinCompilationImpl(
override val output: KotlinCompilationOutput,
override val arguments: KotlinCompilationArguments,
override val dependencyClasspath: Array<String>,
override val kotlinTaskProperties: KotlinTaskProperties
override val kotlinTaskProperties: KotlinTaskProperties,
override val konanTarget: String? = null
) : KotlinCompilation {
// create deep copy
@@ -104,7 +105,8 @@ data class KotlinCompilationImpl(
KotlinCompilationOutputImpl(kotlinCompilation.output),
KotlinCompilationArgumentsImpl(kotlinCompilation.arguments),
kotlinCompilation.dependencyClasspath,
KotlinTaskPropertiesImpl(kotlinCompilation.kotlinTaskProperties)
KotlinTaskPropertiesImpl(kotlinCompilation.kotlinTaskProperties),
kotlinCompilation.konanTarget
) {
disambiguationClassifier = kotlinCompilation.disambiguationClassifier
platform = kotlinCompilation.platform