Add new example project
diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md new file mode 100644 index 0000000..7fb6b4f --- /dev/null +++ b/examples/hello-world/README.md
@@ -0,0 +1,62 @@ +# KSP Hello World Example + +This project is a minimal, self-contained KSP project. + +It configures KSP via its Gradle plugin as one would normally do, +in the hopes of providing an example that a new user can read and learn from. + +The project has three subprojects: +- `annotations`: This subproject defines the `Gen` annotation. By keeping annotations in a separate + module, we avoid leaking the processor's implementation or the KSP API to the app's runtime. +- `processor`: This subproject defines a minimal processor that selects functions annotated with + `Gen` and generates a function that prints a message. +- `app`: This subproject defines a single `main` that calls the generated function. + The `main` function is annotated with `Gen` to trigger the processor. + +## Key Concepts + +While this is a minimal example, it demonstrates several core KSP mechanisms that are essential to +understand: + +### Selecting Functions to Process + +The processor's entry point is the `process` method, which receives a `Resolver`. +The `Resolver` type is the primary tool for querying the KSP's view of the source code. + +In `HelloWorldProcessor.kt`, we use: +- `resolver.getSymbolsWithAnnotation(...)`: To find all symbols annotated with our fully qualified + annotation name (`com.example.annotations.Gen`). +- `.filterIsInstance<KSFunctionDeclaration>()`: Since our processor is designed to work on + functions, we filter the results to ensure we are only dealing with function declarations. + +Once filtered, we use the **Visitor Pattern** (`it.accept(HelloWorldVisitor(), Unit)`) to perform the actual code generation. This is the recommended way to traverse the KSP AST (Abstract Syntax Tree). + +Note the file in `processor/src/main/resources/META-INF/services/`. +It contains the fully qualified name of the `SymbolProcessorProvider` implementation. +Without this, KSP will not know your processor exists. + +### Generating Code + +Using the selected function in the `HelloWorldVisitor`, we can create a new file and write directly +to it like any other output stream. + +### Dependency Management + +In `app/build.gradle.kts`, notice how the dependencies are split: +- `implementation(project(":annotations"))`: Gives the app access to the `Gen` annotation + definition. +- `ksp(project(":processor"))`: Tells the KSP plugin to run the processor during compilation. + +The separation ensures the processor's code only exists during the build phase and isn't bundled +into your final application. +Note in `processor/build.gradle.kts` that the processor also depends on the annotations project with +`implementation(project(":annotations"))`. + +### Inspecting Generated Code + +You can find the source code generated by this processor in the build directory of the app: +`app/build/generated/ksp/main/kotlin/GeneratedHelloWorld.kt` + +## Further Reading + +Visit the [KSP documentation](https://kotlinlang.org/docs/ksp-overview.html) for more information.
diff --git a/examples/hello-world/annotations/build.gradle.kts b/examples/hello-world/annotations/build.gradle.kts new file mode 100644 index 0000000..fb0d35a --- /dev/null +++ b/examples/hello-world/annotations/build.gradle.kts
@@ -0,0 +1,7 @@ +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() +}
diff --git a/examples/hello-world/annotations/src/main/kotlin/com/example/annotations/Gen.kt b/examples/hello-world/annotations/src/main/kotlin/com/example/annotations/Gen.kt new file mode 100644 index 0000000..d329d1d --- /dev/null +++ b/examples/hello-world/annotations/src/main/kotlin/com/example/annotations/Gen.kt
@@ -0,0 +1,25 @@ +/* + * Copyright 2026 Google LLC + * Copyright 2010-2026 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. + */ + +package com.example.annotations + +/** + * The Gen annotation. + * + * Annotating a function with this will trigger the processor, thus generating a `helloWorld` function. + */ +annotation class Gen
diff --git a/examples/hello-world/app/build.gradle.kts b/examples/hello-world/app/build.gradle.kts new file mode 100644 index 0000000..db43f69 --- /dev/null +++ b/examples/hello-world/app/build.gradle.kts
@@ -0,0 +1,18 @@ +plugins { + kotlin("jvm") + application + id("com.google.devtools.ksp") version "2.3.5" +} + +application { + mainClass.set("MainKt") +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":annotations")) + ksp(project(":processor")) +}
diff --git a/examples/hello-world/app/src/main/kotlin/Main.kt b/examples/hello-world/app/src/main/kotlin/Main.kt new file mode 100644 index 0000000..4c936e1 --- /dev/null +++ b/examples/hello-world/app/src/main/kotlin/Main.kt
@@ -0,0 +1,28 @@ +/* + * Copyright 2026 Google LLC + * Copyright 2010-2026 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. + */ + +import com.example.annotations.Gen + +/** + * The main entrypoint. + * + * Annotated with `Gen` to trigger the processor which generates `helloWorld`. + */ +@Gen +fun main() { + helloWorld() +}
diff --git a/examples/hello-world/build.gradle.kts b/examples/hello-world/build.gradle.kts new file mode 100644 index 0000000..72c43d1 --- /dev/null +++ b/examples/hello-world/build.gradle.kts
@@ -0,0 +1,16 @@ +plugins { + kotlin("jvm") version "2.3.0" apply false +} + +buildscript { + dependencies { + classpath(kotlin("gradle-plugin", version = "2.3.0")) + } +} + +group = "com.example" +version = "1.0-SNAPSHOT" + +tasks.register<GradleBuild>("run") { + tasks.add(":app:run") +}
diff --git a/examples/hello-world/processor/build.gradle.kts b/examples/hello-world/processor/build.gradle.kts new file mode 100644 index 0000000..9d020a4 --- /dev/null +++ b/examples/hello-world/processor/build.gradle.kts
@@ -0,0 +1,12 @@ +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":annotations")) + implementation("com.google.devtools.ksp:symbol-processing-api:2.3.5") +}
diff --git a/examples/hello-world/processor/src/main/kotlin/HelloWorldProcessor.kt b/examples/hello-world/processor/src/main/kotlin/HelloWorldProcessor.kt new file mode 100644 index 0000000..e39813c --- /dev/null +++ b/examples/hello-world/processor/src/main/kotlin/HelloWorldProcessor.kt
@@ -0,0 +1,90 @@ +/* + * Copyright 2026 Google LLC + * Copyright 2010-2026 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. + */ + +import com.google.devtools.ksp.processing.CodeGenerator +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.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSVisitorVoid +import com.google.devtools.ksp.validate +import java.io.OutputStream + +/** + * A code generator, called a processor, that uses KSP. + * It implements the [SymbolProcessor] interface that comes from KSP. + */ +class HelloWorldProcessor(val codeGenerator: CodeGenerator) : SymbolProcessor { + + /** + * Selects functions annotated with `Gen` generates `helloWorld`. + * Always returns an empty list since it either generates the `helloWorld` function or not. + */ + override fun process(resolver: Resolver): List<KSAnnotated> { + resolver + .getSymbolsWithAnnotation("com.example.annotations.Gen") + .filter { it.validate() } + .filterIsInstance<KSFunctionDeclaration>() + .forEach { it.accept(HelloWorldVisitor(), Unit) } + + return emptyList() + } + + /** + * Local visitor for the KSP AST. + * It extends the [KSVisitorVoid] which knows how to traverse the AST, but + * only overrides the `visitFunctionDeclaration` method, since it is only ever passed + * the handle to the `main` function. + */ + inner class HelloWorldVisitor : KSVisitorVoid() { + + /** + * Generates the `helloWorld` function. + */ + 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() + ) + } + } + } + + /** + * Creates a new file for `function`. + */ + private fun createNewFileFrom(function: KSFunctionDeclaration): OutputStream { + return codeGenerator.createNewFile( + dependencies = createDependencyOn(function), + packageName = "", + fileName = "GeneratedHelloWorld" + ) + } + + /** + * Creates a dependency for the file containing `function`. + */ + private fun createDependencyOn(function: KSFunctionDeclaration): Dependencies { + return Dependencies(aggregating = false, function.containingFile!!) + } + +}
diff --git a/examples/hello-world/processor/src/main/kotlin/HelloWorldProcessorProvider.kt b/examples/hello-world/processor/src/main/kotlin/HelloWorldProcessorProvider.kt new file mode 100644 index 0000000..24d4a1a --- /dev/null +++ b/examples/hello-world/processor/src/main/kotlin/HelloWorldProcessorProvider.kt
@@ -0,0 +1,31 @@ +/* + * Copyright 2026 Google LLC + * Copyright 2010-2026 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. + */ + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +/** + * Provides an instance of a [HelloWorldProcessor] (defined in this project). + * The KSP framework calls this class to construct our processor. + * KSP knows about this class because we defined it in the META-INF directory. + */ +class HelloWorldProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return HelloWorldProcessor(environment.codeGenerator) + } +}
diff --git a/examples/hello-world/processor/src/main/kotlin/Util.kt b/examples/hello-world/processor/src/main/kotlin/Util.kt new file mode 100644 index 0000000..ff6fc1c --- /dev/null +++ b/examples/hello-world/processor/src/main/kotlin/Util.kt
@@ -0,0 +1,22 @@ +/* + * Copyright 2026 Google LLC + * Copyright 2010-2026 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. + */ + +import java.io.OutputStream + +fun OutputStream.write(string: String): Unit { + this.write(string.toByteArray()) +}
diff --git a/examples/hello-world/processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/examples/hello-world/processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000..58dfa70 --- /dev/null +++ b/examples/hello-world/processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
@@ -0,0 +1 @@ +HelloWorldProcessorProvider \ No newline at end of file
diff --git a/examples/hello-world/settings.gradle.kts b/examples/hello-world/settings.gradle.kts new file mode 100644 index 0000000..f0cb5ec --- /dev/null +++ b/examples/hello-world/settings.gradle.kts
@@ -0,0 +1,11 @@ +pluginManagement { + repositories { + gradlePluginPortal() + } +} + +rootProject.name = "ksp-hello-world" + +include(":annotations") +include(":app") +include(":processor")