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 c86bba9f3d1..99f255cd5a1 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 @@ -354,7 +354,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { sourceSetMap, artifactsMap, dataNode, - preprocessDependencies(compilation.dependencies, ideProject), + preprocessDependencies(compilation.dependencies), ideProject ) for (sourceSet in compilation.sourceSets) { @@ -432,7 +432,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { sourceSetMap, artifactsMap, fromDataNode, - preprocessDependencies(mergedDependencies, ideProject), + preprocessDependencies(mergedDependencies), ideProject ) } @@ -445,49 +445,12 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { else -> null } - private fun preprocessDependencies( - dependencies: Set, - ideProject: DataNode - ): List { + private fun preprocessDependencies(dependencies: Set): List { return dependencies .groupBy { it.id } .mapValues { it.value.firstOrNull { it.scope == "COMPILE" } ?: it.value.lastOrNull() } .values .filterNotNull() - .adjustDependencies(ideProject) - } - - private fun List.adjustDependencies(ideProject: DataNode): List { - val result = LinkedHashSet(this) - for (dependency in this) { - if (dependency !is ExternalProjectDependency) continue - val projectPath = dependency.projectPath - val classifier = dependency.classifier ?: continue - val targetModuleNode = ExternalSystemApiUtil.findFirstRecursively(ideProject) { - (it.data as? ModuleData)?.id == projectPath - } ?: continue - val candidateDataNodes = ExternalSystemApiUtil.getChildren(targetModuleNode, GradleSourceSetData.KEY) - if (classifier != KotlinTarget.METADATA_TARGET_NAME) { - val compilationName = candidateDataNodes - .mapNotNull { sourceSetNode -> - sourceSetNode.kotlinSourceSet?.kotlinModule as? KotlinCompilation - } - .firstOrNull { - it.name == KotlinCompilation.MAIN_COMPILATION_NAME && it.target.disambiguationClassifier == classifier - } - ?.fullName() ?: continue - result += DefaultExternalProjectDependency(dependency).also { - it.configurationName = compilationName - } - } - - result += DefaultExternalProjectDependency(dependency).also { - it.configurationName = KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME - } - - result -= dependency - } - return result.toList() } private fun addDependency(fromModule: DataNode<*>, toModule: DataNode<*>) { diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformProjectImportingTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformProjectImportingTest.kt index 8223a9ab710..bba986695a5 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformProjectImportingTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformProjectImportingTest.kt @@ -602,6 +602,174 @@ class NewMultiplatformProjectImportingTest : GradleImportingTestCase() { } } + @Test + fun testNestedDependencies() { + createProjectSubFile( + "settings.gradle", + "include 'aaa', 'bbb', 'ccc'" + ) + createProjectSubFile( + "build.gradle", + """ + buildscript { + repositories { + mavenLocal() + jcenter() + maven { url 'https://dl.bintray.com/kotlin/kotlin-dev' } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + } + } + + allprojects { + repositories { + mavenLocal() + jcenter() + maven { url 'https://dl.bintray.com/kotlin/kotlin-dev' } + } + } + """.trimIndent() + ) + createProjectSubFile( + "aaa/build.gradle", + """ + apply plugin: 'kotlin-multiplatform' + + kotlin { + sourceSets { + commonMain { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' + api project(':bbb') + } + } + } + targets { + fromPreset(presets.jvm, 'jvm') + } + } + """.trimIndent() + ) + createProjectSubFile( + "bbb/build.gradle", + """ + apply plugin: 'kotlin-multiplatform' + + kotlin { + sourceSets { + commonMain { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' + api project(':ccc') + } + } + } + targets { + fromPreset(presets.jvm, 'jvm') + } + } + """.trimIndent() + ) + createProjectSubFile( + "ccc/build.gradle", + """ + apply plugin: 'kotlin-multiplatform' + + kotlin { + sourceSets { + commonMain { + dependencies { + implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' + } + } + } + targets { + fromPreset(presets.jvm, 'jvm') + } + } + """.trimIndent() + ) + + importProject() + + checkProjectStructure(exhaustiveSourceSourceRootList = false) { + module("project") + module("aaa") + module("aaa_commonMain") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.COMPILE) + moduleDependency("bbb_commonMain", DependencyScope.COMPILE) + moduleDependency("ccc_commonMain", DependencyScope.COMPILE) + } + module("aaa_commonTest") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.TEST) + moduleDependency("aaa_commonMain", DependencyScope.TEST) + moduleDependency("bbb_commonMain", DependencyScope.TEST) + moduleDependency("ccc_commonMain", DependencyScope.TEST) + } + module("aaa_jvmMain") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.COMPILE) + moduleDependency("aaa_commonMain", DependencyScope.COMPILE) + moduleDependency("bbb_commonMain", DependencyScope.COMPILE) + moduleDependency("bbb_jvmMain", DependencyScope.COMPILE) + moduleDependency("ccc_commonMain", DependencyScope.COMPILE) + moduleDependency("ccc_jvmMain", DependencyScope.COMPILE) + } + module("aaa_jvmTest") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.TEST) + moduleDependency("aaa_commonMain", DependencyScope.TEST) + moduleDependency("aaa_commonTest", DependencyScope.TEST) + moduleDependency("aaa_jvmMain", DependencyScope.TEST) + moduleDependency("bbb_commonMain", DependencyScope.TEST) + moduleDependency("bbb_jvmMain", DependencyScope.TEST) + moduleDependency("ccc_commonMain", DependencyScope.TEST) + moduleDependency("ccc_jvmMain", DependencyScope.TEST) + } + module("bbb") + module("bbb_commonMain") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.COMPILE) + moduleDependency("ccc_commonMain", DependencyScope.COMPILE) + } + module("bbb_commonTest") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.TEST) + moduleDependency("bbb_commonMain", DependencyScope.TEST) + moduleDependency("ccc_commonMain", DependencyScope.TEST) + } + module("bbb_jvmMain") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.COMPILE) + moduleDependency("bbb_commonMain", DependencyScope.COMPILE) + moduleDependency("ccc_commonMain", DependencyScope.COMPILE) + moduleDependency("ccc_jvmMain", DependencyScope.COMPILE) + } + module("bbb_jvmTest") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.TEST) + moduleDependency("bbb_commonMain", DependencyScope.TEST) + moduleDependency("bbb_commonTest", DependencyScope.TEST) + moduleDependency("bbb_jvmMain", DependencyScope.TEST) + moduleDependency("ccc_commonMain", DependencyScope.TEST) + moduleDependency("ccc_jvmMain", DependencyScope.TEST) + } + module("ccc") + module("ccc_commonMain") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.COMPILE) + } + module("ccc_commonTest") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.TEST) + moduleDependency("ccc_commonMain", DependencyScope.TEST) + } + module("ccc_jvmMain") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.COMPILE) + moduleDependency("ccc_commonMain", DependencyScope.COMPILE) + } + module("ccc_jvmTest") { + libraryDependency("Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0-rc-146", DependencyScope.TEST) + moduleDependency("ccc_commonMain", DependencyScope.TEST) + moduleDependency("ccc_commonTest", DependencyScope.TEST) + moduleDependency("ccc_jvmMain", DependencyScope.TEST) + } + } + } + private fun checkProjectStructure( exhaustiveModuleList: Boolean = true, exhaustiveSourceSourceRootList: Boolean = true, diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index 0fd7636d063..adf9874fc86 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -9,6 +9,7 @@ import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.file.FileCollection @@ -62,14 +63,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { .forEach { logger.warn(it) } } - private fun Project.getChildProjectByPath(path: String): Project? { - var project = this - for (name in path.split(":").asSequence().drop(1)) { - project = project.childProjects[name] ?: return null - } - return project - } - private fun getCoroutinesState(project: Project): String? { val kotlinExt = project.extensions.findByName("kotlin") ?: return null val getExperimental = kotlinExt.javaClass.getMethodOrNull("getExperimental") ?: return null @@ -139,45 +132,15 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val configurationName = getConfigurationName(dependencyHolder) as? String ?: return emptyList() val configuration = project.configurations.findByName(configurationName) ?: return emptyList() if (!configuration.isCanBeResolved) return emptyList() - val dependenciesByProjectPath = configuration - .resolvedConfiguration - .lenientConfiguration - .firstLevelModuleDependencies - .mapNotNull { dependency -> - val artifact = dependency.moduleArtifacts.firstOrNull { - it.id.componentIdentifier is ProjectComponentIdentifier - } ?: return@mapNotNull null - dependency to artifact - } - .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } - val resolvedDependencies = dependencyResolver.resolveDependencies(configuration) + + val dependencyAdjuster = DependencyAdjuster(configuration, scope, project) + + val resolvedDependencies = dependencyResolver + .resolveDependencies(configuration) .apply { forEach { (it as? AbstractExternalDependency)?.scope = scope } } - .map { dependency -> - if (dependency !is ExternalProjectDependency - || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION - ) return@map dependency - val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@map dependency - val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { - it.first.configuration - }.singleOrNull() ?: return@map dependency - val taskGetterName = when (scope) { - "COMPILE" -> "getApiElementsConfigurationName" - "RUNTIME" -> "getRuntimeElementsConfigurationName" - else -> return@map dependency - } - val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@map dependency - val gradleTarget = targets.firstOrNull { - val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false - getter(it) == artifactConfiguration - } ?: return@map dependency - val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String - ?: return@map dependency - DefaultExternalProjectDependency(dependency).apply { - this.classifier = classifier - } - } + .flatMap(dependencyAdjuster::adjustDependency) val singleDependencyFiles = resolvedDependencies.mapNotNullTo(LinkedHashSet()) { (it as? FileCollectionDependency)?.files?.singleOrNull() } @@ -199,13 +162,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { return project.getTargets()?.mapNotNull { buildTarget(it, sourceSetMap, dependencyResolver, project) } } - private fun Project.getTargets(): Collection? { - val kotlinExt = project.extensions.findByName("kotlin") ?: return null - val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null - @Suppress("UNCHECKED_CAST") - return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() - } - private fun buildTarget( gradleTarget: Named, sourceSetMap: Map, @@ -389,7 +345,83 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } + private class DependencyAdjuster( + private val configuration: Configuration, + private val scope: String, + private val project: Project + ) { + private val adjustmentMap = HashMap>() + + val dependenciesByProjectPath by lazy { + configuration + .resolvedConfiguration + .lenientConfiguration + .allModuleDependencies + .mapNotNull { dependency -> + val artifact = dependency.moduleArtifacts.firstOrNull { + it.id.componentIdentifier is ProjectComponentIdentifier + } ?: return@mapNotNull null + dependency to artifact + } + .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } + } + + private fun wrapDependency(dependency: ExternalProjectDependency, newConfigurationName: String): ExternalProjectDependency { + return DefaultExternalProjectDependency(dependency).apply { + this.configurationName = newConfigurationName + + val nestedDependencies = this.dependencies.flatMap(::adjustDependency) + this.dependencies.clear() + this.dependencies.addAll(nestedDependencies) + } + } + + fun adjustDependency(dependency: ExternalDependency): List { + return adjustmentMap.getOrPut(dependency) { + if (dependency !is ExternalProjectDependency + || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION + ) return@getOrPut listOf(dependency) + val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@getOrPut listOf(dependency) + val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { + it.first.configuration + }.singleOrNull() ?: return@getOrPut listOf(dependency) + val taskGetterName = when (scope) { + "COMPILE" -> "getApiElementsConfigurationName" + "RUNTIME" -> "getRuntimeElementsConfigurationName" + else -> return@getOrPut listOf(dependency) + } + val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@getOrPut listOf(dependency) + val gradleTarget = targets.firstOrNull { + val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false + getter(it) == artifactConfiguration + } ?: return@getOrPut listOf(dependency) + val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String + ?: return@getOrPut listOf(dependency) + val platformDependency = if (classifier != KotlinTarget.METADATA_TARGET_NAME) { + wrapDependency(dependency, compilationFullName(KotlinCompilation.MAIN_COMPILATION_NAME, classifier)) + } else null + val commonDependency = wrapDependency(dependency, KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + return if (platformDependency != null) listOf(platformDependency, commonDependency) else listOf(commonDependency) + } + } + } + companion object { private val logger = Logging.getLogger(KotlinMPPGradleModelBuilder::class.java) } +} + +private fun Project.getChildProjectByPath(path: String): Project? { + var project = this + for (name in path.split(":").asSequence().drop(1)) { + project = project.childProjects[name] ?: return null + } + return project +} + +private fun Project.getTargets(): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null + @Suppress("UNCHECKED_CAST") + return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() } \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.173 b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.173 index c430dcd3444..0a33a6dd690 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.173 +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.173 @@ -9,6 +9,7 @@ import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.file.FileCollection @@ -61,14 +62,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { .forEach { logger.warn(it) } } - private fun Project.getChildProjectByPath(path: String): Project? { - var project = this - for (name in path.split(":").asSequence().drop(1)) { - project = project.childProjects[name] ?: return null - } - return project - } - private fun getCoroutinesState(project: Project): String? { val kotlinExt = project.extensions.findByName("kotlin") ?: return null val getExperimental = kotlinExt.javaClass.getMethodOrNull("getExperimental") ?: return null @@ -138,45 +131,15 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val configurationName = getConfigurationName(dependencyHolder) as? String ?: return emptyList() val configuration = project.configurations.findByName(configurationName) ?: return emptyList() if (!configuration.isCanBeResolved) return emptyList() - val dependenciesByProjectPath = configuration - .resolvedConfiguration - .lenientConfiguration - .firstLevelModuleDependencies - .mapNotNull { dependency -> - val artifact = dependency.moduleArtifacts.firstOrNull { - it.id.componentIdentifier is ProjectComponentIdentifier - } ?: return@mapNotNull null - dependency to artifact - } - .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } - val resolvedDependencies = dependencyResolver.resolveDependencies(configuration) + + val dependencyAdjuster = DependencyAdjuster(configuration, scope, project) + + val resolvedDependencies = dependencyResolver + .resolveDependencies(configuration) .apply { forEach { (it as? AbstractExternalDependency)?.scope = scope } } - .map { dependency -> - if (dependency !is ExternalProjectDependency - || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION - ) return@map dependency - val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@map dependency - val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { - it.first.configuration - }.singleOrNull() ?: return@map dependency - val taskGetterName = when (scope) { - "COMPILE" -> "getApiElementsConfigurationName" - "RUNTIME" -> "getRuntimeElementsConfigurationName" - else -> return@map dependency - } - val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@map dependency - val gradleTarget = targets.firstOrNull { - val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false - getter(it) == artifactConfiguration - } ?: return@map dependency - val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String - ?: return@map dependency - DefaultExternalProjectDependency(dependency).apply { - this.classifier = classifier - } - } + .flatMap(dependencyAdjuster::adjustDependency) val singleDependencyFiles = resolvedDependencies.mapNotNullTo(LinkedHashSet()) { (it as? FileCollectionDependency)?.files?.singleOrNull() } @@ -198,13 +161,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { return project.getTargets()?.mapNotNull { buildTarget(it, sourceSetMap, dependencyResolver, project) } } - private fun Project.getTargets(): Collection? { - val kotlinExt = project.extensions.findByName("kotlin") ?: return null - val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null - @Suppress("UNCHECKED_CAST") - return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() - } - private fun buildTarget( gradleTarget: Named, sourceSetMap: Map, @@ -383,7 +339,83 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } + private class DependencyAdjuster( + private val configuration: Configuration, + private val scope: String, + private val project: Project + ) { + private val adjustmentMap = HashMap>() + + val dependenciesByProjectPath by lazy { + configuration + .resolvedConfiguration + .lenientConfiguration + .allModuleDependencies + .mapNotNull { dependency -> + val artifact = dependency.moduleArtifacts.firstOrNull { + it.id.componentIdentifier is ProjectComponentIdentifier + } ?: return@mapNotNull null + dependency to artifact + } + .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } + } + + private fun wrapDependency(dependency: ExternalProjectDependency, newConfigurationName: String): ExternalProjectDependency { + return DefaultExternalProjectDependency(dependency).apply { + this.configurationName = newConfigurationName + + val nestedDependencies = this.dependencies.flatMap(::adjustDependency) + this.dependencies.clear() + this.dependencies.addAll(nestedDependencies) + } + } + + fun adjustDependency(dependency: ExternalDependency): List { + return adjustmentMap.getOrPut(dependency) { + if (dependency !is ExternalProjectDependency + || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION + ) return@getOrPut listOf(dependency) + val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@getOrPut listOf(dependency) + val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { + it.first.configuration + }.singleOrNull() ?: return@getOrPut listOf(dependency) + val taskGetterName = when (scope) { + "COMPILE" -> "getApiElementsConfigurationName" + "RUNTIME" -> "getRuntimeElementsConfigurationName" + else -> return@getOrPut listOf(dependency) + } + val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@getOrPut listOf(dependency) + val gradleTarget = targets.firstOrNull { + val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false + getter(it) == artifactConfiguration + } ?: return@getOrPut listOf(dependency) + val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String + ?: return@getOrPut listOf(dependency) + val platformDependency = if (classifier != KotlinTarget.METADATA_TARGET_NAME) { + wrapDependency(dependency, compilationFullName(KotlinCompilation.MAIN_COMPILATION_NAME, classifier)) + } else null + val commonDependency = wrapDependency(dependency, KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + return if (platformDependency != null) listOf(platformDependency, commonDependency) else listOf(commonDependency) + } + } + } + companion object { private val logger = Logging.getLogger(KotlinMPPGradleModelBuilder::class.java) } +} + +private fun Project.getChildProjectByPath(path: String): Project? { + var project = this + for (name in path.split(":").asSequence().drop(1)) { + project = project.childProjects[name] ?: return null + } + return project +} + +private fun Project.getTargets(): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null + @Suppress("UNCHECKED_CAST") + return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() } \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 index 209a2f8906b..cd2057c265e 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 @@ -9,6 +9,7 @@ import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.file.FileCollection @@ -62,14 +63,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { .forEach { logger.warn(it) } } - private fun Project.getChildProjectByPath(path: String): Project? { - var project = this - for (name in path.split(":").asSequence().drop(1)) { - project = project.childProjects[name] ?: return null - } - return project - } - private fun getCoroutinesState(project: Project): String? { val kotlinExt = project.extensions.findByName("kotlin") ?: return null val getExperimental = kotlinExt.javaClass.getMethodOrNull("getExperimental") ?: return null @@ -139,45 +132,15 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val configurationName = getConfigurationName(dependencyHolder) as? String ?: return emptyList() val configuration = project.configurations.findByName(configurationName) ?: return emptyList() if (!configuration.isCanBeResolved) return emptyList() - val dependenciesByProjectPath = configuration - .resolvedConfiguration - .lenientConfiguration - .firstLevelModuleDependencies - .mapNotNull { dependency -> - val artifact = dependency.moduleArtifacts.firstOrNull { - it.id.componentIdentifier is ProjectComponentIdentifier - } ?: return@mapNotNull null - dependency to artifact - } - .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } - val resolvedDependencies = dependencyResolver.resolveDependencies(configuration) + + val dependencyAdjuster = DependencyAdjuster(configuration, scope, project) + + val resolvedDependencies = dependencyResolver + .resolveDependencies(configuration) .apply { forEach { (it as? AbstractExternalDependency)?.scope = scope } } - .map { dependency -> - if (dependency !is ExternalProjectDependency - || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION - ) return@map dependency - val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@map dependency - val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { - it.first.configuration - }.singleOrNull() ?: return@map dependency - val taskGetterName = when (scope) { - "COMPILE" -> "getApiElementsConfigurationName" - "RUNTIME" -> "getRuntimeElementsConfigurationName" - else -> return@map dependency - } - val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@map dependency - val gradleTarget = targets.firstOrNull { - val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false - getter(it) == artifactConfiguration - } ?: return@map dependency - val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String - ?: return@map dependency - DefaultExternalProjectDependency(dependency).apply { - this.classifier = classifier - } - } + .flatMap(dependencyAdjuster::adjustDependency) val singleDependencyFiles = resolvedDependencies.mapNotNullTo(LinkedHashSet()) { (it as? FileCollectionDependency)?.files?.singleOrNull() } @@ -199,13 +162,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { return project.getTargets()?.mapNotNull { buildTarget(it, sourceSetMap, dependencyResolver, project) } } - private fun Project.getTargets(): Collection? { - val kotlinExt = project.extensions.findByName("kotlin") ?: return null - val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null - @Suppress("UNCHECKED_CAST") - return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() - } - private fun buildTarget( gradleTarget: Named, sourceSetMap: Map, @@ -389,7 +345,83 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } + private class DependencyAdjuster( + private val configuration: Configuration, + private val scope: String, + private val project: Project + ) { + private val adjustmentMap = HashMap>() + + val dependenciesByProjectPath by lazy { + configuration + .resolvedConfiguration + .lenientConfiguration + .allModuleDependencies + .mapNotNull { dependency -> + val artifact = dependency.moduleArtifacts.firstOrNull { + it.id.componentIdentifier is ProjectComponentIdentifier + } ?: return@mapNotNull null + dependency to artifact + } + .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } + } + + private fun wrapDependency(dependency: ExternalProjectDependency, newConfigurationName: String): ExternalProjectDependency { + return DefaultExternalProjectDependency(dependency).apply { + this.configurationName = newConfigurationName + + val nestedDependencies = this.dependencies.flatMap(::adjustDependency) + this.dependencies.clear() + this.dependencies.addAll(nestedDependencies) + } + } + + fun adjustDependency(dependency: ExternalDependency): List { + return adjustmentMap.getOrPut(dependency) { + if (dependency !is ExternalProjectDependency + || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION + ) return@getOrPut listOf(dependency) + val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@getOrPut listOf(dependency) + val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { + it.first.configuration + }.singleOrNull() ?: return@getOrPut listOf(dependency) + val taskGetterName = when (scope) { + "COMPILE" -> "getApiElementsConfigurationName" + "RUNTIME" -> "getRuntimeElementsConfigurationName" + else -> return@getOrPut listOf(dependency) + } + val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@getOrPut listOf(dependency) + val gradleTarget = targets.firstOrNull { + val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false + getter(it) == artifactConfiguration + } ?: return@getOrPut listOf(dependency) + val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String + ?: return@getOrPut listOf(dependency) + val platformDependency = if (classifier != KotlinTarget.METADATA_TARGET_NAME) { + wrapDependency(dependency, compilationFullName(KotlinCompilation.MAIN_COMPILATION_NAME, classifier)) + } else null + val commonDependency = wrapDependency(dependency, KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + return if (platformDependency != null) listOf(platformDependency, commonDependency) else listOf(commonDependency) + } + } + } + companion object { private val logger = Logging.getLogger(KotlinMPPGradleModelBuilder::class.java) } +} + +private fun Project.getChildProjectByPath(path: String): Project? { + var project = this + for (name in path.split(":").asSequence().drop(1)) { + project = project.childProjects[name] ?: return null + } + return project +} + +private fun Project.getTargets(): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null + @Suppress("UNCHECKED_CAST") + return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() } \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.as31 b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.as31 index c430dcd3444..0a33a6dd690 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.as31 +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.as31 @@ -9,6 +9,7 @@ import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.file.FileCollection @@ -61,14 +62,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { .forEach { logger.warn(it) } } - private fun Project.getChildProjectByPath(path: String): Project? { - var project = this - for (name in path.split(":").asSequence().drop(1)) { - project = project.childProjects[name] ?: return null - } - return project - } - private fun getCoroutinesState(project: Project): String? { val kotlinExt = project.extensions.findByName("kotlin") ?: return null val getExperimental = kotlinExt.javaClass.getMethodOrNull("getExperimental") ?: return null @@ -138,45 +131,15 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { val configurationName = getConfigurationName(dependencyHolder) as? String ?: return emptyList() val configuration = project.configurations.findByName(configurationName) ?: return emptyList() if (!configuration.isCanBeResolved) return emptyList() - val dependenciesByProjectPath = configuration - .resolvedConfiguration - .lenientConfiguration - .firstLevelModuleDependencies - .mapNotNull { dependency -> - val artifact = dependency.moduleArtifacts.firstOrNull { - it.id.componentIdentifier is ProjectComponentIdentifier - } ?: return@mapNotNull null - dependency to artifact - } - .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } - val resolvedDependencies = dependencyResolver.resolveDependencies(configuration) + + val dependencyAdjuster = DependencyAdjuster(configuration, scope, project) + + val resolvedDependencies = dependencyResolver + .resolveDependencies(configuration) .apply { forEach { (it as? AbstractExternalDependency)?.scope = scope } } - .map { dependency -> - if (dependency !is ExternalProjectDependency - || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION - ) return@map dependency - val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@map dependency - val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { - it.first.configuration - }.singleOrNull() ?: return@map dependency - val taskGetterName = when (scope) { - "COMPILE" -> "getApiElementsConfigurationName" - "RUNTIME" -> "getRuntimeElementsConfigurationName" - else -> return@map dependency - } - val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@map dependency - val gradleTarget = targets.firstOrNull { - val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false - getter(it) == artifactConfiguration - } ?: return@map dependency - val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String - ?: return@map dependency - DefaultExternalProjectDependency(dependency).apply { - this.classifier = classifier - } - } + .flatMap(dependencyAdjuster::adjustDependency) val singleDependencyFiles = resolvedDependencies.mapNotNullTo(LinkedHashSet()) { (it as? FileCollectionDependency)?.files?.singleOrNull() } @@ -198,13 +161,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { return project.getTargets()?.mapNotNull { buildTarget(it, sourceSetMap, dependencyResolver, project) } } - private fun Project.getTargets(): Collection? { - val kotlinExt = project.extensions.findByName("kotlin") ?: return null - val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null - @Suppress("UNCHECKED_CAST") - return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() - } - private fun buildTarget( gradleTarget: Named, sourceSetMap: Map, @@ -383,7 +339,83 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } } + private class DependencyAdjuster( + private val configuration: Configuration, + private val scope: String, + private val project: Project + ) { + private val adjustmentMap = HashMap>() + + val dependenciesByProjectPath by lazy { + configuration + .resolvedConfiguration + .lenientConfiguration + .allModuleDependencies + .mapNotNull { dependency -> + val artifact = dependency.moduleArtifacts.firstOrNull { + it.id.componentIdentifier is ProjectComponentIdentifier + } ?: return@mapNotNull null + dependency to artifact + } + .groupBy { (it.second.id.componentIdentifier as ProjectComponentIdentifier).projectPath } + } + + private fun wrapDependency(dependency: ExternalProjectDependency, newConfigurationName: String): ExternalProjectDependency { + return DefaultExternalProjectDependency(dependency).apply { + this.configurationName = newConfigurationName + + val nestedDependencies = this.dependencies.flatMap(::adjustDependency) + this.dependencies.clear() + this.dependencies.addAll(nestedDependencies) + } + } + + fun adjustDependency(dependency: ExternalDependency): List { + return adjustmentMap.getOrPut(dependency) { + if (dependency !is ExternalProjectDependency + || dependency.configurationName != Dependency.DEFAULT_CONFIGURATION + ) return@getOrPut listOf(dependency) + val artifacts = dependenciesByProjectPath[dependency.projectPath] ?: return@getOrPut listOf(dependency) + val artifactConfiguration = artifacts.mapTo(LinkedHashSet()) { + it.first.configuration + }.singleOrNull() ?: return@getOrPut listOf(dependency) + val taskGetterName = when (scope) { + "COMPILE" -> "getApiElementsConfigurationName" + "RUNTIME" -> "getRuntimeElementsConfigurationName" + else -> return@getOrPut listOf(dependency) + } + val targets = project.rootProject.getChildProjectByPath(dependency.projectPath)?.getTargets() ?: return@getOrPut listOf(dependency) + val gradleTarget = targets.firstOrNull { + val getter = it.javaClass.getMethodOrNull(taskGetterName) ?: return@firstOrNull false + getter(it) == artifactConfiguration + } ?: return@getOrPut listOf(dependency) + val classifier = gradleTarget.javaClass.getMethodOrNull("getDisambiguationClassifier")?.invoke(gradleTarget) as? String + ?: return@getOrPut listOf(dependency) + val platformDependency = if (classifier != KotlinTarget.METADATA_TARGET_NAME) { + wrapDependency(dependency, compilationFullName(KotlinCompilation.MAIN_COMPILATION_NAME, classifier)) + } else null + val commonDependency = wrapDependency(dependency, KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + return if (platformDependency != null) listOf(platformDependency, commonDependency) else listOf(commonDependency) + } + } + } + companion object { private val logger = Logging.getLogger(KotlinMPPGradleModelBuilder::class.java) } +} + +private fun Project.getChildProjectByPath(path: String): Project? { + var project = this + for (name in path.split(":").asSequence().drop(1)) { + project = project.childProjects[name] ?: return null + } + return project +} + +private fun Project.getTargets(): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null + @Suppress("UNCHECKED_CAST") + return (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() } \ No newline at end of file