GradleBuildRoot: extract GradleBuildRootsLocator for testing

This commit is contained in:
Sergey Rostov
2020-05-28 11:59:41 +03:00
parent c0f4ee7dc9
commit a3750b6419
8 changed files with 217 additions and 101 deletions
+15
View File
@@ -13,6 +13,21 @@
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<MarkdownNavigatorCodeStyleSettings>
@@ -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<ScriptingSupport> =
@@ -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
@@ -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<String>
@@ -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<String>
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
}
}
@@ -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())
}
}
@@ -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<GradleExecutionSettings>(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) {
@@ -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<String, GradleScriptInfo>()
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<String> = listOf(""),
relativeScripts: List<String> = 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)
}
@@ -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)
}
}