[Analysis API] Fix WITH_STDLIB for common modules in Analysis API tests

The previous implementation in 'TestModuleStructureFactory' had very
custom and redundant logic for binary dependency collection.
In particular, JDK and kotlin-stdlib were specifically computed,
although both already were in the module classpath, which was handled in
'createLibrariesByCompilerConfigurators()'. In addition, there was no
support for common modules.

The new behavior is much closer to what happens in production.
This commit is contained in:
Yan Zhulanow
2023-11-07 11:37:06 +09:00
committed by Space Team
parent 1a5b9ad79c
commit 75abae23d8
5 changed files with 99 additions and 147 deletions
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtMod
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtModuleFactory import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtModuleFactory
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtSourceModuleFactory import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtSourceModuleFactory
import org.jetbrains.kotlin.analysis.test.framework.project.structure.TestModuleStructureFactory import org.jetbrains.kotlin.analysis.test.framework.project.structure.TestModuleStructureFactory
import org.jetbrains.kotlin.analysis.test.framework.services.configuration.AnalysisApiJvmEnvironmentConfigurator
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestServiceRegistrar import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestServiceRegistrar
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.FrontendKind import org.jetbrains.kotlin.analysis.test.framework.test.configurators.FrontendKind
@@ -24,6 +25,8 @@ import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestModuleStructure import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.extension import kotlin.io.path.extension
import kotlin.io.path.nameWithoutExtension import kotlin.io.path.nameWithoutExtension
@@ -39,6 +42,11 @@ object AnalysisApiFe10TestConfigurator : AnalysisApiTestConfigurator() {
override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) { override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) {
builder.apply { builder.apply {
useAdditionalService<KtModuleFactory> { KtSourceModuleFactory } useAdditionalService<KtModuleFactory> { KtSourceModuleFactory }
useConfigurators(
::CommonEnvironmentConfigurator,
::AnalysisApiJvmEnvironmentConfigurator,
::JsEnvironmentConfigurator
)
} }
} }
@@ -8,31 +8,28 @@ package org.jetbrains.kotlin.analysis.test.framework.project.structure
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.KtNotUnderContentRootModule import org.jetbrains.kotlin.analysis.project.structure.KtNotUnderContentRootModule
import org.jetbrains.kotlin.analysis.test.framework.services.environmentManager import org.jetbrains.kotlin.analysis.test.framework.services.environmentManager
import org.jetbrains.kotlin.analysis.utils.errors.requireIsInstance
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.platform.isCommon import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.platform.isJs import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.js.JsPlatforms
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.test.TestInfrastructureInternals
import org.jetbrains.kotlin.test.model.DependencyRelation import org.jetbrains.kotlin.test.model.DependencyRelation
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.util.KtTestUtil import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.addIfNotNull
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.absolute import java.nio.file.Paths
import kotlin.io.path.exists import kotlin.io.path.exists
import kotlin.io.path.extension import kotlin.io.path.extension
import kotlin.io.path.nameWithoutExtension import kotlin.io.path.nameWithoutExtension
@@ -49,7 +46,7 @@ object TestModuleStructureFactory {
val moduleEntriesByName = moduleEntries.associateByName() val moduleEntriesByName = moduleEntries.associateByName()
val binaryModulesBySourceRoots = mutableMapOf<Set<Path>, KtBinaryModule>() val libraryCache = mutableMapOf<Set<Path>, KtBinaryModule>()
for (testModule in moduleStructure.modules) { for (testModule in moduleStructure.modules) {
val moduleWithFiles = moduleEntriesByName[testModule.name] ?: moduleEntriesByName.getValue(testModule.files.single().name) val moduleWithFiles = moduleEntriesByName[testModule.name] ?: moduleEntriesByName.getValue(testModule.files.single().name)
@@ -58,61 +55,21 @@ object TestModuleStructureFactory {
// Not-under-content-root modules have no external dependencies on purpose // Not-under-content-root modules have no external dependencies on purpose
} }
is KtModuleWithModifiableDependencies -> { is KtModuleWithModifiableDependencies -> {
if (ktModule is KtBinaryModule) {
binaryModulesBySourceRoots.getOrPut(ktModule.getBinaryRoots().toSet()) { ktModule }
}
addModuleDependencies(testModule, moduleEntriesByName, ktModule) addModuleDependencies(testModule, moduleEntriesByName, ktModule)
stdlibAndSdkDependencies(ktModule, testModule, project, testServices).forEach { library -> addLibraryDependencies(testModule, testServices, project, ktModule, libraryCache::getOrPut)
val cachedLibrary = binaryModulesBySourceRoots.getOrPut(library.getBinaryRoots().toSet()) { library }
ktModule.directRegularDependencies.add(cachedLibrary)
}
} }
else -> error("Unexpected module type: " + ktModule.javaClass.name) else -> error("Unexpected module type: " + ktModule.javaClass.name)
} }
} }
return KtModuleProjectStructure(moduleEntries, binaryModulesBySourceRoots.values) return KtModuleProjectStructure(moduleEntries, libraryCache.values)
} }
private fun stdlibAndSdkDependencies( private fun addModuleDependencies(
module: KtModule,
testModule: TestModule, testModule: TestModule,
project: Project, moduleByName: Map<String, KtModuleWithFiles>,
testServices: TestServices, ktModule: KtModuleWithModifiableDependencies
): List<KtBinaryModule> { ) {
return when {
module.platform.isJvm() -> jvmStdlibAndJdkDependencies(testModule, project, testServices)
module.platform.isJs() -> jsStdlibDependencies(project, testServices)
module.platform.isCommon() -> jvmStdlibAndJdkDependencies(testModule, project, testServices) // TODO: KT-63769
else -> error("Unsupported platform ${module.platform} in this test")
}
}
private fun jvmStdlibAndJdkDependencies(testModule: TestModule, project: Project, testServices: TestServices): List<KtBinaryModule> {
return buildList {
addIfNotNull(getJdkModule(testModule, project, testServices))
addAll(getStdlibModules(testModule, project, testServices))
addAll(getLibraryModules(testServices, testModule, project))
addAll(createLibrariesByCompilerConfigurators(testModule, testServices, project))
}
}
@OptIn(TestInfrastructureInternals::class)
private fun createLibrariesByCompilerConfigurators(
testModule: TestModule,
testServices: TestServices,
project: Project
): List<KtLibraryModuleImpl> {
val compilerConfiguration = createCompilerConfiguration(testModule, testServices.environmentConfigurators)
val contentRoots = compilerConfiguration[CLIConfigurationKeys.CONTENT_ROOTS, emptyList()]
return contentRoots
.filterIsInstance<JvmClasspathRoot>()
.map { root -> createKtLibraryModuleByJar(root.file.toPath(), testServices, project) }
}
private fun addModuleDependencies(testModule: TestModule, moduleByName: Map<String, KtModuleWithFiles>, ktModule: KtModule) {
requireIsInstance<KtModuleWithModifiableDependencies>(ktModule)
testModule.allDependencies.forEach { dependency -> testModule.allDependencies.forEach { dependency ->
val dependencyKtModule = moduleByName.getValue(dependency.moduleName).ktModule val dependencyKtModule = moduleByName.getValue(dependency.moduleName).ktModule
when (dependency.relation) { when (dependency.relation) {
@@ -123,104 +80,76 @@ object TestModuleStructureFactory {
} }
} }
private fun getLibraryModules( private fun addLibraryDependencies(
testServices: TestServices,
testModule: TestModule, testModule: TestModule,
project: Project testServices: TestServices,
): List<KtLibraryModuleImpl> { project: Project,
val configurationKind = JvmEnvironmentConfigurator.extractConfigurationKind(testModule.directives) ktModule: KtModuleWithModifiableDependencies,
return JvmEnvironmentConfigurator libraryCache: (paths: Set<Path>, factory: () -> KtBinaryModule) -> KtBinaryModule
.getLibraryFilesExceptRealRuntime(testServices, configurationKind, testModule.directives) ) {
.map { it.toPath().toAbsolutePath() } val compilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(testModule)
.map { jar ->
createKtLibraryModuleByJar( val classpathRoots = compilerConfiguration[CLIConfigurationKeys.CONTENT_ROOTS, emptyList()]
jar, .mapNotNull { (it as? JvmClasspathRoot)?.file?.toPath() }
testServices,
project, if (classpathRoots.isNotEmpty()) {
) val jdkKind = JvmEnvironmentConfigurator.extractJdkKind(testModule.directives)
val jdkHome = JvmEnvironmentConfigurator.getJdkHome(jdkKind)?.toPath()
?: JvmEnvironmentConfigurator.getJdkClasspathRoot(jdkKind)?.toPath()
?: Paths.get(System.getProperty("java.home"))
val (jdkRoots, libraryRoots) = classpathRoots.partition { jdkHome != null && it.startsWith(jdkHome) }
if (testModule.targetPlatform.isJvm() && jdkRoots.isNotEmpty()) {
val jdkModule = libraryCache(jdkRoots.toSet()) {
val jdkScope = getScopeForLibraryByRoots(jdkRoots, testServices)
KtJdkModuleImpl("jdk", JvmPlatforms.defaultJvmPlatform, jdkScope, project, jdkRoots)
}
ktModule.directRegularDependencies.add(jdkModule)
} }
}
private fun createKtLibraryModuleByJar( for (libraryRoot in libraryRoots) {
jar: Path, check(libraryRoot.extension == "jar")
testServices: TestServices,
project: Project, val libraryModule = libraryCache(setOf(libraryRoot)) {
libraryName: String = jar.nameWithoutExtension, createLibraryModule(project, libraryRoot, JvmPlatforms.defaultJvmPlatform, testServices)
): KtLibraryModuleImpl { }
check(jar.extension == "jar")
check(jar.exists()) { ktModule.directRegularDependencies.add(libraryModule)
"library $jar does not exist" }
}
val jsLibraryRootPaths = compilerConfiguration[JSConfigurationKeys.LIBRARIES].orEmpty()
for (libraryRootPath in jsLibraryRootPaths) {
val libraryRoot = Paths.get(libraryRootPath)
check(libraryRoot.extension == KLIB_FILE_EXTENSION)
val libraryModule = libraryCache(setOf(libraryRoot)) {
createLibraryModule(project, libraryRoot, JsPlatforms.defaultJsPlatform, testServices)
}
ktModule.directRegularDependencies.add(libraryModule)
} }
return KtLibraryModuleImpl(
libraryName,
JvmPlatforms.defaultJvmPlatform,
StandaloneProjectFactory.createSearchScopeByLibraryRoots(
listOf(jar),
testServices.environmentManager.getProjectEnvironment()
),
project,
listOf(jar),
librarySources = null,
)
} }
private fun getStdlibModules( private fun createLibraryModule(
testModule: TestModule,
project: Project, project: Project,
libraryFile: Path,
platform: TargetPlatform,
testServices: TestServices, testServices: TestServices,
): List<KtLibraryModule> { ): KtLibraryModuleImpl {
val configurationKind = JvmEnvironmentConfigurator.extractConfigurationKind(testModule.directives) check(libraryFile.exists()) { "Library $libraryFile does not exist" }
if (!configurationKind.withRuntime) return emptyList()
val lib = testServices.standardLibrariesPathProvider.runtimeJarForTests().toPath().absolute() val libraryName = libraryFile.nameWithoutExtension
return listOf( val libraryScope = getScopeForLibraryByRoots(listOf(libraryFile), testServices)
createKtLibraryModuleByJar(lib, testServices, project, PathUtil.KOTLIN_JAVA_STDLIB_NAME), return KtLibraryModuleImpl(libraryName, platform, libraryScope, project, listOf(libraryFile), librarySources = null)
)
} }
private fun getJdkModule( fun getScopeForLibraryByRoots(roots: Collection<Path>, testServices: TestServices): GlobalSearchScope {
testModule: TestModule, return StandaloneProjectFactory.createSearchScopeByLibraryRoots(
project: Project, roots,
testServices: TestServices, testServices.environmentManager.getProjectEnvironment()
): KtJdkModuleImpl? {
val jdkKind = JvmEnvironmentConfigurator.extractJdkKind(testModule.directives)
val jdkSourceRoots = buildList {
JvmEnvironmentConfigurator.getJdkHome(jdkKind)?.let { add(it.toPath()) }
JvmEnvironmentConfigurator.getJdkClasspathRoot(jdkKind)?.let { add(it.toPath()) }
}.mapTo(mutableListOf()) { it.toAbsolutePath() }
if (jdkSourceRoots.isEmpty()) return null
return KtJdkModuleImpl(
"jdk",
JvmPlatforms.defaultJvmPlatform,
StandaloneProjectFactory.createSearchScopeByLibraryRoots(
jdkSourceRoots,
testServices.environmentManager.getProjectEnvironment()
),
project,
jdkSourceRoots
)
}
private fun jsStdlibDependencies(project: Project, testServices: TestServices): List<KtBinaryModule> {
return listOf(
jsStdlib(project, testServices),
)
}
private fun jsStdlib(project: Project, testServices: TestServices): KtBinaryModule {
val jar = testServices.standardLibrariesPathProvider.fullJsStdlib().toPath()
return KtLibraryModuleImpl(
PathUtil.JS_LIB_NAME,
JvmPlatforms.defaultJvmPlatform,
StandaloneProjectFactory.createSearchScopeByLibraryRoots(
listOf(jar),
testServices.environmentManager.getProjectEnvironment()
),
project,
listOf(jar),
librarySources = null,
) )
} }
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.analysis.test.framework.test.configurators.FrontendK
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.services.TestModuleStructure import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
object AnalysisApiFirLibraryBinaryTestConfigurator : AnalysisApiTestConfigurator() { object AnalysisApiFirLibraryBinaryTestConfigurator : AnalysisApiTestConfigurator() {
override val analyseInDependentSession: Boolean get() = false override val analyseInDependentSession: Boolean get() = false
@@ -39,7 +41,11 @@ object AnalysisApiFirLibraryBinaryTestConfigurator : AnalysisApiTestConfigurator
builder.apply { builder.apply {
useAdditionalService<TestModuleCompiler> { DispatchingTestModuleCompiler() } useAdditionalService<TestModuleCompiler> { DispatchingTestModuleCompiler() }
useAdditionalService<TestModuleDecompiler> { TestModuleDecompilerJar() } useAdditionalService<TestModuleDecompiler> { TestModuleDecompilerJar() }
useConfigurators(::AnalysisApiJvmEnvironmentConfigurator) useConfigurators(
::CommonEnvironmentConfigurator,
::AnalysisApiJvmEnvironmentConfigurator,
::JsEnvironmentConfigurator
)
} }
} }
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.test.preprocessors.ExternalAnnotationsSourcePreproce
import org.jetbrains.kotlin.test.services.TestModuleStructure import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.configuration.ExternalAnnotationsEnvironmentConfigurator import org.jetbrains.kotlin.test.services.configuration.ExternalAnnotationsEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
abstract class AnalysisApiFirSourceLikeTestConfigurator(override val analyseInDependentSession: Boolean) : AnalysisApiTestConfigurator() { abstract class AnalysisApiFirSourceLikeTestConfigurator(override val analyseInDependentSession: Boolean) : AnalysisApiTestConfigurator() {
override val frontendKind: FrontendKind get() = FrontendKind.Fir override val frontendKind: FrontendKind get() = FrontendKind.Fir
@@ -32,8 +34,12 @@ abstract class AnalysisApiFirSourceLikeTestConfigurator(override val analyseInDe
useDirectives(SealedClassesInheritorsCaclulatorPreAnalysisHandler.Directives) useDirectives(SealedClassesInheritorsCaclulatorPreAnalysisHandler.Directives)
usePreAnalysisHandlers(::SealedClassesInheritorsCaclulatorPreAnalysisHandler) usePreAnalysisHandlers(::SealedClassesInheritorsCaclulatorPreAnalysisHandler)
configureOptionalTestCompilerPlugin() configureOptionalTestCompilerPlugin()
useConfigurators(::AnalysisApiJvmEnvironmentConfigurator) useConfigurators(
useConfigurators(::ExternalAnnotationsEnvironmentConfigurator) ::CommonEnvironmentConfigurator,
::AnalysisApiJvmEnvironmentConfigurator,
::JsEnvironmentConfigurator,
::ExternalAnnotationsEnvironmentConfigurator
)
useSourcePreprocessor(::ExternalAnnotationsSourcePreprocessor) useSourcePreprocessor(::ExternalAnnotationsSourcePreprocessor)
} }
} }
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.js.config.* import org.jetbrains.kotlin.js.config.*
import org.jetbrains.kotlin.js.facade.MainCallParameters import org.jetbrains.kotlin.js.facade.MainCallParameters
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.platform.js.JsPlatforms
import org.jetbrains.kotlin.resolve.CompilerEnvironment import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.resolve.TargetEnvironment import org.jetbrains.kotlin.resolve.TargetEnvironment
import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor
@@ -236,6 +237,8 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
} }
override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) { override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) {
if (module.targetPlatform !in JsPlatforms.allJsPlatforms) return
val registeredDirectives = module.directives val registeredDirectives = module.directives
val moduleKinds = registeredDirectives[MODULE_KIND] val moduleKinds = registeredDirectives[MODULE_KIND]
val moduleKind = when (moduleKinds.size) { val moduleKind = when (moduleKinds.size) {