Primary/Kotlin

kapt를 KSP로 migrate하기

해스끼 2024. 6. 8. 15:47

kapt란?

Java annotation processor를 Kotlin에서 사용할 수 있게 해주는 도구이다. Dagger나 Data Binding 등을 Kotlin 프로젝트에서도 사용할 수 있다는 것. 안드로이드에서는 대표적으로 Hilt, Room 등의 라이브러리와 함께 사용한다.

 

내부적으로는 annotation 처리가 필요한 클래스마다 Java stub을 만드는 방식으로 구현되어 있다. 

kapt가 생성한 MainActivity의 Stub

그런데 이 과정에서 Kotlin 코드를 Java로 변환하고, 변환된 코드에 Java annotation processor를 적용하기 때문에, 코드를 거의 두 번 컴파일하는 셈이다. 당연히 빌드 속도에는 좋지 않다.

 

kapt의 단점을 해결하기 위해 KSP(Kotlin Symbol Processing)가 개발되었다. KSP는 Kotlin 코드를 직접 분석할 수 있고, JetBrains의 주장에 따르면 kapt 대비 2배 이상 빠르게 작동한다고 한다.

 

KSP API는 코드를 symbol level에서 분석한다. 즉 클래스, 클래스의 멤버 변수와 멤버 함수 등을 분석할 수 있지만, ``if``나 ``for`` 블럭 등 세부 구문까지는 분석할 수 없다.

// KSP 관점에서 본 프로젝트
KSFile
  packageName: KSName
  fileName: String
  annotations: List<KSAnnotation>  (File annotations)
  declarations: List<KSDeclaration>
    KSClassDeclaration // class, interface, object
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      classKind: ClassKind
      primaryConstructor: KSFunctionDeclaration
      superTypes: List<KSTypeReference>
      // contains inner classes, member functions, properties, etc.
      declarations: List<KSDeclaration>
    KSFunctionDeclaration // top level function
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      functionKind: FunctionKind
      extensionReceiver: KSTypeReference?
      returnType: KSTypeReference
      parameters: List<KSValueParameter>
      // contains local classes, local functions, local variables, etc.
      declarations: List<KSDeclaration>
    KSPropertyDeclaration // global variable
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      extensionReceiver: KSTypeReference?
      type: KSTypeReference
      getter: KSPropertyGetter
        returnType: KSTypeReference
      setter: KSPropertySetter
        parameter: KSValueParameter

 

KSP를 적용한 프로젝트는 다음과 같이 컴파일된다.

  1. KSP가 소스 코드와 resource를 읽고 분석한다.
  2. KSP가 코드 또는 다른 형태의 output을 생성한다.
  3. Kotlin 컴파일러가 KSP의 output과 소스 코드 등을 참고하여 프로그램을 컴파일한다.

kapt in maintenance mode

KSP가 개발됨에 따라, kapt는 Kotlin 업데이트 대응을 제외한 모든 업데이트를 중지한다. 따라서 kapt를 가급적 빨리 KSP로 migrate하는 것이 좋다.

 

KSP migration은 모듈 단위로 하면 좋다. 프로젝트 전체에서는 KSP와 kapt를 둘 다 사용하고 있다고 하더라도, 어떤 모듈에서 KSP만 사용하고 있다면 그 모듈을 컴파일할 때는 KSP만 사용된다.

KSP migration

블린더에 KSP를 적용해 보자.

1. 라이브러리가 KSP를 지원하는지 확인

kapt와 함께 사용하고 있는 라이브러리가 KSP를 지원하는지 확인하자. Dagger, Glide, Room 같은 유명한 라이브러리는 거의 다 지원한다. 아래 링크에서도 리스트를 확인할 수 있다.

 

참고로 Data Binding은 KSP를 지원할 계획이 없다고 한다. (참고)

 

Kotlin Symbol Processing API | Kotlin

 

kotlinlang.org

블린더에서는 Hilt와 Room을 kapt로 사용하고 있는데, 두 라이브러리 모두 KSP를 지원한다.

2. 프로젝트에 KSP 추가

Top-level ``build.gradle.kts`` 파일에 KSP 플러그인을 추가하자. 프로젝트의 Kotlin 버전과 호환되는 KSP 버전을 사용해야 한다.

plugins {
    id("com.google.devtools.ksp") version "2.0.0-1.0.22" apply false
}

버전명이 좀 특이한데, 아마 하이픈 앞 부분이 호환되는 Kotlin 버전을(``2.0.0``), 뒷부분이 ksp 버전을 의미하는 듯하다(``1.0.22``).

 

이제 KSP를 사용할 모듈의 ``bulid.gradle.kts``에 KSP 플러그인을 추가하자.

plugins {
    id("com.google.devtools.ksp")
}

3. kapt를 KSP로 변경

모든 kapt 호출을 KSP로 바꾸자. 그냥 호출하는 함수(?) 이름만 바꾸면 된다.

ksp(libs.hilt.compiler)
ksp(libs.hilt.compiler.androidx)
kspAndroidTest(libs.hilt.compiler)

4. kapt 플러그인 제거

정상적으로 빌드 되는지 확인했으면, 이제 kapt를 제거하자. 바이바이

plugins {
    id("org.jetbrains.kotlin.kapt") // 제거!
}

kapt 설정도 제거해야 한다.

kapt {
    correctErrorTypes = true
    useBuildCache = true
}

빌드 시간 테스트

블린더에 KSP를 적용한 후 컴파일 시간을 비교해 보았다. (5번 반복하여 평균을 계산)

  • 1.9.23: 146초
  • 2.0.0: 140초
  • 2.0.0 + kapt에 2.0 적용: 75초
  • 2.0.0 + KSP: 68초

컴파일 시간이 약 10% 정도 빨라졌다. kapt를 많이 사용하고 있던 프로젝트일수록 체감이 클 것 같다.

참고자료

 

kapt에서 KSP로 이전  |  Android Studio  |  Android Developers

주석 프로세서의 사용을 kapt에서 KSP로 이전합니다.

developer.android.com

 

Kotlin Symbol Processing API | Kotlin

 

kotlinlang.org