From 715fad849dec50d27e59bdc3c08aed4af5884ae2 Mon Sep 17 00:00:00 2001 From: Andrey Uskov Date: Mon, 9 Sep 2019 20:05:34 +0300 Subject: [PATCH] Importing test tasks and targets is implemented --- .../idea/configuration/KotlinMPPDataNodes.kt | 2 + .../configuration/KotlinMPPDataNodes.kt.191 | 2 + .../KotlinMPPGradleProjectResolver.kt | 61 +++++++++++++- .../KotlinSourceSetDataService.kt | 1 + ...chicalMultiplatformProjectImportingTest.kt | 1 + .../kotlin/gradle/KaptImportingTest.kt | 4 +- ...ewMultiplatformKaptProjectImportingTest.kt | 3 +- .../NewMultiplatformProjectImportingTest.kt | 29 +++++++ .../gradle/PackagePrefixImportingTest.kt | 3 +- .../kotlin/gradle/projectStructureDSL.kt | 22 ++++- .../kotlin/config/KotlinFacetSettings.kt | 13 +++ .../kotlin/config/facetSerialization.kt | 29 ++++--- .../src/KotlinMPPGradleModel.kt | 1 + .../src/KotlinMPPGradleModelBuilder.kt | 80 ++++++++++++++----- .../src/KotlinMPPGradleModelImpl.kt | 5 +- .../jetbrains/kotlin/idea/facet/facetUtils.kt | 2 + .../importTestsAndTargets/build.gradle | 65 +++++++++++++++ .../importTestsAndTargets/gradle.properties | 1 + .../importTestsAndTargets/settings.gradle | 18 +++++ 19 files changed, 302 insertions(+), 40 deletions(-) create mode 100644 idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/build.gradle create mode 100644 idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/gradle.properties create mode 100644 idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/settings.gradle 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 e797a70c296..2766a45788e 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 @@ -15,6 +15,7 @@ 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.config.ExternalSystemTestTask import org.jetbrains.kotlin.gradle.* import org.jetbrains.kotlin.idea.util.CopyableDataNodeUserDataProperty import org.jetbrains.plugins.gradle.util.GradleConstants @@ -48,6 +49,7 @@ class KotlinSourceSetInfo @PropertyMapping("kotlinModule") constructor(val kotli var isTestModule: Boolean = false var sourceSetIdsByName: MutableMap = LinkedHashMap() var dependsOn: List = emptyList() + var externalSystemTestTasks: Collection = emptyList() } class KotlinAndroidSourceSetData @PropertyMapping("sourceSetInfos") constructor(val sourceSetInfos: List diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt.191 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt.191 index 461d1ddaf1c..ff139fb3697 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt.191 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt.191 @@ -15,6 +15,7 @@ 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.config.ExternalSystemTestTask import org.jetbrains.kotlin.gradle.* import org.jetbrains.kotlin.idea.util.CopyableDataNodeUserDataProperty import org.jetbrains.plugins.gradle.util.GradleConstants @@ -44,6 +45,7 @@ class KotlinSourceSetInfo(val kotlinModule: KotlinModule) : Serializable { var isTestModule: Boolean = false var sourceSetIdsByName: MutableMap = LinkedHashMap() var dependsOn: List = emptyList() + var externalSystemTestTasks: Collection = emptyList() } class KotlinAndroidSourceSetData( 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 c5ccd076550..313fe8365de 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 @@ -30,10 +30,12 @@ import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.ManualLanguageFeatureSetting import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments +import org.jetbrains.kotlin.config.ExternalSystemTestTask 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.utils.addToStdlib.firstNotNullResult import org.jetbrains.plugins.gradle.model.* import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData @@ -259,6 +261,8 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { val sourceSetMap = projectDataNode.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS)!! + val sourceSetToTestTasks = calculateTestTasks(mppModel, gradleModule, resolverCtx) + val sourceSetToCompilationData = LinkedHashMap>() for (target in mppModel.targets) { if (target.platform == KotlinPlatform.ANDROID) continue @@ -305,7 +309,13 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { it.sdkName = jdkName } - val kotlinSourceSet = createSourceSetInfo(compilation, gradleModule, resolverCtx) ?: continue + val kotlinSourceSet = createSourceSetInfo( + compilation, + gradleModule, + resolverCtx + ) ?: continue + kotlinSourceSet.externalSystemTestTasks = + compilation.sourceSets.firstNotNullResult { sourceSetToTestTasks[it] } ?: emptyList() if (compilation.platform == KotlinPlatform.JVM || compilation.platform == KotlinPlatform.ANDROID) { compilationData.targetCompatibility = (kotlinSourceSet.compilerArguments as? K2JVMCompilerArguments)?.jvmTarget @@ -364,6 +374,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } val kotlinSourceSet = createSourceSetInfo(sourceSet, gradleModule, resolverCtx) ?: continue + kotlinSourceSet.externalSystemTestTasks = sourceSetToTestTasks[sourceSet] ?: emptyList() val sourceSetDataNode = (existingSourceSetDataNode ?: mainModuleNode.createChild(GradleSourceSetData.KEY, sourceSetData)).also { @@ -383,6 +394,34 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { mainModuleNode.coroutines = mppModel.extraFeatures.coroutinesState mainModuleNode.isHmpp = mppModel.extraFeatures.isHMPPEnabled + //TODO improve passing version of used multiplatform + } + + private fun calculateTestTasks( + mppModel: KotlinMPPGradleModel, + gradleModule: IdeaModule, + resolverCtx: ProjectResolverContext + ): Map> { + val sourceSetToTestTasks: MutableMap> = HashMap() + val dependsOnReverseGraph: MutableMap> = HashMap() + mppModel.targets.forEach { target -> + target.compilations.forEach { compilation -> + val testTasks = target.testTasks.filter { testTask -> testTask.compilationName == compilation.name } + .map { ExternalSystemTestTask(it.taskName, getKotlinModuleId(gradleModule, compilation, resolverCtx), target.name) } + compilation.sourceSets.forEach { sourceSet -> + sourceSetToTestTasks.getOrPut(sourceSet) { LinkedHashSet() } += testTasks + sourceSet.dependsOnSourceSets.forEach { dependentModule -> + dependsOnReverseGraph.getOrPut(dependentModule) { LinkedHashSet() } += sourceSet + } + } + } + } + mppModel.sourceSets.forEach { (sourceSetName, sourceSet) -> + dependsOnReverseGraph[sourceSetName]?.forEach { dependingSourceSet -> + sourceSetToTestTasks.getOrPut(sourceSet) { LinkedHashSet() } += sourceSetToTestTasks[dependingSourceSet] ?: emptyList() + } + } + return sourceSetToTestTasks } fun populateContentRoots( @@ -567,7 +606,12 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { return ideModule.findChildModuleById(usedModuleId) } - private fun createContentRootData(sourceDirs: Set, sourceType: ExternalSystemSourceType, packagePrefix: String?, parentNode: DataNode<*>) { + private fun createContentRootData( + sourceDirs: Set, + sourceType: ExternalSystemSourceType, + packagePrefix: String?, + parentNode: DataNode<*> + ) { for (sourceDir in sourceDirs) { val contentRootData = ContentRootData(GradleConstants.SYSTEM_ID, sourceDir.absolutePath) contentRootData.storePath(sourceType, sourceDir.absolutePath, packagePrefix) @@ -652,7 +696,11 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { return PathUtilRt.suggestFileName(moduleName.toString(), true, false) } - private fun createExternalSourceSet(compilation: KotlinCompilation, compilationData: GradleSourceSetData, mppModel: KotlinMPPGradleModel): ExternalSourceSet { + private fun createExternalSourceSet( + compilation: KotlinCompilation, + compilationData: GradleSourceSetData, + mppModel: KotlinMPPGradleModel + ): ExternalSourceSet { return DefaultExternalSourceSet().also { sourceSet -> val effectiveClassesDir = compilation.output.effectiveClassesDir val resourcesDir = compilation.output.resourcesDir @@ -688,7 +736,11 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } - private fun createExternalSourceSet(ktSourceSet: KotlinSourceSet, ktSourceSetData: GradleSourceSetData, mppModel: KotlinMPPGradleModel): ExternalSourceSet { + private fun createExternalSourceSet( + ktSourceSet: KotlinSourceSet, + ktSourceSetData: GradleSourceSetData, + mppModel: KotlinMPPGradleModel + ): ExternalSourceSet { return DefaultExternalSourceSet().also { sourceSet -> sourceSet.name = ktSourceSet.name sourceSet.targetCompatibility = ktSourceSetData.targetCompatibility @@ -745,6 +797,7 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { } // TODO: Unite with other createSourceSetInfo + // This method is used in Android side of import and it's signature could not be changed fun createSourceSetInfo( compilation: KotlinCompilation, gradleModule: IdeaModule, diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt index 9bc8e240c2e..1e25c01cabd 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt @@ -150,6 +150,7 @@ class KotlinSourceSetDataService : AbstractProjectDataService Unit = {} ) { checkProjectStructure( @@ -781,6 +809,7 @@ class NewMultiplatformProjectImportingTest : MultiplePluginVersionGradleImportin exhaustiveModuleList, exhaustiveSourceSourceRootList, exhaustiveDependencyList, + exhaustiveTestsList, body) } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/PackagePrefixImportingTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/PackagePrefixImportingTest.kt index 0a182eb622c..9ed8d13b569 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/PackagePrefixImportingTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/PackagePrefixImportingTest.kt @@ -27,7 +27,8 @@ class PackagePrefixImportingTest : MultiplePluginVersionGradleImportingTestCase( projectPath, exhaustiveModuleList = true, exhaustiveSourceSourceRootList = true, - exhaustiveDependencyList = false + exhaustiveDependencyList = false, + exhaustiveTestsList = false ) { module("project") { } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt index c19f4e89cf0..18efc49461f 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt @@ -14,11 +14,12 @@ import com.intellij.openapi.roots.impl.ModuleOrderEntryImpl import com.intellij.openapi.util.io.FileUtil import org.jetbrains.jps.model.module.JpsModuleSourceRootType import org.jetbrains.jps.util.JpsPathUtil +import org.jetbrains.kotlin.config.ExternalSystemTestTask import org.jetbrains.kotlin.idea.facet.KotlinFacet +import org.jetbrains.kotlin.idea.facet.externalSystemTestTasks import org.jetbrains.kotlin.idea.project.isHMPPEnabled import org.jetbrains.kotlin.idea.project.languageVersionSettings import org.jetbrains.kotlin.idea.project.platform -import org.jetbrains.kotlin.platform.SimplePlatform import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.presentableDescription @@ -42,7 +43,8 @@ class ProjectInfo( internal val projectPath: String, internal val exhaustiveModuleList: Boolean, internal val exhaustiveSourceSourceRootList: Boolean, - internal val exhaustiveDependencyList: Boolean + internal val exhaustiveDependencyList: Boolean, + internal val exhaustiveTestsList: Boolean ) { internal val messageCollector = MessageCollector() private val moduleManager = ModuleManager.getInstance(project) @@ -88,6 +90,8 @@ class ModuleInfo( private val rootModel = module.rootManager private val expectedDependencyNames = HashSet() private val expectedSourceRoots = HashSet() + private val expectedExternalSystemTestTasks = ArrayList() + private val sourceFolderByPath by lazy { rootModel.contentEntries.asSequence() .flatMap { it.sourceFolders.asSequence() } @@ -156,6 +160,10 @@ class ModuleInfo( } } + fun externalSystemTestTask(taskName: String, projectId: String, targetName: String) { + expectedExternalSystemTestTasks.add(ExternalSystemTestTask(taskName, projectId, targetName)) + } + fun libraryDependency(libraryName: String, scope: DependencyScope) { val libraryEntries = rootModel.orderEntries.filterIsInstance().filter { it.libraryName == libraryName } if (libraryEntries.size > 1) { @@ -271,6 +279,12 @@ class ModuleInfo( } } + if ((!module.externalSystemTestTasks().containsAll(expectedExternalSystemTestTasks)) || (projectInfo.exhaustiveTestsList && (module.externalSystemTestTasks() != expectedExternalSystemTestTasks))) { + projectInfo.messageCollector.report( + "Module '${module.name}': Expected tests list $expectedExternalSystemTestTasks doesn't match the actual one: ${module.externalSystemTestTasks()}" + ) + } + if (projectInfo.exhaustiveSourceSourceRootList) { val actualSourceRoots = sourceFolderByPath.keys.sorted() val expectedSourceRoots = expectedSourceRoots.sorted() @@ -319,6 +333,7 @@ fun checkProjectStructure( exhaustiveModuleList: Boolean, exhaustiveSourceSourceRootList: Boolean, exhaustiveDependencyList: Boolean, + exhaustiveTestsList: Boolean, body: ProjectInfo.() -> Unit = {} ) { ProjectInfo( @@ -326,6 +341,7 @@ fun checkProjectStructure( projectPath, exhaustiveModuleList, exhaustiveSourceSourceRootList, - exhaustiveDependencyList + exhaustiveDependencyList, + exhaustiveTestsList ).run(body) } \ No newline at end of file diff --git a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt index 78ae53efb2f..303ac20aedc 100644 --- a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt +++ b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt @@ -153,6 +153,18 @@ enum class KotlinMultiplatformVersion(val version: Int) { get() = version >= 3 } +data class ExternalSystemTestTask(val testName: String, val externalSystemProjectId: String, val targetName: String?) { + + fun toStringRepresentation() = "$testName|$externalSystemProjectId|$targetName" + + companion object { + fun fromStringRepresentation(line: String) = + line.split("|").let { if (it.size == 3) ExternalSystemTestTask(it[0], it[1], it[2]) else null } + } + + override fun toString() = "$testName@$externalSystemProjectId [$targetName]" +} + class KotlinFacetSettings { companion object { // Increment this when making serialization-incompatible changes to configuration data @@ -219,6 +231,7 @@ class KotlinFacetSettings { return field } + var externalSystemTestTasks: List = emptyList() @Suppress("DEPRECATION_ERROR") @Deprecated( diff --git a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt index 436b626f6bd..0a533b69d7a 100644 --- a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt +++ b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt @@ -136,8 +136,11 @@ private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings { element.getAttributeValue("useProjectSettings")?.let { useProjectSettings = it.toBoolean() } val targetPlatform = element.getFacetPlatformByConfigurationElement() this.targetPlatform = targetPlatform - readModulesList(element, "implements", "implement")?.let { implementedModuleNames = it } - readModulesList(element, "dependsOnModuleNames", "dependsOn")?.let { dependsOnModuleNames = it } + readElementsList(element, "implements", "implement")?.let { implementedModuleNames = it } + readElementsList(element, "dependsOnModuleNames", "dependsOn")?.let { dependsOnModuleNames = it } + readElementsList(element, "externalSystemTestTasks", "externalSystemTestTask")?.let { + externalSystemTestTasks = it.mapNotNull { ExternalSystemTestTask.fromStringRepresentation(it) } + } element.getChild("sourceSets")?.let { val items = it.getChildren("sourceSet") @@ -178,7 +181,7 @@ private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings { } } -private fun readModulesList(element: Element, rootElementName: String, elementName: String): List? { +private fun readElementsList(element: Element, rootElementName: String, elementName: String): List? { element.getChild(rootElementName)?.let { val items = it.getChildren(elementName) return if (items.isNotEmpty()) { @@ -310,8 +313,8 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) { if (!useProjectSettings) { element.setAttribute("useProjectSettings", useProjectSettings.toString()) } - saveModulesList(element, implementedModuleNames, "implements", "implement") - saveModulesList(element, dependsOnModuleNames, "dependsOnModuleNames", "dependsOn") + saveElementsList(element, implementedModuleNames, "implements", "implement") + saveElementsList(element, dependsOnModuleNames, "dependsOnModuleNames", "dependsOn") if (sourceSetNames.isNotEmpty()) { element.addContent( @@ -330,6 +333,14 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) { if (isHmppEnabled) { element.setAttribute("isHmppProject", isHmppEnabled.toString()) } + if (externalSystemTestTasks.isNotEmpty()) { + saveElementsList( + element, + externalSystemTestTasks.map { it.toStringRepresentation() }, + "externalSystemTestTasks", + "externalSystemTestTask" + ) + } if (pureKotlinSourceFolders.isNotEmpty()) { element.setAttribute("pureKotlinSourceFolders", pureKotlinSourceFolders.joinToString(";")) } @@ -354,15 +365,15 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) { } } -private fun saveModulesList(element: Element, moduleList: List, rootElementName: String, elementName: String) { - if (moduleList.isNotEmpty()) { +private fun saveElementsList(element: Element, elementsList: List, rootElementName: String, elementName: String) { + if (elementsList.isNotEmpty()) { element.addContent( Element(rootElementName).apply { - val singleModule = moduleList.singleOrNull() + val singleModule = elementsList.singleOrNull() if (singleModule != null) { addContent(singleModule) } else { - moduleList.map { addContent(Element(elementName).apply { addContent(it) }) } + elementsList.map { addContent(Element(elementName).apply { addContent(it) }) } } } ) diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt index 7429ecdd659..6c66c4a4ae6 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt @@ -151,6 +151,7 @@ interface KotlinTarget : Serializable { interface KotlinTestTask : Serializable { val taskName: String + val compilationName: String } interface ExtraFeatures : Serializable { diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index f0d6858d1db..4fdac0f63a0 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -15,6 +15,7 @@ import org.gradle.api.logging.Logging import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Exec +import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel.Companion.NO_KOTLIN_NATIVE_HOME import org.jetbrains.plugins.gradle.model.* import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder @@ -26,6 +27,9 @@ import java.io.File import java.lang.reflect.Method class KotlinMPPGradleModelBuilder : ModelBuilderService { + // This flag enables import of source sets which do not belong to any compilation + private val DEFAULT_IMPORT_ORPHAN_SOURCE_SETS = true + override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { return ErrorMessageBuilder .create(project, e, "Gradle import errors") @@ -53,7 +57,7 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { reportUnresolvedDependencies(targets) val kotlinNativeHome = KotlinNativeHomeEvaluator.getKotlinNativeHome(project) ?: NO_KOTLIN_NATIVE_HOME return KotlinMPPGradleModelImpl( - sourceSetMap, + filterOrphanSourceSets(sourceSetMap, targets, project), targets, ExtraFeaturesImpl(coroutinesState, isHMPPEnabled(project)), kotlinNativeHome, @@ -61,8 +65,21 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { ) } + private fun filterOrphanSourceSets( + sourceSets: Map, + targets: Collection, + project: Project + ): Map { + if (project.properties["import_orphan_source_sets"]?.toString()?.toBoolean() ?: DEFAULT_IMPORT_ORPHAN_SOURCE_SETS) return sourceSets + val compiledSourceSets: Collection = targets.flatMap { it.compilations }.flatMap { it.sourceSets }.flatMap { it.dependsOnSourceSets.union(listOf(it.name)) }.distinct() + sourceSets.filter { !compiledSourceSets.contains(it.key) }.forEach { + logger.warn("[sync warning] Source set \"${it.key}\" is not compiled with any compilation. This source set is not imported in the IDE.") + } + return sourceSets.filter { compiledSourceSets.contains(it.key) } + } + private fun isHMPPEnabled(project: Project): Boolean { - //TODO(auskov): replace with Project.isKotlinGranularMetadataEnabled after merging with gradle pranch + //TODO(auskov): replace with Project.isKotlinGranularMetadataEnabled after merging with gradle branch return (project.findProperty("kotlin.mpp.enableGranularSourceSetsMetadata") as? String)?.toBoolean() ?: false } @@ -314,7 +331,24 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { } private fun buildTestTasks(project: Project, gradleTarget: Named): Collection { - // TODO: use the test runs API to extract the test tasks once it is available + val getTestRunsMethod = gradleTarget.javaClass.getMethodOrNull("getTestRuns") + if (getTestRunsMethod != null) { + val testRuns = getTestRunsMethod?.invoke(gradleTarget) as? Iterable + if (testRuns != null) { + val testReports = testRuns.mapNotNull { (it.javaClass.getMethodOrNull("getExecutionTask")?.invoke(it) as? TaskProvider)?.get() } + val testTasks = testReports.flatMap { + ((it.javaClass.getMethodOrNull("getTestTasks")?.invoke(it) as? Collection>)?.map { it.get() }) + ?: listOf(it) + } + return testTasks.mapNotNull { + val name = it.name + val compilation = it.javaClass.getMethodOrNull("getCompilation")?.invoke(it) + val compilationName = compilation?.javaClass?.getMethodOrNull("getCompilationName")?.invoke(compilation)?.toString() ?: KotlinCompilation.TEST_COMPILATION_NAME + KotlinTestTaskImpl(name, compilationName) + }.toList() + } + return emptyList() + } // Otherwise, find the Kotlin test task with names matching the target name. This is a workaround that makes assumptions about // the tasks naming logic and is therefore an unstable and temporary solution until test runs API is implemented: @@ -335,14 +369,23 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { // The 'targetName' of a test task matches the target disambiguation classifier, potentially with suffix, e.g. jsBrowser val getTargetName = kotlinTestTaskClass.getDeclaredMethodOrNull("getTargetName") ?: return emptyList() - return project.tasks.filter { kotlinTestTaskClass.isInstance(it) }.mapNotNull { task -> - val testTaskDisambiguationClassifier = getTargetName(task) as String? + val jvmTestTaskClass = try { + gradleTarget.javaClass.classLoader.loadClass("org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest") as Class + } catch (_: ClassNotFoundException) { + return emptyList() + } + val getJvmTargetName = jvmTestTaskClass.getDeclaredMethodOrNull("getTargetName") ?: return emptyList() + + + return project.tasks.filter { kotlinTestTaskClass.isInstance(it) || jvmTestTaskClass.isInstance(it)}.mapNotNull { task -> + val testTaskDisambiguationClassifier = + (if (kotlinTestTaskClass.isInstance(task)) getTargetName(task) else getJvmTargetName(task)) as String? task.name.takeIf { targetDisambiguationClassifier.isNullOrEmpty() || testTaskDisambiguationClassifier != null && testTaskDisambiguationClassifier.startsWith(targetDisambiguationClassifier.orEmpty()) } - }.map(::KotlinTestTaskImpl) + }.map { KotlinTestTaskImpl(it, KotlinCompilation.TEST_COMPILATION_NAME) } } private fun buildTargetJar(gradleTarget: Named, project: Project): KotlinTargetJar? { @@ -544,21 +587,14 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { for (compilation in target.compilations) { for (sourceSet in compilation.sourceSets) { sourceSetToCompilations.getOrPut(sourceSet) { LinkedHashSet() } += compilation + sourceSet.dependsOnSourceSets.mapNotNull { sourceSets[it] }.forEach { + sourceSetToCompilations.getOrPut(it) { LinkedHashSet() } += compilation + } } } } for (sourceSet in sourceSets.values) { - val name = sourceSet.name - if (name == KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) { - sourceSet.isTestModule = false - continue - } - if (name == KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) { - sourceSet.isTestModule = true - continue - } - val compilations = sourceSetToCompilations[sourceSet] if (compilations != null) { val platforms = compilations.map { it.platform } @@ -572,8 +608,16 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { sourceSet.isTestModule = compilations.all { it.isTestModule } } else { - // TODO: change me after design about it - sourceSet.isTestModule = "Test" in sourceSet.name + //TODO(auskov): remove this branch as far as import of orphan source sets is dropped + val name = sourceSet.name + if (name == KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) { + sourceSet.isTestModule = false + continue + } + if (name == KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) { + sourceSet.isTestModule = true + continue + } } } } diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt index 04e658ad6e1..d7699732040 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt @@ -150,7 +150,7 @@ data class KotlinTargetImpl( } }.toList(), target.testTasks.map { initialTestTask -> - (cloningCache[initialTestTask] as? KotlinTestTask) ?: KotlinTestTaskImpl(initialTestTask.taskName).also { + (cloningCache[initialTestTask] as? KotlinTestTask) ?: KotlinTestTaskImpl(initialTestTask.taskName, initialTestTask.compilationName).also { cloningCache[initialTestTask] = it } }, @@ -160,7 +160,8 @@ data class KotlinTargetImpl( } data class KotlinTestTaskImpl( - override val taskName: String + override val taskName: String, + override val compilationName: String ) : KotlinTestTask data class ExtraFeaturesImpl( diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt b/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt index 5a83013df2a..22a28bb4540 100644 --- a/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt @@ -188,6 +188,8 @@ fun KotlinFacet.configureFacet( module.externalCompilerVersion = compilerVersion } +fun Module.externalSystemTestTasks() = KotlinFacetSettingsProvider.getInstance(project).getInitializedSettings(this).externalSystemTestTasks + @Suppress("DEPRECATION_ERROR", "DeprecatedCallableAddReplaceWith") @Deprecated( message = "IdePlatform is deprecated and will be removed soon, please, migrate to org.jetbrains.kotlin.platform.TargetPlatform", diff --git a/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/build.gradle b/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/build.gradle new file mode 100644 index 00000000000..c5a95df5858 --- /dev/null +++ b/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/build.gradle @@ -0,0 +1,65 @@ +plugins { + id 'org.jetbrains.kotlin.multiplatform' version '1.3.50' +} +repositories { + mavenLocal() + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + mavenCentral() + +} + + +group 'com.example' +version '0.0.1' + +apply plugin: 'maven-publish' + +kotlin { + jvm { + } + + js { + browser { + } + nodejs { + } + } + + + sourceSets { + commonMain { + dependencies { + implementation kotlin('stdlib-common') + } + } + commonTest { + dependencies { + implementation kotlin('test-common') + implementation kotlin('test-annotations-common') + } + } + + jvmMain { + dependencies { + implementation kotlin('stdlib-jdk8') + } + } + jvmTest { + dependencies { + implementation kotlin('test') + implementation kotlin('test-junit') + } + } + jsMain { + dependencies { + implementation kotlin('stdlib-js') + } + } + jsTest { + dependencies { + implementation kotlin('test-js') + } + } + + } +} diff --git a/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/gradle.properties b/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/gradle.properties new file mode 100644 index 00000000000..7fc6f1ff272 --- /dev/null +++ b/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/settings.gradle b/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/settings.gradle new file mode 100644 index 00000000000..775de056c30 --- /dev/null +++ b/idea/testData/gradle/newMultiplatformImport/importTestsAndTargets/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + + mavenLocal() + + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + + mavenCentral() + + maven { url 'https://plugins.gradle.org/m2/' } + + + } +} +rootProject.name = 'project' + + +enableFeaturePreview('GRADLE_METADATA')