Kotlin 2.0으로 마이그레이션하기

Kotlin 2.0 마이그레이션 가보자고 🚀

HyunWoo Lee
13 min readMay 24, 2024
Photo by Kirill Fokin on Unsplash

Kotlin 2.0이 지난 24/05/21에 릴리즈되었습니다. 🎉

아직 한국어로 된 레퍼런스가 없어서 마이그레이션을 진행하고 싶은 분들은 대부분 외국에서 작성된 아티클을 읽거나 공식문서를 읽으면서 진행을 할 것 같은데요. 이번 글에서는 제가 속했던 동아리 내 어플리케이션에 Kotlin 2.0 적용기와 TroubleShooting에 대해 다뤄보려고 합니다.

Kotlin 2.0

Kotlin 2.0은 Kotlin K2 Compiler의 stable 버전이 적용된 첫 버전입니다. K2 컴파일러는 Kotlin이 지원하는 다양한 플랫폼(JVM/JS/wasm/Native 등)에 코드를 효율적으로 제공하기 위해, 컴파일러 프론트엔드 부분에서 FIR(Frontend Intermediate Representation) 자료구조만을 형성하도록 제작하여 프론트엔드단에서 성능향상을 가져오고자 설계를 변경하였습니다.

Image from Jetpack Compose compiler moving to the Kotlin repository

또한, Jetpack Compose Compiler가 Kotlin의 관리를 받게 되는 첫 버전으로 이번 버전부터 Compose의 버전관리는 Kotlin과 같이 이뤄지게 됩니다. 이전까지 Kotlin 버전 변경 이후 Compose Compiler의 버전 변경이 일어나게 되어 그 사이 기간 동안 상위 버전을 실질적으로 못 사용하는 일을 줄일 수 있고 이는 개발자 생산성 향상으로 이어질 수 있을 것입니다.

How to Migrate?

우선 Kotlin 2.0으로 변경되면서 아래와 같은 변경사항이 생겼습니다.

  • Kotlin의 빌드 결과물은 새로운 디렉토리(.kotlin)에 저장됩니다.
  • kotlinOptions 함수가 본격적으로 deprecate 되고 이를 compilerOptions로 대체합니다.
  • Compose 컴파일러를 org.jetbrains.kotlin.plugin.compose (Compose Compiler)로 관리합니다.

이 세가지를 주안점으로 프로젝트 내에 변경사항들을 어떻게 적용해야하는지 알아가보도록 합시다.

gitignore 변경사항

빌드 결과물이 .kotlin에 저장되는만큼 해당 디렉토리 역시 .gitignore에 추가되어야 합니다.

# .gitignore
.DS_Store
.kotlin/

Replace kotlinOptions with compilerOptions

kotlinOptions 는 Kotlin 컴파일러의 프로젝트 환경설정을 하는 함수입니다. 함수 블록내에서 컴파일 결과로 나오는 바이트코드의 JVM 타깃 레벨(jvmTarget)을 설정하거나 컴파일러에 전달할 인자를 설정(freeCompilerArgs)할 수 있고 이외에도 다양한 옵션들을 설정할 수 있습니다.

Kotlin 1.8대부터 kotlinOptionscompilerOptions로 대체될 수 있다는 내용이 릴리즈노트로 나왔고, Kotlin 2.0부터는 기존 kotlinOptions를 deprecate시키고 compilerOptions를 사용하도록 하고 있습니다.

Gradle 스크립트(build.gradle.kts/build.gradle) 내에서 kotlinOptions를 사용하시던 분들은 해당 부분을 compilerOptions로 변경하시면 됩니다. 단 compilerOptionsandroid 블록 내에 정의가 되어있지 않아, kotlin 블록으로 한번 감싸신 다음에 내부에서 compilerOptions를 추가하셔야 합니다.

// AS-IS
android {
kotlinOptions {
jvmTarget = "17"
}
}

// TO-BE
android {
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
}

이제 문제는 build-logic이나 buildSrc혹은 이외의 디렉토리에서 Gradle Convention Script를 사용하시는 분들에게 발생합니다. 아마 기존 코드에서 kotlinOptions 함수를 compilerOptions로 전환을 하시면 인식이 안된다는 에러가 발생할 것입니다. 이는 compilerOptions가 정의된 형태가 기존 kotlinOptions와 달라서 그런 것입니다.

kotlinOptionsBaseAppModuleExtensions의 확장함수 형태로 정의가 되고 있습니다.

public fun com.android.build.gradle.internal.dsl.BaseAppModuleExtension.kotlinOptions(
configure: org.gradle.api.Action<org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions>
)

하지만 compilerOptionsKotlinAndroidProjectExtension 의 멤버함수로 등록되어있습니다.

