Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of KSP 2 Gradle plugin #1446

Merged
merged 3 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AbstractKotlinCompileTool<*>>,
processorClasspath: Configuration,
kspGeneratedSourceSet: KotlinSourceSet,
): TaskProvider<KspAATask> {
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<String>

@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<File>

@get:Internal
abstract val projectBaseDir: Property<File>

@get:Internal
abstract val outputBaseDir: Property<File>

@get:Internal
abstract val cachesDir: Property<File>

@get:OutputDirectory
abstract val kotlinOutputDir: Property<File>

@get:OutputDirectory
abstract val javaOutputDir: Property<File>

@get:OutputDirectory
abstract val classOutputDir: Property<File>

@get:OutputDirectory
abstract val resourceOutputDir: Property<File>

@get:Input
abstract val languageVersion: Property<String>

@get:Input
abstract val apiVersion: Property<String>
}

interface KspAAWorkParameter : WorkParameters {
var config: KspGradleConfig
}

abstract class KspAAWorkerAction : WorkAction<KspAAWorkParameter> {
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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 -> {
Expand Down
1 change: 1 addition & 0 deletions integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ tasks.named<Test>("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
Expand Down
Loading
Loading