diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 8c3c5379392..252628460f1 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -13,6 +13,21 @@ + + diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt index ef3f31a0956..bad6d933696 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/configuration/ScriptingSupport.kt @@ -31,10 +31,10 @@ import org.jetbrains.kotlin.psi.KtFile * * @sample GradleBuildRootsManager */ -abstract class ScriptingSupport { - abstract fun isApplicable(file: VirtualFile): Boolean - abstract fun isConfigurationLoadingInProgress(file: KtFile): Boolean - abstract fun collectConfigurations(builder: ScriptClassRootsBuilder) +interface ScriptingSupport { + fun isApplicable(file: VirtualFile): Boolean + fun isConfigurationLoadingInProgress(file: KtFile): Boolean + fun collectConfigurations(builder: ScriptClassRootsBuilder) companion object { val EPN: ExtensionPointName = diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt index fdc9a682d12..c04f3a302b1 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleKotlinScriptConfigurationInputs.kt @@ -15,10 +15,9 @@ import org.jetbrains.kotlin.psi.KtFile /** * Up to date of gradle script depends on following factors: - * 1. It is out of date when essential [sections] are changed - * @see getGradleScriptInputsStamp + * 1. It is out of date when essential [sections] are changed. See [getGradleScriptInputsStamp]. * 2. When some related file is changed (other gradle script, gradle.properties file) - * @see GradleBuildRoot.Linked.areRelatedFilesChangedBefore + * See [GradleBuildRoot.Linked.areRelatedFilesChangedBefore]. * * [lastModifiedTs] is needed to check if some related file was changed since last update */ @@ -35,7 +34,7 @@ data class GradleKotlinScriptConfigurationInputs( return buildRoot == null || GradleBuildRootsManager.getInstance(project) - .getBuildRoot(buildRoot) + .getBuildRootByWorkingDir(buildRoot) ?.areRelatedFilesChangedBefore(file, lastModifiedTs) ?: false } catch (cancel: ProcessCanceledException) { return false diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt index d6dfa80232a..c55fb20790d 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.scripting.gradle.roots import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.core.script.ucache.ScriptClassRootsBuilder import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsContributor import org.jetbrains.kotlin.idea.scripting.gradle.LastModifiedFiles @@ -40,8 +41,6 @@ sealed class GradleBuildRoot { @Volatile var importing = false - abstract val manager: GradleBuildRootsManager - abstract val pathPrefix: String abstract val projectRoots: Collection @@ -52,7 +51,13 @@ sealed class GradleBuildRoot { private lateinit var lastModifiedFiles: LastModifiedFiles fun loadLastModifiedFiles() { - lastModifiedFiles = dir?.let { LastModifiedFiles.read(it) } ?: LastModifiedFiles() + val loaded = if (!skipLastModifiedFilesLoading) { + val dir = dir + if (dir != null) LastModifiedFiles.read(dir) + else null + } else null + + lastModifiedFiles = loaded ?: LastModifiedFiles() } fun saveLastModifiedFiles() { @@ -64,7 +69,6 @@ sealed class GradleBuildRoot { fun fileChanged(filePath: String, ts: Long) { lastModifiedFiles.fileChanged(ts, filePath) - manager.scheduleLastModifiedFilesSave() } } @@ -81,7 +85,6 @@ sealed class GradleBuildRoot { * Gradle build with old Gradle version (<6.0) */ class Legacy( - override val manager: GradleBuildRootsManager, settings: GradleProjectSettings ) : WithoutScriptModels(settings) { init { @@ -93,7 +96,6 @@ sealed class GradleBuildRoot { * Linked but not yet imported Gradle build. */ class New( - override val manager: GradleBuildRootsManager, settings: GradleProjectSettings ) : WithoutScriptModels(settings) { init { @@ -106,14 +108,10 @@ sealed class GradleBuildRoot { * Each imported build have info about all of it's Kotlin Build Scripts. */ class Imported( - override val manager: GradleBuildRootsManager, override val pathPrefix: String, val javaHome: File?, val data: GradleBuildRootData ) : Linked() { - val project: Project - get() = manager.project - override val projectRoots: Collection get() = data.projectRoots @@ -126,7 +124,7 @@ sealed class GradleBuildRoot { builder.sdks.addSdk(javaHome) } - val definitions = GradleScriptDefinitionsContributor.getDefinitions(project) + val definitions = GradleScriptDefinitionsContributor.getDefinitions(builder.project) builder.addTemplateClassesRoots(data.templateClasspath) @@ -151,4 +149,10 @@ sealed class GradleBuildRoot { return definitions.firstOrNull { it.isScript(scriptSource) } } } + + companion object { + @set:TestOnly + @get:TestOnly + internal var skipLastModifiedFilesLoading = false + } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocator.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocator.kt new file mode 100644 index 00000000000..a5ef4ff1116 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocator.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scripting.gradle.roots + +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.utils.addToStdlib.lastIndexOfOrNull + +/** + * Internal logic about finding script root for [GradleBuildRootsManager]. + * Extracted only for tests. + * + * @see GradleBuildRootsManager for details. + */ +abstract class GradleBuildRootsLocator { + protected val roots = GradleBuildRootIndex() + + abstract fun getScriptInfo(localPath: String): GradleScriptInfo? + + fun getBuildRootByWorkingDir(gradleWorkingDir: String) = + roots.getBuildByRootDir(gradleWorkingDir) + + fun getScriptInfo(file: VirtualFile): GradleScriptInfo? = + getScriptInfo(file.localPath) + + private val VirtualFile.localPath + get() = path + + fun maybeAffectedGradleProjectFile(filePath: String): Boolean = + filePath.endsWith("/gradle.properties") || + filePath.endsWith("/gradle.local") || + filePath.endsWith("/gradle-wrapper.properties") || + filePath.endsWith("/build.gradle.kts") || + filePath.endsWith("/settings.gradle.kts") || + filePath.endsWith("/init.gradle.kts") + + fun isAffectedGradleProjectFile(filePath: String): Boolean = + findAffectedFileRoot(filePath) != null + + fun findAffectedFileRoot(filePath: String): GradleBuildRoot.Linked? { + if (filePath.endsWith("/gradle.properties") || + filePath.endsWith("/gradle.local") + ) { + return roots.getBuildByProjectDir(filePath.substringBeforeLast("/")) + } else if (filePath.endsWith("/gradle-wrapper.properties")) { + val gradleWrapperDirIndex = filePath.lastIndexOfOrNull('/') ?: return null + val gradleDirIndex = filePath.lastIndexOfOrNull('/', gradleWrapperDirIndex - 1) ?: return null + val buildDirIndex = filePath.lastIndexOfOrNull('/', gradleDirIndex - 1) ?: return null + return roots.getBuildByRootDir(filePath.substring(0, buildDirIndex)) + } + + return findScriptBuildRoot(filePath, searchNearestLegacy = false)?.root as? GradleBuildRoot.Linked + } + + class ScriptUnderRoot( + val root: GradleBuildRoot?, + val script: GradleScriptInfo? = null + ) + + fun findScriptBuildRoot(gradleKtsFile: VirtualFile): ScriptUnderRoot? = + findScriptBuildRoot(gradleKtsFile.path) + + protected fun findScriptBuildRoot(filePath: String, searchNearestLegacy: Boolean = true): ScriptUnderRoot? { + if (!filePath.endsWith(".gradle.kts")) return null + + val scriptInfo = getScriptInfo(filePath) + val imported = scriptInfo?.buildRoot + if (imported != null) return ScriptUnderRoot(imported, scriptInfo) + + if (filePath.endsWith("/build.gradle.kts") || + filePath.endsWith("/settings.gradle.kts") || + filePath.endsWith("/init.gradle.kts") + ) { + // build|settings|init.gradle.kts scripts should be located near gradle project root only + val gradleBuild = roots.getBuildByProjectDir(filePath.substringBeforeLast("/")) + if (gradleBuild != null) return ScriptUnderRoot(gradleBuild) + } + + // other scripts: "included", "precompiled" scripts, scripts in unlinked projects, + // or just random files with ".gradle.kts" ending + + // todo(gradle6): remove, it is required only for projects with old gradle + if (searchNearestLegacy) { + val found = roots.findNearestRoot(filePath) + if (found is GradleBuildRoot.Legacy) return ScriptUnderRoot(found) + } + + return ScriptUnderRoot(GradleBuildRoot.Unlinked()) + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt index ea384c98be9..06bad6871ba 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt @@ -20,13 +20,13 @@ import kotlinx.coroutines.launch import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.configuration.CompositeScriptConfigurationManager import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport +import org.jetbrains.kotlin.idea.core.script.configuration.ScriptingSupport.Companion.EPN import org.jetbrains.kotlin.idea.core.script.ucache.ScriptClassRootsBuilder import org.jetbrains.kotlin.idea.core.util.EDT import org.jetbrains.kotlin.idea.scripting.gradle.* import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslGradleBuildSync import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance -import org.jetbrains.kotlin.utils.addToStdlib.lastIndexOfOrNull import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter import org.jetbrains.plugins.gradle.settings.* import org.jetbrains.plugins.gradle.util.GradleConstants @@ -56,15 +56,13 @@ import java.util.concurrent.atomic.AtomicBoolean * - [GradleBuildRoot.New] - not yet imported * - [GradleBuildRoot.Imported] - imported */ -class GradleBuildRootsManager(val project: Project) : ScriptingSupport() { +class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), ScriptingSupport { private val manager: CompositeScriptConfigurationManager get() = ScriptConfigurationManager.getInstance(project) as CompositeScriptConfigurationManager private val updater get() = manager.updater - private val roots = GradleBuildRootIndex() - //////////// /// ScriptingSupport.Provider implementation: @@ -98,87 +96,14 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport() { ////////////////// - private val VirtualFile.localPath - get() = path - - fun getScriptInfo(file: VirtualFile): GradleScriptInfo? = - getScriptInfo(file.localPath) - - fun getScriptInfo(localPath: String): GradleScriptInfo? = + override fun getScriptInfo(localPath: String): GradleScriptInfo? = manager.getLightScriptInfo(localPath) as? GradleScriptInfo - class ScriptUnderRoot( - val root: GradleBuildRoot?, - val script: GradleScriptInfo? = null - ) - - fun findScriptBuildRoot(gradleKtsFile: VirtualFile): ScriptUnderRoot? = - findScriptBuildRoot(gradleKtsFile.path) - - /** - * Fast method that can return false positive - */ - fun maybeAffectedGradleProjectFile(filePath: String): Boolean = - filePath.endsWith("/gradle.properties") || - filePath.endsWith("/gradle.local") || - filePath.endsWith("/gradle-wrapper.properties") || - filePath.endsWith("/build.gradle.kts") || - filePath.endsWith("/settings.gradle.kts") || - filePath.endsWith("/init.gradle.kts") - - fun isAffectedGradleProjectFile(filePath: String): Boolean = - findAffectedFileRoot(filePath) != null - fun fileChanged(filePath: String, ts: Long = System.currentTimeMillis()) { findAffectedFileRoot(filePath)?.fileChanged(filePath, ts) + scheduleLastModifiedFilesSave() } - private fun findAffectedFileRoot(filePath: String): GradleBuildRoot.Linked? { - if (filePath.endsWith("/gradle.properties") || - filePath.endsWith("/gradle.local") - ) { - return roots.getBuildByProjectDir(filePath.substringBeforeLast("/")) - } else if (filePath.endsWith("/gradle-wrapper.properties")) { - val gradleWrapperDirIndex = filePath.lastIndexOfOrNull('/') ?: return null - val gradleDirIndex = filePath.lastIndexOfOrNull('/', gradleWrapperDirIndex - 1) ?: return null - val buildDirIndex = filePath.lastIndexOfOrNull('/', gradleDirIndex - 1) ?: return null - return roots.getBuildByRootDir(filePath.substring(0, buildDirIndex)) - } - - return findScriptBuildRoot(filePath, searchNearestLegacy = false)?.root as? GradleBuildRoot.Linked - } - - private fun findScriptBuildRoot(filePath: String, searchNearestLegacy: Boolean = true): ScriptUnderRoot? { - if (!filePath.endsWith(".gradle.kts")) return null - - val scriptInfo = getScriptInfo(filePath) - val imported = scriptInfo?.buildRoot - if (imported != null) return ScriptUnderRoot(imported, scriptInfo) - - if (filePath.endsWith("/build.gradle.kts") || - filePath.endsWith("/settings.gradle.kts") || - filePath.endsWith("/init.gradle.kts") - ) { - // build|settings|init.gradle.kts scripts should be located near gradle project root only - val gradleBuild = roots.getBuildByProjectDir(filePath.substringBeforeLast("/")) - if (gradleBuild != null) return ScriptUnderRoot(gradleBuild) - } - - // other scripts: "included", "precompiled" scripts, scripts in unlinked projects, - // or just random files with ".gradle.kts" ending - - // todo(gradle6): remove, it is required only for projects with old gradle - if (searchNearestLegacy) { - val found = roots.findNearestRoot(filePath) - if (found is GradleBuildRoot.Legacy) return ScriptUnderRoot(found) - } - - return ScriptUnderRoot(GradleBuildRoot.Unlinked()) - } - - fun getBuildRoot(gradleWorkingDir: String) = - roots.getBuildByRootDir(gradleWorkingDir) - fun markImportingInProgress(workingDir: String, inProgress: Boolean = true) { actualizeBuildRoot(workingDir)?.importing = inProgress updateNotifications(workingDir) @@ -188,7 +113,7 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport() { fun update(build: KotlinDslGradleBuildSync) { // fast path for linked gradle builds without .gradle.kts support if (build.models.isEmpty()) { - val root = roots.getBuildByRootDir(build.workingDir) ?: return + val root = getBuildRootByWorkingDir(build.workingDir) ?: return if (root is GradleBuildRoot.Imported && root.data.models.isEmpty()) return } @@ -277,7 +202,7 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport() { */ private fun actualizeBuildRoot(workingDir: String): GradleBuildRoot.Linked? { val actualSettings = getGradleProjectSettings(workingDir) - val buildRoot = roots.getBuildByRootDir(workingDir) + val buildRoot = getBuildRootByWorkingDir(workingDir) return when { buildRoot != null -> when { @@ -335,7 +260,7 @@ class GradleBuildRootsManager(val project: Project) : ScriptingSupport() { .getExecutionSettings(project, externalProjectPath, GradleConstants.SYSTEM_ID) .javaHome?.let { File(it) } - return GradleBuildRoot.Imported(this, externalProjectPath, javaHome, data) + return GradleBuildRoot.Imported(externalProjectPath, javaHome, data) } private fun add(newRoot: GradleBuildRoot.Linked) { diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/AbstractGradleBuildRootsLocatorTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/AbstractGradleBuildRootsLocatorTest.kt new file mode 100644 index 00000000000..03948ace16d --- /dev/null +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/AbstractGradleBuildRootsLocatorTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scripting.gradle.roots + +import org.jetbrains.kotlin.idea.scripting.gradle.GradleKotlinScriptConfigurationInputs +import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel + +open class AbstractGradleBuildRootsLocatorTest { + init { + GradleBuildRoot.skipLastModifiedFilesLoading = true + } + + private val scripts = mutableMapOf() + private val locator = MyRootsLocator() + + private inner class MyRootsLocator : GradleBuildRootsLocator() { + override fun getScriptInfo(localPath: String): GradleScriptInfo? = scripts[localPath] + fun accessRoots() = roots + } + + private fun add(root: GradleBuildRoot.Linked) { + locator.accessRoots().add(root) + } + + fun newImportedGradleProject( + dir: String, + relativeProjectRoots: List = listOf(""), + relativeScripts: List = listOf("build.gradle.kts") + ) { + val pathPrefix = "$dir/" + val root = GradleBuildRoot.Imported( + dir, + null, + GradleBuildRootData( + relativeProjectRoots.map { (pathPrefix + it).removeSuffix("/") }, + listOf(), + listOf() + ) + ) + + add(root) + + relativeScripts.forEach { + val path = pathPrefix + it + scripts[path] = GradleScriptInfo( + root, + null, + KotlinDslScriptModel( + file = path, + inputs = GradleKotlinScriptConfigurationInputs("", 0, dir), + classPath = listOf(), + sourcePath = listOf(), + imports = listOf(), + messages = listOf() + ) + ) + } + } + + fun findScriptBuildRoot(filePath: String, searchNearestLegacy: Boolean = true) = + locator.findScriptBuildRoot(filePath, searchNearestLegacy) +} \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocatorTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocatorTest.kt new file mode 100644 index 00000000000..471b758d575 --- /dev/null +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocatorTest.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.scripting.gradle.roots + +import org.junit.Test + +class GradleBuildRootsLocatorTest : AbstractGradleBuildRootsLocatorTest() { + @Test + fun testUnlinkedBuildGradleKtsNearProjectRoot() { + newImportedGradleProject("imported", relativeScripts = listOf()) + assert(findScriptBuildRoot("imported/build.gradle.kts")?.root is GradleBuildRoot.Unlinked) + } +} \ No newline at end of file