You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

KSP注解处理器的输出目录与源码集配置问题

问题背景与疑问

我们在混编Java/Kotlin的原生Android项目中,用基于KSP的注解处理器收集带项目内部特定注解的类定义,生成顶层函数供构建流程使用,整体运行正常。
注解处理器实现于独立模块,和业务代码分离,只能通过字符串指定注解名称扫描目标类,无法依赖注解所在模块。
为避免注解重构(如重命名)但处理器内注解名称未同步导致生成空列表文件引发应用异常,我们加了单元测试检查生成文件的顶层函数是否存在且列表非空。
但执行单元测试时遇到问题:KSP再次触发,在build目录的不同子文件夹(当前构建变体为devDebug,对应devDebug与devDebugUnitTest)生成内容为空的文件,推测是KSP未扫描到目标业务类。

现提出两个问题:

  1. 如何让environment.codeGenerator.createNewFile()生成的文件始终输出至同一目录,不受触发范围影响?
  2. 如何配置让KSP扫描应用实际源码目录?找到的srcDirs配置示例均针对Android插件模块,而该处理器模块仅为纯Kotlin模块,未使用Android插件。

处理器模块Gradle配置

plugins {
    id 'java-library'
    id 'org.jetbrains.kotlin.jvm'
    id 'com.google.devtools.ksp'}

java {
    sourceCompatibility = versions.java.javaVersion
    targetCompatibility = versions.java.javaVersion
}

dependencies {
    implementation libs.googleDevtoolsKspSymbolProcessing
}

处理器核心代码

class SomeAnnotationSymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {

    override fun process(resolver: Resolver): List<KSClassDeclaration> {
        val annotatedClasses: Sequence<KSClassDeclaration> =
            resolver.getSymbolsWithAnnotation(SOME_ANNOTATION)
                .filterIsInstance<KSClassDeclaration>()
                .filter {
                    it.annotations.any()
                }

        try {
            environment.logger.info("Creating file '$GENERATED_CLASS_NAME' based on found '$SOME_ANNOTATION_NAME' annotations.")
            annotatedClasses.mapNotNull { it.containingFile }.run {
                environment.codeGenerator.createNewFile(
                    dependencies = Dependencies(
                        false,
                        *this.toList().toTypedArray(),
                    ),
                    packageName = SCAN_BARCODES_PACKAGE,
                    fileName = GENERATED_CLASS_NAME,
                ).also {
                    it.write(assembleClassImplementation(annotatedClasses.toList()).toByteArray())
                }
            }
        } catch (_: FileAlreadyExistsException) {
            environment.logger.info("Existing file '$GENERATED_CLASS_NAME' has been overwritten silently.")
        }

        // Provide list of skipped symbols to the next round of symbol processing.
        // Those symbols will get processed in the next round, the processor automatically takes care of that based on the `Sequence` logic.
        return annotatedClasses.filterNot { it.validate() }.toList().also {
            environment.logger.info("Files not yet considered valid and held back for next round: ${it.joinToString(", ")}")
        }
    }
}

解决方案

问题1:统一生成文件的输出目录

KSP默认会跟着构建任务(比如主编译、单元测试编译)把文件输出到对应变体的目录里,要统一路径可以用以下两种方法:

方法1:自定义生成目录(直接控制路径)

绕过codeGenerator.createNewFile()的默认逻辑,直接指定固定目录创建文件:

// 拿到KSP默认生成根目录,或自定义项目内路径
val baseDir = environment.options["ksp.generated.dir"] ?: environment.codeGenerator.generatedDir
val unifiedOutputDir = File(baseDir, "shared-generated")
unifiedOutputDir.mkdirs()

// 构建包含包名结构的目标文件路径
val packageDir = File(unifiedOutputDir, SCAN_BARCODES_PACKAGE.replace(".", "/"))
packageDir.mkdirs()
val targetFile = File(packageDir, "$GENERATED_CLASS_NAME.kt")

// 写入生成内容
targetFile.writeBytes(assembleClassImplementation(annotatedClasses.toList()).toByteArray())

注意:这种方式需要自己处理文件重复和增量编译逻辑,若要保留KSP的增量编译支持,用下面的方法。

方法2:通过Gradle传参统一配置目录

在应用模块的Gradle中添加KSP参数,统一指定输出目录:

ksp {
    arg("fixedOutputDir", "${project.buildDir}/generated/ksp/shared")
}

然后在处理器代码中读取该参数,按自定义路径创建文件:

val fixedDir = environment.options["fixedOutputDir"] ?: error("请在应用模块配置fixedOutputDir参数")
val packageDir = File(fixedDir, SCAN_BARCODES_PACKAGE.replace(".", "/"))
packageDir.mkdirs()
val targetFile = File(packageDir, "$GENERATED_CLASS_NAME.kt")
// 写入生成内容...

问题2:让KSP扫描应用实际源码目录

处理器模块是纯Kotlin库,本身不需要扫描源码,问题出在单元测试任务触发时,KSP只扫描了测试源码,没覆盖主业务源码。解决方式如下:

  1. 在应用模块(Android模块)的Gradle中配置,让单元测试的KSP任务包含主源码:
tasks.withType(Test) {
    ksp {
        sourceSets project.sourceSets.main
    }
}

或者直接让所有KSP任务扫描主+测试源码:

ksp {
    sourceSets project.sourceSets.main, project.sourceSets.test
}
  1. 如果处理器模块自身单元测试需要扫描应用源码,在处理器模块的Gradle中添加依赖和配置:
// 处理器模块build.gradle
dependencies {
    testImplementation project(":app") // 引入应用模块代码
}

tasks.withType(Test) {
    ksp {
        // 添加应用模块的主源码路径
        srcDirs project(":app").sourceSets.main.java.srcDirs
        srcDirs project(":app").sourceSets.main.kotlin.srcDirs
    }
}

内容的提问来源于stack exchange,提问作者arkascha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 05:07:50