Support different target platforms for modules in AnalyzerFacade

Instead of reusing the same AnalyzerFacade that is used for resolution
of a module to resolve its dependencies, analyze each dependency
module/library with a facade depending on its target platform. Introduce
and use CommonLibraryDetectionUtil in addition to
KotlinJavaScriptLibraryDetectionUtil, to detect common libraries (with
.kotlin_metadata files).

Note that before multi-platform projects, this was not needed because
there were only two platforms (JVM and JS), and JVM module had only JVM
modules/libraries as dependencies, JS module had only JS
modules/libraries as dependencies. Now, for example, a JVM module can
have a common module/library as a dependency, and it would be incorrect
to analyze that dependency with JvmAnalyzerFacade because that facade
does not know anything about .kotlin_metadata files.

The changes in Dsl.kt and KotlinCacheServiceImpl.kt are needed because
PsiElement.getJavaDescriptorResolver, called from some IDE code, started
to fail on a common module, because the container for a common module
does not have the JavaDescriptorResolver
This commit is contained in:
Alexander Udalov
2017-04-11 19:25:42 +03:00
parent 722687acd6
commit 7fe9b99087
18 changed files with 202 additions and 73 deletions
@@ -34,9 +34,12 @@ inline fun <reified T : Any> ComponentProvider.get(): T {
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> ComponentProvider.tryGetService(request: Class<T>): T? {
return resolve(request)?.getValue() as T?
}
fun <T : Any> ComponentProvider.getService(request: Class<T>): T {
val valueDescriptor = resolve(request) ?: throw IllegalArgumentException("Unresolved service: $request")
return valueDescriptor.getValue() as T
return tryGetService(request) ?: throw IllegalArgumentException("Unresolved service: $request")
}
fun StorageComponentContainer.useInstance(instance: Any) {
@@ -157,66 +157,71 @@ interface ModuleInfo {
}
abstract class AnalyzerFacade<in P : PlatformAnalysisParameters> {
fun <M : ModuleInfo> setupResolverForProject(
debugName: String,
projectContext: ProjectContext,
modules: Collection<M>,
modulesContent: (M) -> ModuleContent,
platformParameters: P,
targetEnvironment: TargetEnvironment = CompilerEnvironment,
builtIns: (M) -> KotlinBuiltIns = { DefaultBuiltIns.Instance },
delegateResolver: ResolverForProject<M> = EmptyResolverForProject(),
packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty },
firstDependency: M? = null,
modulePlatforms: (M) -> MultiTargetPlatform?
): ResolverForProject<M> {
val storageManager = projectContext.storageManager
companion object {
fun <P : PlatformAnalysisParameters, M : ModuleInfo> setupResolverForProject(
debugName: String,
projectContext: ProjectContext,
modules: Collection<M>,
analyzerFacade: (M) -> AnalyzerFacade<P>,
modulesContent: (M) -> ModuleContent,
platformParameters: P,
targetEnvironment: TargetEnvironment = CompilerEnvironment,
builtIns: (M) -> KotlinBuiltIns = { DefaultBuiltIns.Instance },
delegateResolver: ResolverForProject<M> = EmptyResolverForProject(),
packagePartProviderFactory: (M, ModuleContent) -> PackagePartProvider = { _, _ -> PackagePartProvider.Empty },
firstDependency: M? = null,
modulePlatforms: (M) -> MultiTargetPlatform?
): ResolverForProject<M> {
val storageManager = projectContext.storageManager
val resolverForProject = ResolverForProjectImpl(debugName, modules.keysToMap { module ->
ModuleDescriptorImpl(module.name, storageManager, builtIns(module), modulePlatforms(module), module.capabilities)
}, delegateResolver)
val resolverForProject = ResolverForProjectImpl(debugName, modules.keysToMap { module ->
ModuleDescriptorImpl(module.name, storageManager, builtIns(module), modulePlatforms(module), module.capabilities)
}, delegateResolver)
for (module in modules) {
resolverForProject.descriptorForModule(module).setDependencies(LazyModuleDependencies(
storageManager,
computeDependencies = {
val orderedDependencies = listOfNotNull(firstDependency) + module.dependencies()
val dependenciesDescriptors = orderedDependencies.mapTo(ArrayList<ModuleDescriptorImpl>()) { dependencyInfo ->
resolverForProject.descriptorForModule(dependencyInfo as M)
for (module in modules) {
resolverForProject.descriptorForModule(module).setDependencies(LazyModuleDependencies(
storageManager,
computeDependencies = {
val orderedDependencies = listOfNotNull(firstDependency) + module.dependencies()
val dependenciesDescriptors = orderedDependencies.mapTo(ArrayList<ModuleDescriptorImpl>()) { dependencyInfo ->
resolverForProject.descriptorForModule(dependencyInfo as M)
}
module.dependencyOnBuiltIns().adjustDependencies(
resolverForProject.descriptorForModule(module).builtIns.builtInsModule, dependenciesDescriptors)
dependenciesDescriptors
},
computeModulesWhoseInternalsAreVisible = {
module.modulesWhoseInternalsAreVisible().mapTo(LinkedHashSet()) {
resolverForProject.descriptorForModule(it as M)
}
},
computeImplementingModules = {
if (modulePlatforms(module) != MultiTargetPlatform.Common) emptySet()
else modules
.filter { modulePlatforms(it) != MultiTargetPlatform.Common && module in it.dependencies() }
.mapTo(mutableSetOf(), resolverForProject::descriptorForModule)
}
module.dependencyOnBuiltIns().adjustDependencies(
resolverForProject.descriptorForModule(module).builtIns.builtInsModule, dependenciesDescriptors)
dependenciesDescriptors
},
computeModulesWhoseInternalsAreVisible = {
module.modulesWhoseInternalsAreVisible().mapTo(LinkedHashSet()) { resolverForProject.descriptorForModule(it as M) }
},
computeImplementingModules = {
if (modulePlatforms(module) != MultiTargetPlatform.Common) emptySet()
else modules
.filter { modulePlatforms(it) != MultiTargetPlatform.Common && module in it.dependencies() }
.mapTo(mutableSetOf(), resolverForProject::descriptorForModule)
}
)
)
}
for (module in modules) {
val descriptor = resolverForProject.descriptorForModule(module)
val computeResolverForModule = storageManager.createLazyValue {
val content = modulesContent(module)
createResolverForModule(
module, descriptor, projectContext.withModule(descriptor), modulesContent(module),
platformParameters, targetEnvironment, resolverForProject,
packagePartProviderFactory(module, content)
)
)
}
descriptor.initialize(DelegatingPackageFragmentProvider { computeResolverForModule().packageFragmentProvider })
resolverForProject.resolverByModuleDescriptor[descriptor] = computeResolverForModule
}
for (module in modules) {
val descriptor = resolverForProject.descriptorForModule(module)
val computeResolverForModule = storageManager.createLazyValue {
val content = modulesContent(module)
analyzerFacade(module).createResolverForModule(
module, descriptor, projectContext.withModule(descriptor), modulesContent(module),
platformParameters, targetEnvironment, resolverForProject,
packagePartProviderFactory(module, content)
)
}
return resolverForProject
descriptor.initialize(DelegatingPackageFragmentProvider { computeResolverForModule().packageFragmentProvider })
resolverForProject.resolverByModuleDescriptor[descriptor] = computeResolverForModule
}
return resolverForProject
}
}
protected abstract fun <M : ModuleInfo> createResolverForModule(
@@ -72,7 +72,7 @@ object DefaultAnalyzerFacade : AnalyzerFacade<PlatformAnalysisParameters>() {
val project = files.firstOrNull()?.project ?: throw AssertionError("No files to analyze")
val resolver = setupResolverForProject(
"sources for metadata serializer",
ProjectContext(project), listOf(moduleInfo),
ProjectContext(project), listOf(moduleInfo), { DefaultAnalyzerFacade },
{ ModuleContent(files, GlobalSearchScope.allScope(project)) },
object : PlatformAnalysisParameters {},
packagePartProviderFactory = packagePartProviderFactory,
@@ -61,6 +61,11 @@ public class ForTestCompileRuntime {
return assertExists(new File("dist/kotlinc/lib/kotlin-runtime-sources.jar"));
}
@NotNull
public static File stdlibCommonForTests() {
return assertExists(new File("dist/common/kotlin-stdlib-common.jar"));
}
// TODO: Do not use these classes, remove them after stdlib tests are merged in the same build as the compiler
@NotNull
@Deprecated
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve.lazy
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.AnalyzerFacade
import org.jetbrains.kotlin.analyzer.ModuleContent
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.container.get
@@ -35,9 +36,9 @@ fun createResolveSessionForFiles(
): ResolveSession {
val projectContext = ProjectContext(project)
val testModule = TestModule(addBuiltIns)
val resolverForProject = JvmAnalyzerFacade.setupResolverForProject(
val resolverForProject = AnalyzerFacade.setupResolverForProject(
"test",
projectContext, listOf(testModule),
projectContext, listOf(testModule), { JvmAnalyzerFacade },
{ ModuleContent(syntheticFiles, GlobalSearchScope.allScope(project)) },
JvmPlatformParameters { testModule },
modulePlatforms = { MultiTargetPlatform.Specific("JVM") }
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.jvm.compiler
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.AnalyzerFacade
import org.jetbrains.kotlin.analyzer.ModuleContent
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.ResolverForProject
@@ -65,9 +66,9 @@ class MultiModuleJavaAnalysisCustomTest : KtUsefulTestCase() {
val modules = setupModules(environment, moduleDirs)
val projectContext = ProjectContext(environment.project)
val builtIns = JvmBuiltIns(projectContext.storageManager)
val resolverForProject = JvmAnalyzerFacade.setupResolverForProject(
val resolverForProject = AnalyzerFacade.setupResolverForProject(
"test",
projectContext, modules,
projectContext, modules, { JvmAnalyzerFacade },
{ module -> ModuleContent(module.kotlinFiles, module.javaFilesScope) },
JvmPlatformParameters {
javaClass ->
@@ -67,7 +67,7 @@ object JsAnalyzerFacade : AnalyzerFacade<PlatformAnalysisParameters>() {
)
var packageFragmentProvider = container.get<ResolveSession>().packageFragmentProvider
if (moduleInfo is LibraryModuleInfo && moduleInfo.isJsLibrary()) {
if (moduleInfo is LibraryModuleInfo && moduleInfo.libraryPlatform == JsPlatform) {
val providers = moduleInfo.getLibraryRoots()
.flatMap { KotlinJavascriptMetadataUtils.loadMetadata(it) }
.filter { it.version.isCompatible() }
@@ -17,8 +17,10 @@
package org.jetbrains.kotlin.caches.resolve
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.resolve.TargetPlatform
interface LibraryModuleInfo : ModuleInfo {
fun isJsLibrary(): Boolean
val libraryPlatform: TargetPlatform
fun getLibraryRoots(): Collection<String>
}
@@ -32,11 +32,15 @@ import com.intellij.util.SmartList
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.framework.CommonLibraryDetectionUtil
import org.jetbrains.kotlin.idea.framework.KotlinJavaScriptLibraryDetectionUtil
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
import org.jetbrains.kotlin.idea.util.isInSourceContentWithoutInjected
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.GlobalSearchScopeWithModuleSources
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import java.util.*
interface IdeaModuleInfo : ModuleInfo {
@@ -200,7 +204,12 @@ class LibraryInfo(val project: Project, val library: Library) : IdeaModuleInfo,
return result.toList()
}
override fun isJsLibrary(): Boolean = KotlinJavaScriptLibraryDetectionUtil.isKotlinJavaScriptLibrary(library)
override val libraryPlatform: TargetPlatform
get() = when {
KotlinJavaScriptLibraryDetectionUtil.isKotlinJavaScriptLibrary(library) -> JsPlatform
CommonLibraryDetectionUtil.isCommonLibrary(library) -> TargetPlatform.Default
else -> JvmPlatform
}
override val sourcesModuleInfo: SourceForBinaryModuleInfo
get() = LibrarySourceInfo(project, library)
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.analyzer.EmptyResolverForProject
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.container.getService
import org.jetbrains.kotlin.container.tryGetService
import org.jetbrains.kotlin.context.GlobalContext
import org.jetbrains.kotlin.context.GlobalContextImpl
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
@@ -164,7 +165,7 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
}
fun <T : Any> tryGetProjectService(platform: TargetPlatform, ideaModuleInfo: IdeaModuleInfo, serviceClass: Class<T>): T? {
return globalFacade(platform, ideaModuleInfo.sdk).tryGetResolverForModuleInfo(ideaModuleInfo)?.componentProvider?.getService(serviceClass)
return globalFacade(platform, ideaModuleInfo.sdk).tryGetResolverForModuleInfo(ideaModuleInfo)?.componentProvider?.tryGetService(serviceClass)
}
private fun globalFacade(platform: TargetPlatform, sdk: Sdk?) =
@@ -23,10 +23,12 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.JdkOrderEntry
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.ModuleRootManager
import org.jetbrains.kotlin.analyzer.AnalyzerFacade
import org.jetbrains.kotlin.analyzer.ModuleContent
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.ResolverForProject
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.caches.resolve.LibraryModuleInfo
import org.jetbrains.kotlin.context.GlobalContextImpl
import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.idea.project.AnalyzerFacadeProvider
@@ -78,15 +80,13 @@ fun createModuleResolverProvider(
psiClass.getNullableModuleInfo()
}
return AnalyzerFacadeProvider.getAnalyzerFacade(platform).setupResolverForProject(
debugName, globalContext.withProject(project), modulesToCreateResolversFor, modulesContent,
jvmPlatformParameters, IdeaEnvironment, builtInsProvider,
return AnalyzerFacade.setupResolverForProject(
debugName, globalContext.withProject(project), modulesToCreateResolversFor,
{ module -> AnalyzerFacadeProvider.getAnalyzerFacade(module.getTargetPlatform() ?: platform) },
modulesContent, jvmPlatformParameters, IdeaEnvironment, builtInsProvider,
delegateResolver, { _, c -> IDEPackagePartProvider(c.moduleContentScope) },
sdk?.let { SdkInfo(project, it) },
modulePlatforms = { moduleInfo ->
val module = (moduleInfo as? ModuleSourceInfo)?.module
module?.let { TargetPlatformDetector.getPlatform(module).multiTargetPlatform }
}
modulePlatforms = { module -> module.getTargetPlatform()?.multiTargetPlatform }
)
}
@@ -105,6 +105,13 @@ fun createModuleResolverProvider(
)
}
private fun ModuleInfo.getTargetPlatform(): TargetPlatform? =
when (this) {
is ModuleSourceInfo -> TargetPlatformDetector.getPlatform(module)
is LibraryModuleInfo -> libraryPlatform
else -> null
}
fun collectAllModuleInfosFromIdeaModel(project: Project): List<IdeaModuleInfo> {
val ideaModules = ModuleManager.getInstance(project).modules.toList()
val modulesSourcesInfos = ideaModules.flatMap { listOf(it.productionSourceInfo(), it.testSourceInfo()) }
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.framework
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.caches.JarUserDataManager
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment
object CommonLibraryDetectionUtil {
fun isCommonLibrary(library: Library): Boolean {
if (library is LibraryEx && library.isDisposed) return false
for (root in library.getFiles(OrderRootType.CLASSES)) {
val hasMetadata = JarUserDataManager.hasFileWithProperty(HasCommonKotlinMetadataInJar, root)
if (hasMetadata != null) {
return hasMetadata
}
if (!VfsUtilCore.processFilesRecursively(root) { !isKotlinMetadataFile(it) }) {
return true
}
}
return false
}
private fun isKotlinMetadataFile(file: VirtualFile): Boolean =
!file.isDirectory &&
file.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION
object HasCommonKotlinMetadataInJar : JarUserDataManager.JarBooleanPropertyCounter(HasCommonKotlinMetadataInJar::class.simpleName!!) {
override fun hasProperty(file: VirtualFile) = isKotlinMetadataFile(file)
}
}
@@ -29,6 +29,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.caches.JarUserDataManager;
import org.jetbrains.kotlin.idea.debugger.filter.DebuggerFiltersUtilKt;
import org.jetbrains.kotlin.idea.framework.CommonLibraryDetectionUtil;
import org.jetbrains.kotlin.idea.framework.KotlinJavaScriptLibraryDetectionUtil;
import org.jetbrains.kotlin.utils.PathUtil;
@@ -59,6 +60,7 @@ public class PluginStartupComponent implements ApplicationComponent {
}
JarUserDataManager.INSTANCE.register(KotlinJavaScriptLibraryDetectionUtil.HasKotlinJSMetadataInJar.INSTANCE);
JarUserDataManager.INSTANCE.register(CommonLibraryDetectionUtil.HasCommonKotlinMetadataInJar.INSTANCE);
DebuggerFiltersUtilKt.addKotlinStdlibDebugFilterIfNeeded();
@@ -0,0 +1,3 @@
package test
class MyException : Exception()
@@ -0,0 +1,10 @@
package test
fun test(f: () -> Unit) {
try {
f()
}
catch (e: MyException) {
f()
}
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.openapi.roots.DependencyScope
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.TargetPlatformKind
@@ -64,9 +65,12 @@ class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
class MultiPlatform : AbstractMultiModuleHighlightingTest() {
override val testPath get() = super.testPath + "multiplatform/"
private fun doTest(vararg platforms: TargetPlatformKind<*>) {
private fun doTest(vararg platforms: TargetPlatformKind<*>, withStdlibCommon: Boolean = false) {
val commonModule = module("common")
commonModule.createFacet(TargetPlatformKind.Common)
if (withStdlibCommon) {
commonModule.addLibrary(ForTestCompileRuntime.stdlibCommonForTests())
}
for (platform in platforms) {
val path = when (platform) {
@@ -106,5 +110,9 @@ class MultiModuleHighlightingTest : AbstractMultiModuleHighlightingTest() {
fun testSuppressHeaderWithoutImpl() {
doTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6])
}
fun testCatchHeaderExceptionInPlatformModule() {
doTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], withStdlibCommon = true)
}
}
}
@@ -24,8 +24,11 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.module.StdModuleTypes
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.ui.configuration.libraryEditor.NewLibraryEditor
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.testFramework.PsiTestUtil
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.parser.com.sampullara.cli.Argument
@@ -35,6 +38,7 @@ import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.facet.getOrCreateFacet
import org.jetbrains.kotlin.idea.facet.initializeIfNeeded
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.KotlinJdkAndLibraryProjectDescriptor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import java.io.File
@@ -75,6 +79,13 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() {
exported: Boolean = false
) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported)
protected fun Module.addLibrary(jar: File) {
ConfigLibraryUtil.addLibrary(NewLibraryEditor().apply {
name = KotlinJdkAndLibraryProjectDescriptor.LIBRARY_NAME
addRoot(VfsUtil.getUrlForLibraryRoot(jar), OrderRootType.CLASSES)
}, this)
}
protected fun Module.createFacet(platformKind: TargetPlatformKind<*>? = null) {
val accessToken = WriteAction.start()
try {
+10 -1
View File
@@ -43,4 +43,13 @@ task sourcesJar(type: Jar, dependsOn: classes) {
artifacts {
archives sourcesJar
archives javadocJar
}
}
// TODO: call the "dist" task instead, once we need to publish kotlin-stdlib-common.jar with the compiler distribution
task distCommon(type: Copy) {
from(jar)
into "$distDir/common"
rename "-${java.util.regex.Pattern.quote(version)}", ''
}
dist.dependsOn distCommon