Use script context to resolve SDK for a library that no modules depend on

Problem: The initial issues KTIJ-27480 [AE "Can't find built-in class kotlin.Cloneable" on opening build.gradle.kts with broken syntax highlighting] and KTIJ-25236 ["AssertionError: Can't find built-in class kotlin.Cloneable" after reload build.gradle.kts configuration] arose due to the following chain of events: we attempt to resolve a KTS script ⇒ in the script dependencies, we found a symbol for which Kotlin built-ins need to be created ⇒ creating of built-ins depends on the SDK and stdlib ⇒ (1) we find the SDK from the module to which this library is bound ⇒ (2) in our case, buildSrc module has no source set, so the library ends up hanging and not bound to any module ⇒ sdk=null ⇒ built-ins=Default (instead of JVM ones) ⇒ the required built-in is not found ⇒ internal resolve error. Such a situation (2) can occur if, for example, the buildSrc module does not have source folders and the Kotlin plugin is not applied. In this case, its list of source roots will be empty, and all libraries needed to resolve the script are not bound to any modules. Important notice: here we determine the SDK from the module. This behavior appears only when using the Composite resolver (only in the case of MPP projects). In the case of SEPARATE mode - the SDK is provided for the entire project, and such an error does not occur, while in SEPARATE it is prohibited and the SDK needs to be computed depending on the module.

Motivation: The situation where a library is not associated with any module because it is only used in a script remains unhandled. In this fix, we take into account this possible scenario and handle it to ensure correctness.

Solution: at the moment of building the mapping (1) between libraries and modules that use them, we also use information from `ScriptConfigurationManager` (if the search by direct dependencies does not yield results), to find the script that depends on this library. From this script, we add SDK information.

Note: in general, the architectural problem lies in the lack of context - in resolving, we can come to the same library from different contexts - scripts have their classpath, which requires the use of the Gradle JDK, the same library can be in the dependencies of buildSrc - there user can set any JDK using the toolchain. We can also find this library through symbol search. Thus, it turns out that the same library can have different environments, about which nothing is known at the moment.

^KTIJ-27480 Fixed


Merge-request: IJ-MR-129468
Merged-by: Aleksei Cherepanov <aleksei.cherepanov@jetbrains.com>

GitOrigin-RevId: 151c0bc82278ad379a8c091bcfc46850c9d83e9e
This commit is contained in:
Aleksei.Cherepanov
2024-03-22 17:46:39 +00:00
committed by intellij-monorepo-bot
parent 22790fbb78
commit d6d385913f
4 changed files with 24 additions and 2 deletions
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.idea.base.util.caching.findSdkBridge
import org.jetbrains.kotlin.idea.base.util.caching.getChanges
import org.jetbrains.kotlin.idea.caches.project.*
import org.jetbrains.kotlin.idea.configuration.isMavenized
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -177,6 +178,13 @@ class LibraryDependenciesCacheImpl(private val project: Project) : LibraryDepend
val filteredLibraries = filterForBuiltins(libraryInfo, libraryDependencyCandidatesAndSdkInfos.libraryDependencyCandidates)
libraryDependencyCandidatesAndSdkInfos.sdkInfos.takeIf { it.isEmpty() }?.apply {
val scriptConfigurationManager = ScriptConfigurationManager.getInstance(project)
scriptConfigurationManager.getScriptDependingOn(libraryInfo.getLibraryRoots())
?.let { script -> scriptConfigurationManager.getScriptSdk(script) }
?.let { sdk -> add(SdkInfo(project, sdk)) }
}
return LibraryDependencyCandidatesAndSdkInfos(filteredLibraries, libraryDependencyCandidatesAndSdkInfos.sdkInfos)
}
@@ -119,6 +119,7 @@ interface ScriptConfigurationManager {
fun getAllScriptDependenciesSources(): Collection<VirtualFile>
fun getAllScriptsSdkDependenciesClassFiles(): Collection<VirtualFile>
fun getAllScriptSdkDependenciesSources(): Collection<VirtualFile>
fun getScriptDependingOn(dependencies: Collection<String>): VirtualFile?
companion object {
fun getServiceIfCreated(project: Project): ScriptConfigurationManager? = project.serviceIfCreated()
@@ -4,11 +4,11 @@ package org.jetbrains.kotlin.idea.core.script.configuration
import com.intellij.codeInsight.daemon.OutsidersPsiFileSupport
import com.intellij.ide.scratch.ScratchUtil
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.backend.workspace.WorkspaceModelChangeListener
import com.intellij.platform.backend.workspace.WorkspaceModelTopics
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.idea.core.script.ucache.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
import java.nio.file.Path
/**
* The [CompositeScriptConfigurationManager] will provide redirection of [ScriptConfigurationManager] calls to the
@@ -183,6 +184,15 @@ class CompositeScriptConfigurationManager(val project: Project, val scope: Corou
override fun getAllScriptSdkDependenciesSources(): Collection<VirtualFile> =
classpathRoots.sdks.nonIndexedSourceRoots
override fun getScriptDependingOn(dependencies: Collection<String>): VirtualFile? =
classpathRoots.scriptsPaths().firstNotNullOfOrNull { scriptPath ->
VfsUtil.findFile(Path.of(scriptPath), true)?.takeIf { scriptVirtualFile ->
getScriptDependenciesClassFiles(scriptVirtualFile).any { scriptDependency ->
dependencies.contains(scriptDependency.presentableUrl)
}
}
}
override fun getScriptDependenciesClassFiles(file: VirtualFile): Collection<VirtualFile> =
classpathRoots.getScriptDependenciesClassFiles(file)
@@ -148,8 +148,11 @@ class ScriptClassRootsCache(
fun getScriptConfiguration(file: VirtualFile): ScriptCompilationConfigurationWrapper? =
getHeavyScriptInfo(file.path)?.scriptConfiguration
fun getScriptSdk(path: String): Sdk? =
getHeavyScriptInfo(path)?.sdk
fun getScriptSdk(file: VirtualFile): Sdk? =
getHeavyScriptInfo(file.path)?.sdk
getScriptSdk(file.path)
fun getScriptDependenciesClassFilesScope(file: VirtualFile): GlobalSearchScope =
getHeavyScriptInfo(file.path)?.classFilesScope ?: GlobalSearchScope.EMPTY_SCOPE