[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.KtSourceModuleFactory
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.AnalysisApiTestServiceRegistrar
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.TestServices
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 kotlin.io.path.extension
import kotlin.io.path.nameWithoutExtension
@@ -39,6 +42,11 @@ object AnalysisApiFe10TestConfigurator : AnalysisApiTestConfigurator() {
override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) {
builder.apply {
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.psi.PsiFile
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.KtModuleWithFiles
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.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.KtNotUnderContentRootModule
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.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.platform.isJs
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
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.isJvm
import org.jetbrains.kotlin.test.TestInfrastructureInternals
import org.jetbrains.kotlin.test.model.DependencyRelation
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
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 kotlin.io.path.absolute
import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.nameWithoutExtension
@@ -49,7 +46,7 @@ object TestModuleStructureFactory {
val moduleEntriesByName = moduleEntries.associateByName()
val binaryModulesBySourceRoots = mutableMapOf<Set<Path>, KtBinaryModule>()
val libraryCache = mutableMapOf<Set<Path>, KtBinaryModule>()
for (testModule in moduleStructure.modules) {
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
}
is KtModuleWithModifiableDependencies -> {
if (ktModule is KtBinaryModule) {
binaryModulesBySourceRoots.getOrPut(ktModule.getBinaryRoots().toSet()) { ktModule }
}
addModuleDependencies(testModule, moduleEntriesByName, ktModule)
stdlibAndSdkDependencies(ktModule, testModule, project, testServices).forEach { library ->
val cachedLibrary = binaryModulesBySourceRoots.getOrPut(library.getBinaryRoots().toSet()) { library }
ktModule.directRegularDependencies.add(cachedLibrary)
}
addLibraryDependencies(testModule, testServices, project, ktModule, libraryCache::getOrPut)
}
else -> error("Unexpected module type: " + ktModule.javaClass.name)
}
}
return KtModuleProjectStructure(moduleEntries, binaryModulesBySourceRoots.values)
return KtModuleProjectStructure(moduleEntries, libraryCache.values)
}
private fun stdlibAndSdkDependencies(
module: KtModule,
private fun addModuleDependencies(
testModule: TestModule,
project: Project,
testServices: TestServices,
): 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)
moduleByName: Map<String, KtModuleWithFiles>,
ktModule: KtModuleWithModifiableDependencies
) {
testModule.allDependencies.forEach { dependency ->
val dependencyKtModule = moduleByName.getValue(dependency.moduleName).ktModule
when (dependency.relation) {
@@ -123,104 +80,76 @@ object TestModuleStructureFactory {
}
}
private fun getLibraryModules(
testServices: TestServices,
private fun addLibraryDependencies(
testModule: TestModule,
project: Project
): List<KtLibraryModuleImpl> {
val configurationKind = JvmEnvironmentConfigurator.extractConfigurationKind(testModule.directives)
return JvmEnvironmentConfigurator
.getLibraryFilesExceptRealRuntime(testServices, configurationKind, testModule.directives)
.map { it.toPath().toAbsolutePath() }
.map { jar ->
createKtLibraryModuleByJar(
jar,
testServices,
project,
)
testServices: TestServices,
project: Project,
ktModule: KtModuleWithModifiableDependencies,
libraryCache: (paths: Set<Path>, factory: () -> KtBinaryModule) -> KtBinaryModule
) {
val compilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(testModule)
val classpathRoots = compilerConfiguration[CLIConfigurationKeys.CONTENT_ROOTS, emptyList()]
.mapNotNull { (it as? JvmClasspathRoot)?.file?.toPath() }
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(
jar: Path,
testServices: TestServices,
project: Project,
libraryName: String = jar.nameWithoutExtension,
): KtLibraryModuleImpl {
check(jar.extension == "jar")
check(jar.exists()) {
"library $jar does not exist"
for (libraryRoot in libraryRoots) {
check(libraryRoot.extension == "jar")
val libraryModule = libraryCache(setOf(libraryRoot)) {
createLibraryModule(project, libraryRoot, JvmPlatforms.defaultJvmPlatform, testServices)
}
ktModule.directRegularDependencies.add(libraryModule)
}
}
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(
testModule: TestModule,
private fun createLibraryModule(
project: Project,
libraryFile: Path,
platform: TargetPlatform,
testServices: TestServices,
): List<KtLibraryModule> {
val configurationKind = JvmEnvironmentConfigurator.extractConfigurationKind(testModule.directives)
if (!configurationKind.withRuntime) return emptyList()
val lib = testServices.standardLibrariesPathProvider.runtimeJarForTests().toPath().absolute()
return listOf(
createKtLibraryModuleByJar(lib, testServices, project, PathUtil.KOTLIN_JAVA_STDLIB_NAME),
)
): KtLibraryModuleImpl {
check(libraryFile.exists()) { "Library $libraryFile does not exist" }
val libraryName = libraryFile.nameWithoutExtension
val libraryScope = getScopeForLibraryByRoots(listOf(libraryFile), testServices)
return KtLibraryModuleImpl(libraryName, platform, libraryScope, project, listOf(libraryFile), librarySources = null)
}
private fun getJdkModule(
testModule: TestModule,
project: Project,
testServices: TestServices,
): 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,
fun getScopeForLibraryByRoots(roots: Collection<Path>, testServices: TestServices): GlobalSearchScope {
return StandaloneProjectFactory.createSearchScopeByLibraryRoots(
roots,
testServices.environmentManager.getProjectEnvironment()
)
}
@@ -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.services.TestModuleStructure
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() {
override val analyseInDependentSession: Boolean get() = false
@@ -39,7 +41,11 @@ object AnalysisApiFirLibraryBinaryTestConfigurator : AnalysisApiTestConfigurator
builder.apply {
useAdditionalService<TestModuleCompiler> { DispatchingTestModuleCompiler() }
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.TestServices
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() {
override val frontendKind: FrontendKind get() = FrontendKind.Fir
@@ -32,8 +34,12 @@ abstract class AnalysisApiFirSourceLikeTestConfigurator(override val analyseInDe
useDirectives(SealedClassesInheritorsCaclulatorPreAnalysisHandler.Directives)
usePreAnalysisHandlers(::SealedClassesInheritorsCaclulatorPreAnalysisHandler)
configureOptionalTestCompilerPlugin()
useConfigurators(::AnalysisApiJvmEnvironmentConfigurator)
useConfigurators(::ExternalAnnotationsEnvironmentConfigurator)
useConfigurators(
::CommonEnvironmentConfigurator,
::AnalysisApiJvmEnvironmentConfigurator,
::JsEnvironmentConfigurator,
::ExternalAnnotationsEnvironmentConfigurator
)
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.facade.MainCallParameters
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.platform.js.JsPlatforms
import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.resolve.TargetEnvironment
import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor
@@ -236,6 +237,8 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
}
override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) {
if (module.targetPlatform !in JsPlatforms.allJsPlatforms) return
val registeredDirectives = module.directives
val moduleKinds = registeredDirectives[MODULE_KIND]
val moduleKind = when (moduleKinds.size) {