다중 라운드 처리(Multiple-round processing)

다중 라운드 처리(Multiple-round processing)

KSP는 다중 라운드 처리, 즉 파일을 여러 라운드에 걸쳐 처리하는 방식을 지원해요. 각 처리 라운드의 산출물이 그다음 라운드의 추가 입력으로 사용되지요.

출처: Multiple-round processing

본문

다중 라운드 처리를 사용하려면 SymbolProcessor.process()에서 지연(deferred)할 심볼들을 List<KSAnnotated>로 반환하면 돼요. KSP는 이 심볼들을 다음 라운드에서 처리합니다.

유효하지 않은 심볼을 지연하려면 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
}

어떤 라운드에서도 새 파일이 생성되지 않으면 다중 라운드 처리가 끝나요. 지연된 심볼이 남아 있는데도 처리되지 않으면, KSP는 지연 심볼이 남아 있는 각 프로세서에 대해 오류를 기록합니다.

심볼을 다음 라운드로 지연하기

프로세서는 다른 프로세서로부터 추가 정보가 필요할 때 심볼을 더 나중 라운드로 지연할 수 있어요. 필요한 정보가 생길 때까지 프로세서는 같은 심볼을 여러 라운드에 걸쳐 계속 지연할 수 있고, 정보가 준비되면 그때 심볼을 처리합니다.

다음 경우에만 심볼을 지연하세요.

  • 심볼을 처리하기 전에 추가 정보가 필요한 경우.
  • 심볼이 소스 코드에서 비롯된 경우.

클래스패스(classpath)에서 온 심볼은 절대 지연하지 마세요. KSP가 클래스패스 심볼은 자동으로 걸러 줍니다.

예를 들어 애너테이션이 달린 클래스의 빌더를 생성하는 프로세서는 모든 생성자 파라미터 타입이 구체적인 타입으로 해석(resolve)되길 요구할 수 있어요. 첫 번째 라운드에서는 어떤 파라미터 타입이 해석되지 않을 수 있는데, 그 사이에 생성된 파일 덕분에 더 나중 라운드에서는 해석 가능해질 수 있죠. 그러면 프로세서가 그 클래스를 처리할 수 있어요.

심볼 검증하기

검증(validation)은 심볼을 더 나중 라운드로 지연할지 결정하는 편리한 방법이에요. 프로세서는 심볼을 올바르게 처리하는 데 필요한 정보를 정의해야 합니다.

검증은 종종 타입 해석을 필요로 하므로 비용이 많이 들 수 있어요. 심볼을 처리하는 데 필요한 정보만 확인하세요.

기본 검증 동작이 모든 사용 사례에 적합한 것은 아니에요. 검증을 커스터마이즈하려면 KSValidateVisitor를 사용하고, 검증할 심볼을 선택하는 predicate 람다를 제공하면 됩니다.

커스텀 검증을 구현할 때는 KSType.isError를 사용해 타입이 유효한지 판단하세요. isErrortrue라면 KSP가 타입을 해석하지 못한 거예요. 이 정보를 바탕으로 처리를 더 나중 라운드로 지연할지 결정할 수 있습니다.

파일과 심볼에 접근하기

새로 생성된 파일과 기존 파일 모두 Resolver를 통해 접근할 수 있어요.

KSP는 파일 접근을 위한 두 가지 API를 제공합니다.

  • Resolver.getAllFiles()는 기존 파일과 새로 생성된 파일 모두의 목록을 반환해요.
  • Resolver.getNewFiles()는 이전 라운드에서 생성된 파일만 반환합니다.

관련 심볼을 얻는 주된 진입점으로는 Resolver.getSymbolsWithAnnotation()을 사용하세요.

각 라운드에서 Resolver.getSymbolsWithAnnotation()은 새로 생성된 파일의 심볼과 이전 라운드에서 지연된 심볼만 반환해요. 이 덕분에 불필요한 재처리를 피할 수 있습니다.

프로세서 인스턴스화

KSP는 프로세서 인스턴스를 딱 한 번만 만들어요. 프로세서 인스턴스에 정보를 저장해 두고 여러 라운드에 걸쳐 재사용할 수 있습니다.

하지만 모든 KSP 심볼을 라운드마다 재사용할 수 있는 건 아니에요. 프로세서가 새 파일을 생성하면 심볼 해석 결과가 바뀔 수 있고, 이는 이전에 해석된 심볼의 유효성에도 영향을 줄 수 있습니다.

현재 라운드에서 프로세서에 전달된 Resolver 인스턴스만 사용하세요. Resolver를 저장해 두었다가 여러 라운드에 걸쳐 재사용하면 안 됩니다.

오류와 예외 처리

오류(Errors)

프로세서는 KSPLogger.error()를 호출해 오류를 보고해요.

프로세서가 오류를 보고하면 KSP는 SymbolProcessor.finish() 대신 SymbolProcessor.onError()를 호출합니다. 현재 라운드가 끝난 뒤 처리는 중단돼요.

그 라운드 동안 다른 프로세서들은 정상적으로 계속 처리됩니다. KSP는 모든 프로세서가 현재 라운드를 마친 뒤에만 오류를 처리합니다.

예외(Exceptions)

KSP는 KSP가 던진 예외와 프로세서가 던진 예외를 구분해요. 두 유형 모두 즉시 처리를 중단시키고 KSPLogger를 통해 오류로 기록됩니다.

KSP가 던진 예외는 조사를 위해 KSP 개발자에게 보고하세요. KSP 이슈 트래커에 이슈를 만들어 주시면 됩니다.

오류나 예외가 발생한 라운드가 끝날 때, KSP는 모든 프로세서에 대해 SymbolProcessor.onError()를 호출해요. SymbolProcessoronError()의 기본 no-op 구현을 제공하므로, 커스텀 오류 처리 로직이 필요하면 이 메서드를 재정의하면 됩니다.

더 알아보기