From d053ee33a8fb0fcbf7fa5620ac25ccb7a0c99310 Mon Sep 17 00:00:00 2001 From: Yaroslav Chernyshev Date: Thu, 11 Mar 2021 22:30:38 +0300 Subject: [PATCH] Precise platform importing changes ported from new repository --- .../idea/configuration/KotlinMPPDataNodes.kt | 2 +- .../src/ExperimentalGradleToolingApi.kt | 9 ++ .../src/KotlinImportingDiagnostic.kt | 2 +- .../src/KotlinMPPGradleModel.kt | 98 ++++++++++++++-- .../src/KotlinMPPGradleModelBuilder.kt | 107 +++++++++--------- .../src/KotlinSourceSetContainer.kt | 48 ++++++++ .../src/MultiplatformModelImportingChecker.kt | 2 +- .../src/MultiplatformModelImportingContext.kt | 34 +++--- idea/kotlin-gradle-tooling/src/utils.kt | 12 +- .../precisePlatformsHmpp/build.gradle.kts | 71 ++++++++++++ .../precisePlatformsHmpp/gradle.properties | 1 + .../precisePlatformsHmpp/settings.gradle.kts | 9 ++ .../build.gradle.kts | 71 ++++++++++++ .../gradle.properties | 1 + .../settings.gradle.kts | 11 ++ .../submodule/build.gradle.kts | 18 +++ 16 files changed, 409 insertions(+), 87 deletions(-) create mode 100644 idea/kotlin-gradle-tooling/src/ExperimentalGradleToolingApi.kt create mode 100644 idea/kotlin-gradle-tooling/src/KotlinSourceSetContainer.kt create mode 100644 idea/testData/gradle/precisePlatformsHmpp/build.gradle.kts create mode 100644 idea/testData/gradle/precisePlatformsHmpp/gradle.properties create mode 100644 idea/testData/gradle/precisePlatformsHmpp/settings.gradle.kts create mode 100644 idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/build.gradle.kts create mode 100644 idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/gradle.properties create mode 100644 idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/settings.gradle.kts create mode 100644 idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/submodule/build.gradle.kts diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt index 58c282ef311..04f01afd9f4 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt @@ -41,7 +41,7 @@ class KotlinSourceSetInfo @PropertyMapping("kotlinModule") constructor(val kotli @Deprecated("Returns only single TargetPlatform", ReplaceWith("actualPlatforms.actualPlatforms"), DeprecationLevel.ERROR) val platform: KotlinPlatform - get() = actualPlatforms.getSinglePlatform() + get() = actualPlatforms.platforms.singleOrNull() ?: KotlinPlatform.COMMON @Transient var defaultCompilerArguments: CommonCompilerArguments? = null diff --git a/idea/kotlin-gradle-tooling/src/ExperimentalGradleToolingApi.kt b/idea/kotlin-gradle-tooling/src/ExperimentalGradleToolingApi.kt new file mode 100644 index 00000000000..ea5cc084b9f --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/ExperimentalGradleToolingApi.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2021 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 + +@RequiresOptIn(level = RequiresOptIn.Level.WARNING, message = "This API might change in the future") +annotation class ExperimentalGradleToolingApi diff --git a/idea/kotlin-gradle-tooling/src/KotlinImportingDiagnostic.kt b/idea/kotlin-gradle-tooling/src/KotlinImportingDiagnostic.kt index 7eff2987dc6..50294feabf0 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinImportingDiagnostic.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinImportingDiagnostic.kt @@ -21,5 +21,5 @@ interface KotlinSourceSetImportingDiagnostic : KotlinImportingDiagnostic { data class OrphanSourceSetsImportingDiagnostic(override val kotlinSourceSet: KotlinSourceSet) : KotlinSourceSetImportingDiagnostic { override fun deepCopy(cache: MutableMap): OrphanSourceSetsImportingDiagnostic = (cache[kotlinSourceSet] as? KotlinSourceSet)?.let { OrphanSourceSetsImportingDiagnostic(it) } - ?: OrphanSourceSetsImportingDiagnostic(KotlinSourceSetImpl(kotlinSourceSet, cache).apply { cache[kotlinSourceSet] = this }) + ?: OrphanSourceSetsImportingDiagnostic(KotlinSourceSetImpl(kotlinSourceSet).apply { cache[kotlinSourceSet] = this }) } \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt index 16c48041aae..53df1b8bf53 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt @@ -3,6 +3,8 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ +@file:Suppress("DeprecatedCallableAddReplaceWith") + package org.jetbrains.kotlin.gradle import org.jetbrains.plugins.gradle.model.ExternalDependency @@ -53,12 +55,43 @@ interface KotlinSourceSet : KotlinModule { val languageSettings: KotlinLanguageSettings val sourceDirs: Set val resourceDirs: Set - val dependsOnSourceSets: Set val actualPlatforms: KotlinPlatformContainer - @Deprecated("Returns single target platform", ReplaceWith("actualPlatforms.actualPlatforms"), DeprecationLevel.ERROR) + + /** + * All source sets that this source set explicitly declared a 'dependsOn' relation to + */ + val declaredDependsOnSourceSets: Set + + /** + * The whole transitive closure of source sets this source set depends on. + * ([declaredDependsOnSourceSets] and their dependencies recursively) + */ + @Suppress("DEPRECATION") + @Deprecated( + "This property might be misleading. " + + "Replace with 'KotlinSourceSetContainer.resolveAllDependsOnSourceSets' to make intention of " + + "receiving the full transitive closure explicit", + level = DeprecationLevel.ERROR + ) + val dependsOnSourceSets: Set + get() = allDependsOnSourceSets + + /** + * The whole transitive closure of source sets this source set depends on. + * ([declaredDependsOnSourceSets] and their dependencies recursively) + */ + @Deprecated( + "This set of source sets might be inconsistent with any KotlinSourceSetContainer different to the one used to build this instance" + + "Replace with 'KotlinSourceSetContainer.resolveAllDependsOnSourceSets' to get consistent resolution", + level = DeprecationLevel.WARNING + ) + val allDependsOnSourceSets: Set + + + @Deprecated("Returns single target platform. Use actualPlatforms instead", level = DeprecationLevel.ERROR) val platform: KotlinPlatform - get() = actualPlatforms.getSinglePlatform() + get() = actualPlatforms.platforms.singleOrNull() ?: KotlinPlatform.COMMON companion object { @@ -98,6 +131,11 @@ interface KotlinNativeCompilationExtensions : Serializable { } interface KotlinCompilation : KotlinModule { + + @Deprecated("Use allSourceSets or declaredSourceSets instead") + val sourceSets: Collection + get() = declaredSourceSets + /** * All source sets participated in this compilation, including those available * via dependsOn. @@ -105,14 +143,14 @@ interface KotlinCompilation : KotlinModule { val allSourceSets: Collection /** - * Only default source sets of this compilation, i.e. those which are included + * Only directly declared source sets of this compilation, i.e. those which are included * into compilations directly. * * Usually, those are automatically created source sets for automatically created * compilations (like jvmMain for JVM compilations) or manually included source sets * (like 'jvm().compilations["main"].source(mySourceSet)' ) */ - val defaultSourceSets: Collection + val declaredSourceSets: Collection val output: KotlinCompilationOutput val arguments: KotlinCompilationArguments @@ -140,15 +178,50 @@ enum class KotlinPlatform(val id: String) { } } -interface KotlinPlatformContainer : Serializable { +interface KotlinPlatformContainer : Serializable, Iterable { + /** + * Distinct collection of Platforms. + * Keeping 'Collection' as type for binary compatibility + */ val platforms: Collection val arePlatformsInitialized: Boolean + @Deprecated( + "Ambiguous semantics of 'supports' for COMMON or (ANDROID/JVM) platforms. Use 'platforms' directly to express clear intention", + level = DeprecationLevel.ERROR + ) fun supports(simplePlatform: KotlinPlatform): Boolean - fun addSimplePlatforms(platforms: Collection) - + @Deprecated( + "Unclear semantics: Use 'platforms' directly to express intention", + level = DeprecationLevel.ERROR + ) fun getSinglePlatform() = platforms.singleOrNull() ?: KotlinPlatform.COMMON + + @Deprecated( + "Unclear semantics: Use 'pushPlatform' instead", + ReplaceWith("pushPlatform"), + level = DeprecationLevel.ERROR + ) + fun addSimplePlatforms(platforms: Collection) = pushPlatforms(platforms) + + /** + * Adds the given [platforms] to this container. + * Note: If any of the pushed [platforms] is common, then this container will drop all non-common platforms and subsequent invocations + * to this function will have no further effect. + */ + fun pushPlatforms(platforms: Iterable) + + /** + * @see pushPlatforms + */ + fun pushPlatforms(vararg platform: KotlinPlatform) { + pushPlatforms(platform.toList()) + } + + override fun iterator(): Iterator { + return platforms.toSet().iterator() + } } @@ -190,14 +263,19 @@ interface ExtraFeatures : Serializable { val isNativeDependencyPropagationEnabled: Boolean } -interface KotlinMPPGradleModel : Serializable { +interface KotlinMPPGradleModel : KotlinSourceSetContainer, Serializable { val dependencyMap: Map - val sourceSets: Map val targets: Collection val extraFeatures: ExtraFeatures val kotlinNativeHome: String val kotlinImportingDiagnostics: KotlinImportingDiagnosticsContainer + @Deprecated("Use 'sourceSetsByName' instead", ReplaceWith("sourceSetsByName"), DeprecationLevel.ERROR) + val sourceSets: Map + get() = sourceSetsByName + + override val sourceSetsByName: Map + companion object { const val NO_KOTLIN_NATIVE_HOME = "" } diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index e42aca77d21..84559d2d11d 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -45,46 +45,46 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } override fun buildAll(modelName: String, project: Project): Any? { - val projectTargets = project.getTargets() ?: return null - val dependencyResolver = DependencyResolverImpl( - project, - false, - false, - true, - SourceSetCachedFinder(project) - ) - val dependencyMapper = KotlinDependencyMapper() - val importingContext = MultiplatformModelImportingContextImpl(project) + try { + val projectTargets = project.getTargets() ?: return null + val dependencyResolver = DependencyResolverImpl(project, false, true, SourceSetCachedFinder(project)) + val dependencyMapper = KotlinDependencyMapper() + val importingContext = MultiplatformModelImportingContextImpl(project) - importingContext.initializeSourceSets(buildSourceSets(importingContext, dependencyResolver, dependencyMapper) ?: return null) + importingContext.initializeSourceSets(buildSourceSets(importingContext, dependencyResolver, dependencyMapper) ?: return null) - val targets = buildTargets(importingContext, projectTargets, dependencyResolver, dependencyMapper) - importingContext.initializeTargets(targets) - importingContext.initializeCompilations(targets.flatMap { it.compilations }) + val targets = buildTargets(importingContext, projectTargets, dependencyResolver, dependencyMapper) + importingContext.initializeTargets(targets) + importingContext.initializeCompilations(targets.flatMap { it.compilations }) - computeSourceSetsDeferredInfo(importingContext) + computeSourceSetsDeferredInfo(importingContext) - val coroutinesState = getCoroutinesState(project) - val kotlinNativeHome = KotlinNativeHomeEvaluator.getKotlinNativeHome(project) ?: NO_KOTLIN_NATIVE_HOME - return KotlinMPPGradleModelImpl( - filterOrphanSourceSets(importingContext), - importingContext.targets, - ExtraFeaturesImpl( - coroutinesState, - importingContext.getProperty(IS_HMPP_ENABLED), - importingContext.getProperty(ENABLE_NATIVE_DEPENDENCY_PROPAGATION) - ), - kotlinNativeHome, - dependencyMapper.toDependencyMap() - ).apply { - kotlinImportingDiagnostics += collectDiagnostics(importingContext) + val coroutinesState = getCoroutinesState(project) + val kotlinNativeHome = KotlinNativeHomeEvaluator.getKotlinNativeHome(project) ?: NO_KOTLIN_NATIVE_HOME + val model = KotlinMPPGradleModelImpl( + sourceSetsByName = filterOrphanSourceSets(importingContext), + targets = importingContext.targets, + extraFeatures = ExtraFeaturesImpl( + coroutinesState = coroutinesState, + isHMPPEnabled = importingContext.getProperty(IS_HMPP_ENABLED), + isNativeDependencyPropagationEnabled = importingContext.getProperty(ENABLE_NATIVE_DEPENDENCY_PROPAGATION) + ), + kotlinNativeHome = kotlinNativeHome, + dependencyMap = dependencyMapper.toDependencyMap() + ).apply { + kotlinImportingDiagnostics += collectDiagnostics(importingContext) + } + return model + } catch (throwable: Throwable) { + project.logger.error("Failed building KotlinMPPGradleModel", throwable) + throw throwable } } private fun filterOrphanSourceSets( importingContext: MultiplatformModelImportingContext ): Map { - if (importingContext.getProperty(IMPORT_ORPHAN_SOURCE_SETS)) return importingContext.sourceSetsByNames + if (importingContext.getProperty(IMPORT_ORPHAN_SOURCE_SETS)) return importingContext.sourceSetsByName val (orphanSourceSets, nonOrphanSourceSets) = importingContext.sourceSets.partition { importingContext.isOrphanSourceSet(it) } @@ -118,17 +118,16 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val allSourceSetsProtosByNames = sourceSets.mapNotNull { buildSourceSet(it, dependencyResolver, importingContext.project, dependencyMapper, androidDeps) }.associateBy { it.name } - val dependsOnCache = HashMap>() // Some performance optimisation: do not build metadata dependencies if source set is not common return if (importingContext.getProperty(BUILD_METADATA_DEPENDENCIES)) { allSourceSetsProtosByNames.mapValues { (_, proto) -> - proto.buildKotlinSourceSetImpl(true, allSourceSetsProtosByNames, dependsOnCache) + proto.buildKotlinSourceSetImpl(true, allSourceSetsProtosByNames) } } else { val unactualizedSourceSets = allSourceSetsProtosByNames.values.flatMap { it.dependsOnSourceSets }.distinct() allSourceSetsProtosByNames.mapValues { (name, proto) -> - proto.buildKotlinSourceSetImpl(unactualizedSourceSets.contains(name), allSourceSetsProtosByNames, dependsOnCache) + proto.buildKotlinSourceSetImpl(unactualizedSourceSets.contains(name), allSourceSetsProtosByNames) } } } @@ -306,8 +305,9 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { ): KotlinTarget? { val targetClass = gradleTarget.javaClass - val metadataTargetClass = targetClass.classLoader.loadClass("org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget") - if (metadataTargetClass.isInstance(gradleTarget)) return null + /* Loading class safely to still support Kotlin Gradle Plugin 1.3.30 */ + val metadataTargetClass = targetClass.classLoader.loadClassOrNull("org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget") + if (metadataTargetClass?.isInstance(gradleTarget) == true) return null val getPlatformType = targetClass.getMethodOrNull("getPlatformType") ?: return null val getDisambiguationClassifier = targetClass.getMethodOrNull("getDisambiguationClassifier") ?: return null @@ -494,23 +494,20 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } val nativeExtensions = konanTarget?.let(::KotlinNativeCompilationExtensionsImpl) - val allSourceSets = if (platform != KotlinPlatform.ANDROID) { - kotlinSourceSets - } else { - kotlinSourceSets.flatMap { it.dependsOnSourceSets }.mapNotNull { importingContext.sourceSetByName(it) } - .union(kotlinSourceSets) - } + val allSourceSets = kotlinSourceSets + .flatMap { sourceSet -> importingContext.resolveAllDependsOnSourceSets(sourceSet) } + .union(kotlinSourceSets) return KotlinCompilationImpl( - gradleCompilation.name, - allSourceSets, - kotlinSourceSets, - dependencies.map { dependencyMapper.getId(it) }.distinct().toTypedArray(), - output, - arguments, - dependencyClasspath.toTypedArray(), - kotlinTaskProperties, - nativeExtensions + name = gradleCompilation.name, + allSourceSets = allSourceSets, + declaredSourceSets = if (platform == KotlinPlatform.ANDROID) allSourceSets else kotlinSourceSets, + dependencies = dependencies.map { dependencyMapper.getId(it) }.distinct().toTypedArray(), + output = output, + arguments = arguments, + dependencyClasspath = dependencyClasspath.toTypedArray(), + kotlinTaskProperties = kotlinTaskProperties, + nativeExtensions = nativeExtensions ) } @@ -737,24 +734,24 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { // Explicitly set platform of orphan source-sets to only used platforms, not all supported platforms // Otherwise, the tooling might be upset after trying to provide some support for a target which actually // doesn't exist in this project (e.g. after trying to draw gutters, while test tasks do not exist) - sourceSet.actualPlatforms.addSimplePlatforms(projectPlatforms) + sourceSet.actualPlatforms.pushPlatforms(projectPlatforms) return } if (shouldCoerceToCommon(sourceSet)) { - sourceSet.actualPlatforms.addSimplePlatforms(listOf(KotlinPlatform.COMMON)) + sourceSet.actualPlatforms.pushPlatforms(KotlinPlatform.COMMON) return } - if (!getProperty(IS_HMPP_ENABLED) && !isDefaultSourceSet(sourceSet)) { + if (!getProperty(IS_HMPP_ENABLED) && !isDeclaredSourceSet(sourceSet)) { // intermediate source sets should be common if HMPP is disabled - sourceSet.actualPlatforms.addSimplePlatforms(listOf(KotlinPlatform.COMMON)) + sourceSet.actualPlatforms.pushPlatforms(KotlinPlatform.COMMON) return } compilationsBySourceSet(sourceSet)?.let { compilations -> val platforms = compilations.map { it.platform } - sourceSet.actualPlatforms.addSimplePlatforms(platforms) + sourceSet.actualPlatforms.pushPlatforms(platforms) } } diff --git a/idea/kotlin-gradle-tooling/src/KotlinSourceSetContainer.kt b/idea/kotlin-gradle-tooling/src/KotlinSourceSetContainer.kt new file mode 100644 index 00000000000..4480a6885ab --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/KotlinSourceSetContainer.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2021 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 + +interface KotlinSourceSetContainer { + val sourceSetsByName: Map +} + +val KotlinSourceSetContainer.sourceSets: List get() = sourceSetsByName.values.toList() + +fun KotlinSourceSetContainer.resolveDeclaredDependsOnSourceSets(sourceSet: KotlinSourceSet): Set { + return sourceSet.declaredDependsOnSourceSets.mapNotNull { name -> sourceSetsByName[name] }.toSet() +} + +fun KotlinSourceSetContainer.resolveAllDependsOnSourceSets(sourceSet: KotlinSourceSet): Set { + /* Fast path */ + if (sourceSet.declaredDependsOnSourceSets.isEmpty()) return emptySet() + + /* Aggregating set containing all currently resolved source sets */ + val resolvedSourceSets = mutableSetOf() + + /* Queue of source set names that shall be resolved */ + val declaredDependsOnSourceSetsQueue = ArrayDeque() + + declaredDependsOnSourceSetsQueue.addAll(sourceSet.declaredDependsOnSourceSets) + while (declaredDependsOnSourceSetsQueue.isNotEmpty()) { + val sourceSetName = declaredDependsOnSourceSetsQueue.removeFirst() + val resolvedSourceSet = sourceSetsByName[sourceSetName] + if (resolvedSourceSet != null) { + if (resolvedSourceSets.add(resolvedSourceSet)) { + declaredDependsOnSourceSetsQueue.addAll(resolvedSourceSet.declaredDependsOnSourceSets) + } + } + } + + return resolvedSourceSets +} + +fun KotlinSourceSetContainer.isDependsOn(from: KotlinSourceSet, to: KotlinSourceSet): Boolean { + return to in resolveAllDependsOnSourceSets(from) +} + +fun KotlinSourceSet.isDependsOn(model: KotlinSourceSetContainer, sourceSet: KotlinSourceSet): Boolean { + return model.isDependsOn(from = this, to = sourceSet) +} diff --git a/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingChecker.kt b/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingChecker.kt index 528ae745103..7795aa06d58 100644 --- a/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingChecker.kt +++ b/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingChecker.kt @@ -15,7 +15,7 @@ internal object OrphanSourceSetImportingChecker : MultiplatformModelImportingChe reportTo: KotlinImportingDiagnosticsContainer, context: MultiplatformModelImportingContext ) { - model.sourceSets.values.filter { context.isOrphanSourceSet(it) } + model.sourceSetsByName.values.filter { context.isOrphanSourceSet(it) } .mapTo(reportTo) { OrphanSourceSetsImportingDiagnostic(it) } } } \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingContext.kt b/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingContext.kt index e2b31f8e8e9..5487a69701b 100644 --- a/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingContext.kt +++ b/idea/kotlin-gradle-tooling/src/MultiplatformModelImportingContext.kt @@ -8,9 +8,8 @@ package org.jetbrains.kotlin.gradle import org.gradle.api.Project import org.gradle.api.logging.Logging -private val logger = Logging.getLogger(KotlinMPPGradleModelBuilder::class.java) -internal interface MultiplatformModelImportingContext { +internal interface MultiplatformModelImportingContext: KotlinSourceSetContainer { val project: Project val targets: Collection @@ -20,8 +19,8 @@ internal interface MultiplatformModelImportingContext { * All source sets in a project, including those that are created but not included into any compilations * (so-called "orphan" source sets). Use [isOrphanSourceSet] to get only compiled source sets */ - val sourceSets: Collection - val sourceSetsByNames: Map + val sourceSets: Collection get() = sourceSetsByName.values + override val sourceSetsByName: Map /** * Platforms, which are actually used in this project (i.e. platforms, for which targets has been created) @@ -41,12 +40,12 @@ internal interface MultiplatformModelImportingContext { fun isOrphanSourceSet(sourceSet: KotlinSourceSet): Boolean = compilationsBySourceSet(sourceSet) == null /** - * "Default" source-set is a source-set which is included into compilation directly, rather + * "Declared" source-set is a source-set which is included into compilation directly, rather * through closure over dependsOn-relation. * - * See also KDoc for [KotlinCompilation.defaultSourceSets] + * See also KDoc for [KotlinCompilation.declaredSourceSets] */ - fun isDefaultSourceSet(sourceSet: KotlinSourceSet): Boolean + fun isDeclaredSourceSet(sourceSet: KotlinSourceSet): Boolean } internal fun MultiplatformModelImportingContext.getProperty(property: GradleImportProperties): Boolean = project.getProperty(property) @@ -75,16 +74,14 @@ internal enum class GradleImportProperties(val id: String, val defaultValue: Boo internal class MultiplatformModelImportingContextImpl(override val project: Project) : MultiplatformModelImportingContext { /** see [initializeSourceSets] */ - override lateinit var sourceSetsByNames: Map + override lateinit var sourceSetsByName: Map private set - override val sourceSets: Collection - get() = sourceSetsByNames.values /** see [initializeCompilations] */ override lateinit var compilations: Collection private set private lateinit var sourceSetToParticipatedCompilations: Map> - private lateinit var allDefaultSourceSets: Set + private lateinit var allDeclaredSourceSets: Set /** see [initializeTargets] */ @@ -95,12 +92,13 @@ internal class MultiplatformModelImportingContextImpl(override val project: Proj private set internal fun initializeSourceSets(sourceSetsByNames: Map) { - require(!this::sourceSetsByNames.isInitialized) { - "Attempt to re-initialize source sets for $this. Previous value: ${this.sourceSetsByNames}" + require(!this::sourceSetsByName.isInitialized) { + "Attempt to re-initialize source sets for $this. Previous value: ${this.sourceSetsByName}" } - this.sourceSetsByNames = sourceSetsByNames + this.sourceSetsByName = sourceSetsByNames } + @OptIn(ExperimentalGradleToolingApi::class) internal fun initializeCompilations(compilations: Collection) { require(!this::compilations.isInitialized) { "Attempt to re-initialize compilations for $this. Previous value: ${this.compilations}" } this.compilations = compilations @@ -111,7 +109,7 @@ internal class MultiplatformModelImportingContextImpl(override val project: Proj for (compilation in target.compilations) { for (sourceSet in compilation.allSourceSets) { sourceSetToCompilations.getOrPut(sourceSet) { LinkedHashSet() } += compilation - sourceSet.dependsOnSourceSets.mapNotNull { sourceSetByName(it) }.forEach { + resolveAllDependsOnSourceSets(sourceSet).forEach { sourceSetToCompilations.getOrPut(it) { LinkedHashSet() } += compilation } } @@ -120,7 +118,7 @@ internal class MultiplatformModelImportingContextImpl(override val project: Proj this.sourceSetToParticipatedCompilations = sourceSetToCompilations - this.allDefaultSourceSets = compilations.flatMapTo(mutableSetOf()) { it.defaultSourceSets } + this.allDeclaredSourceSets = compilations.flatMapTo(mutableSetOf()) { it.declaredSourceSets } } internal fun initializeTargets(targets: Collection) { @@ -132,10 +130,10 @@ internal class MultiplatformModelImportingContextImpl(override val project: Proj // overload for small optimization override fun isOrphanSourceSet(sourceSet: KotlinSourceSet): Boolean = sourceSet !in sourceSetToParticipatedCompilations.keys - override fun isDefaultSourceSet(sourceSet: KotlinSourceSet): Boolean = sourceSet in allDefaultSourceSets + override fun isDeclaredSourceSet(sourceSet: KotlinSourceSet): Boolean = sourceSet in allDeclaredSourceSets override fun compilationsBySourceSet(sourceSet: KotlinSourceSet): Collection? = sourceSetToParticipatedCompilations[sourceSet] - override fun sourceSetByName(name: String): KotlinSourceSet? = sourceSetsByNames[name] + override fun sourceSetByName(name: String): KotlinSourceSet? = sourceSetsByName[name] } diff --git a/idea/kotlin-gradle-tooling/src/utils.kt b/idea/kotlin-gradle-tooling/src/utils.kt index 9e98c6e1c93..d9d3cf8e1cf 100644 --- a/idea/kotlin-gradle-tooling/src/utils.kt +++ b/idea/kotlin-gradle-tooling/src/utils.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.gradle +import org.gradle.api.Named + fun Class<*>.getMethodOrNull(name: String, vararg parameterTypes: Class<*>) = try { getMethod(name, *parameterTypes) @@ -19,5 +21,13 @@ fun Class<*>.getDeclaredMethodOrNull(name: String, vararg parameterTypes: Class< null } +fun ClassLoader.loadClassOrNull(name: String): Class<*>? { + return try { + loadClass(name) + } catch (e: Exception) { + return null + } +} + fun compilationFullName(simpleName: String, classifier: String?) = - if (classifier != null) classifier + simpleName.capitalize() else simpleName \ No newline at end of file + if (classifier != null) classifier + simpleName.capitalize() else simpleName diff --git a/idea/testData/gradle/precisePlatformsHmpp/build.gradle.kts b/idea/testData/gradle/precisePlatformsHmpp/build.gradle.kts new file mode 100644 index 00000000000..02d7e599cad --- /dev/null +++ b/idea/testData/gradle/precisePlatformsHmpp/build.gradle.kts @@ -0,0 +1,71 @@ +/** + * commonMain---- + * / \ + * jvmAndLinuxMain \ + * / \ \ + * jvm linuxX64 macosX64 + * + * (the tests structure is the same) + */ + +buildscript { + repositories { + {{kts_kotlin_plugin_repositories}} + } +} + +repositories { + {{kts_kotlin_plugin_repositories}} +} + +plugins { + kotlin("multiplatform").version("{{kotlin_plugin_version}}") +} + +group = "project" +version = "1.0" + +kotlin { + jvm() + linuxX64() + macosX64() + + sourceSets { + val commonMain by getting { + } + + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + + val jvmAndLinuxMain by creating { + dependsOn(commonMain) + } + + val jvmAndLinuxTest by creating { + dependsOn(commonTest) + } + + val jvmMain by getting { + dependsOn(jvmAndLinuxMain) + } + + val jvmTest by getting { + dependsOn(jvmAndLinuxTest) + dependencies { + implementation(kotlin("test-junit")) + } + } + + val linuxX64Main by getting { + dependsOn(jvmAndLinuxMain) + } + + val linuxX64Test by getting { + dependsOn(jvmAndLinuxTest) + } + } +} \ No newline at end of file diff --git a/idea/testData/gradle/precisePlatformsHmpp/gradle.properties b/idea/testData/gradle/precisePlatformsHmpp/gradle.properties new file mode 100644 index 00000000000..5947cb440c4 --- /dev/null +++ b/idea/testData/gradle/precisePlatformsHmpp/gradle.properties @@ -0,0 +1 @@ +kotlin.mpp.enableGranularSourceSetsMetadata=true diff --git a/idea/testData/gradle/precisePlatformsHmpp/settings.gradle.kts b/idea/testData/gradle/precisePlatformsHmpp/settings.gradle.kts new file mode 100644 index 00000000000..176c45caae2 --- /dev/null +++ b/idea/testData/gradle/precisePlatformsHmpp/settings.gradle.kts @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + {{kts_kotlin_plugin_repositories}} + } +} + +rootProject.name = "my-app" + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/build.gradle.kts b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/build.gradle.kts new file mode 100644 index 00000000000..02d7e599cad --- /dev/null +++ b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/build.gradle.kts @@ -0,0 +1,71 @@ +/** + * commonMain---- + * / \ + * jvmAndLinuxMain \ + * / \ \ + * jvm linuxX64 macosX64 + * + * (the tests structure is the same) + */ + +buildscript { + repositories { + {{kts_kotlin_plugin_repositories}} + } +} + +repositories { + {{kts_kotlin_plugin_repositories}} +} + +plugins { + kotlin("multiplatform").version("{{kotlin_plugin_version}}") +} + +group = "project" +version = "1.0" + +kotlin { + jvm() + linuxX64() + macosX64() + + sourceSets { + val commonMain by getting { + } + + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + + val jvmAndLinuxMain by creating { + dependsOn(commonMain) + } + + val jvmAndLinuxTest by creating { + dependsOn(commonTest) + } + + val jvmMain by getting { + dependsOn(jvmAndLinuxMain) + } + + val jvmTest by getting { + dependsOn(jvmAndLinuxTest) + dependencies { + implementation(kotlin("test-junit")) + } + } + + val linuxX64Main by getting { + dependsOn(jvmAndLinuxMain) + } + + val linuxX64Test by getting { + dependsOn(jvmAndLinuxTest) + } + } +} \ No newline at end of file diff --git a/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/gradle.properties b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/gradle.properties new file mode 100644 index 00000000000..5947cb440c4 --- /dev/null +++ b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/gradle.properties @@ -0,0 +1 @@ +kotlin.mpp.enableGranularSourceSetsMetadata=true diff --git a/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/settings.gradle.kts b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/settings.gradle.kts new file mode 100644 index 00000000000..4b74ba26c9f --- /dev/null +++ b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/settings.gradle.kts @@ -0,0 +1,11 @@ +pluginManagement { + repositories { + {{kts_kotlin_plugin_repositories}} + } +} + +rootProject.name = "my-app" + +enableFeaturePreview("GRADLE_METADATA") + +include(":submodule") \ No newline at end of file diff --git a/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/submodule/build.gradle.kts b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/submodule/build.gradle.kts new file mode 100644 index 00000000000..d828b0d967c --- /dev/null +++ b/idea/testData/gradle/precisePlatformsWithUnrelatedModuleHmpp/submodule/build.gradle.kts @@ -0,0 +1,18 @@ +buildscript { + repositories { + {{kts_kotlin_plugin_repositories}} + } +} + +repositories { + {{kts_kotlin_plugin_repositories}} +} + +plugins { + kotlin("multiplatform") +} + +kotlin { + js() + jvm() +} \ No newline at end of file