다중 라운드 처리
KSP는 다중 라운드 처리(multiple round processing), 즉 여러 라운드에 걸쳐 파일을 처리하는 기능을 지원합니다. 이는 후속 라운드에서 이전 라운드의 출력을 추가 입력으로 사용함을 의미합니다.
프로세서의 변경 사항
다중 라운드 처리를 사용하려면 SymbolProcessor.process() 함수가 유효하지 않은 심볼에 대한 지연된 심볼 리스트(List<KSAnnotated>)를 반환해야 합니다. KSAnnotated.validate()를 사용하여 다음 라운드로 지연시킬 유효하지 않은 심볼을 필터링하세요.
다음 샘플 코드는 유효성 검사(validation check)를 사용하여 유효하지 않은 심볼을 지연시키는 방법을 보여줍니다.
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 또한 소스 코드가 아닌 지연된 심볼은 모두 필터링합니다.
예를 들어, 어노테이션이 달린 클래스에 대한 빌더를 생성하는 프로세서는 생성자의 모든 매개변수 타입이 유효해야(구체적인 타입으로 리졸브되어야) 할 수 있습니다. 첫 번째 라운드에서 매개변수 타입 중 하나를 리졸브할 수 없는 경우, 첫 번째 라운드에서 생성된 파일들 덕분에 두 번째 라운드에서는 리졸브가 가능해질 수 있습니다.
심볼 유효성 검사
심볼 지연 여부를 결정하는 편리한 방법은 유효성 검사(validation)를 통하는 것입니다. 프로세서는 심볼을 제대로 처리하기 위해 어떤 정보가 필요한지 알고 있어야 합니다. 유효성 검사에는 대개 비용이 많이 드는 리졸루션(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에서 확인이 필요한 심볼을 필터링하는 데 사용됩니다.
