diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/analysisapi/providers/IncrementalKotlinDeclarationProvider.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/analysisapi/providers/IncrementalKotlinDeclarationProvider.kt new file mode 100644 index 0000000000..e373e0269f --- /dev/null +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/analysisapi/providers/IncrementalKotlinDeclarationProvider.kt @@ -0,0 +1,105 @@ +package com.google.devtools.ksp.analysisapi.providers + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analysis.project.structure.KtModule +import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProvider +import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProviderFactory +import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticDeclarationProviderFactory +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtClassLikeDeclaration +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.psi.KtTypeAlias + +class IncrementalKotlinDeclarationProvider(var del: KotlinDeclarationProvider) : KotlinDeclarationProvider() { + override fun computePackageSetWithTopLevelCallableDeclarations(): Set { + return del.computePackageSetWithTopLevelCallableDeclarations() + } + + override fun findFilesForFacade(facadeFqName: FqName): Collection { + return del.findFilesForFacade(facadeFqName) + } + + override fun findFilesForFacadeByPackage(packageFqName: FqName): Collection { + return del.findFilesForFacadeByPackage(packageFqName) + } + + override fun findFilesForScript(scriptFqName: FqName): Collection { + return del.findFilesForScript(scriptFqName) + } + + override fun findInternalFilesForFacade(facadeFqName: FqName): Collection { + return del.findInternalFilesForFacade(facadeFqName) + } + + override fun getAllClassesByClassId(classId: ClassId): Collection { + return del.getAllClassesByClassId(classId) + } + + override fun getAllTypeAliasesByClassId(classId: ClassId): Collection { + return del.getAllTypeAliasesByClassId(classId) + } + + override fun getClassLikeDeclarationByClassId(classId: ClassId): KtClassLikeDeclaration? { + return del.getClassLikeDeclarationByClassId(classId) + } + + override fun getTopLevelCallableFiles(callableId: CallableId): Collection { + return del.getTopLevelCallableFiles(callableId) + } + + override fun getTopLevelCallableNamesInPackage(packageFqName: FqName): Set { + return del.getTopLevelCallableNamesInPackage(packageFqName) + } + + override fun getTopLevelFunctions(callableId: CallableId): Collection { + return del.getTopLevelFunctions(callableId) + } + + override fun getTopLevelKotlinClassLikeDeclarationNamesInPackage(packageFqName: FqName): Set { + return del.getTopLevelKotlinClassLikeDeclarationNamesInPackage(packageFqName) + } + + override fun getTopLevelProperties(callableId: CallableId): Collection { + return del.getTopLevelProperties(callableId) + } +} + +class IncrementalKotlinDeclarationProviderFactory( + private val project: Project, +) : KotlinDeclarationProviderFactory() { + private var provider: IncrementalKotlinDeclarationProvider? = null + private lateinit var scope: GlobalSearchScope + private var contextualModule: KtModule? = null + private var files: Collection = emptyList() + + override fun createDeclarationProvider( + scope: GlobalSearchScope, + contextualModule: KtModule? + ): KotlinDeclarationProvider { + this.scope = scope + this.contextualModule = contextualModule + return IncrementalKotlinDeclarationProvider(createDelegateProvider()).also { + provider = it + } + } + + fun update(files: Collection) { + this.files = files + provider?.let { + it.del = createDelegateProvider() + } + } + + private fun createDelegateProvider(): KotlinDeclarationProvider { + val staticFactory = KotlinStaticDeclarationProviderFactory(project, files) + return staticFactory.createDeclarationProvider(scope, contextualModule) + } +} diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/analysisapi/providers/IncrementalKotlinPackageProvider.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/analysisapi/providers/IncrementalKotlinPackageProvider.kt new file mode 100644 index 0000000000..827a4fddc7 --- /dev/null +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/analysisapi/providers/IncrementalKotlinPackageProvider.kt @@ -0,0 +1,72 @@ +package com.google.devtools.ksp.analysisapi.providers + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analysis.providers.KotlinPackageProvider +import org.jetbrains.kotlin.analysis.providers.KotlinPackageProviderFactory +import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticPackageProviderFactory +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.psi.KtFile + +class IncrementalKotlinPackageProvider(var del: KotlinPackageProvider) : KotlinPackageProvider() { + override fun doesKotlinOnlyPackageExist(packageFqName: FqName): Boolean { + return del.doesKotlinOnlyPackageExist(packageFqName) + } + + override fun doesPackageExist(packageFqName: FqName, platform: TargetPlatform): Boolean { + return del.doesPackageExist(packageFqName, platform) + } + + override fun doesPlatformSpecificPackageExist(packageFqName: FqName, platform: TargetPlatform): Boolean { + return del.doesPlatformSpecificPackageExist(packageFqName, platform) + } + + override fun getKotlinOnlySubPackagesFqNames(packageFqName: FqName, nameFilter: (Name) -> Boolean): Set { + return del.getKotlinOnlySubPackagesFqNames(packageFqName, nameFilter) + } + + override fun getPlatformSpecificSubPackagesFqNames( + packageFqName: FqName, + platform: TargetPlatform, + nameFilter: (Name) -> Boolean + ): Set { + return del.getPlatformSpecificSubPackagesFqNames(packageFqName, platform, nameFilter) + } + + override fun getSubPackageFqNames( + packageFqName: FqName, + platform: TargetPlatform, + nameFilter: (Name) -> Boolean + ): Set { + return del.getSubPackageFqNames(packageFqName, platform, nameFilter) + } +} + +class IncrementalKotlinPackageProviderFactory( + private val project: Project, +) : KotlinPackageProviderFactory() { + private var provider: IncrementalKotlinPackageProvider? = null + private lateinit var scope: GlobalSearchScope + private var files: Collection = emptyList() + + override fun createPackageProvider(searchScope: GlobalSearchScope): KotlinPackageProvider { + this.scope = searchScope + return IncrementalKotlinPackageProvider(createDelegateProvider()).also { + provider = it + } + } + + fun update(files: Collection) { + this.files = files + provider?.let { + it.del = createDelegateProvider() + } + } + + private fun createDelegateProvider(): KotlinPackageProvider { + val staticFactory = KotlinStaticPackageProviderFactory(project, files) + return staticFactory.createPackageProvider(scope) + } +} diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/KotlinSymbolProcessing.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/KotlinSymbolProcessing.kt index b099a9f989..44c99d9fb4 100644 --- a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/KotlinSymbolProcessing.kt +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/KotlinSymbolProcessing.kt @@ -15,29 +15,100 @@ * limitations under the License. */ +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package com.google.devtools.ksp.impl import com.google.devtools.ksp.AnyChanges +import com.google.devtools.ksp.KSObjectCacheManager +import com.google.devtools.ksp.analysisapi.providers.IncrementalKotlinDeclarationProviderFactory +import com.google.devtools.ksp.analysisapi.providers.IncrementalKotlinPackageProviderFactory import com.google.devtools.ksp.impl.symbol.kotlin.KSFileImpl +import com.google.devtools.ksp.impl.symbol.kotlin.KSFileJavaImpl import com.google.devtools.ksp.impl.symbol.kotlin.analyze import com.google.devtools.ksp.processing.* import com.google.devtools.ksp.processing.impl.CodeGeneratorImpl import com.google.devtools.ksp.processing.impl.JvmPlatformInfoImpl import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSFile import com.google.devtools.ksp.toKotlinVersion import com.intellij.core.CoreApplicationEnvironment +import com.intellij.core.CorePackageIndex +import com.intellij.ide.highlighter.JavaFileType import com.intellij.mock.MockProject +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.Application +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.PackageIndex +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem +import com.intellij.psi.PsiFileSystemItem +import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiTreeChangeAdapter +import com.intellij.psi.PsiTreeChangeListener +import com.intellij.psi.impl.file.impl.JavaFileManager +import com.intellij.psi.search.DelegatingGlobalSearchScope +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.ProjectScope +import com.intellij.util.io.URLUtil import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals +import org.jetbrains.kotlin.analysis.api.impl.base.util.LibraryUtils import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider +import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider -import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.api.session.KtAnalysisSessionProvider +import org.jetbrains.kotlin.analysis.api.standalone.KotlinStaticPackagePartProviderFactory +import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.FirStandaloneServiceRegistrar +import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory +import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.FirSealedClassInheritorsProcessorFactory import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.JvmFirDeserializedSymbolProviderFactory -import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider +import org.jetbrains.kotlin.analysis.project.structure.KtModule +import org.jetbrains.kotlin.analysis.project.structure.KtModuleScopeProvider +import org.jetbrains.kotlin.analysis.project.structure.KtModuleScopeProviderImpl +import org.jetbrains.kotlin.analysis.project.structure.builder.KtModuleBuilder +import org.jetbrains.kotlin.analysis.project.structure.builder.KtModuleProviderBuilder +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtLibraryModule +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSdkModule +import org.jetbrains.kotlin.analysis.project.structure.builder.buildKtSourceModule +import org.jetbrains.kotlin.analysis.project.structure.impl.KtModuleProviderImpl +import org.jetbrains.kotlin.analysis.project.structure.impl.getSourceFilePaths +import org.jetbrains.kotlin.analysis.providers.KotlinAnnotationsResolverFactory +import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProviderFactory +import org.jetbrains.kotlin.analysis.providers.KotlinDeclarationProviderMerger +import org.jetbrains.kotlin.analysis.providers.KotlinModificationTrackerFactory +import org.jetbrains.kotlin.analysis.providers.KotlinPackageProviderFactory +import org.jetbrains.kotlin.analysis.providers.KotlinPsiDeclarationProviderFactory +import org.jetbrains.kotlin.analysis.providers.KotlinResolutionScopeProvider +import org.jetbrains.kotlin.analysis.providers.PackagePartProviderFactory +import org.jetbrains.kotlin.analysis.providers.impl.KotlinByModulesResolutionScopeProvider +import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticAnnotationsResolverFactory +import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticDeclarationProviderMerger +import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticModificationTrackerFactory +import org.jetbrains.kotlin.analysis.providers.impl.KotlinStaticPsiDeclarationProviderFactory +import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCliJavaFileManagerImpl +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment +import org.jetbrains.kotlin.cli.jvm.compiler.computeDefaultRootModules import org.jetbrains.kotlin.cli.jvm.compiler.createSourceFilesFromSourceRoots +import org.jetbrains.kotlin.cli.jvm.compiler.getJavaModuleRoots +import org.jetbrains.kotlin.cli.jvm.compiler.setupIdeaStandaloneExecution +import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoot import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.config.jvmModularRoots +import org.jetbrains.kotlin.cli.jvm.index.JavaRoot +import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesDynamicCompoundIndex +import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndexImpl +import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex +import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder +import org.jetbrains.kotlin.cli.jvm.modules.JavaModuleGraph import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration @@ -45,10 +116,220 @@ import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl import org.jetbrains.kotlin.config.languageVersionSettings +import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProvider +import org.jetbrains.kotlin.fir.declarations.SealedClassInheritorsProviderImpl +import org.jetbrains.kotlin.load.kotlin.PackagePartProvider +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.psi.KtFile +import java.nio.file.Files +import java.nio.file.Paths class KotlinSymbolProcessing( val kspConfig: KSPJvmConfig, ) { + init { + // We depend on swing (indirectly through PSI or something), so we want to declare headless mode, + // to avoid accidentally starting the UI thread. But, don't set it if it was set externally. + if (System.getProperty("java.awt.headless") == null) { + System.setProperty("java.awt.headless", "true") + } + setupIdeaStandaloneExecution() + } + + @OptIn(KtAnalysisApiInternals::class) + private fun createAASession( + compilerConfiguration: CompilerConfiguration, + applicationDisposable: Disposable = Disposer.newDisposable("StandaloneAnalysisAPISession.application"), + projectDisposable: Disposable = Disposer.newDisposable("StandaloneAnalysisAPISession.project"), + ): Triple> { + val kotlinCoreProjectEnvironment: KotlinCoreProjectEnvironment = + StandaloneProjectFactory.createProjectEnvironment( + projectDisposable, + applicationDisposable, + false, + classLoader = MockProject::class.java.classLoader + ) + + val application: Application = kotlinCoreProjectEnvironment.environment.application + val project: Project = kotlinCoreProjectEnvironment.project + val configLanguageVersionSettings = compilerConfiguration[CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS] + + CoreApplicationEnvironment.registerExtensionPoint( + project.extensionArea, + KtResolveExtensionProvider.EP_NAME.name, + KtResolveExtensionProvider::class.java + ) + (project as MockProject).registerService( + KtLifetimeTokenProvider::class.java, + KtAlwaysAccessibleLifeTimeTokenProvider::class.java + ) + + // replaces buildKtModuleProviderByCompilerConfiguration(compilerConfiguration) + val projectStructureProvider = KtModuleProviderBuilder().apply { + val compilerConfig = compilerConfiguration + val platform = JvmPlatforms.defaultJvmPlatform + + fun KtModuleBuilder.addModuleDependencies(moduleName: String) { + val libraryRoots = compilerConfig.jvmModularRoots + compilerConfig.jvmClasspathRoots + addRegularDependency( + buildKtLibraryModule { + this.platform = platform + this.project = project + contentScope = ProjectScope.getLibrariesScope(project) + binaryRoots = libraryRoots.map { it.toPath() } + libraryName = "Library for $moduleName" + } + ) + compilerConfig.get(JVMConfigurationKeys.JDK_HOME)?.let { jdkHome -> + val vfm = VirtualFileManager.getInstance() + val jdkHomePath = jdkHome.toPath() + val jdkHomeVirtualFile = vfm.findFileByNioPath(jdkHomePath) + val binaryRoots = LibraryUtils.findClassesFromJdkHome(jdkHomePath).map { + Paths.get(URLUtil.extractPath(it)) + } + addRegularDependency( + buildKtSdkModule { + this.platform = platform + this.project = project + contentScope = GlobalSearchScope.fileScope(project, jdkHomeVirtualFile) + this.binaryRoots = binaryRoots + sdkName = "JDK for $moduleName" + } + ) + } + } + + buildKtSourceModule { + configLanguageVersionSettings?.let { this.languageVersionSettings = it } + this.project = project + this.platform = platform + this.moduleName = compilerConfig.get(CommonConfigurationKeys.MODULE_NAME) ?: "" + + addModuleDependencies(moduleName) + + // Single file java source roots are added in reinitJavaFileManager() later. + val roots = kspConfig.sourceRoots + kspConfig.commonSourceRoots + kspConfig.javaSourceRoots + + listOf(kspConfig.kotlinOutputDir, kspConfig.javaOutputDir) + roots.forEach { + it.mkdirs() + } + val fs = StandardFileSystems.local() + contentScope = DirectoriesScope(project, roots.mapNotNull { fs.findFileByPath(it.path) }.toSet()) + }.apply(::addModule) + + this.platform = platform + this.project = project + }.build() + + // register services and build session + val ktModuleProviderImpl = projectStructureProvider as KtModuleProviderImpl + val modules = ktModuleProviderImpl.mainModules + val allSourceFiles = ktModuleProviderImpl.allSourceFiles() + StandaloneProjectFactory.registerServicesForProjectEnvironment( + kotlinCoreProjectEnvironment, + projectStructureProvider, + modules, + allSourceFiles, + ) + val ktFiles = allSourceFiles.filterIsInstance() + val libraryRoots = StandaloneProjectFactory.getAllBinaryRoots(modules, kotlinCoreProjectEnvironment) + val createPackagePartProvider = + StandaloneProjectFactory.createPackagePartsProvider( + project as MockProject, + libraryRoots, + ) + registerProjectServices( + kotlinCoreProjectEnvironment, + ktFiles, + createPackagePartProvider, + ) + + kotlinCoreProjectEnvironment.project.apply { + registerService( + KotlinPsiDeclarationProviderFactory::class.java, + KotlinStaticPsiDeclarationProviderFactory( + this, + ktModuleProviderImpl.binaryModules, + kotlinCoreProjectEnvironment.environment.jarFileSystem as CoreJarFileSystem + ) + ) + } + + project.registerService( + JvmFirDeserializedSymbolProviderFactory::class.java, + JvmFirDeserializedSymbolProviderFactory::class.java + ) + CoreApplicationEnvironment.registerExtensionPoint( + project.extensionArea, PsiTreeChangeListener.EP.name, PsiTreeChangeAdapter::class.java + ) + return Triple( + StandaloneAnalysisAPISession(kotlinCoreProjectEnvironment, createPackagePartProvider), + kotlinCoreProjectEnvironment, + modules + ) + } + + // TODO: org.jetbrains.kotlin.analysis.providers.impl.KotlinStatic* + @OptIn(KtAnalysisApiInternals::class) + private fun registerProjectServices( + kotlinCoreProjectEnvironment: KotlinCoreProjectEnvironment, + ktFiles: List, + packagePartProvider: (GlobalSearchScope) -> PackagePartProvider, + ) { + val project = kotlinCoreProjectEnvironment.project + project.apply { + FirStandaloneServiceRegistrar.registerProjectServices(project) + FirStandaloneServiceRegistrar.registerProjectExtensionPoints(project) + FirStandaloneServiceRegistrar.registerProjectModelServices( + project, + kotlinCoreProjectEnvironment.parentDisposable + ) + + registerService( + KotlinModificationTrackerFactory::class.java, + KotlinStaticModificationTrackerFactory::class.java + ) + registerService( + KtLifetimeTokenProvider::class.java, + KtReadActionConfinementLifetimeTokenProvider::class.java + ) + + registerService(KtModuleScopeProvider::class.java, KtModuleScopeProviderImpl()) + // Despite being a static implementation, this is only used by IDE tests + registerService( + KotlinAnnotationsResolverFactory::class.java, + KotlinStaticAnnotationsResolverFactory(ktFiles) + ) + registerService( + KotlinResolutionScopeProvider::class.java, + KotlinByModulesResolutionScopeProvider::class.java + ) + registerService( + KotlinDeclarationProviderFactory::class.java, + IncrementalKotlinDeclarationProviderFactory(this) + ) + registerService( + KotlinDeclarationProviderMerger::class.java, + KotlinStaticDeclarationProviderMerger(this) + ) + registerService(KotlinPackageProviderFactory::class.java, IncrementalKotlinPackageProviderFactory(project)) + + registerService( + FirSealedClassInheritorsProcessorFactory::class.java, + object : FirSealedClassInheritorsProcessorFactory() { + override fun createSealedClassInheritorsProvider(): SealedClassInheritorsProvider { + return SealedClassInheritorsProviderImpl + } + } + ) + + registerService( + PackagePartProviderFactory::class.java, + KotlinStaticPackagePartProviderFactory(packagePartProvider) + ) + } + } + @OptIn(KtAnalysisApiInternals::class) fun execute() { val deferredSymbols = mutableMapOf>() @@ -57,7 +338,9 @@ class KotlinSymbolProcessing( // TODO: CompilerConfiguration is deprecated. val compilerConfiguration: CompilerConfiguration = CompilerConfiguration().apply { addKotlinSourceRoots(kspConfig.sourceRoots.map { it.path }) + addKotlinSourceRoot(kspConfig.kotlinOutputDir.path) addJavaSourceRoots(kspConfig.javaSourceRoots) + addJavaSourceRoot(kspConfig.javaOutputDir) addJvmClasspathRoots(kspConfig.libraries) put(CommonConfigurationKeys.MODULE_NAME, kspConfig.moduleName) kspConfig.jdkHome?.let { @@ -71,77 +354,239 @@ class KotlinSymbolProcessing( ) } - val analysisAPISession = buildStandaloneAnalysisAPISession(withPsiDeclarationFromBinaryModuleProvider = true) { - CoreApplicationEnvironment.registerExtensionPoint( - project.extensionArea, - KtResolveExtensionProvider.EP_NAME.name, - KtResolveExtensionProvider::class.java - ) - (project as MockProject).registerService( - KtLifetimeTokenProvider::class.java, - KtAlwaysAccessibleLifeTimeTokenProvider::class.java + val (analysisAPISession, kotlinCoreProjectEnvironment, modules) = createAASession(compilerConfiguration) + + val kspCoreEnvironment = KSPCoreEnvironment(analysisAPISession.project as MockProject) + + // TODO: deferred symbols: use PSIs; they don't change. + // TODO: error handling, onError() + // TODO: performance + val project = analysisAPISession.project + val psiManager = PsiManager.getInstance(project) + var finished = false + var initialized = false + lateinit var codeGenerator: CodeGeneratorImpl + lateinit var processors: List + lateinit var newKSFiles: List + lateinit var newFileNames: Set + while (!finished) { + val ktFiles = createSourceFilesFromSourceRoots( + compilerConfiguration, analysisAPISession.project, compilerConfiguration.kotlinSourceRoots + ).toSet().toList() + val psiFiles = getPsiFilesFromPaths( + project, + getSourceFilePaths(compilerConfiguration, includeDirectoryRoot = true) ) - buildKtModuleProviderByCompilerConfiguration(compilerConfiguration) - }.apply { - (project as MockProject).registerService( - JvmFirDeserializedSymbolProviderFactory::class.java, - JvmFirDeserializedSymbolProviderFactory::class.java + + // Update Kotlin providers for newly generated source files. + ( + project.getService( + KotlinDeclarationProviderFactory::class.java + ) as IncrementalKotlinDeclarationProviderFactory + ).update(ktFiles) + ( + project.getService( + KotlinPackageProviderFactory::class.java + ) as IncrementalKotlinPackageProviderFactory + ).update(ktFiles) + + // Update Java providers for newly generated source files. + reinitJavaFileManager(kotlinCoreProjectEnvironment, modules, psiFiles) + + ResolverAAImpl.ktModule = modules.single() + val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) + val javaRoots = kspConfig.javaSourceRoots + kspConfig.javaOutputDir + // Get non-symbolic paths first + val javaFiles = javaRoots.sortedBy { Files.isSymbolicLink(it.toPath()) } + .flatMap { root -> root.walk().filter { it.isFile && it.extension == "java" }.toList() } + // This time is for .java files + .sortedBy { Files.isSymbolicLink(it.toPath()) } + .distinctBy { it.canonicalPath } + .mapNotNull { localFileSystem.findFileByPath(it.path)?.let { psiManager.findFile(it) } as? PsiJavaFile } + + val allKSFiles = + ktFiles.map { analyze { KSFileImpl.getCached(it.getFileSymbol()) } } + + javaFiles.map { KSFileJavaImpl.getCached(it) } + if (!initialized) { + val anyChangesWildcard = AnyChanges(kspConfig.projectBaseDir) + codeGenerator = CodeGeneratorImpl( + kspConfig.classOutputDir, + { kspConfig.javaOutputDir }, + kspConfig.kotlinOutputDir, + kspConfig.resourceOutputDir, + kspConfig.projectBaseDir, + anyChangesWildcard, + allKSFiles, + kspConfig.incremental + ) + + processors = providers.mapNotNull { provider -> + var processor: SymbolProcessor? = null + processor = provider.create( + SymbolProcessorEnvironment( + kspConfig.processorOptions, + kspConfig.languageVersion.toKotlinVersion(), + codeGenerator, + kspConfig.logger, + kspConfig.apiVersion.toKotlinVersion(), + // TODO: compilerVersion + KotlinVersion.CURRENT, + // TODO: fix platform info + listOf(JvmPlatformInfoImpl("JVM", "1.8", "disable")) + ) + ) + processor.also { deferredSymbols[it] = mutableListOf() } + } + + newKSFiles = allKSFiles + + initialized = true + } else { + newKSFiles = allKSFiles.filter { + it.filePath in newFileNames + } + } + // TODO: support no kotlin source input. + val resolver = ResolverAAImpl( + allKSFiles, + newKSFiles, + kspConfig, + analysisAPISession.project ) - } + ResolverAAImpl.instance = resolver - val kspCoreEnvironment = KSPCoreEnvironment(analysisAPISession.project as MockProject) + // TODO: multiple rounds + processors.forEach { it.process(resolver) } + + if (codeGenerator.generatedFile.isEmpty()) { + finished = true + processors.forEach { it.finish() } + } else { + // Drop caches + KotlinModificationTrackerFactory.getService(project) + .incrementModificationsCount(includeBinaryTrackers = false) + KtAnalysisSessionProvider.getInstance(project).clearCaches() + psiManager.dropResolveCaches() + psiManager.dropPsiCaches() - val ktFiles = createSourceFilesFromSourceRoots( - compilerConfiguration, analysisAPISession.project, compilerConfiguration.kotlinSourceRoots - ).toSet().toList() + KSObjectCacheManager.clear() - // TODO: support no Kotlin source mode. - ResolverAAImpl.ktModule = ktFiles.first().let { - analysisAPISession.project.getService(ProjectStructureProvider::class.java) - .getModule(it, null) + newFileNames = codeGenerator.generatedFile.filter { it.extension == "kt" || it.extension == "java" } + .map { it.canonicalPath }.toSet() + } + + codeGenerator.closeFiles() } - val ksFiles = ktFiles.map { file -> - analyze { KSFileImpl.getCached(file.getFileSymbol()) } + } +} + +private inline fun getPsiFilesFromPaths( + project: Project, + paths: Collection, +): List { + val fs = StandardFileSystems.local() + val psiManager = PsiManager.getInstance(project) + return buildList { + for (path in paths) { + val vFile = fs.findFileByPath(path) ?: continue + val psiFileSystemItem = + if (vFile.isDirectory) + psiManager.findDirectory(vFile) as? T + else + psiManager.findFile(vFile) as? T + psiFileSystemItem?.let { add(it) } } - val anyChangesWildcard = AnyChanges(kspConfig.projectBaseDir) - val codeGenerator = CodeGeneratorImpl( - kspConfig.classOutputDir, - { kspConfig.javaOutputDir }, - kspConfig.kotlinOutputDir, - kspConfig.resourceOutputDir, - kspConfig.projectBaseDir, - anyChangesWildcard, - ksFiles, - kspConfig.incremental - ) - val processors = providers.mapNotNull { provider -> - var processor: SymbolProcessor? = null - processor = provider.create( - SymbolProcessorEnvironment( - kspConfig.processorOptions, - kspConfig.languageVersion.toKotlinVersion(), - codeGenerator, - kspConfig.logger, - kspConfig.apiVersion.toKotlinVersion(), - // TODO: compilerVersion - KotlinVersion.CURRENT, - // TODO: fix platform info - listOf(JvmPlatformInfoImpl("JVM", "1.8", "disable")) - ) - ) - processor.also { deferredSymbols[it] = mutableListOf() } + } +} + +class DirectoriesScope( + project: Project, + private val directories: Set +) : DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) { + private val fileSystems = directories.mapTo(hashSetOf(), VirtualFile::getFileSystem) + + override fun contains(file: VirtualFile): Boolean { + if (file.fileSystem !in fileSystems) return false + + var parent: VirtualFile = file + while (true) { + if (parent in directories) return true + parent = parent.parent ?: return false } - // TODO: support no kotlin source input. - val resolver = ResolverAAImpl( - ktFiles.map { - analyze { it.getFileSymbol() } - }, - kspConfig, - analysisAPISession.project - ) - ResolverAAImpl.instance = resolver + } + + override fun toString() = "All files under: $directories" +} + +private fun reinitJavaFileManager( + environment: KotlinCoreProjectEnvironment, + modules: List, + sourceFiles: List, +) { + val project = environment.project + val javaFileManager = project.getService(JavaFileManager::class.java) as KotlinCliJavaFileManagerImpl + val javaModuleFinder = CliJavaModuleFinder(null, null, javaFileManager, project, null) + val javaModuleGraph = JavaModuleGraph(javaModuleFinder) + val allSourceFileRoots = sourceFiles.map { JavaRoot(it.virtualFile, JavaRoot.RootType.SOURCE) } + val jdkRoots = getDefaultJdkModuleRoots(javaModuleFinder, javaModuleGraph) + val libraryRoots = StandaloneProjectFactory.getAllBinaryRoots(modules, environment) + + val rootsWithSingleJavaFileRoots = buildList { + addAll(libraryRoots) + addAll(allSourceFileRoots) + addAll(jdkRoots) + } + + val (roots, singleJavaFileRoots) = rootsWithSingleJavaFileRoots.partition { (file) -> + file.isDirectory || file.extension != JavaFileType.DEFAULT_EXTENSION + } + + val corePackageIndex = project.getService(PackageIndex::class.java) as CorePackageIndex + val rootsIndex = JvmDependenciesDynamicCompoundIndex().apply { + addIndex(JvmDependenciesIndexImpl(roots)) + indexedRoots.forEach { javaRoot -> + if (javaRoot.file.isDirectory) { + if (javaRoot.type == JavaRoot.RootType.SOURCE) { + // NB: [JavaCoreProjectEnvironment#addSourcesToClasspath] calls: + // 1) [CoreJavaFileManager#addToClasspath], which is used to look up Java roots; + // 2) [CorePackageIndex#addToClasspath], which populates [PackageIndex]; and + // 3) [FileIndexFacade#addLibraryRoot], which conflicts with this SOURCE root when generating a library scope. + // Thus, here we manually call first two, which are used to: + // 1) create [PsiPackage] as a package resolution result; and + // 2) find directories by package name. + // With both supports, annotations defined in package-info.java can be properly propagated. + javaFileManager.addToClasspath(javaRoot.file) + corePackageIndex.addToClasspath(javaRoot.file) + } else { + environment.addSourcesToClasspath(javaRoot.file) + } + } + } + } + + javaFileManager.initialize( + rootsIndex, + listOf( + StandaloneProjectFactory.createPackagePartsProvider( + project, + libraryRoots + jdkRoots, + LanguageVersionSettingsImpl(LanguageVersion.LATEST_STABLE, ApiVersion.LATEST) + ).invoke(ProjectScope.getLibrariesScope(project)) + ), + SingleJavaFileRootsIndex(singleJavaFileRoots), + true + ) +} - // TODO: multiple rounds - processors.forEach { it.process(resolver) } +private fun getDefaultJdkModuleRoots( + javaModuleFinder: CliJavaModuleFinder, + javaModuleGraph: JavaModuleGraph +): List { + // In contrast to `ClasspathRootsResolver.addModularRoots`, we do not need to handle automatic Java modules because JDK modules + // aren't automatic. + return javaModuleGraph.getAllDependencies(javaModuleFinder.computeDefaultRootModules()).flatMap { moduleName -> + val module = javaModuleFinder.findModule(moduleName) ?: return@flatMap emptyList() + val result = module.getJavaModuleRoots() + result } } diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt index fad062ca77..ebfc8ca71b 100644 --- a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/ResolverAAImpl.kt @@ -33,8 +33,6 @@ import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.hasAnnotation import com.google.devtools.ksp.impl.symbol.kotlin.KSClassDeclarationEnumEntryImpl import com.google.devtools.ksp.impl.symbol.kotlin.KSClassDeclarationImpl -import com.google.devtools.ksp.impl.symbol.kotlin.KSFileImpl -import com.google.devtools.ksp.impl.symbol.kotlin.KSFileJavaImpl import com.google.devtools.ksp.impl.symbol.kotlin.KSFunctionDeclarationImpl import com.google.devtools.ksp.impl.symbol.kotlin.KSPropertyAccessorImpl import com.google.devtools.ksp.impl.symbol.kotlin.KSPropertyDeclarationImpl @@ -60,15 +58,10 @@ import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.toKSName import com.google.devtools.ksp.visitor.CollectAnnotatedSymbolsVisitor import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.psi.PsiJavaFile -import com.intellij.psi.PsiManager import com.intellij.psi.PsiMethod import com.intellij.psi.impl.file.impl.JavaFileManager import org.jetbrains.kotlin.analysis.api.fir.types.KtFirType import org.jetbrains.kotlin.analysis.api.symbols.KtEnumEntrySymbol -import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtJavaFieldSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol @@ -87,12 +80,11 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.name.Name import org.jetbrains.org.objectweb.asm.Opcodes -import java.io.File -import java.nio.file.Files @OptIn(KspExperimental::class) class ResolverAAImpl( - val ktFiles: List, + val allKSFiles: List, + val newKSFiles: List, val kspConfig: KSPJvmConfig, val project: Project ) : Resolver { @@ -101,23 +93,7 @@ class ResolverAAImpl( lateinit var ktModule: KtModule } - val javaFiles: List val javaFileManager = project.getService(JavaFileManager::class.java) as KotlinCliJavaFileManagerImpl - init { - val psiManager = PsiManager.getInstance(project) - val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) - // Get non-symbolic paths first - javaFiles = kspConfig.javaSourceRoots.sortedBy { Files.isSymbolicLink(it.toPath()) } - .flatMap { root -> root.walk().filter { it.isFile && it.extension == "java" }.toList() } - // This time is for .java files - .sortedBy { Files.isSymbolicLink(it.toPath()) } - .distinctBy { it.canonicalPath } - .mapNotNull { localFileSystem.findFileByPath(it.path)?.let { psiManager.findFile(it) } as? PsiJavaFile } - } - - private val ksFiles by lazy { - ktFiles.map { KSFileImpl.getCached(it) } + javaFiles.map { KSFileJavaImpl.getCached(it) } - } // TODO: fix in upstream for builtin types. override val builtIns: KSBuiltIns by lazy { @@ -265,7 +241,7 @@ class ResolverAAImpl( } override fun getAllFiles(): Sequence { - return ksFiles.asSequence() + return allKSFiles.asSequence() } override fun getClassDeclarationByName(name: KSName): KSClassDeclaration? { @@ -322,31 +298,10 @@ class ResolverAAImpl( else -> null } } - }.plus(javaPackageToClassMap.getOrDefault(packageName, emptyList()).asSequence()).asSequence() + }.asSequence() } } - private val javaPackageToClassMap: Map> by lazy { - val packageToClassMapping = mutableMapOf>() - ksFiles - .filter { file -> - file.origin == Origin.JAVA && - kspConfig.javaSourceRoots.any { root -> - file.filePath.startsWith(root.absolutePath) && - file.filePath.substringAfter(root.absolutePath) - .dropLastWhile { c -> c != File.separatorChar }.dropLast(1).drop(1) - .replace(File.separatorChar, '.') == file.packageName.asString() - } - } - .forEach { - packageToClassMapping.put( - it.packageName.asString(), - packageToClassMapping.getOrDefault(it.packageName.asString(), emptyList()).plus(it.declarations) - ) - } - packageToClassMapping - } - override fun getDeclarationsInSourceOrder(container: KSDeclarationContainer): Sequence { TODO("Not yet implemented") } @@ -433,7 +388,7 @@ class ResolverAAImpl( // FIXME: correct implementation after incremental is ready. override fun getNewFiles(): Sequence { - return getAllFiles().asSequence() + return newKSFiles.asSequence() } override fun getOwnerJvmClassName(declaration: KSFunctionDeclaration): String? { diff --git a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/KSPAATest.kt b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/KSPAATest.kt index 92e7118a04..4ec373bd54 100644 --- a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/KSPAATest.kt +++ b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/KSPAATest.kt @@ -635,4 +635,10 @@ class KSPAATest : AbstractKSPAATest() { fun testVisibilities() { runTest("../test-utils/testData/api/visibilities.kt") } + + @TestMetadata("multipleround.kt") + @Test + fun testMultipleround() { + runTest("../test-utils/testData/api/multipleround.kt") + } } diff --git a/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/ConstructorDeclarationsProcessor.kt b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/ConstructorDeclarationsProcessor.kt index ff51f80c63..8556027699 100644 --- a/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/ConstructorDeclarationsProcessor.kt +++ b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/ConstructorDeclarationsProcessor.kt @@ -24,9 +24,10 @@ import com.google.devtools.ksp.symbol.* class ConstructorDeclarationsProcessor : AbstractTestProcessor() { val visitor = ConstructorsVisitor() + lateinit var result: List override fun toResult(): List { - return visitor.toResult() + return result } override fun process(resolver: Resolver): List { @@ -39,6 +40,7 @@ class ConstructorDeclarationsProcessor : AbstractTestProcessor() { .getClassDeclarationByName("lib.${it.simpleName.asString()}") ?.accept(visitor, Unit) } + result = visitor.toResult() return emptyList() } diff --git a/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/MultipleroundProcessor.kt b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/MultipleroundProcessor.kt new file mode 100644 index 0000000000..d028f6a585 --- /dev/null +++ b/test-utils/src/main/kotlin/com/google/devtools/ksp/processor/MultipleroundProcessor.kt @@ -0,0 +1,59 @@ +package com.google.devtools.ksp.processor + +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSAnnotated + +class MultipleroundProcessor : AbstractTestProcessor() { + val result = mutableListOf() + override fun toResult(): List { + return result + } + + var round = 0 + override fun process(resolver: Resolver): List { + fun gen(cls: String, ext: String) { + env.codeGenerator.createNewFile(Dependencies(false), "com.example", cls, ext).use { + it.write("package com.example;".toByteArray()) + it.write("interface $cls {}".toByteArray()) + } + } + + when (round) { + 0 -> gen("I0", "kt") + 1 -> gen("I1", "java") + 2 -> gen("I2", "kt") + 3 -> gen("I3", "java") + 4 -> gen("I4", "kt") + 5 -> gen("I5", "java") + } + + fun check(cls: String) { + resolver.getClassDeclarationByName(resolver.getKSNameFromString(cls))?.let { c -> + result.add( + "${c.simpleName.asString()} : " + + c.superTypes.map { it.resolve().declaration.simpleName.asString() }.joinToString() + ) + } + } + + result.add("Round $round:") + check("K") + check("J") + val newFiles = resolver.getNewFiles().map { it.fileName }.toSet() + val allFiles = resolver.getAllFiles().map { it.fileName } + result.add(allFiles.map { if (it in newFiles) "+$it" else it }.sorted().joinToString()) + + round++ + return emptyList() + } + + lateinit var env: SymbolProcessorEnvironment + + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + env = environment + return this + } +} diff --git a/test-utils/testData/api/multipleround.kt b/test-utils/testData/api/multipleround.kt new file mode 100644 index 0000000000..1afec0cc26 --- /dev/null +++ b/test-utils/testData/api/multipleround.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * 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. + */ + +// TEST PROCESSOR: MultipleroundProcessor +// EXPECTED: +// Round 0: +// K : , , , , , +// J : , , , , , +// +J.java, +K.kt +// Round 1: +// K : I0, , , , , +// J : I0, , , , , +// +I0.kt, J.java, K.kt +// Round 2: +// K : I0, I1, , , , +// J : I0, I1, , , , +// +I1.java, I0.kt, J.java, K.kt +// Round 3: +// K : I0, I1, I2, , , +// J : I0, I1, I2, , , +// +I2.kt, I0.kt, I1.java, J.java, K.kt +// Round 4: +// K : I0, I1, I2, I3, , +// J : I0, I1, I2, I3, , +// +I3.java, I0.kt, I1.java, I2.kt, J.java, K.kt +// Round 5: +// K : I0, I1, I2, I3, I4, +// J : I0, I1, I2, I3, I4, +// +I4.kt, I0.kt, I1.java, I2.kt, I3.java, J.java, K.kt +// Round 6: +// K : I0, I1, I2, I3, I4, I5 +// J : I0, I1, I2, I3, I4, I5 +// +I5.java, I0.kt, I1.java, I2.kt, I3.java, I4.kt, J.java, K.kt +// END + +// FILE: K.kt +import com.example.* + +class K : I0, I1, I2, I3, I4, I5 + +// FILE: J.java +import com.example.*; + +class J implements I0, I1, I2, I3, I4, I5 { + +}