複数ラウンド処理
KSPは_複数ラウンド処理_、つまり複数ラウンドにわたるファイルの処理をサポートしています。これは、後続のラウンドが前のラウンドからの出力を追加の入力として使用することを意味します。
プロセッサへの変更点
複数ラウンド処理を使用するには、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
}
複数ラウンドの動作
シンボルを次のラウンドに遅延させる
プロセッサは、特定のシンボルの処理を次のラウンドに遅延させることができます。シンボルが遅延されると、プロセッサは他のプロセッサが追加情報を提供するのを待機します。必要に応じて、何ラウンドでもシンボルを遅延させ続けることができます。他のプロセッサが必要な情報を提供すると、プロセッサは遅延されたシンボルを処理できるようになります。プロセッサは、必要な情報が不足している無効なシンボルのみを遅延させるべきです。したがって、プロセッサはクラスパスからのシンボルを遅延させるべきではありません。KSPは、ソースコードではない遅延されたシンボルもフィルタリングします。
例として、アノテーション付きクラスのビルダーを作成するプロセッサは、そのコンストラクタのすべてのパラメータ型が有効であること(具体的な型に解決されていること)を要求する場合があります。最初のラウンドでは、パラメータ型の1つが解決不可能です。その後、2番目のラウンドで、最初のラウンドで生成されたファイルのために解決可能になります。
シンボルの検証
シンボルを遅延させるかどうかを決定する便利な方法は、検証によるものです。プロセッサは、シンボルを適切に処理するためにどの情報が必要かを知っている必要があります。検証には通常、コストのかかる解決(resolution)が必要となるため、必要なものだけをチェックすることを推奨します。前の例を続けると、ビルダープロセッサにとって理想的な検証は、アノテーション付きシンボルのコンストラクタのすべての解決済みパラメータ型が isError == false
を含むかどうかだけをチェックします。
KSPはデフォルトの検証ユーティリティを提供します。詳細については、高度な設定セクションを参照してください。
終了条件
複数ラウンド処理は、1回の完全な処理ラウンドで新しいファイルが生成されなかったときに終了します。終了条件が満たされたときに未処理の遅延シンボルがまだ存在する場合、KSPは未処理の遅延シンボルを持つ各プロセッサに対してエラーメッセージをログに記録します。
各ラウンドでアクセス可能なファイル
新規生成されたファイルと既存のファイルの両方が Resolver
を介してアクセス可能です。KSPはファイルにアクセスするための2つの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が提供するデフォルトの検証ロジックは、検証対象のシンボルの囲むスコープ内にある、直接到達可能なすべてのシンボルを検証します。デフォルトの検証は、囲まれたスコープ内の参照が具体的な型に解決可能かどうかをチェックしますが、参照されている型に再帰的に入り込んで検証を行うことはありません。
独自の検証ロジックを記述する
デフォルトの検証動作は、すべてのケースに適しているとは限りません。KSValidateVisitor
を参照し、カスタムの predicate
ラムダを提供することで独自の検証ロジックを記述できます。このラムダは KSValidateVisitor
によって、チェックする必要があるシンボルをフィルタリングするために使用されます。