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

Kotlin KSP聚合输出文件生成的正确实现策略

解决KSP多处理器依赖与聚合生成的正确策略

针对你遇到的KSP处理器聚合输出、跨处理器依赖以及多轮处理的问题,以下是可靠的解决方案,替代硬编码轮次的不可靠实现:

1. 核心思路:基于符号稳定状态的多轮触发

KSP的处理器通过返回未处理的符号(或返回false)告知框架需要重新运行处理,直到所有处理器返回空列表表示处理完成。我们可以利用这一机制,让处理器在依赖的符号稳定后再完成生成,无需硬编码轮次。


2. 各处理器的具体实现方案

AutoModuleProcessor(聚合@AutoModule类生成GeneratedModule)

解决重复生成、停止条件的问题:

  • 维护集合跟踪已收集的@AutoModule类全限定名,仅在集合稳定(无新类加入)时生成文件
  • 每轮对比当前收集的类与上一轮集合,判断是否需要继续触发下一轮
class AutoModuleProcessor(private val codeGenerator: CodeGenerator) : SymbolProcessor {
    private val collectedAutoModules = mutableSetOf<String>()

    override fun process(resolver: Resolver): List<KSAnnotated> {
        // 收集当前所有可解析的@AutoModule标注类
        val autoModuleSymbols = resolver.getSymbolsWithAnnotation(AutoModule::class.qualifiedName!!)
            .filterIsInstance<KSClassDeclaration>()
            .filter { !it.isError }
        val currentModules = autoModuleSymbols.map { it.qualifiedName!!.asString() }.toSet()

        // 检查集合是否稳定:当前收集与上一轮完全一致
        if (currentModules == collectedAutoModules) {
            // 集合稳定,仅生成一次GeneratedModule
            generateGeneratedModule(currentModules)
            return emptyList() // 空列表表示处理完成
        } else {
            // 集合有更新,更新跟踪集合,触发下一轮处理
            collectedAutoModules.clear()
            collectedAutoModules.addAll(currentModules)
            return autoModuleSymbols.toList() // 返回符号告知KSP重新处理
        }
    }

    private fun generateGeneratedModule(moduleNames: Set<String>) {
        val file = codeGenerator.createNewFile(
            dependencies = Dependencies.ALL_FILES,
            packageName = "com.your.package",
            fileName = "GeneratedModule"
        )
        file.bufferedWriter().use { writer ->
            writer.write("""
                package com.your.package;

                import dagger.Module;

                @Module(includes = {${moduleNames.joinToString(", ") { "$it.class" }}})
                public class GeneratedModule {
                }
            """.trimIndent())
        }
    }
}

GenerateAutoModuleProcessor(生成带@AutoModule的类)

确保生成的类能被AutoModuleProcessor收集:

  • 处理所有@GenerateAutoModule标注的类,生成对应的@AutoModule类
  • 若有文件生成,返回未处理符号触发下一轮,让AutoModuleProcessor能收集新生成的类
class GenerateAutoModuleProcessor(private val codeGenerator: CodeGenerator) : SymbolProcessor {
    override fun process(resolver: Resolver): List<KSAnnotated> {
        val generateSymbols = resolver.getSymbolsWithAnnotation(GenerateAutoModule::class.qualifiedName!!)
            .filterIsInstance<KSClassDeclaration>()
            .filter { !it.isError }

        var hasGeneratedFiles = false
        generateSymbols.forEach { symbol ->
            val className = symbol.simpleName.asString()
            val packageName = symbol.packageName.asString()
            // 生成带@AutoModule的类
            val file = codeGenerator.createNewFile(
                dependencies = Dependencies(aggregating = true, sources = arrayOf(symbol.containingFile!!)),
                packageName = packageName,
                fileName = "${className}AutoModule"
            )
            file.bufferedWriter().use { writer ->
                writer.write("""
                    package $packageName;

                    import com.your.package.AutoModule;

                    @AutoModule
                    public class ${className}AutoModule {
                    }
                """.trimIndent())
            }
            hasGeneratedFiles = true
        }

        // 有文件生成则返回符号触发下一轮,否则结束处理
        return if (hasGeneratedFiles) generateSymbols.toList() else emptyList()
    }
}

ComponentProcessor(依赖GeneratedModule生成代码)

解决无法解析GeneratedModule的问题:

  • 推迟@Component的处理,直到GeneratedModule可被解析
  • 若依赖类不存在,返回未处理符号触发下一轮
class ComponentProcessor(private val codeGenerator: CodeGenerator) : SymbolProcessor {
    override fun process(resolver: Resolver): List<KSAnnotated> {
        val componentSymbols = resolver.getSymbolsWithAnnotation(Component::class.qualifiedName!!)
            .filterIsInstance<KSClassDeclaration>()
            .filter { !it.isError }

        val unprocessedSymbols = mutableListOf<KSAnnotated>()
        componentSymbols.forEach { symbol ->
            // 获取@Component的modules参数,检查GeneratedModule是否可解析
            val componentAnnotation = symbol.annotations.find {
                it.shortName.asString() == "Component"
            } ?: return@forEach
            val modulesArg = componentAnnotation.arguments.find { it.name?.asString() == "modules" }?.value
                as? List<KSClassDeclaration> ?: return@forEach

            // 校验所有modules是否可解析(重点是GeneratedModule)
            val allModulesResolvable = modulesArg.all { module ->
                resolver.getClassDeclarationByName(module.qualifiedName!!) != null
            }

            if (!allModulesResolvable) {
                // 依赖未就绪,推迟处理
                unprocessedSymbols.add(symbol)
            } else {
                // 依赖就绪,生成目标类
                val className = symbol.simpleName.asString()
                val packageName = symbol.packageName.asString()
                val file = codeGenerator.createNewFile(
                    dependencies = Dependencies.ALL_FILES,
                    packageName = packageName,
                    fileName = "Generated${className}"
                )
                file.bufferedWriter().use { writer ->
                    writer.write("""
                        package $packageName;

                        import dagger.Component;

                        @Component(modules = GeneratedModule.class)
                        public interface Generated${className} {
                            // 生成对应业务方法
                        }
                    """.trimIndent())
                }
            }
        }

        // 返回未处理符号,触发下一轮直到依赖满足
        return unprocessedSymbols
    }
}

3. 关键注意事项

  • 不在finish()中生成文件:finish()是一轮处理结束后调用,此时依赖的处理器可能还未完成生成,导致后续处理器无法解析符号,应在process()中判断条件成熟时生成。
  • 正确指定Dependencies:生成文件时设置合理的依赖参数,让KSP能准确跟踪文件依赖,避免不必要的重处理。
  • 过滤错误符号:处理前过滤isError的符号,避免无效处理引发异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 09:25:56