abstract class KotlinAndroidProjectExtension(project: Project) : KotlinSingleTargetExtension<KotlinAndroidTarget>(project) {
override val target: KotlinAndroidTarget get() = targetFuture.getOrThrow()
override val targetFuture = CompletableFuture<KotlinAndroidTarget>()

open fun target(body: KotlinAndroidTarget.() -> Unit) = project.launch(Undispatched) {
targetFuture.await().body()
}

val compilerOptions: KotlinJvmCompilerOptions = project.objects
.newInstance(KotlinJvmCompilerOptionsDefault::class.java)
.configureExperimentalTryNext(project)

fun compilerOptions(configure: Action<KotlinJvmCompilerOptions>) {
configure.execute(compilerOptions)
}

fun compilerOptions(configure: KotlinJvmCompilerOptions.() -> Unit) {
configure(compilerOptions)
}
}

즉, compilerOptions를 사용하기 위해서는 BaseExtension(혹은 이를 확장한 클래스)에서 사용하지 말고 KotlinAndroidProjectExtension에서 사용하셔야 합니다.

// AS-IS
extensions.getByType<BaseExtension>().apply {
(this as ExtensionAware).configure<KotlinJvmOptions> {
jvmTarget = "17"
}
}

// TO-BE
extensions.getByType<KotlinAndroidProjectExtension>().apply {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}

Use Kotlin Compose Compiler

위에서도 언급했다시피 Jetpack Compose Compiler가 Kotlin 2.0부터 Kotlin에서 관리되기에 이 역시 마이그레이션되어야 합니다.

우선 Compose Compiler는 Gradle의 플러그인 형식으로 관리가 되기에 Version Catalog에 하단 내용을 추가합니다.

[plugins]
# compose-compiler의 버전은 Kotlin과 같게 설정하면 됩니다.
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

이를 프로젝트 수준의 build.gradle.kts에서 아래와 같이 추가합니다.

plugins {
// 기존에 추가된 플러그인들
alias(libs.plugins.compose.compiler) apply false
}

이제 컴포즈를 사용하는 모듈들의 gradle script 내에 Compose Compiler를 적용하고, 기존 composeOptions는 지우셔야 합니다.

// app/build.gradle.kts
// AS-IS
android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
}

// TO-BE
plugins {
// 기존 플러그인들
alias(libs.plugins.compose.compiler) // 추가
}

android {
composeCompiler {
enableStrongSkippingMode = true
includeSourceInformation = true
// composeCompiler 블록내의 설정들은 하단 Reference를 참고해보세요
// Compose compiler -> Compose compiler options dsl
}
}

단 이 경우에도, Gradle Convention Script를 사용하시는 분들은 추가작업을 해주셔야 합니다.

composeCompiler 함수는 ComposeCompilerGradlePluginExtension 의 프로퍼티들을 설정합니다. 하지만 builg-logic 와 같은 Gradle Script가 들어있는 모듈에서는 이를(ComposeCompilerGradlePluginExtension) 탐색할 수 없다는 에러가 뜰 것입니다.

ComposeCompilerGradlePluginExtension은 Kotlin의 compose-compiler-gradle-plugin 라이브러리 내에 존재하므로, 이 라이브러리를 build-logic과 같은 모듈의 의존성으로 추가합니다. 이는 프로젝트 내의 의존성이 아닌, 빌드 스크립트 모듈을 작성하는 모듈에만 추가해줘야 합니다. 컴파일 클래스 패스에만 추가시켜도 되니 implementation이 아닌 compileOnly형식으로 추가시켜도 될것입니다.

# libs.versions.toml
[libraries]
# 버전은 Kotlin과 같은 버전으로
compose-compiler-extension = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }

# build-logic > build.gradle.kts
dependencies {
// 추가
compileOnly(libs.compose.compiler.extension)
}

주의사항

프로젝트 내의 의존성으로 추가하는 경우에는 kotlin-compiler-embeddable 등 다양한 Kotlin 컴파일러 관련 라이브러리가 중복선언(Duplicate class existed) 에러가 뜰것입니다.

그리고 Convention Build Script 내에 하단과 같이 코드를 수정하시면 됩니다.

// AS-IS
extensions.getByType<BaseExtension>().apply {
buildFeatures.apply {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.findVersion("compose-compiler").get().requiredVersion
}
}

// TO-BE
extensions.getByType<ComposeCompilerGradlePluginExtension>().apply {
enableStrongSkippingMode.set(true)
includeSourceInformation.set(true)
}

Conclusion

아직 출시된 지 3일밖에 안된 버전인만큼 안정성/성능에 대한 보고가 명확히 나온 것이 없습니다. 따라서 현실 프로덕트에 적용되기에는 무리가 있어보입니다만, Kotlin 팀에서 장기적으로 준비한 프로젝트이고 성능향상에 많은 노력을 기울인만큼 빠른 시일내에 안정성이 검증되어서 이를 실사용하는 팀이 많아지기를 소망합니다.

그 과정에서 이 게시글이 한국에 있는 Kotliner들에게 도움이 되기를 바랍니다.

Reference

--

--