다중 라운드 처리
KSP는 *다중 라운드 처리(multiple round processing)*를 지원하며, 이는 여러 라운드에 걸쳐 파일을 처리하는 것을 의미합니다. 후속 라운드는 이전 라운드의 결과물을 추가 입력으로 사용합니다.
프로세서 변경 사항
다중 라운드 처리를 사용하려면 SymbolProcessor.process()
함수가 유효하지 않은 심볼에 대해 지연된 심볼( List<KSAnnotated>
) 목록을 반환해야 합니다. KSAnnotated.validate()
를 사용하여 다음 라운드로 지연될 유효하지 않은 심볼을 필터링합니다.
다음 샘플 코드는 유효성 검사를 통해 유효하지 않은 심볼을 지연시키는 방법을 보여줍니다.
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Builder")
val result = symbols.filter { !it.validate() }
symbols
.filter { it is KSClassDeclaration && it.validate() }
.map { it.accept(BuilderVisitor(), Unit) }
return result
}
다중 라운드 동작
심볼을 다음 라운드로 지연시키기
프로세서는 특정 심볼의 처리를 다음 라운드로 지연시킬 수 있습니다. 심볼이 지연되면 프로세서는 다른 프로세서가 추가 정보를 제공하기를 기다립니다. 필요한 만큼 여러 라운드에 걸쳐 심볼을 계속 지연시킬 수 있습니다. 다른 프로세서가 필요한 정보를 제공하면 프로세서는 지연된 심볼을 처리할 수 있습니다. 프로세서는 필요한 정보가 부족한 유효하지 않은 심볼만 지연시켜야 합니다. 따라서 프로세서는 클래스패스(classpath)의 심볼을 지연시켜서는 안 되며, KSP는 소스 코드에서 가져온 것이 아닌 지연된 심볼도 필터링합니다.
예를 들어, 어노테이션(annotation)이 지정된 클래스에 대한 빌더(builder)를 생성하는 프로세서는 생성자의 모든 파라미터 타입(parameter type)이 유효(구체적인 타입으로 해석)해야 할 수 있습니다. 첫 번째 라운드에서는 파라미터 타입 중 하나를 해석할 수 없습니다. 그러다가 두 번째 라운드에서는 첫 번째 라운드에서 생성된 파일 덕분에 해석 가능해집니다.
심볼 유효성 검사
심볼을 지연시켜야 할지 결정하는 편리한 방법은 유효성 검사를 통하는 것입니다. 프로세서는 심볼을 제대로 처리하는 데 어떤 정보가 필요한지 알아야 합니다. 유효성 검사는 일반적으로 비용이 많이 들 수 있는 해석(resolution)을 필요로 하므로, 필요한 것만 확인하는 것을 권장합니다. 이전 예시를 계속해서, 빌더 프로세서에 대한 이상적인 유효성 검사는 어노테이션이 지정된 심볼의 생성자에서 해석된 모든 파라미터 타입이 isError == false
를 포함하는지 여부만 확인합니다.
KSP는 기본 유효성 검사 유틸리티를 제공합니다. 자세한 내용은 고급 섹션을 참조하세요.
종료 조건
다중 라운드 처리는 한 라운드의 전체 처리에서 새로운 파일이 생성되지 않을 때 종료됩니다. 종료 조건이 충족되었을 때 처리되지 않은 지연된 심볼이 여전히 존재하면 KSP는 처리되지 않은 지연된 심볼이 있는 각 프로세서에 대해 오류 메시지를 기록합니다.
각 라운드에서 접근 가능한 파일
새로 생성된 파일과 기존 파일 모두 Resolver
를 통해 접근할 수 있습니다. KSP는 파일 접근을 위한 두 가지 API를 제공합니다: Resolver.getAllFiles()
와 Resolver.getNewFiles()
. getAllFiles()
는 기존 파일과 새로 생성된 파일의 결합된 목록을 반환하며, getNewFiles()
는 새로 생성된 파일만 반환합니다.
getSymbolsAnnotatedWith() 변경 사항
불필요한 심볼 재처리를 피하기 위해 getSymbolsAnnotatedWith()
는 새로 생성된 파일에서 발견된 심볼과 이전 라운드에서 지연된 심볼만 반환합니다.
프로세서 인스턴스화
프로세서 인스턴스는 한 번만 생성되므로 프로세서 객체에 정보를 저장하여 이후 라운드에서 사용할 수 있습니다.
라운드 간 일관성 있는 정보
모든 KSP 심볼은 여러 라운드에 걸쳐 재사용할 수 없습니다. 이는 해석 결과가 이전 라운드에서 생성된 내용에 따라 잠재적으로 변경될 수 있기 때문입니다. 그러나 KSP는 기존 코드 수정을 허용하지 않으므로, 심볼 이름의 문자열 값과 같은 일부 정보는 여전히 재사용할 수 있어야 합니다. 요약하자면, 프로세서는 이전 라운드의 정보를 저장할 수 있지만, 이 정보가 향후 라운드에서 유효하지 않을 수 있다는 점을 명심해야 합니다.
오류 및 예외 처리
오류(프로세서가 KSPLogger.error()
를 호출하여 정의된) 또는 예외가 발생하면 현재 라운드가 완료된 후 처리가 중단됩니다. 모든 프로세서는 onError()
메서드를 호출하며 finish()
메서드는 호출하지 않습니다.
오류가 발생했더라도 다른 프로세서는 해당 라운드 동안 정상적으로 처리를 계속합니다. 이는 오류 처리가 해당 라운드의 처리가 완료된 후에 발생한다는 것을 의미합니다.
예외 발생 시 KSP는 KSP에서 발생한 예외와 프로세서에서 발생한 예외를 구별하려고 시도합니다. 예외는 즉시 처리 종료로 이어지며 KSPLogger에 오류로 기록됩니다. KSP에서 발생한 예외는 추가 조사를 위해 KSP 개발자에게 보고되어야 합니다. 예외 또는 오류가 발생한 라운드 말에 모든 프로세서는 onError()
함수를 호출하여 자체적인 오류 처리를 수행합니다.
KSP는 SymbolProcessor
인터페이스의 일부로 onError()
에 대한 기본 노옵(no-op) 구현을 제공합니다. 이 메서드를 오버라이드하여 자체 오류 처리 로직을 제공할 수 있습니다.
고급
유효성 검사의 기본 동작
KSP가 제공하는 기본 유효성 검사 로직은 유효성 검사 중인 심볼의 포함 범위(enclosing scope) 내에서 직접 접근 가능한 모든 심볼의 유효성을 검사합니다. 기본 유효성 검사는 포함된 범위의 참조가 구체적인 타입으로 해석 가능한지 확인하지만, 참조된 타입으로 재귀적으로 들어가 유효성 검사를 수행하지는 않습니다.
자신만의 유효성 검사 로직 작성
기본 유효성 검사 동작이 모든 경우에 적합하지 않을 수 있습니다. KSValidateVisitor
를 참조하여 커스텀 predicate
람다를 제공함으로써 자신만의 유효성 검사 로직을 작성할 수 있습니다. 이 람다는 KSValidateVisitor
에서 검사해야 할 심볼을 필터링하는 데 사용됩니다.