目次

02.KSPビルドモジュールの作成



Roomのデータベース定義では

@Database(
  entities = [Settings::class],
  version = 1,
  exportSchema = false
)

のように、entitiesでデータベース内のエンティティを列挙します。

データベース定義は:appモジュールで行いますが、そうすると各機能モジュールのエンティティを:appモジュールが知る必要があり、モジュール結合を疎にするという設計方針から外れてしまいます。

そこで、各機能モジュールのエンティティ定義を自動で収集するKSP(Kotlin Symbol Processing)を使ったエンティティコレクターを作成することにします。

KSPライブラリの導入

エンティティコレクターに必要なKSPライブラリをバージョンカタログに追記します。

[versions]
   :
# ./gradlew -versionで表示されるKotlinのバージョンに適した最新バージョンにします(自動で上げない)
#noinspection NewerVersionAvailable
gradle-ksp = "2.0.21-1.0.28"        # https://mvnrepository.com/artifact/com.google.devtools.ksp/symbol-processing-api
kotlin-poet = "2.2.0"               # https://mvnrepository.com/artifact/com.squareup/kotlinpoet
   :
[libraries]
   :
gradle-ksp  = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "gradle-ksp" }
kotlin-poet = { module = "com.squareup:kotlinpoet", version.ref = "kotlin-poet"  }
   :

5行目
コメント部分のURLを参照して、gradleKotlinに合った最新安定バージョンを指定します。
6行目
コメント部分のURLを参照して、最新安定バージョンを指定します。
10行目~11行目
エンティティコレクターに必要なライブラリを追記します。

追記後、『Sync Now』で内容をプロジェクトに反映させます。

ビルドモジュールの作成

エンティティコレクターは、ビルド用の機能ですので、/build-logicディレクトリ内に作成します。

/build-logic直下にkspディレクトリを追加し、直下にkotlinスクリプトbuild.gradle.ktsを作成してモジュール化します。

plugins {
  `kotlin-dsl`
}

dependencies {
  implementation(libs.gradle.ksp)
  implementation(libs.kotlin.poet)
}

6行目~7行目
エンティティコレクターに必要な依存ライブラリを指定しています。

/build-logic直下のsettings.gradle.kts:build-logic:kspモジュールをビルド対象として追記します。

   :
include(":ksp")

2行目
:build-logic:kspモジュールをビルド対象に指定しています。

エンティティコレクターの作成

:build-logic:kspモジュールにsrc/main/kotlinディレクトリを作成し、jp/co/progress_llc/portal/buildlogic/kspパッケージ(ディレクトリ)を追加します。

作成したパッケージ内にRoomEntityCollector.ktを作成します。

package jp.co.progress_llc.portal.buildlogic.ksp

import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.*
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import kotlin.reflect.KClass

/**
 * Roomの@Entityアノテーションが付いたクラスを収集するKSPプロセッサー
 */
class RoomEntityCollector(
  private val codeGenerator: CodeGenerator,
  private val logger: KSPLogger
) : SymbolProcessor {
  override fun process(resolver: Resolver): List<KSAnnotated> {
    // Roomの@Entityアノテーションが付いたクラスを収集します
    val entities = resolver
      .getSymbolsWithAnnotation("androidx.room.Entity")
      .filterIsInstance<KSClassDeclaration>()
      .toList()
    logger.info("RoomEntityCollector: found ${entities.size} entities")
    if (entities.isEmpty())     return emptyList()
    // 収集したクラスをファイルへ書き出します
    val arrayContent = entities.joinToString(",\n") {
      it.qualifiedName!!.asString() + "::class"
    }
    val kClassStar = KClass::class.asClassName().parameterizedBy(STAR)
    val typeSpec = TypeSpec.objectBuilder("RoomEntities")
      .addProperty(
        PropertySpec.builder(
          "entities",
          ARRAY.parameterizedBy(kClassStar)
        )
          .initializer("arrayOf(\n$arrayContent\n)")
          .build()
      )
      .build()
    val fileSpec = FileSpec.builder(
      "jp.co.progress_llc.portal.buildlogic.ksp.generated",
      "RoomEntities"
    )
      .addType(typeSpec)
      .build()
    codeGenerator.createNewFile(
      Dependencies(false),
      "jp.co.progress_llc.portal.buildlogic.ksp.generated",
      "RoomEntities"
    ).writer().use {
      fileSpec.writeTo(it)
    }
    return emptyList()
  }
}

プロバイダーの作成

エンティティコレクターをKSPに提供するプロバイダーRoomEntityCollectorProvider.ktを作成します。

package jp.co.progress_llc.portal.buildlogic.ksp

import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider

/**
 * RoomEntityCollectorのプロバイダー
 */
class RoomEntityCollectorProvider : SymbolProcessorProvider {
  override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
    return RoomEntityCollector(
      codeGenerator = environment.codeGenerator,
      logger = environment.logger
    )
  }
}

インスタンス化

プロバイダーをKSPのサービスに登録してインスタンス化します。

:build-logic:kspモジュールの/src/mainセットにresources/META-INF/servicesディレクトリを追加し、直下にcom.google.devtools.ksp.processing.SymbolProcessorProviderを作成します。

jp.co.progress_llc.portal.buildlogic.ksp.RoomEntityCollectorProvider

1行目
RoomのエンティティコレクターのプロバイダーをKSPのプロセッサーに登録して、インスタンス化しています。