diff --git a/docs/topics/ksp/ksp-quickstart.md b/docs/topics/ksp/ksp-quickstart.md index 641c38b3396..bbbfb57456c 100644 --- a/docs/topics/ksp/ksp-quickstart.md +++ b/docs/topics/ksp/ksp-quickstart.md @@ -1,81 +1,58 @@ [//]: # (title: KSP quickstart) -For a quick start, you can create your own processor or get a [sample one](https://github.com/google/ksp/tree/main/examples/playground). +In this guide you will learn: -## Add a processor +* How to add KSP-based annotation processors to your project. +* How to create your own annotation processor with the KSP API. +* Where to find the code generated by the processor. -To add a processor, you need to include the KSP Gradle Plugin and add a dependency on the processor: +## Add a KSP-based processor to your project -1. Add the KSP Gradle Plugin `com.google.devtools.ksp` to your `build.gradle(.kts)` file: +To use an external processor in your project, add KSP to the [`plugins{}` block](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block) in your `build.gradle(.kts)` file. If the processor is +only needed in a specific module, add it to that module's `build.gradle(.kts)` file instead. - - - - ```kotlin - plugins { - id("com.google.devtools.ksp") version "%kspVersion%" - } - ``` - - - - - ```groovy - plugins { - id 'com.google.devtools.ksp' version '%kspVersion%' - } - ``` - - - - -2. Add a dependency on the processor. -This example uses [Dagger](https://dagger.dev/dev-guide/ksp.html). Replace it with the processor you want to add. - - - - - ```kotlin - dependencies { - implementation("com.google.dagger:dagger-compiler:2.51.1") - ksp("com.google.dagger:dagger-compiler:2.51.1") - } - ``` - - - - - ```groovy - dependencies { - implementation 'com.google.dagger:dagger-compiler:2.51.1' - ksp 'com.google.dagger:dagger-compiler:2.51.1' - } - ``` - - - + + -3. Run `./gradlew build`. You can find the generated code in the `build/generated/ksp` directory. +```kotlin +// build.gradle.kts -Here is a full example: +plugins { + kotlin("jvm") version "2.3.0" + id("com.google.devtools.ksp") version "2.3.6" +} +``` - - + + + +```groovy +// build.gradle -```kotlin plugins { - id("com.google.devtools.ksp") version "%kspVersion%" - kotlin("jvm") + id 'org.jetbrains.kotlin.jvm' version '2.3.0' + id 'com.google.devtools.ksp' version '2.3.6' } +``` -repositories { - mavenCentral() -} + + + +> To find the latest version of KSP, check the GitHub [Releases](https://github.com/google/ksp/releases). +> +{style="tip"} + +In the top-level `dependencies{}` block, add the processor you want to use. In this example, we use +[Moshi](https://github.com/square/moshi?tab=readme-ov-file#codegen), but the approach is the same for other processors: + + + + +```kotlin +// build.gradle.kts dependencies { - implementation(kotlin("stdlib-jdk8")) - implementation("com.google.dagger:dagger-compiler:2.51.1") - ksp("com.google.dagger:dagger-compiler:2.51.1") + ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2") } ``` @@ -83,210 +60,88 @@ dependencies { ```groovy -plugins { - id 'com.google.devtools.ksp' version '%kspSupportedKotlinVersion%-%%' - id 'org.jetbrains.kotlin.jvm' version '%kotlinVersion%' +// build.gradle + +dependencies { + ksp 'com.squareup.moshi:moshi-kotlin-codegen:1.15.2' } +``` + + + + + +## Create your own processor + +By following these steps you will create a simple annotation processor that will generate a `helloWorld()` function. +While not very useful in practice, it demonstrates the basics of creating your own processors and annotations. + +An outline of the final [project structure](#project-structure) is available below for reference. -repositories { - mavenCentral() +### Add KSP to the project + +Start from an empty project, and add KSP as a plugin in the top-level `build.gradle(.kts)` file: + + + + +```kotlin +// build.gradle.kts + +plugins { + kotlin("jvm") version "2.3.0" + id("com.google.devtools.ksp") version "2.3.6" apply false } +``` -dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib:%kotlinVersion%' - implementation 'com.google.dagger:dagger-compiler:2.51.1' - ksp 'com.google.dagger:dagger-compiler:2.51.1' + + + +```groovy +// build.gradle + +plugins { + id 'org.jetbrains.kotlin.jvm' version '2.3.0' + id 'com.google.devtools.ksp' version '2.3.6' apply false } ``` -## Create a processor of your own +### Create an annotation -1. Create an empty gradle project. -2. Specify version `%kspSupportedKotlinVersion%` of the Kotlin plugin in the root project for use in other project modules: +Create a new Kotlin module in the project from **File** | **New** | **Module** and name it `annotations`. - - - - ```kotlin - plugins { - kotlin("jvm") version "%kspSupportedKotlinVersion%" apply false - } - - buildscript { - dependencies { - classpath(kotlin("gradle-plugin", version = "%kspSupportedKotlinVersion%")) - } - } - ``` - - - - - ```groovy - plugins { - id 'org.jetbrains.kotlin.jvm' version '%kspSupportedKotlinVersion%' apply false - } - - buildscript { - dependencies { - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:%kspSupportedKotlinVersion%' - } - } - ``` - - - +Next, create the following file and declare the annotation `HelloWorldAnnotation`: -3. Add a module for hosting the processor. +```kotlin +//annotations/src/main/kotlin/com/example/annotations/MyAnnotation.kt +package com.example.annotations -4. In the module's build script, apply Kotlin plugin and add the KSP API to the `dependencies` block. +annotation class HelloWorldAnnotation +``` - - - - ```kotlin - plugins { - kotlin("jvm") - } - - repositories { - mavenCentral() - } - - dependencies { - implementation("com.google.devtools.ksp:symbol-processing-api:%kspVersion%") - } - ``` - - - - - ```groovy - plugins { - id 'org.jetbrains.kotlin.jvm' version '%kotlinVersion%' - } - - repositories { - mavenCentral() - } - - dependencies { - implementation 'com.google.devtools.ksp:symbol-processing-api:%kspVersion%' - } - ``` - - - - -5. You'll need to implement [`com.google.devtools.ksp.processing.SymbolProcessor`](https://github.com/google/ksp/tree/main/api/src/main/kotlin/com/google/devtools/ksp/processing/SymbolProcessor.kt) - and [`com.google.devtools.ksp.processing.SymbolProcessorProvider`](https://github.com/google/ksp/tree/main/api/src/main/kotlin/com/google/devtools/ksp/processing/SymbolProcessorProvider.kt). - Your implementation of `SymbolProcessorProvider` will be loaded as a service to instantiate the `SymbolProcessor` you implement. - Note the following: - * Implement [`SymbolProcessorProvider.create()`](https://github.com/google/ksp/blob/master/api/src/main/kotlin/com/google/devtools/ksp/processing/SymbolProcessorProvider.kt) - to create a `SymbolProcessor`. Pass dependencies that your processor needs (such as `CodeGenerator`, processor options) - through the parameters of `SymbolProcessorProvider.create()`. - * Your main logic should be in the [`SymbolProcessor.process()`](https://github.com/google/ksp/blob/master/api/src/main/kotlin/com/google/devtools/ksp/processing/SymbolProcessor.kt) method. - * Use `resolver.getSymbolsWithAnnotation()` to get the symbols you want to process, given the fully-qualified name of - an annotation. - * A common use case for KSP is to implement a customized visitor (interface `com.google.devtools.ksp.symbol.KSVisitor`) - for operating on symbols. A simple template visitor is `com.google.devtools.ksp.symbol.KSDefaultVisitor`. - * For sample implementations of the `SymbolProcessorProvider` and `SymbolProcessor` interfaces, see the following files - in the sample project. - * `src/main/kotlin/BuilderProcessor.kt` - * `src/main/kotlin/TestProcessor.kt` - * After writing your own processor, register your processor provider to the package by including its fully-qualified - name in `src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider`. - -## Use your own processor in a project - -1. Create another module that contains a workload where you want to try out your processor. - - - - - ```kotlin - pluginManagement { - repositories { - gradlePluginPortal() - } - } - ``` - - - - - ```groovy - pluginManagement { - repositories { - gradlePluginPortal() - } - } - ``` - - - - -2. In the module's build script, apply the `com.google.devtools.ksp` plugin with the specified version and - add your processor to the list of dependencies. - - - - - ```kotlin - plugins { - id("com.google.devtools.ksp") version "%kspVersion%" - } - - dependencies { - implementation(kotlin("stdlib-jdk8")) - implementation(project(":test-processor")) - ksp(project(":test-processor")) - } - ``` - - - - - ```groovy - plugins { - id 'com.google.devtools.ksp' version '%kspVersion%' - } - - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib:%kotlinVersion%' - implementation project(':test-processor') - ksp project(':test-processor') - } - ``` - - - +### Create and register a processor -3. Run `./gradlew build`. You can find the generated code under - `build/generated/ksp`. +Create another Kotlin module. You can give it whatever name you want. In this guide we name it `processor`. -Here's a sample build script to apply the KSP plugin to a workload: +Add the necessary dependencies to the module's `build.gradle(.kts)`. You will need the KSP API and the annotation +you just declared: - + ```kotlin -plugins { - id("com.google.devtools.ksp") version "%kspVersion%" - kotlin("jvm") -} - -repositories { - mavenCentral() +// processor/build.gradle.kts + +plugins { + kotlin("jvm") } - + dependencies { - implementation(kotlin("stdlib-jdk8")) - implementation(project(":test-processor")) - ksp(project(":test-processor")) + implementation(project(":annotations")) + implementation("com.google.devtools.ksp:symbol-processing-api:2.3.6") } ``` @@ -294,73 +149,141 @@ dependencies { ```groovy -plugins { - id 'com.google.devtools.ksp' version '%kspVersion%' - id 'org.jetbrains.kotlin.jvm' version '%kotlinVersion%' -} +// processor/build.gradle -repositories { - mavenCentral() +plugins { + id 'org.jetbrains.kotlin.jvm' } dependencies { - implementation 'org.jetbrains.kotlin:kotlin-stdlib:%kotlinVersion%' - implementation project(':test-processor') - ksp project(':test-processor') + implementation project ':annotations' + implementation 'com.google.devtools.ksp:symbol-processing-api:2.3.6' } ``` -## Pass options to processors +Copy the following code in a new file called `HelloWorldProcessor.kt`: -Processor options in `SymbolProcessorEnvironment.options` are specified in gradle build scripts: +```kotlin +// processor/src/main/kotlin/HelloWorldProcessor.kt +class HelloWorldProcessor(val codeGenerator: CodeGenerator) : SymbolProcessor { + // (1) process() method + override fun process(resolver: Resolver): List { + resolver + .getSymbolsWithAnnotation("com.example.annotations.HelloWorldAnnotation") + .filter { it.validate() } + .filterIsInstance() + .forEach { it.accept(HelloWorldVisitor(), Unit) } + + return emptyList() + } + + // (2) visitor + inner class HelloWorldVisitor : KSVisitorVoid() { + override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) { + createNewFileFrom(function).use { file -> + file.write( + """ + fun helloWorld(): Unit { + println("Hello world from function generated by KSP") + } + """.trimIndent() + ) + } + } + } + + // (3a) + private fun createNewFileFrom(function: KSFunctionDeclaration): OutputStream { + return codeGenerator.createNewFile( + dependencies = createDependencyOn(function), + packageName = "", + fileName = "GeneratedHelloWorld" + ) + } + + // (3b) + private fun createDependencyOn(function: KSFunctionDeclaration): Dependencies { + return Dependencies(aggregating = false, function.containingFile!!) + } +} -```none -ksp { - arg("option1", "value1") - arg("option2", "value2") - ... +// Util function +fun OutputStream.write(string: String): Unit { + this.write(string.toByteArray()) } ``` -## Make IDE aware of generated code +Let's go through it: -> Generated source files are registered automatically since KSP 1.8.0-1.0.9. -> If you're using KSP 1.0.9 or newer and don't need to make the IDE aware of generated resources, -> feel free to skip this section. -> -{style="note"} +1. The main logic of the processor is in the `process()` method. In this case, the method gets a list of every symbol + annotated with `HelloWorldAnnotation`, and calls `HelloWorldVisitor` for each of them. -By default, IntelliJ IDEA or other IDEs don't know about the generated code. So it will mark references to generated -symbols unresolvable. To make an IDE be able to reason about the generated symbols, mark the -following paths as generated source roots: + The method `process()` returns a list of unprocessed symbols, to be processed at a later round. In our case, we can + safely return an emptyList(). For more information, see [Multiple round processing](ksp-multi-round.md). -```text -build/generated/ksp/main/kotlin/ -build/generated/ksp/main/java/ + +2. Processors traverse KSP's view of the Kotlin abstract syntax tree (AST) using visitors. Inside the + `HelloWorldPocessor` class, create a visitor class. `HelloWorldAnnotation` will only be used on a function, so we only need + to override the `visitFunctionDeclaration()` method. + + > `KSVisitorVoid` is one of the visitors KSP provides to be overridden and adapted. It is also possible to create + > your own by implementing the `KSVisitor` [interface](https://github.com/google/ksp/blob/main/api/src/main/kotlin/com/google/devtools/ksp/symbol/KSVisitor.kt). + > + {style="tip"} + +3. `createNewFile()` and `createDependency()` are used to create the file where KSP will put the generated code. + +Next, you need to implement a `SymbolProcessorProvider`. The KSP framework calls this class to construct the processor. +Create a file and copy the following code: + +```kotlin +// processor/src/main/kotlin/HelloWorldProcessorProvider.kt + +class HelloWorldProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return HelloWorldProcessor(environment.codeGenerator) + } +} ``` -If your IDE supports resource directories, also mark the following one: +> Any dependencies your processor needs (such as `CodeGenerator`) will be passed as parameters to the `create()` +> method. +> +{style="tip"} -```text -build/generated/ksp/main/resources/ +Finally, register the processor provider. To do that, create the following file, and copy in it the fully qualified +name of the provider: + +```kotlin +//processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider + +HelloWorldProcessorProvider ``` -It may also be necessary to configure these directories in your KSP consumer module's build script: +### Test your processor + +Create one last module and name it `app`. In the module's `build.gradle(.kts)`: - +* Add KSP to the `plugins {}` block. +* Add your processor and annotation to the `dependencies {}` block. + + ```kotlin -kotlin { - sourceSets.main { - kotlin.srcDir("build/generated/ksp/main/kotlin") - } - sourceSets.test { - kotlin.srcDir("build/generated/ksp/test/kotlin") - } +// app/build.gradle.kts + +plugins { + kotlin("jvm") + id("com.google.devtools.ksp") +} + +dependencies { + implementation(project(":annotations")) + ksp(project(":processor")) } ``` @@ -368,63 +291,110 @@ kotlin { ```groovy -kotlin { - sourceSets { - main.kotlin.srcDirs += 'build/generated/ksp/main/kotlin' - test.kotlin.srcDirs += 'build/generated/ksp/test/kotlin' - } +// app/build.gradle + +plugins { + id 'com.google.devtools.ksp' +} + +dependencies { + implementation project (':annotations') + ksp project (':processor') } ``` -If you are using IntelliJ IDEA and KSP in a Gradle plugin then the above snippet will give the following warning: -```text -Execution optimizations have been disabled for task ':publishPluginJar' to ensure correctness due to the following reasons: -Gradle detected a problem with the following location: '../build/generated/ksp/main/kotlin'. -Reason: Task ':publishPluginJar' uses this output of task ':kspKotlin' without declaring an explicit or implicit dependency. -``` - -In this case, use the following script instead: - - +In the top-level `settings.gradle.kts`, include all the submodules: + + ```kotlin -plugins { - // ... - idea -} +// settings.gradle.kts -idea { - module { - // Not using += due to https://github.com/gradle/gradle/issues/8749 - sourceDirs = sourceDirs + file("build/generated/ksp/main/kotlin") // or tasks["kspKotlin"].destination - testSourceDirs = testSourceDirs + file("build/generated/ksp/test/kotlin") - generatedSourceDirs = generatedSourceDirs + file("build/generated/ksp/main/kotlin") + file("build/generated/ksp/test/kotlin") - } -} +include("annotations") +include("app") +include("processor") ``` ```groovy -plugins { - // ... - id 'idea' -} +// settings.gradle -idea { - module { - // Not using += due to https://github.com/gradle/gradle/issues/8749 - sourceDirs = sourceDirs + file('build/generated/ksp/main/kotlin') // or tasks["kspKotlin"].destination - testSourceDirs = testSourceDirs + file('build/generated/ksp/test/kotlin') - generatedSourceDirs = generatedSourceDirs + file('build/generated/ksp/main/kotlin') + file('build/generated/ksp/test/kotlin') - } -} +include 'processor' +include 'annotations' +include 'app' ``` + +Now you are ready to try it. In the `app` module, create `Main.kt` and add the following code: + +```kotlin +// app/src/main/kotlin/Main.kt +import com.example.annotations.HelloWorldAnnotation + +@HelloWorldAnnotation +fun main() { + helloWorld() +} +``` + +Notice that, in `main()`, the function `helloWorld()` is called. It doesn’t exist yet, and your IDE might flag this. +But KSP will create it when you compile and run your project, so it won’t be a problem! + +Run the program. You should see the output of the `helloWorld()` function in your console. + +The code generated by KSP can be found here: + +```text +app/build/generated/ksp/main/kotlin/GeneratedHelloWorld.kt +``` + +### Project structure + +Your project’s final file structure should look like this (you might have additional directories, for example +`gradle`, `gradlew`, or `.idea`): + +```text +. +├── app +│ ├── build.gradle.kts +│ └── src +│ └── main +│ └── kotlin +│ └── Main.kt +├── annotations +│ ├── build.gradle.kts +│ └── src +│ └── main +│ └── kotlin +| └── com +| └── example +| └── annotations +| └── HelloWorldAnnotation.kt +├── processor +│ ├── build.gradle.kts +│ └── src +│ └── main +│ ├── kotlin +│ │ ├── HelloWorldProcessor.kt +│ │ └── HelloWorldProcessorProvider.kt +│ └── resources/META-INF/services +| └── com.google.devtools.ksp.processing.SymbolProcessorProvider +├── build.gradle.kts +└── settings.gradle.kts + +``` + + +## What's next? + +* Explore the full code for this example in [the KSP repository](https://github.com/google/ksp/pull/2804/changes). +* Find more complex, real-world examples in [the KSP repository](https://github.com/google/ksp/blob/main/examples/playground/test-processor/src/main/kotlin/BuilderProcessor.kt). +* Browse the list of [supported libraries](ksp-overview.md#supported-libraries). \ No newline at end of file