diff --git a/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt index bc5515bfeba..f6db0358ce6 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt @@ -21,18 +21,30 @@ import org.gradle.api.Task import org.gradle.api.artifacts.ProjectDependency import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder import org.jetbrains.plugins.gradle.tooling.ModelBuilderService +import java.io.File import java.io.Serializable import java.lang.Exception import java.lang.reflect.InvocationTargetException import java.util.* import kotlin.collections.HashSet -typealias CompilerArgumentsBySourceSet = Map> +interface ArgsInfo : Serializable { + val currentArguments: List + val defaultArguments: List + val dependencyClasspath: List +} + +class ArgsInfoImpl( + override val currentArguments: List, + override val defaultArguments: List, + override val dependencyClasspath: List +) : ArgsInfo + +typealias CompilerArgumentsBySourceSet = Map interface KotlinGradleModel : Serializable { val hasKotlinPlugin: Boolean - val currentCompilerArgumentsBySourceSet: CompilerArgumentsBySourceSet - val defaultCompilerArgumentsBySourceSet: CompilerArgumentsBySourceSet + val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet val coroutines: String? val platformPluginId: String? val transitiveCommonDependencies: Set @@ -40,8 +52,7 @@ interface KotlinGradleModel : Serializable { class KotlinGradleModelImpl( override val hasKotlinPlugin: Boolean, - override val currentCompilerArgumentsBySourceSet: CompilerArgumentsBySourceSet, - override val defaultCompilerArgumentsBySourceSet: CompilerArgumentsBySourceSet, + override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet, override val coroutines: String?, override val platformPluginId: String?, override val transitiveCommonDependencies: Set @@ -58,6 +69,7 @@ class KotlinGradleModelBuilder : ModelBuilderService { ) val kotlinPluginIds = listOf("kotlin", "kotlin2js", "kotlin-android") private val kotlinPlatformCommonPluginId = "kotlin-platform-common" + private val ABSTRACT_KOTLIN_COMPILE_CLASS = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile" } override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { @@ -104,25 +116,36 @@ class KotlinGradleModelBuilder : ModelBuilderService { } @Suppress("UNCHECKED_CAST") - private fun collectCompilerArguments( - compileTask: Task, - methodName: String, - argumentsBySourceSet: MutableMap> - ) { - val taskClass = compileTask::class.java - val sourceSetName = try { - taskClass.methods.firstOrNull { it.name.startsWith("getSourceSetName") && it.parameterCount == 0 }?.invoke(compileTask) as? String - } catch (e : InvocationTargetException) { - null // can be thrown if property is not initialized yet - } ?: "main" - try { - argumentsBySourceSet[sourceSetName] = taskClass.getDeclaredMethod(methodName).invoke(compileTask) as List + private fun Task.getCompilerArguments(methodName: String): List { + return try { + javaClass.getDeclaredMethod(methodName).invoke(this) as List } catch (e : NoSuchMethodException) { // No argument accessor method is available + emptyList() } } + private fun Task.getDependencyClasspath(): List { + try { + val abstractKotlinCompileClass = javaClass.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE_CLASS) + val getCompileClasspath = abstractKotlinCompileClass.getDeclaredMethod("getCompileClasspath").apply { isAccessible = true } + @Suppress("UNCHECKED_CAST") + return (getCompileClasspath.invoke(this) as Collection).map { it.path } + } + catch(e: ClassNotFoundException) { + // Leave arguments unchanged + } + catch (e: NoSuchMethodException) { + // Leave arguments unchanged + } + catch (e: InvocationTargetException) { + // We can safely ignore this exception here as getCompileClasspath() gets called again at a later time + // Leave arguments unchanged + } + return emptyList() + } + private fun getCoroutines(project: Project): String? { val kotlinExtension = project.extensions.findByName("kotlin") ?: return null val experimentalExtension = try { @@ -140,18 +163,28 @@ class KotlinGradleModelBuilder : ModelBuilderService { } } + private fun Task.getSourceSetName(): String { + return try { + javaClass.methods.firstOrNull { it.name.startsWith("getSourceSetName") && it.parameterCount == 0 }?.invoke(this) as? String + } catch (e : InvocationTargetException) { + null // can be thrown if property is not initialized yet + } ?: "main" + } + override fun buildAll(modelName: String?, project: Project): KotlinGradleModelImpl { val kotlinPluginId = kotlinPluginIds.singleOrNull { project.plugins.findPlugin(it) != null } val platformPluginId = platformPluginIds.singleOrNull { project.plugins.findPlugin(it) != null } - val currentCompilerArgumentsBySourceSet = LinkedHashMap>() - val defaultCompilerArgumentsBySourceSet = LinkedHashMap>() + val compilerArgumentsBySourceSet = LinkedHashMap() project.getAllTasks(false)[project]?.forEach { compileTask -> if (compileTask.javaClass.name !in kotlinCompileTaskClasses) return@forEach - collectCompilerArguments(compileTask, "getSerializedCompilerArguments", currentCompilerArgumentsBySourceSet) - collectCompilerArguments(compileTask, "getDefaultSerializedCompilerArguments", defaultCompilerArgumentsBySourceSet) + val sourceSetName = compileTask.getSourceSetName() + val currentArguments = compileTask.getCompilerArguments("getSerializedCompilerArguments") + val defaultArguments = compileTask.getCompilerArguments("getDefaultSerializedCompilerArguments") + val dependencyClasspath = compileTask.getDependencyClasspath() + compilerArgumentsBySourceSet[sourceSetName] = ArgsInfoImpl(currentArguments, defaultArguments, dependencyClasspath) } val platform = platformPluginId ?: pluginToPlatform.entries.singleOrNull { project.plugins.findPlugin(it.key) != null }?.value @@ -159,8 +192,7 @@ class KotlinGradleModelBuilder : ModelBuilderService { return KotlinGradleModelImpl( kotlinPluginId != null || platformPluginId != null, - currentCompilerArgumentsBySourceSet, - defaultCompilerArgumentsBySourceSet, + compilerArgumentsBySourceSet, getCoroutines(project), platform, transitiveCommon diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt index f18b11cbb2f..33d38c257d1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt @@ -37,10 +37,8 @@ import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.ge var DataNode.hasKotlinPlugin by NotNullableUserDataProperty(Key.create("HAS_KOTLIN_PLUGIN"), false) -var DataNode.currentCompilerArgumentsBySourceSet +var DataNode.compilerArgumentsBySourceSet by UserDataProperty(Key.create("CURRENT_COMPILER_ARGUMENTS")) -var DataNode.defaultCompilerArgumentsBySourceSet - by UserDataProperty(Key.create("DEFAULT_COMPILER_ARGUMENTS")) var DataNode.coroutines by UserDataProperty(Key.create("KOTLIN_COROUTINES")) var DataNode.platformPluginId @@ -71,8 +69,7 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() } ideModule.hasKotlinPlugin = gradleModel.hasKotlinPlugin - ideModule.currentCompilerArgumentsBySourceSet = gradleModel.currentCompilerArgumentsBySourceSet - ideModule.defaultCompilerArgumentsBySourceSet = gradleModel.defaultCompilerArgumentsBySourceSet + ideModule.compilerArgumentsBySourceSet = gradleModel.compilerArgumentsBySourceSet ideModule.coroutines = gradleModel.coroutines ideModule.platformPluginId = gradleModel.platformPluginId diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt index 96f3c5f7e4a..1dd2ba787ab 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt @@ -25,6 +25,8 @@ import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjec import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project +import com.intellij.util.PathUtil +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.config.CoroutineSupport import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.LanguageFeature @@ -36,6 +38,7 @@ import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion import org.jetbrains.kotlin.idea.inspections.gradle.getResolvedKotlinStdlibVersionByModuleData import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import java.io.File import java.util.* interface GradleProjectImportHandler { @@ -130,15 +133,29 @@ private fun configureFacetByGradleModule( val sourceSetName = sourceSetNode?.data?.id?.let { it.substring(it.lastIndexOf(':') + 1) } ?: "main" - val currentCompilerArguments = moduleNode.currentCompilerArgumentsBySourceSet?.get(sourceSetName) - val defaultCompilerArguments = moduleNode.defaultCompilerArgumentsBySourceSet?.get(sourceSetName) ?: emptyList() - if (currentCompilerArguments != null) { - parseCompilerArgumentsToFacet(currentCompilerArguments, defaultCompilerArguments, kotlinFacet) + val argsInfo = moduleNode.compilerArgumentsBySourceSet?.get(sourceSetName) + if (argsInfo != null) { + val currentCompilerArguments = argsInfo.currentArguments + val defaultCompilerArguments = argsInfo.defaultArguments + val dependencyClasspath = argsInfo.dependencyClasspath.map { PathUtil.toSystemIndependentName(it) } + if (currentCompilerArguments.isNotEmpty()) { + parseCompilerArgumentsToFacet(currentCompilerArguments, defaultCompilerArguments, kotlinFacet) + } + adjustClasspath(kotlinFacet, dependencyClasspath) } return kotlinFacet } +private fun adjustClasspath(kotlinFacet: KotlinFacet, dependencyClasspath: List) { + if (dependencyClasspath.isEmpty()) return + val arguments = kotlinFacet.configuration.settings.compilerArguments as? K2JVMCompilerArguments ?: return + val fullClasspath = arguments.classpath?.split(File.pathSeparator) ?: emptyList() + if (fullClasspath.isEmpty()) return + val newClasspath = fullClasspath - dependencyClasspath + arguments.classpath = if (newClasspath.isNotEmpty()) newClasspath.joinToString(File.pathSeparator) else null +} + private val gradlePropertyFiles = listOf("local.properties", "gradle.properties") private fun findKotlinCoroutinesProperty(project: Project): String { diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt index 3d2099a3fa0..3ee457abd9b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt @@ -1009,4 +1009,74 @@ class GradleFacetImportTest : GradleImportingTestCase() { Assert.assertNull(KotlinFacet.get(getModule("m1_main"))) Assert.assertNull(KotlinFacet.get(getModule("m1_test"))) } + + @Test + fun testClasspathWithDependenciesImport() { + createProjectSubFile("build.gradle", """ + group 'Again' + version '1.0-SNAPSHOT' + + buildscript { + repositories { + mavenCentral() + maven { + url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1' + } + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0") + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:1.1.0" + compile "org.apache.logging.log4j:log4j-core:2.7" + } + + compileKotlin { + kotlinOptions.freeCompilerArgs += ["-cp", "tmp.jar"] + } + """) + importProject() + + with (facetSettings) { + Assert.assertEquals("tmp.jar", (compilerArguments as K2JVMCompilerArguments).classpath) + } + } + + @Test + fun testDependenciesClasspathImport() { + createProjectSubFile("build.gradle", """ + group 'Again' + version '1.0-SNAPSHOT' + + buildscript { + repositories { + mavenCentral() + maven { + url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1' + } + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0") + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:1.1.0" + compile "org.apache.logging.log4j:log4j-core:2.7" + } + """) + importProject() + + with (facetSettings) { + Assert.assertEquals(null, (compilerArguments as K2JVMCompilerArguments).classpath) + } + } }