From 701e92ace2489961b79c4840613531e68a1410d4 Mon Sep 17 00:00:00 2001 From: Ilya Matveev Date: Thu, 19 Sep 2019 13:44:36 +0700 Subject: [PATCH] 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 --- .../KotlinMPPGradleProjectResolver.kt | 81 +++++++++++++++++++ .../idea/configuration/nativeLibrariesUtil.kt | 5 +- .../src/KotlinMPPGradleModel.kt | 1 + .../src/KotlinMPPGradleModelBuilder.kt | 11 ++- .../src/KotlinMPPGradleModelImpl.kt | 6 +- 5 files changed, 97 insertions(+), 7 deletions(-) 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 7d7dfa103df..dec31508089 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 @@ -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 + ) { + val konanTarget: String? + get() = compilation.konanTarget + + val dependencyNames: Map by lazy { + substitutedDependencies.associateBy { it.name.removeSuffixIfPresent(" [$konanTarget]") } + } + } + fun populateModuleDependencies( gradleModule: IdeaModule, ideProject: DataNode, @@ -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>() val processedModuleIds = HashSet() 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().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, 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): List { + 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.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()] diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt index 3b75b3e3331..518f17a80c6 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt @@ -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 diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt index 6c66c4a4ae6..f38ba55f319 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt @@ -100,6 +100,7 @@ interface KotlinCompilation : KotlinModule { val platform: KotlinPlatform val kotlinTaskProperties: KotlinTaskProperties + val konanTarget: String? companion object { const val MAIN_COMPILATION_NAME = "main" diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index 4fdac0f63a0..3205cc83a11 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -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 ) } diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt index d7699732040..a208f5cccb8 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt @@ -89,7 +89,8 @@ data class KotlinCompilationImpl( override val output: KotlinCompilationOutput, override val arguments: KotlinCompilationArguments, override val dependencyClasspath: Array, - 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