Back to list

#02-A. 추가 내용들 (Maven GAV, Ksp vs Kapt)

목차6

개요

지난 게시글 내용을 공부하면서 추가로 정리해둔 내용들이다. 추가 내용이므로, 맥락 없이 열거만 할 예정.


1. Maven GAV, Plugin Marker Artifact

Maven GAV (Group, Artifact, Version) 방식

어떤 라이브러리를 Maven 저장소에서 찾을 때, 당연히 아무렇게나 찾지 않을 것이다 — Maven GAV라는, 규격화된 ‘좌표’를 통해 탐색하는 방식을 취한다:

io.ktor:ktor-client-core:3.3.3

이를 :를 기준으로 3개의 요소로 분리할 수 있다.

  • io.ktor: Group, 네임스페이스
  • ktor-client-core: Artifact (name), 라이브러리 이름
  • 3.3.3: version

Plugin Marker Artifact

라이브러리의 경우에는 GAV를 통해 바로 필요한 바이너리 파일을 다운받을 수 있으나, 플러그인은 조금 다르게 동작한다. 이전에, 플러그인은 libs.version.toml에서 아래와 같이 정의하였다:

[plugins]
some-plugin = { id = "com.some.plugin", version.ref = "plugin-version" }

이후 some-plugin이 저장소에서 조회될 때, id로 조회하지 않고 [id]:[id].gradle.plugin:[Version] 와 같이 조립된 “Marker”를 기준으로 저장소를 조회한다.

예를 들어 android-application = { id = "com.android.application", version.ref = "agp" }와 같이 정의된 플러그인의 경우, com.android.application:com.android.application.gradle.plugin:9.0.1 위와 같이 조립된다(agp = “9.0.1” 가정).

이 Marker는 다운로드 가능한 파일이 아니라, 이 플러그인이 실행되기 위해 필요한 ‘진짜 JAR 의존성’들이 어디있는지 알려줄 뿐이다. 어찌 보니 포인터(pointer)와 비슷한 개념같기는 한데, 아무튼 원리는 이렇다고 한다.


2. Plugin vs Library vs Dependency

각각 “플러그인”, “라이브러리”, “의존성”이라 불린다.

  • Dependency: 흔히 말하는 ‘의존성’으로, 아래의 ‘라이브러리’와 ‘플러그인’, ‘로컬 파일’ 등을 모두 포함하는 최상위 개념이다. 프로젝트의 빌드 혹은 실행을 위해 외부에서 끌어다 쓰는 모든 아티팩트(Artifact)를 총칭한다.
  • Library: 소스 코드가 의존하는 대상으로, 외부에서 끌어다 쓰는 Class 혹은 fun 등이다.
  • Plugin: Gradle 빌드 시스템이 의존하는 대상으로, gradle daemon이 빌드 시 사용할 수 있는 Task, DSL 문법 등을 포함한다.

표로 정리하면 아래와 같다:

구분libraryplugin
의존앱 코드빌드 시스템
제공 범위Class, functionGradle의 Task, DSL, 코드 생성 기능 등
사용 시기런타임빌드 중 (Gradle daemon이 사용)

3. Ksp vs Kapt

Java/Kotlin으로 코딩하다보면, @로 시작하는 Annotation을 자주 사용하게 된다. 애노테이션은 일종의 ‘레시피’로, 컴파일 시 필요한 파일을 생성하는 역할을 한다 — Hilt가 생성하는 ~_Factory 같은 파일들이 그 예시.

여기서 파일을 생성하는 주체가 ‘Kapt’ 혹은 ‘Ksp’로, 둘 중 어느 것을 선택하느냐에 따라 어떻게 Kotlin Annotation을 처리할 것인지가 결정된다.

  • Kapt (구)

    • Kotlin → Java Stub → Java Annotation Processor (APT) 실행
    • Kotlin이 Java Stub을 거쳐야하므로 느림
    • Java 관점의 코드이므로 Nullability와 같은 Kotlin의 강력한 기능이 손실됨
  • Ksp = Kotlin Symbol Processing (신)

    • Kotlin 언어를 직접 읽고, 경량 심볼 API로 변환
    • Java로의 변환이 없으므로 속도가 빠름 (벤치마크에 의하면 2배 이상?)
    • Kotlin Native = Kotlin의 모든 기능을 누림

요즘도 그런지는 모르겠지만, 몇년 전 까지만 하더라도 kapt가 종종 쓰였던 것 같다. (내 기억에는 stable 버전에서 일부 라이브러리들이 그랬다) 물론 요즘은 대부분 ksp로 전환된 것 같아(Kotlin 2.0부터 deprecated 수순이라고 한), 실제로 볼 일은 거의 없어진 것 같다.

아무튼 Ksp를 사용하기 위해서는, build.gradle.kts의 최상단의 plugin 블록에 KSP를 사용하겠다고 선언해준 후, 필요한 의존성을 ksp로 호출해주면 된다 - 주로 Compiler와 관련된 친구들이다:

plugins {
    alias(libs.plugins.ksp)
}

dependencies {
    ksp(libs.hilt.android.compiler)
}

여기서, KSP는 Kotlin 컴파일러 내부 구문을 직접적으로 읽어버리므로, Kotlin 버전이 올라가면 이에 맞춰 KSP 버전도 같이 올려줘야한다. 그렇다면, 차라리 Ksp나 Kapt를 Compiler에 포함시켜도 되지 않았을까?

굳이 둘을 구분해둔 이유는, 그냥 이 편이 더 가볍기 때문이다. (당연한 소리지만) 만약 프로젝트에서 Room DB를 사용하지 않을것이라면, 굳이 컴파일러가 @Query와 같은 구문을 이해하고 코드를 생성할 필요도 없을 것이다. 따라서 필요할 때 레고 블록처럼 추가하고, 필요 없어지면 편하게 떼어낼 수 있는 형태를 취한 것이다 - 이거를 우리는 “모듈화”라고 부르기로 했다.


Comments

링크가 복사되었습니다