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 631fcf9905a..70e7a0f3467 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 @@ -9,9 +9,11 @@ import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.ProjectKeys import com.intellij.openapi.externalSystem.model.project.AbstractExternalEntityData import com.intellij.openapi.externalSystem.model.project.AbstractNamedData +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType import com.intellij.openapi.externalSystem.model.project.ModuleData import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.util.Key +import com.intellij.util.containers.MultiMap import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.gradle.KotlinModule import org.jetbrains.kotlin.gradle.KotlinPlatform @@ -53,4 +55,10 @@ class KotlinTargetData(name: String) : AbstractNamedData(GradleConstants.SYSTEM_ companion object { val KEY = ExternalKey.create(KotlinTargetData::class.java, ProjectKeys.MODULE.processingWeight + 1) } +} + +class KotlinOutputPathsData(val paths: MultiMap) : AbstractExternalEntityData(GradleConstants.SYSTEM_ID) { + companion object { + val KEY = ExternalKey.create(KotlinOutputPathsData::class.java, ProjectKeys.CONTENT_ROOT.processingWeight + 1) + } } \ No newline at end of file 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 94206b9b20b..5f7eb8f5b44 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 @@ -110,6 +110,8 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { recordOutputDir(it, it, compilation.resourceType, moduleData, moduleOutputsMap, gradleOutputMap) } } + + dataNode.createChild(KotlinOutputPathsData.KEY, KotlinOutputPathsData(gradleOutputMap.copy())) } if (outputDirs.any { FileUtil.isAncestor(ideaOutDir, File(it), false) }) { excludeOutDir(ideModule, ideaOutDir) diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinGradleOrderEnumerationHandler.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinGradleOrderEnumerationHandler.kt index 68332b501d4..dd94a6daf66 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinGradleOrderEnumerationHandler.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/gradle/execution/KotlinGradleOrderEnumerationHandler.kt @@ -5,15 +5,97 @@ package org.jetbrains.kotlin.idea.gradle.execution +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.externalSystem.util.ExternalSystemUtil.getExternalProjectInfo import com.intellij.openapi.module.Module +import com.intellij.openapi.roots.ModuleRootModel +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.vfs.VfsUtilCore import org.jetbrains.kotlin.idea.caches.project.isMPPModule +import org.jetbrains.kotlin.idea.caches.project.isNewMPPModule +import org.jetbrains.kotlin.idea.configuration.KotlinOutputPathsData import org.jetbrains.plugins.gradle.execution.GradleOrderEnumeratorHandler +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil +import org.jetbrains.plugins.gradle.util.GradleConstants open class KotlinGradleOrderEnumerationHandler(module: Module) : GradleOrderEnumeratorHandler(module) { + private companion object { + private val PRODUCTION_SOURCE_TYPES = listOf( + ExternalSystemSourceType.SOURCE, + ExternalSystemSourceType.RESOURCE, + ExternalSystemSourceType.SOURCE_GENERATED + ) + + private val TEST_SOURCE_TYPES = listOf( + ExternalSystemSourceType.TEST, + ExternalSystemSourceType.TEST_RESOURCE, + ExternalSystemSourceType.TEST_GENERATED + ) + } + override fun shouldIncludeTestsFromDependentModulesToTestClasspath(): Boolean { return true } + override fun addCustomModuleRoots( + type: OrderRootType, + rootModel: ModuleRootModel, + result: MutableCollection, + includeProduction: Boolean, + includeTests: Boolean + ): Boolean { + if (super.addCustomModuleRoots(type, rootModel, result, includeProduction, includeTests)) { + // The code below works as a fallback when the default implementation fails + return true + } + + val module = rootModel.module.takeIf { it.isNewMPPModule } ?: return false + val projectPath = ExternalSystemApiUtil.getExternalProjectPath(module) ?: return false + val externalProjectInfo = getExternalProjectInfo(module.project, GradleConstants.SYSTEM_ID, projectPath) ?: return false + val moduleData = GradleProjectResolverUtil.findModule(externalProjectInfo.externalProjectStructure, projectPath) ?: return false + + val sourceSetData = ExternalSystemApiUtil.getChildren(moduleData, GradleSourceSetData.KEY) + .filter { it.data.internalName == module.name } + + if (sourceSetData.isEmpty()) { + return false + } + + val existingRoots = result.toMutableSet() + var hasNewRoots = false + + fun appendPaths(paths: Collection) { + for (path in paths) { + val url = VfsUtilCore.pathToUrl(path) + if (existingRoots.add(url)) { + result.add(url) + if (!hasNewRoots) { + hasNewRoots = true + } + } + } + } + + fun appendPaths(outputPaths: KotlinOutputPathsData, configurations: List) { + configurations.forEach { appendPaths(outputPaths.paths[it]) } + } + + for (data in sourceSetData) { + for (outputPaths in ExternalSystemApiUtil.findAll(data, KotlinOutputPathsData.KEY)) { + if (includeProduction) { + appendPaths(outputPaths.data, PRODUCTION_SOURCE_TYPES) + } + if (includeTests) { + appendPaths(outputPaths.data, TEST_SOURCE_TYPES) + } + } + } + + return hasNewRoots + } + open class Factory : GradleOrderEnumeratorHandler.FactoryImpl() { override fun isApplicable(module: Module): Boolean { return module.isMPPModule diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformKaptProjectImportingTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformKaptProjectImportingTest.kt index 7e3cb448373..ab35825253c 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformKaptProjectImportingTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/NewMultiplatformKaptProjectImportingTest.kt @@ -5,7 +5,9 @@ package org.jetbrains.kotlin.gradle +import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.roots.* +import com.intellij.openapi.roots.impl.ModulesOrderEnumerator import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.idea.codeInsight.gradle.MultiplePluginVersionGradleImportingTestCase import org.jetbrains.kotlin.idea.util.sourceRoots @@ -66,6 +68,37 @@ class NewMultiplatformKaptProjectImportingTest : MultiplePluginVersionGradleImpo } } + @Test + fun testRuntimeClasspath() { + configureByFiles() + + val projectPath = this.projectPath + + val expectedRoots = listOf( + "build/tmp/kapt3/classes/main", + "build/classes/java/main", + "build/classes/kotlin/jvm/main", + "build/processedResources/jvm/main" + ).map { + File(projectPath, it).apply { mkdirs() } + } + + importProject() + + val jvmMainModule = ModuleManager.getInstance(project).modules.first { it.name == "project_jvmMain" } + + val enumerator = ModulesOrderEnumerator(listOf(jvmMainModule)) + val roots = enumerator.classesRoots + + fun isRootPresent(file: File) = roots.any { it.path == file.path } + + val missingRoots = expectedRoots.filter { !isRootPresent(it) } + + assert(missingRoots.isEmpty()) { + "Missing roots found: " + missingRoots.joinToString() + } + } + override fun importProject() { val isCreateEmptyContentRootDirectories = currentExternalProjectSettings.isCreateEmptyContentRootDirectories currentExternalProjectSettings.isCreateEmptyContentRootDirectories = true diff --git a/idea/testData/gradle/newMultiplatformImport/runtimeClasspath/build.gradle b/idea/testData/gradle/newMultiplatformImport/runtimeClasspath/build.gradle new file mode 100644 index 00000000000..9336153a1ee --- /dev/null +++ b/idea/testData/gradle/newMultiplatformImport/runtimeClasspath/build.gradle @@ -0,0 +1,25 @@ +buildscript { + repositories { + mavenCentral() + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } + } + + dependencies { + apply from: "include.gradle" + def kotlinVersion = gradleKotlinPluginVersion('1.3.40-eap-67') + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + } +} + +apply plugin: 'kotlin-multiplatform' +apply plugin: 'kotlin-kapt' + + +repositories { + mavenCentral() + maven { url 'https://dl.bintray.com/kotlin/kotlin-dev' } +} + +kotlin { + jvm('jvm') { withJava() } +} \ No newline at end of file