diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 64e6e5c4f6..b5e77a19c3 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -22,12 +22,14 @@ plugins { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin-api:$kotlinBaseVersion") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinBaseVersion") + compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin-api:$kotlinBaseVersion") + compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinBaseVersion") compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinBaseVersion") // replace AGP dependency w/ gradle-api when we have source registering API available. compileOnly("com.android.tools.build:gradle:$agpBaseVersion") compileOnly(gradleApi()) + compileOnly(project(":kotlin-analysis-api")) + compileOnly(project(":api")) testImplementation(gradleApi()) testImplementation(project(":api")) testImplementation("junit:junit:$junitVersion") diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt new file mode 100644 index 0000000000..4d8415e4ab --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt @@ -0,0 +1,210 @@ +package com.google.devtools.ksp.gradle + +import com.google.devtools.ksp.impl.CommandLineKSPLogger +import com.google.devtools.ksp.impl.KSPJvmConfig +import com.google.devtools.ksp.impl.KotlinSymbolProcessing +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import org.gradle.api.DefaultTask +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskProvider +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool +import java.io.File +import java.net.URLClassLoader +import java.util.ServiceLoader +import javax.inject.Inject + +abstract class KspAATask @Inject constructor( + private val workerExecutor: WorkerExecutor, +) : DefaultTask() { + @get:Classpath + abstract val kspClasspath: ConfigurableFileCollection + + @get:Nested + abstract val kspConfig: KspGradleConfig + + @TaskAction + fun execute() { + // FIXME: Create a class loader with clean classpath instead of shadowing existing ones. It'll require either: + // 1. passing arguments by data structures in stdlib, or + // 2. hoisting and publishing KspGradleConfig into another package. + + val workerQueue = workerExecutor.classLoaderIsolation { + it.classpath.setFrom(kspClasspath) + } + workerQueue.submit(KspAAWorkerAction::class.java) { + it.config = kspConfig + } + } + + companion object { + @Internal + internal fun registerKspAATaskJvm( + kotlinCompilation: KotlinCompilation<*>, + kotlinCompileProvider: TaskProvider>, + processorClasspath: Configuration, + kspGeneratedSourceSet: KotlinSourceSet, + ): TaskProvider { + val project = kotlinCompilation.target.project + val target = kotlinCompilation.target.name + val sourceSetName = kotlinCompilation.defaultSourceSet.name + val kspTaskName = kotlinCompileProvider.name.replaceFirst("compile", "ksp") + val kspAADepCfg = project.configurations.detachedConfiguration( + project.dependencies.create("${KspGradleSubplugin.KSP_GROUP_ID}:symbol-processing-aa:$KSP_VERSION") + ).apply { + isTransitive = false + } + val kspTaskProvider = project.tasks.register(kspTaskName, KspAATask::class.java) { kspAATask -> + kspAATask.kspClasspath.from(kspAADepCfg) + kspAATask.kspConfig.let { cfg -> + cfg.processorClasspath.from(processorClasspath) + cfg.moduleName.value(kotlinCompilation.defaultSourceSet.name) + kotlinCompilation.allKotlinSourceSets + .filterNot { it == kspGeneratedSourceSet } + .forEach { sourceSet -> + cfg.sourceRoots.from(sourceSet.kotlin) + cfg.javaSourceRoots.from(sourceSet.kotlin) + } + if (kotlinCompilation is KotlinCommonCompilation) { + cfg.commonSourceRoots.from(kotlinCompilation.defaultSourceSet.kotlin) + } + cfg.libraries.from(kotlinCompilation.compileDependencyFiles) + val options = kotlinCompilation.compilerOptions.options + if (options is KotlinJvmCompilerOptions) { + // TODO: set proper jdk home + cfg.jdkHome.value(File(System.getProperty("java.home"))) + } + + val compilerOptions = kotlinCompilation.compilerOptions.options + val langVer = compilerOptions.languageVersion.orNull?.version ?: KSP_KOTLIN_BASE_VERSION + val apiVer = compilerOptions.apiVersion.orNull?.version ?: KSP_KOTLIN_BASE_VERSION + cfg.languageVersion.value(langVer.split('.', '-').take(2).joinToString(".")) + cfg.apiVersion.value(apiVer.split('.', '-').take(2).joinToString(".")) + + cfg.projectBaseDir.value(File(project.project.projectDir.canonicalPath)) + cfg.cachesDir.value(KspGradleSubplugin.getKspCachesDir(project, sourceSetName, target)) + cfg.outputBaseDir.value(KspGradleSubplugin.getKspOutputDir(project, sourceSetName, target)) + cfg.kotlinOutputDir.value(KspGradleSubplugin.getKspKotlinOutputDir(project, sourceSetName, target)) + cfg.javaOutputDir.value(KspGradleSubplugin.getKspJavaOutputDir(project, sourceSetName, target)) + cfg.classOutputDir.value(KspGradleSubplugin.getKspClassOutputDir(project, sourceSetName, target)) + cfg.resourceOutputDir.value( + KspGradleSubplugin.getKspResourceOutputDir( + project, + sourceSetName, + target + ) + ) + } + } + + return kspTaskProvider + } + } +} + +abstract class KspGradleConfig @Inject constructor() { + @get:Classpath + abstract val processorClasspath: ConfigurableFileCollection + + @get:Input + abstract val moduleName: Property + + @get:InputFiles + abstract val sourceRoots: ConfigurableFileCollection + + @get:InputFiles + abstract val commonSourceRoots: ConfigurableFileCollection + + @get:InputFiles + abstract val javaSourceRoots: ConfigurableFileCollection + + @get:Classpath + abstract val libraries: ConfigurableFileCollection + + @get:Input + abstract val jdkHome: Property + + @get:Internal + abstract val projectBaseDir: Property + + @get:Internal + abstract val outputBaseDir: Property + + @get:Internal + abstract val cachesDir: Property + + @get:OutputDirectory + abstract val kotlinOutputDir: Property + + @get:OutputDirectory + abstract val javaOutputDir: Property + + @get:OutputDirectory + abstract val classOutputDir: Property + + @get:OutputDirectory + abstract val resourceOutputDir: Property + + @get:Input + abstract val languageVersion: Property + + @get:Input + abstract val apiVersion: Property +} + +interface KspAAWorkParameter : WorkParameters { + var config: KspGradleConfig +} + +abstract class KspAAWorkerAction : WorkAction { + override fun execute() { + val gradleCfg = parameters.config + val processorClassloader = URLClassLoader( + gradleCfg.processorClasspath.files.map { it.toURI().toURL() }.toTypedArray(), + SymbolProcessorProvider::class.java.classLoader + ) + + val processorProviders = ServiceLoader.load( + SymbolProcessorProvider::class.java, + processorClassloader + ).toList() + val kspConfig = KSPJvmConfig.Builder().apply { + this.processorProviders = processorProviders + moduleName = gradleCfg.moduleName.get() + sourceRoots = gradleCfg.sourceRoots.files.toList() + javaSourceRoots = gradleCfg.javaSourceRoots.files.toList() + commonSourceRoots = gradleCfg.commonSourceRoots.files.toList() + libraries = gradleCfg.libraries.files.toList() + this.jdkHome = gradleCfg.jdkHome.get() + projectBaseDir = gradleCfg.projectBaseDir.get() + outputBaseDir = gradleCfg.outputBaseDir.get() + cachesDir = gradleCfg.cachesDir.get() + kotlinOutputDir = gradleCfg.kotlinOutputDir.get() + javaOutputDir = gradleCfg.javaOutputDir.get() + classOutputDir = gradleCfg.classOutputDir.get() + resourceOutputDir = gradleCfg.resourceOutputDir.get() + + languageVersion = gradleCfg.languageVersion.get() + apiVersion = gradleCfg.apiVersion.get() + + // TODO: + logger = CommandLineKSPLogger() + }.build() + KotlinSymbolProcessing(kspConfig).execute() + } +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt index dc87adfb41..dbdb8deac2 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt @@ -411,44 +411,53 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool } val isIncremental = project.findProperty("ksp.incremental")?.toString()?.toBoolean() ?: true + val isIntermoduleIncremental = + (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && isIncremental + val useKSP2 = project.findProperty("ksp.useK2")?.toString()?.toBoolean() ?: false // Create and configure KSP tasks. val kspTaskProvider = when (kotlinCompilation.platformType) { KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> { - KotlinFactories.registerKotlinJvmCompileTask(project, kspTaskName, kotlinCompilation).also { - it.configure { kspTask -> - val kotlinCompileTask = kotlinCompileProvider.get() as KotlinCompile - maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) - configureAsKspTask(kspTask, isIncremental) - configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) - configurePluginOptions(kspTask) - configureLanguageVersion(kspTask) - if (kspTask.classpathSnapshotProperties.useClasspathSnapshot.get() == false) { - kspTask.compilerOptions.moduleName.convention( - kotlinCompileTask.compilerOptions.moduleName.map { "$it-ksp" } - ) - } - - kspTask.destination.value(kspOutputDir) + if (useKSP2) { + KspAATask.registerKspAATaskJvm( + kotlinCompilation, + kotlinCompileProvider, + processorClasspath, + kspGeneratedSourceSet + ) + } else { + KotlinFactories.registerKotlinJvmCompileTask(project, kspTaskName, kotlinCompilation).also { + it.configure { kspTask -> + val kotlinCompileTask = kotlinCompileProvider.get() as KotlinCompile + maybeBlockOtherPlugins(kspTask as BaseKotlinCompile) + configureAsKspTask(kspTask, isIncremental) + configureAsAbstractKotlinCompileTool(kspTask as AbstractKotlinCompileTool<*>) + configurePluginOptions(kspTask) + configureLanguageVersion(kspTask) + if (kspTask.classpathSnapshotProperties.useClasspathSnapshot.get() == false) { + kspTask.compilerOptions.moduleName.convention( + kotlinCompileTask.compilerOptions.moduleName.map { "$it-ksp" } + ) + } - val isIntermoduleIncremental = - (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && - isIncremental - val classStructureFiles = getClassStructureFiles(project, kspTask.libraries) - kspTask.incrementalChangesTransformers.add( - createIncrementalChangesTransformer( - isIncremental, - isIntermoduleIncremental, - getKspCachesDir(project, sourceSetName, target), - project.provider { classStructureFiles }, - project.provider { kspTask.libraries }, - project.provider { processorClasspath } + kspTask.destination.value(kspOutputDir) + + val classStructureFiles = getClassStructureFiles(project, kspTask.libraries) + kspTask.incrementalChangesTransformers.add( + createIncrementalChangesTransformer( + isIncremental, + isIntermoduleIncremental, + getKspCachesDir(project, sourceSetName, target), + project.provider { classStructureFiles }, + project.provider { kspTask.libraries }, + project.provider { processorClasspath } + ) ) - ) + } + // Don't support binary generation for non-JVM platforms yet. + // FIXME: figure out how to add user generated libraries. + kotlinCompilation.output.classesDirs.from(classOutputDir) } - // Don't support binary generation for non-JVM platforms yet. - // FIXME: figure out how to add user generated libraries. - kotlinCompilation.output.classesDirs.from(classOutputDir) } } KotlinPlatformType.js, KotlinPlatformType.wasm -> { diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index 78e1ab9eb2..2cb53f0ee9 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -27,6 +27,7 @@ tasks.named("test") { dependsOn(":gradle-plugin:publishAllPublicationsToTestRepository") dependsOn(":symbol-processing:publishAllPublicationsToTestRepository") dependsOn(":symbol-processing-cmdline:publishAllPublicationsToTestRepository") + dependsOn(":kotlin-analysis-api:publishAllPublicationsToTestRepository") // JDK_9 environment property is required. // To add a custom location (if not detected automatically) follow https://docs.gradle.org/current/userguide/toolchains.html#sec:custom_loc diff --git a/kotlin-analysis-api/build.gradle.kts b/kotlin-analysis-api/build.gradle.kts index 541ebaac9a..2e1525baab 100644 --- a/kotlin-analysis-api/build.gradle.kts +++ b/kotlin-analysis-api/build.gradle.kts @@ -1,3 +1,4 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.google.devtools.ksp.RelativizingPathProvider description = "Kotlin Symbol Processing implementation using Kotlin Analysis API" @@ -10,10 +11,15 @@ val guavaVersion: String by project val kotlinBaseVersion: String by project val libsForTesting by configurations.creating val libsForTestingCommon by configurations.creating +val signingKey: String? by project +val signingPassword: String? by project plugins { kotlin("jvm") id("org.jetbrains.dokka") + id("com.github.johnrengelman.shadow") + `maven-publish` + signing } dependencies { @@ -49,7 +55,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4") implementation(kotlin("stdlib", kotlinBaseVersion)) - compileOnly("org.jetbrains.kotlin:kotlin-compiler:$kotlinBaseVersion") + implementation("org.jetbrains.kotlin:kotlin-compiler:$kotlinBaseVersion") implementation(project(":api")) implementation(project(":common-util")) @@ -136,3 +142,69 @@ repositories { } maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-ide-plugin-dependencies") } + +tasks.withType { + archiveClassifier.set("real") +} + +tasks.withType() { + archiveClassifier.set("") + minimize() +} + +tasks { + val sourcesJar by creating(Jar::class) { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) + } + val dokkaJavadocJar by creating(Jar::class) { + dependsOn(dokkaJavadoc) + from(dokkaJavadoc.flatMap { it.outputDirectory }) + archiveClassifier.set("javadoc") + } + publish { + dependsOn(shadowJar) + dependsOn(sourcesJar) + dependsOn(dokkaJavadocJar) + } +} + +publishing { + publications { + create("shadow") { + artifactId = "symbol-processing-aa" + artifact(tasks["shadowJar"]) + artifact(project(":kotlin-analysis-api").tasks["dokkaJavadocJar"]) + artifact(project(":kotlin-analysis-api").tasks["sourcesJar"]) + pom { + name.set("com.google.devtools.ksp:symbol-processing-aa") + description.set("KSP implementation on Kotlin Analysis API") + withXml { + fun groovy.util.Node.addDependency( + groupId: String, + artifactId: String, + version: String, + scope: String = "runtime" + ) { + appendNode("dependency").apply { + appendNode("groupId", groupId) + appendNode("artifactId", artifactId) + appendNode("version", version) + appendNode("scope", scope) + } + } + + asNode().appendNode("dependencies").apply { + addDependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlinBaseVersion) + } + } + } + } + } +} + +signing { + isRequired = hasProperty("signingKey") + useInMemoryPgpKeys(signingKey, signingPassword) + sign(extensions.getByType().publications) +} diff --git a/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/KSPConfig.kt b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/KSPConfig.kt new file mode 100644 index 0000000000..61d82221b6 --- /dev/null +++ b/kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/impl/KSPConfig.kt @@ -0,0 +1,172 @@ +package com.google.devtools.ksp.impl + +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import java.io.File + +abstract class KSPConfig( + val moduleName: String, + val sourceRoots: List, + val commonSourceRoots: List, + val libraries: List, + val logger: KSPLogger, + + val processorProviders: List, + val processorOptions: Map, + + val projectBaseDir: File, + val outputBaseDir: File, + val cachesDir: File, + + val classOutputDir: File, + val kotlinOutputDir: File, + val resourceOutputDir: File, + + val incremental: Boolean, + val incrementalLog: Boolean, + val modifiedSources: List, + val removedSources: List, + val changedClasses: List, + + val languageVersion: String, + val apiVersion: String, + + val allWarningsAsErrors: Boolean, + val mapAnnotationArgumentsInJava: Boolean, +) { + abstract class Builder { + lateinit var moduleName: String + lateinit var sourceRoots: List + var commonSourceRoots: List = emptyList() + var libraries: List = emptyList() + lateinit var logger: KSPLogger + + lateinit var processorProviders: List + var processorOptions = mapOf() + + lateinit var projectBaseDir: File + lateinit var outputBaseDir: File + lateinit var cachesDir: File + + lateinit var classOutputDir: File + lateinit var kotlinOutputDir: File + lateinit var resourceOutputDir: File + + var incremental: Boolean = false + var incrementalLog: Boolean = false + var modifiedSources: List = emptyList() + var removedSources: List = emptyList() + var changedClasses: List = emptyList() + + lateinit var languageVersion: String + lateinit var apiVersion: String + + var allWarningsAsErrors: Boolean = false + var mapAnnotationArgumentsInJava: Boolean = false + } +} + +class KSPJvmConfig( + val javaSourceRoots: List, + val javaOutputDir: File, + val jdkHome: File?, + moduleName: String, + sourceRoots: List, + commonSourceRoots: List, + libraries: List, + logger: KSPLogger, + + processorProviders: List, + processorOptions: Map, + + projectBaseDir: File, + outputBaseDir: File, + cachesDir: File, + + classOutputDir: File, + kotlinOutputDir: File, + resourceOutputDir: File, + + incremental: Boolean, + incrementalLog: Boolean, + modifiedSources: List, + removedSources: List, + changedClasses: List, + + languageVersion: String, + apiVersion: String, + + allWarningsAsErrors: Boolean, + mapAnnotationArgumentsInJava: Boolean, +) : KSPConfig( + moduleName, + sourceRoots, + commonSourceRoots, + libraries, + logger, + + processorProviders, + processorOptions, + + projectBaseDir, + outputBaseDir, + cachesDir, + + classOutputDir, + kotlinOutputDir, + resourceOutputDir, + + incremental, + incrementalLog, + modifiedSources, + removedSources, + changedClasses, + + languageVersion, + apiVersion, + + allWarningsAsErrors, + mapAnnotationArgumentsInJava, +) { + class Builder : KSPConfig.Builder() { + var javaSourceRoots: List = emptyList() + lateinit var javaOutputDir: File + var jdkHome: File? = null + + fun build(): KSPJvmConfig { + return KSPJvmConfig( + javaSourceRoots, + javaOutputDir, + jdkHome, + + moduleName, + sourceRoots, + commonSourceRoots, + libraries, + logger, + + processorProviders, + processorOptions, + + projectBaseDir, + outputBaseDir, + cachesDir, + classOutputDir, + kotlinOutputDir, + resourceOutputDir, + + incremental, + incrementalLog, + modifiedSources, + removedSources, + changedClasses, + + languageVersion, + apiVersion, + + allWarningsAsErrors, + mapAnnotationArgumentsInJava + ) + } + } +} 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 6a32d97f78..dbda92ee58 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 @@ -18,122 +18,123 @@ package com.google.devtools.ksp.impl import com.google.devtools.ksp.AnyChanges -import com.google.devtools.ksp.KspOptions import com.google.devtools.ksp.impl.symbol.kotlin.KSFileImpl 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.toKotlinVersion +import com.intellij.core.CoreApplicationEnvironment import com.intellij.mock.MockProject -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.psi.PsiJavaFile -import com.intellij.psi.PsiManager -import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession +import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.JvmFirDeserializedSymbolProviderFactory import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider +import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots import org.jetbrains.kotlin.cli.jvm.compiler.createSourceFilesFromSourceRoots -import org.jetbrains.kotlin.cli.jvm.config.javaSourceRoots +import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration -import java.nio.file.Files +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl +import org.jetbrains.kotlin.config.languageVersionSettings class KotlinSymbolProcessing( - val compilerConfiguration: CompilerConfiguration, - val options: KspOptions, - val logger: KSPLogger, - val analysisAPISession: StandaloneAnalysisAPISession, - val providers: List + val kspConfig: KSPJvmConfig, ) { - val project = analysisAPISession.project as MockProject - val kspCoreEnvironment = KSPCoreEnvironment(project) + fun execute() { + val deferredSymbols = mutableMapOf>() + val providers: List = kspConfig.processorProviders + + // TODO: CompilerConfiguration is deprecated. + val compilerConfiguration: CompilerConfiguration = CompilerConfiguration().apply { + addKotlinSourceRoots(kspConfig.sourceRoots.map { it.path }) + addJavaSourceRoots(kspConfig.javaSourceRoots) + addJvmClasspathRoots(kspConfig.libraries) + put(CommonConfigurationKeys.MODULE_NAME, kspConfig.moduleName) + kspConfig.jdkHome?.let { + put(JVMConfigurationKeys.JDK_HOME, it) + } + val languageVersion = LanguageVersion.fromFullVersionString(kspConfig.languageVersion)!! + val apiVersion = LanguageVersion.fromFullVersionString(kspConfig.apiVersion)!! + languageVersionSettings = LanguageVersionSettingsImpl( + languageVersion, + ApiVersion.createByLanguageVersion(apiVersion) + ) + } + + val analysisAPISession = buildStandaloneAnalysisAPISession(withPsiDeclarationFromBinaryModuleProvider = true) { + CoreApplicationEnvironment.registerExtensionPoint( + project.extensionArea, + KtResolveExtensionProvider.EP_NAME.name, + KtResolveExtensionProvider::class.java + ) + buildKtModuleProviderByCompilerConfiguration(compilerConfiguration) + }.apply { + (project as MockProject).registerService( + JvmFirDeserializedSymbolProviderFactory::class.java, + JvmFirDeserializedSymbolProviderFactory::class.java + ) + } + + val kspCoreEnvironment = KSPCoreEnvironment(analysisAPISession.project as MockProject) - var finished = false - val deferredSymbols = mutableMapOf>() - val ktFiles = createSourceFilesFromSourceRoots( - compilerConfiguration, project, compilerConfiguration.kotlinSourceRoots - ).toSet().toList() - val javaFiles = compilerConfiguration.javaSourceRoots - lateinit var codeGenerator: CodeGeneratorImpl - lateinit var processors: List + val ktFiles = createSourceFilesFromSourceRoots( + compilerConfiguration, analysisAPISession.project, compilerConfiguration.kotlinSourceRoots + ).toSet().toList() - fun prepare() { // TODO: support no Kotlin source mode. ResolverAAImpl.ktModule = ktFiles.first().let { - project.getService(ProjectStructureProvider::class.java) + analysisAPISession.project.getService(ProjectStructureProvider::class.java) .getModule(it, null) } val ksFiles = ktFiles.map { file -> analyze { KSFileImpl.getCached(file.getFileSymbol()) } } - val anyChangesWildcard = AnyChanges(options.projectBaseDir) - codeGenerator = CodeGeneratorImpl( - options.classOutputDir, - { options.javaOutputDir }, - options.kotlinOutputDir, - options.resourceOutputDir, - options.projectBaseDir, + val anyChangesWildcard = AnyChanges(kspConfig.projectBaseDir) + val codeGenerator = CodeGeneratorImpl( + kspConfig.classOutputDir, + { kspConfig.javaOutputDir }, + kspConfig.kotlinOutputDir, + kspConfig.resourceOutputDir, + kspConfig.projectBaseDir, anyChangesWildcard, ksFiles, - options.incremental + kspConfig.incremental ) - processors = providers.mapNotNull { provider -> + val processors = providers.mapNotNull { provider -> var processor: SymbolProcessor? = null processor = provider.create( SymbolProcessorEnvironment( - options.processingOptions, - options.languageVersion, + kspConfig.processorOptions, + kspConfig.languageVersion.toKotlinVersion(), codeGenerator, - logger, - options.apiVersion, - options.compilerVersion, + kspConfig.logger, + kspConfig.apiVersion.toKotlinVersion(), + // TODO: compilerVersion + KotlinVersion.CURRENT, // TODO: fix platform info listOf(JvmPlatformInfoImpl("JVM", "1.8", "disable")) ) ) processor.also { deferredSymbols[it] = mutableListOf() } } - } - - fun execute() { // TODO: support no kotlin source input. - val psiManager = PsiManager.getInstance(project) - val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) - val javaSourceRoots = options.javaSourceRoots - val javaFiles = javaSourceRoots.sortedBy { Files.isSymbolicLink(it.toPath()) } // Get non-symbolic paths first - .flatMap { root -> root.walk().filter { it.isFile && it.extension == "java" }.toList() } - .sortedBy { java.nio.file.Files.isSymbolicLink(it.toPath()) } // This time is for .java files - .distinctBy { it.canonicalPath } - .mapNotNull { localFileSystem.findFileByPath(it.path)?.let { psiManager.findFile(it) } as? PsiJavaFile } val resolver = ResolverAAImpl( ktFiles.map { analyze { it.getFileSymbol() } }, - options, - project + kspConfig, + analysisAPISession.project ) ResolverAAImpl.instance = resolver - processors.forEach { it.process(resolver) } - } -} - -fun main(args: Array) { - val compilerConfiguration = CompilerConfiguration() - val commandLineProcessor = KSPCommandLineProcessor(compilerConfiguration) - val logger = CommandLineKSPLogger() - val analysisSession = buildStandaloneAnalysisAPISession(withPsiDeclarationFromBinaryModuleProvider = true) { - buildKtModuleProviderByCompilerConfiguration(compilerConfiguration) + // TODO: multiple rounds + processors.forEach { it.process(resolver) } } - - val kotlinSymbolProcessing = KotlinSymbolProcessing( - commandLineProcessor.compilerConfiguration, - commandLineProcessor.kspOptions, - logger, - analysisSession, - commandLineProcessor.providers - ) - kotlinSymbolProcessing.prepare() - kotlinSymbolProcessing.execute() } 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 2fd38037e3..badd8bd734 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 @@ -18,7 +18,6 @@ package com.google.devtools.ksp.impl import com.google.devtools.ksp.KspExperimental -import com.google.devtools.ksp.KspOptions import com.google.devtools.ksp.extractThrowsAnnotation import com.google.devtools.ksp.extractThrowsFromClassFile import com.google.devtools.ksp.getClassDeclarationByName @@ -74,7 +73,7 @@ import java.nio.file.Files @OptIn(KspExperimental::class) class ResolverAAImpl( val ktFiles: List, - val options: KspOptions, + val kspConfig: KSPJvmConfig, val project: Project ) : Resolver { companion object { @@ -88,7 +87,7 @@ class ResolverAAImpl( val psiManager = PsiManager.getInstance(project) val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) // Get non-symbolic paths first - javaFiles = options.javaSourceRoots.sortedBy { Files.isSymbolicLink(it.toPath()) } + 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()) } @@ -196,7 +195,7 @@ class ResolverAAImpl( ksFiles .filter { file -> file.origin == Origin.JAVA && - options.javaSourceRoots.any { root -> + kspConfig.javaSourceRoots.any { root -> file.filePath.startsWith(root.absolutePath) && file.filePath.substringAfter(root.absolutePath) .dropLastWhile { c -> c != File.separatorChar }.dropLast(1).drop(1) diff --git a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/AbstractKSPAATest.kt b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/AbstractKSPAATest.kt index ed5f7d379b..0d971cb889 100644 --- a/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/AbstractKSPAATest.kt +++ b/kotlin-analysis-api/src/test/kotlin/com/google/devtools/ksp/impl/test/AbstractKSPAATest.kt @@ -17,26 +17,15 @@ package com.google.devtools.ksp.impl.test -import com.google.devtools.ksp.KspOptions import com.google.devtools.ksp.impl.CommandLineKSPLogger +import com.google.devtools.ksp.impl.KSPJvmConfig import com.google.devtools.ksp.impl.KotlinSymbolProcessing import com.google.devtools.ksp.processor.AbstractTestProcessor import com.google.devtools.ksp.testutils.AbstractKSPTest -import com.intellij.core.CoreApplicationEnvironment -import com.intellij.mock.MockProject -import com.intellij.openapi.vfs.VirtualFile -import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider -import org.jetbrains.kotlin.analysis.api.standalone.StandaloneAnalysisAPISessionBuilder -import org.jetbrains.kotlin.analysis.api.standalone.buildStandaloneAnalysisAPISession -import org.jetbrains.kotlin.analysis.decompiler.stub.file.CachedAttributeData -import org.jetbrains.kotlin.analysis.decompiler.stub.file.ClsKotlinBinaryClassCache -import org.jetbrains.kotlin.analysis.decompiler.stub.file.FileAttributeService -import org.jetbrains.kotlin.analysis.low.level.api.fir.project.structure.JvmFirDeserializedSymbolProviderFactory -import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot -import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler -import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoot -import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.config.jvmModularRoots +import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.test.compileJavaFiles import org.jetbrains.kotlin.test.kotlinPathsForDistDirectoryForTests @@ -50,12 +39,9 @@ import org.jetbrains.kotlin.test.services.javaFiles import org.jetbrains.kotlin.test.util.KtTestUtil import org.jetbrains.kotlin.utils.PathUtil import java.io.ByteArrayOutputStream -import java.io.DataInput -import java.io.DataOutput import java.io.File import java.io.PrintStream import java.net.URLClassLoader -import java.nio.file.Files abstract class AbstractKSPAATest : AbstractKSPTest(FrontendKinds.FIR) { val TestModule.kotlinSrc @@ -120,86 +106,44 @@ abstract class AbstractKSPAATest : AbstractKSPTest(FrontendKinds.FIR) { testProcessor: AbstractTestProcessor ): List { val compilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(mainModule) - compilerConfiguration.put(CommonConfigurationKeys.MODULE_NAME, mainModule.name) - compilerConfiguration.addKotlinSourceRoot(mainModule.kotlinSrc.absolutePath) mainModule.kotlinSrc.mkdirs() - if (!mainModule.javaFiles.isEmpty()) { - mainModule.writeJavaFiles() - compilerConfiguration.addJavaSourceRoot(mainModule.javaDir) - } // Some underlying service needs files backed by local fs. // Therefore, this doesn't work: // val ktFiles = mainModule.loadKtFiles(kotlinCoreEnvironment.project) mainModule.writeKtFiles() - val kotlinSourceFiles = mainModule.files.filter { it.isKtFile }.map { - File(mainModule.kotlinSrc, it.relativePath) + if (!mainModule.javaFiles.isEmpty()) { + mainModule.writeJavaFiles() } - val ktSourceRoots = kotlinSourceFiles - .sortedBy { Files.isSymbolicLink(it.toPath()) } // Get non-symbolic paths first - .distinctBy { it.canonicalPath } - compilerConfiguration.addKotlinSourceRoots(ktSourceRoots.map { it.absolutePath }) val testRoot = mainModule.testRoot - val kspOptions = KspOptions.Builder().apply { + val kspConfig = KSPJvmConfig.Builder().apply { + moduleName = mainModule.name + sourceRoots = listOf(mainModule.kotlinSrc) if (!mainModule.javaFiles.isEmpty()) { - javaSourceRoots.add(mainModule.javaDir) + javaSourceRoots = listOf(mainModule.javaDir) } + this.jdkHome = compilerConfiguration.get(JVMConfigurationKeys.JDK_HOME) + languageVersion = compilerConfiguration.languageVersionSettings.languageVersion.versionString + apiVersion = compilerConfiguration.languageVersionSettings.apiVersion.versionString + libraries = libModules.map { it.outDir } + + compilerConfiguration.jvmModularRoots + + compilerConfiguration.jvmClasspathRoots + logger = CommandLineKSPLogger() + + processorProviders = listOf(testProcessor) + + projectBaseDir = testRoot classOutputDir = File(testRoot, "kspTest/classes/main") javaOutputDir = File(testRoot, "kspTest/src/main/java") kotlinOutputDir = File(testRoot, "kspTest/src/main/kotlin") resourceOutputDir = File(testRoot, "kspTest/src/main/resources") - projectBaseDir = testRoot cachesDir = File(testRoot, "kspTest/kspCaches") - kspOutputDir = File(testRoot, "kspTest") - languageVersionSettings = compilerConfiguration.languageVersionSettings + outputBaseDir = File(testRoot, "kspTest") }.build() - val analysisSession = buildStandaloneAnalysisAPISession(withPsiDeclarationFromBinaryModuleProvider = true) { - registerOnce { DummyFileAttributeService } - registerOnce(::ClsKotlinBinaryClassCache) - CoreApplicationEnvironment.registerExtensionPoint( - project.extensionArea, - KtResolveExtensionProvider.EP_NAME.name, - KtResolveExtensionProvider::class.java - ) - buildKtModuleProviderByCompilerConfiguration(compilerConfiguration) - }.apply { - (project as MockProject).registerService( - JvmFirDeserializedSymbolProviderFactory::class.java, - JvmFirDeserializedSymbolProviderFactory::class.java - ) - } - val ksp = KotlinSymbolProcessing( - compilerConfiguration, - kspOptions, - CommandLineKSPLogger(), - analysisSession, - listOf(testProcessor) - ) - ksp.prepare() + val ksp = KotlinSymbolProcessing(kspConfig) ksp.execute() return testProcessor.toResult() } } - -object DummyFileAttributeService : FileAttributeService { - override fun write( - file: VirtualFile, - id: String, - value: T, - writeValueFun: (DataOutput, T) -> Unit - ): CachedAttributeData { - return CachedAttributeData(value, 0) - } - - override fun read(file: VirtualFile, id: String, readValueFun: (DataInput) -> T): CachedAttributeData? { - return null - } -} - -inline fun StandaloneAnalysisAPISessionBuilder.registerOnce(createInstance: () -> T) { - if (application.getServiceIfCreated(T::class.java) == null) { - registerApplicationService(T::class.java, createInstance()) - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index e127eabdff..621dc738ec 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,6 +4,7 @@ pluginManagement { repositories { gradlePluginPortal() maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") + maven("https://www.jetbrains.com/intellij-repository/snapshots") } } diff --git a/symbol-processing-cmdline/build.gradle.kts b/symbol-processing-cmdline/build.gradle.kts index 9120e98405..e020d0adea 100644 --- a/symbol-processing-cmdline/build.gradle.kts +++ b/symbol-processing-cmdline/build.gradle.kts @@ -68,7 +68,6 @@ publishing { asNode().appendNode("dependencies").apply { addDependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlinBaseVersion) - addDependency("org.jetbrains.kotlin", "kotlin-compiler", kotlinBaseVersion) addDependency("com.google.devtools.ksp", "symbol-processing-api", version) } } diff --git a/symbol-processing/build.gradle.kts b/symbol-processing/build.gradle.kts index 493e204deb..09c0b153ca 100644 --- a/symbol-processing/build.gradle.kts +++ b/symbol-processing/build.gradle.kts @@ -72,7 +72,6 @@ publishing { asNode().appendNode("dependencies").apply { addDependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlinBaseVersion) - addDependency("org.jetbrains.kotlin", "kotlin-compiler-embeddable", kotlinBaseVersion) addDependency("com.google.devtools.ksp", "symbol-processing-api", version) } }