[Tests] Support KLIB metadata for stdlib in test compile utility

Create dependency provider able to resolve code from .knm metadata
files to simplify migration from the legacy kotlin-stdlib-common
artifact.

KTIJ-28668
KTI-1457
This commit is contained in:
Pavel Kirpichenkov
2024-02-21 20:04:52 +02:00
committed by Space Team
parent bfb1ef14f5
commit ae664d37ae
2 changed files with 116 additions and 8 deletions
@@ -146,6 +146,7 @@ class CommonResolverForModuleFactory(
targetEnvironment: TargetEnvironment,
capabilities: Map<ModuleCapability<*>, Any?> = emptyMap(),
dependenciesContainer: CommonDependenciesContainer? = null,
explicitProjectContext: ProjectContext? = null,
metadataPartProviderFactory: (ModuleContent<ModuleInfo>) -> MetadataPartProvider
): AnalysisResult {
val moduleInfo = SourceModuleInfo(
@@ -173,7 +174,7 @@ class CommonResolverForModuleFactory(
dependenciesContainer
)
val projectContext = ProjectContext(project, "metadata serializer")
val projectContext = explicitProjectContext ?: ProjectContext(project, "metadata serializer")
val resolver = ResolverForSingleModuleProject<ModuleInfo>(
"sources for metadata serializer",
@@ -6,6 +6,9 @@
package org.jetbrains.kotlin.test
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.common.CommonDependenciesContainer
import org.jetbrains.kotlin.analyzer.common.CommonPlatformAnalyzerServices
import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataMonolithicSerializer
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
@@ -21,24 +24,29 @@ import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.library.KLIB_FILE_EXTENSION
import org.jetbrains.kotlin.library.KotlinLibraryVersioning
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.ir.backend.js.moduleName
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutForWriter
import org.jetbrains.kotlin.library.impl.KotlinLibraryWriterImpl
import org.jetbrains.kotlin.library.metadata.KlibMetadataFactories
import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import org.jetbrains.kotlin.library.metadata.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.CommonPlatforms
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration
import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.util.DummyLogger
import java.io.File
import java.io.IOException
import java.nio.file.Path
import org.jetbrains.kotlin.konan.file.File as KFile
object KlibTestUtil {
@@ -53,6 +61,7 @@ object KlibTestUtil {
configuration.put(CommonConfigurationKeys.MODULE_NAME, libraryName)
configuration.addKotlinSourceRoots(sourceFiles.map { it.absolutePath })
val stdlibFile = ForTestCompileRuntime.stdlibCommonForTests()
// support for the legacy version of kotlin-stdlib-common (JAR with .kotlin_metadata)
configuration.addJvmClasspathRoot(stdlibFile)
val rootDisposable = Disposer.newDisposable("Disposable for ${KlibTestUtil::class.simpleName}.compileCommonSourcesToKlib")
@@ -63,6 +72,8 @@ object KlibTestUtil {
extensionConfigs = EnvironmentConfigFiles.METADATA_CONFIG_FILES
)
val projectContext = ProjectContext(environment.project, "Compile common sources to KLIB metadata")
val analyzer = AnalyzerWithCompilerReport(
configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY),
configuration.languageVersionSettings,
@@ -77,6 +88,9 @@ object KlibTestUtil {
environment.configuration.languageVersionSettings,
CommonPlatforms.defaultCommonPlatform,
CompilerEnvironment,
explicitProjectContext = projectContext,
// support for the KLIB version of kotlin-stdlib-common (KLIB with .knm metadata)
dependenciesContainer = createDependencyContainerForStdlibIfKlib(stdlibFile.toPath(), environment, projectContext),
) { content ->
environment.createPackagePartProvider(content.moduleContentScope)
}
@@ -153,3 +167,96 @@ object KlibTestUtil {
return module
}
}
/**
* Creates a dependency container that includes common stdlib, if the passed file path points to a metadata KLIB in the supported format.
* Note that [resolveSingleFileKlib] is sensitive to the library layout and to the file extension.
* In the case of a custom klib layout or file extension other than .klib, the library won't be resolved even if it contains .knm files.
* For the current kotlin-stdlib-common.jar it will always return null, the purpose of the function is to simplify future migration.
* It's been checked that the dependency container for a library with the supported layout is functional.
* See KTI-1457.
*/
private fun createDependencyContainerForStdlibIfKlib(
stdlibFilePath: Path,
environment: KotlinCoreEnvironment,
projectContext: ProjectContext,
): CommonDependenciesContainerImpl? {
val stdlibKlib = resolveSingleFileKlib(KFile(stdlibFilePath), strategy = ToolingSingleFileKlibResolveStrategy).also {
try {
it.moduleName // trigger an exception in case of unsupported file/layout
} catch (e: IOException) {
return null
}
}
val stdlibModuleDescriptor = createAndInitializeKlibBasedStdlibCommonDescriptor(stdlibKlib, environment, projectContext)
return CommonDependenciesContainerImpl(listOf(stdlibModuleDescriptor))
}
private fun createAndInitializeKlibBasedStdlibCommonDescriptor(
stdlibKlib: KotlinLibrary,
environment: KotlinCoreEnvironment,
projectContext: ProjectContext,
): ModuleDescriptor {
val stdlibCommonDescriptor = ModuleDescriptorImpl(
Name.special("<stdlib-common>"),
projectContext.storageManager, DefaultBuiltIns.Instance, CommonPlatforms.defaultCommonPlatform
).also {
it.setDependencies(it)
}
val metadataModuleDescriptorFactory: KlibMetadataModuleDescriptorFactory = KlibMetadataFactories(
{ DefaultBuiltIns.Instance },
NullFlexibleTypeDeserializer,
).DefaultDeserializedDescriptorFactory
val klibPackageFragmentProvider = metadataModuleDescriptorFactory.createPackageFragmentProvider(
library = stdlibKlib,
packageAccessHandler = null,
packageFragmentNames = parseModuleHeader(stdlibKlib.moduleHeaderData).packageFragmentNameList,
storageManager = projectContext.storageManager,
moduleDescriptor = stdlibCommonDescriptor,
configuration = CompilerDeserializationConfiguration(environment.configuration.languageVersionSettings),
compositePackageFragmentAddend = null,
lookupTracker = LookupTracker.DO_NOTHING,
)
stdlibCommonDescriptor.initialize(
CompositePackageFragmentProvider(
listOf(
klibPackageFragmentProvider,
DefaultBuiltIns.Instance.builtInsModule.packageFragmentProvider,
),
"Test provider for .knm metadata and built-in declarations of kotlin-stdlib-common"
)
)
return stdlibCommonDescriptor
}
private class CommonDependenciesContainerImpl(dependencies: Collection<ModuleDescriptor>) : CommonDependenciesContainer {
private class ModuleInfoImpl(val module: ModuleDescriptor) : ModuleInfo {
override val name: Name get() = module.name
override fun dependencies(): List<ModuleInfo> = listOf(this)
override fun dependencyOnBuiltIns(): ModuleInfo.DependencyOnBuiltIns = ModuleInfo.DependencyOnBuiltIns.LAST
override val platform: TargetPlatform get() = CommonPlatforms.defaultCommonPlatform
override val analyzerServices: PlatformDependentAnalyzerServices get() = CommonPlatformAnalyzerServices
}
private val moduleDescriptorByModuleInfo: Map<ModuleInfo, ModuleDescriptor> = dependencies.associateBy(::ModuleInfoImpl)
override val moduleInfos: List<ModuleInfo> get() = moduleDescriptorByModuleInfo.keys.toList()
override fun moduleDescriptorForModuleInfo(moduleInfo: ModuleInfo): ModuleDescriptor {
return moduleDescriptorByModuleInfo[moduleInfo]
?: error("Unknown module info $moduleInfo")
}
override fun registerDependencyForAllModules(moduleInfo: ModuleInfo, descriptorForModule: ModuleDescriptorImpl) = Unit
override fun packageFragmentProviderForModuleInfo(moduleInfo: ModuleInfo): PackageFragmentProvider? = null
override val friendModuleInfos: List<ModuleInfo> get() = emptyList()
override val refinesModuleInfos: List<ModuleInfo> get() = emptyList()
}