Kotlin Symbol Processing (KSP)

repository·main·Indexed 25 days ago

https://github.com/google/ksp

A lightweight compiler plugin API for developing symbol processors, designed for Kotlin code analysis and optimized for performance. KSP provides a simplified API for querying symbols, traversing the AST via the Visitor Pattern, and generating source files. The documentation covers implementation steps, Gradle configuration for JVM, Android, and Kotlin Multiplatform (KMP) projects, debugging techniques for KSP1 and KSP2, and detailed API references for the SymbolProcessorEnvironment.

Tokens
6K
Snippets
4
Records
30
Agent score
83%

What's inside KSP

  1. Test KSP processors using Kotlin Compile Testing

    main

    For faster and simpler testing of KSP processors, use the Kotlin Compile Testing library.

    Unlike running through Gradle, this library calls the compiler directly from your test code. This provides two main benefits:

    1. Direct IDE Debugging: The IDE's debugger works out of the box because it is not running inside a separate KotlinCompileDaemon.
    2. Speed: It is significantly faster and easier to manage than using Gradle TestKit.

    Refer to the Kotlin Compile Testing documentation for specific KSP API support details.

  2. Call KSP2 in a program

    main

    To execute KSP2 programmatically, follow these four steps:

    1. Load processors: Use ServiceLoader to find implementations of com.google.devtools.ksp.processing.SymbolProcessorProvider via a URLClassLoader containing your processor's classpath.
    2. Provide a logger: Implement KSPLogger or use the provided KspGradleLogger (which writes to stdout).
    3. Fill KSPConfig: Use a builder (e.g., KSPJvmConfig.Builder()) to configure moduleName, sourceRoots, kotlinOutputDir, and other options.
    4. Execute: Call KotlinSymbolProcessing(kspConfig, processors, kspLogger).execute().
    // Implement a logger or use KspGradleLogger
    val logger = KspGradleLogger(KspGradleLogger.LOGGING_LEVEL_WARN)
    
    // Load processors
    val processorClassloader = URLClassLoader(classpath.map { File(it).toURI().toURL() }.toTypedArray())
    val processorProviders = ServiceLoader.load(
      processorClassloader.loadClass("com.google.devtools.ksp.processing.SymbolProcessorProvider"),
      processorClassloader
    ).toList() as List<SymbolProcessorProvider>
    
    // Fill the config
    val kspConfig = KSPJvmConfig.Builder().apply {
      // All configurations happen here. See KSPConfig.kt for all available options.
      moduleName = "main"
      sourceRoots = listOf(File("/path/to/src1"), File("/path/to/src2"))
      kotlinOutputDir = File("/path/to/kotlin/out")
      // ...
    }.build()
    
    // Run!
    val exitCode = KotlinSymbolProcessing(kspConfig, processorProviders, logger).execute()
  3. Run KSP2 from the Command Line

    main

    KSP2 provides four main entry point classes for different platforms: KSPJvmMain, KSPJsMain, KSPNativeMain, and KSPCommonMain. These are located within the KSP release artifacts.

    To run KSP2 via the command line (using KSPJvmMain as an example), you must construct a classpath including the KSP jar, dependency jars, the Kotlin runtime, and your processor jar.

    Note: Replace 2.3.7 and other version numbers in the example below with the specific versions you are using.

    java -cp \
    symbol-processing-aa-2.3.7.jar:kotlin-analysis-api-2.3.7.jar:common-deps-2.3.7.jar:symbol-processing-api-2.3.7.jar:kotlin-stdlib-2.3.20.jar:kotlinx-coroutines-core-jvm-1.10.2.jar \
    com.google.devtools.ksp.cmdline.KSPJvmMain \
    -jvm-target 11 \
    -module-name=main \
    -source-roots project_dir/src/kotlin/main \
    -project-base-dir project_dir/ \
    -output-base-dir=project_dir/build/ \
    -caches-dir=project_dir/build/caches/ \
    -class-output-dir=project_dir/build/out/main/classes \
    -kotlin-output-dir=project_dir/build/out/main/kotlin/ \
    -java-output-dir project_dir/build/out/main/java/ \
    -resource-output-dir project_dir/build/out/main/res/ \
    -language-version=2.0 \
    -api-version=2.0 \
    path/to/processor.jar
  4. Configure KSP for Single-Platform (JVM & Android) projects

    main

    In single-target JVM or Android projects, use the following Gradle configurations to apply symbol processor dependencies in your build.gradle.kts or build.gradle file.

    • ksp: Applied to the default JVM compilation or Android main source set. Note: This is deprecated in Kotlin Multiplatform (KMP) unless ksp.allow.all.target.configuration=true is set.
    • kspTest: Runs symbol processing exclusively for unit test sources (src/test).
    • ksp<SourceSet>: Generates sources for custom JVM source sets (e.g., kspIntegrationTest).
    • ksp<BuildType>: Runs the processor only for a specific Android build variant (e.g., kspDebug).
    • ksp<Flavor>: Runs the processor for all variants matching a specific product flavor (e.g., kspFree).
    • ksp<Flavor><BuildType>: Targeted execution for a specific flavor and build type combination (e.g., kspFreeDebug).
    • kspTest<Flavor><BuildType>: Runs processing on unit test code in a specific variant/flavor (e.g., kspTestDebug).
    • kspAndroidTest<Variant>: Runs processing on Android instrumentation test code in src/androidTest (e.g., kspAndroidTestDebug).
  5. Run the Kotlin compiler in-process within the Gradle daemon

    main

    By default, the Kotlin compiler runs in a separate KotlinCompileDaemon. You can force the compiler to run directly within the Gradle daemon by setting the following Gradle property:

    kotlin.compiler.execution.strategy=in-process

    When to use this:

    • Use this if you need to debug the build directly from your IDE without attaching to a separate process.

    Warning: Running the compiler in-process can lead to performance and correctness issues. Use this approach only when necessary and at your own discretion.

  6. Use KSP Nightly Builds

    main

    If you need KSP support for the latest Kotlin stable releases that are not yet in the official releases, you can use nightly builds from the Sonatype Maven Snapshot repository.

    maven("https://central.sonatype.com/repository/maven-snapshots/")
  7. Configure KSP for Kotlin Multiplatform (KMP) projects

    main

    For Kotlin Multiplatform projects, use target-specific configurations. KSP target configurations omit the Main suffix (e.g., use kspJvm instead of kspJvmMain).

    • ksp<Target>: Target-specific configuration for KMP target main compilation (e.g., kspJvm, kspJs, kspIosArm64, kspAndroid, kspAndroidHostTest, kspAndroidDeviceTest).
    • ksp<Target>Test: Target-specific configuration for KMP target test compilation (e.g., kspJvmTest, kspJsTest).
    • kspCommonMainMetadata: Configuration for KMP commonMain metadata processing.
  8. Configure KSP Dependencies in Gradle

    main

    When using KSP, you must separate your annotation definitions from your processor implementation to avoid leaking processor logic into your application's runtime.

    In your application's build.gradle.kts:

    • Use implementation(project(":annotations")) to provide the annotation definitions to your app code.
    • Use ksp(project(":processor")) to instruct the KSP plugin to run your processor during compilation.

    In your processor's build.gradle.kts:

    • Use implementation(project(":annotations")) so the processor can reference the annotation types.
  9. Understand KSP2 API behavior changes

    main
    KSP2 introduces refinements in API behavior to improve productivity and error recovery. A key difference is how type resolution handles non-existent types. For example, when resolving a type like Map<String, NonExistentType>, KSP1 returns an error type, whereas KSP2 returns Map<String, ErrorType>.
  10. Implement a KSP Symbol Processor

    main

    To create a KSP processor, implement the process method which receives a Resolver. Use the Resolver to query the source code for specific symbols.

    Key steps:

    1. Query Symbols: Use resolver.getSymbolsWithAnnotation("fully.qualified.annotation.Name") to find annotated elements.
    2. Filter Symbols: Use .filterIsInstance<KSFunctionDeclaration>() (or other KS types) to narrow down the symbols you want to process.
    3. Traverse AST: Use the Visitor Pattern by calling it.accept(YourVisitor(), Unit) on the selected symbols. This is the recommended way to traverse the KSP Abstract Syntax Tree (AST).
    4. Register Provider: You must register your SymbolProcessorProvider implementation by adding its fully qualified name to a file in META-INF/services/ within your processor's resources.
  11. Debug KSP when running from the command line

    main
    When running the compiler directly from the command line (without Gradle), there is no KotlinCompileDaemon process. In this scenario, you must pass debug options directly to the java command rather than using the -Dkotlin.daemon.jvm.options Gradle property.
  12. Report KSP issues and feedback

    main
    To report bugs or provide feedback on KSP, you can file a GitHub issue on the official repository or connect with the KSP team in the #ksp channel on the Kotlin Slack workspace.