코틀린 2.0.0 릴리스가 공개되었으며, 새로운 Kotlin K2 컴파일러가 안정 버전으로 출시되었습니다! 그 외 주요 사항은 다음과 같습니다:
• 새로운 Compose 컴파일러 Gradle 플러그인
• invokedynamic을 사용한 람다 함수 생성
• kotlinx-metadata-jvm 라이브러리가 안정 버전으로 출시
• 애플 플랫폼에서 Kotlin/Native의 GC 성능을 모니터링하기 위한 시그널 지원
• Kotlin/Native에서 Objective-C 메서드 충돌 해결
• Kotlin/Wasm에서 이름 있는 내보내기(named export) 지원
• Kotlin/Wasm에서 @JsExport를 사용하는 함수에 대해 부호 없는 기본 자료형 지원
• Binaryen을 사용해 기본적으로 프로덕션 빌드를 최적화
• 멀티플랫폼 프로젝트에서 컴파일러 옵션을 위한 새로운 Gradle DSL
• enum 클래스의 값에 대한 제네릭 함수의 안정적인 대체 기능
• 안정적인 AutoCloseable 인터페이스 지원
코틀린 2.0은 JetBrains 팀에게 있어 매우 중요한 이정표입니다. 이번 릴리스는 KotlinConf 2024의 중심이었습니다. 개막 기조연설에서 발표된 흥미로운 업데이트와 코틀린 언어의 최근 작업에 대해 더 알아보세요.
IDE 지원
Kotlin 2.0.0을 지원하는 Kotlin 플러그인은 최신 IntelliJ IDEA와 Android Studio에 번들로 제공됩니다. IDE에서 Kotlin 플러그인을 업데이트할 필요 없이, 빌드 스크립트에서 Kotlin 버전을 Kotlin 2.0.0으로 변경하기만 하면 됩니다.
IntelliJ IDEA의 Kotlin K2 컴파일러 지원에 대한 자세한 내용은 IDEs의 지원을 참조하세요.
IntelliJ IDEA의 Kotlin 지원에 대한 더 자세한 내용은 Kotlin 릴리스를 참조하세요.
코틀린 K2 컴파일러
K2 컴파일러 개발 과정은 길었지만, 이제 JetBrains 팀은 안정화 버전을 발표할 준비가 되었습니다. 코틀린 2.0.0에서 새로운 Kotlin K2 컴파일러가 기본으로 사용되며, 모든 대상 플랫폼(JVM, Native, Wasm, JS)에서 안정적으로 지원됩니다. 새로운 컴파일러는 성능을 크게 향상시키고, 새로운 언어 기능 개발을 가속화하며, 코틀린이 지원하는 모든 플랫폼을 통합하고, 멀티플랫폼 프로젝트에 더 나은 아키텍처를 제공합니다.
JetBrains 팀은 1,000만 줄 이상의 사용자 및 내부 프로젝트 코드를 성공적으로 컴파일하여 새로운 컴파일러의 품질을 보장했습니다. 18,000명의 개발자가 K2 컴파일러 안정화 과정에 참여했으며, 총 8만 개의 프로젝트에서 테스트하고 문제를 보고했습니다.
새로운 컴파일러로의 전환 과정을 원활하게 진행할 수 있도록, K2 컴파일러 마이그레이션 가이드를 제공했습니다. 이 가이드는 컴파일러의 다양한 장점을 설명하고, 변경 사항을 안내하며, 필요 시 이전 버전으로 롤백하는 방법도 포함하고 있습니다.
블로그 게시물에서 다양한 프로젝트에서의 K2 컴파일러 성능을 다루고 있습니다. 실질적인 K2 컴파일러 성능 데이터를 확인하고, 프로젝트에서 성능 벤치마크를 수집하는 방법을 알아보세요.
또한 KotlinConf 2024에서 Kotlin의 기능 발전과 K2 컴파일러에 대해 언어 설계 리드인 Michail Zarečenskij가 논의한 발표 영상을 시청할 수 있습니다.
K2 컴파일러 제한 사항
Gradle 프로젝트에서 K2를 활성화할 때 Gradle 8.3 이하 버전을 사용하는 프로젝트에 영향을 미칠 수 있는 제한 사항이 있습니다:
• buildSrc의 소스 코드 컴파일
• 포함된 빌드의 Gradle 플러그인 컴파일
• Gradle 8.3 이하 버전을 사용하는 프로젝트에서 사용되는 기타 Gradle 플러그인 컴파일
• Gradle 플러그인 의존성 빌드
위 문제 중 하나를 겪고 있는 경우 다음 단계를 따라 해결할 수 있습니다:
1. buildSrc, Gradle 플러그인 및 해당 의존성에 대해 언어 버전을 설정하십시오:
kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}
특정 작업에 대해 언어 및 API 버전을 설정하면, compilerOptions 확장에 설정된 값이 재정의됩니다. 이 경우, 언어 및 API 버전은 1.9 이상이 아니어야 합니다.
2. 프로젝트의 Gradle 버전을 8.3 이상으로 업데이트하세요.
스마트 캐스트 개선
코틀린 컴파일러는 특정 상황에서 객체를 자동으로 타입에 맞게 캐스팅하여 수동 캐스팅을 줄여주는 스마트 캐스트 기능을 제공합니다. K2 컴파일러는 이전보다 더 많은 상황에서 스마트 캐스트를 수행할 수 있습니다.
코틀린 2.0.0에서는 스마트 캐스트와 관련하여 다음과 같은 개선이 이루어졌습니다:
• 로컬 변수와 이후의 스코프
• 논리 OR 연산자를 사용한 타입 검사
• 인라인 함수
• 함수 타입을 가진 프로퍼티
• 예외 처리
• 증감 연산자
로컬 변수와 이후의 스코프
이전에는 if 조건 내에서 변수가 null이 아닌 것으로 평가되면 스마트 캐스트가 되었습니다. 그러나 변수를 if 조건 밖에서 선언한 경우, 해당 변수에 대한 정보는 조건 내에서 사용될 수 없어 스마트 캐스트가 불가능했습니다.
Kotlin 2.0.0에서는 변수를 if, when 또는 while 조건 전에 선언하면 컴파일러가 해당 변수에 대해 수집한 정보를 조건 블록에서도 스마트 캐스트에 사용할 수 있습니다. 예를 들어:
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
animal.purr() // Kotlin 2.0.0에서는 스마트 캐스트되어 오류 없이 실행됩니다.
}
}
fun main() {
val kitty = Cat()
petAnimal(kitty) // Purr purr
}
논리 OR 연산자를 사용한 타입 검사
코틀린 2.0.0에서는 객체의 타입 검사를 OR 연산자(||)로 결합하면 가장 가까운 공통 상위 타입으로 스마트 캐스트됩니다. 이전에는 항상 Any 타입으로 캐스트되었습니다. 예:
interface Status {
fun signal() {}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
fun signalCheck(signalStatus: Any) {
if (signalStatus is Postponed || signalStatus is Declined) {
signalStatus.signal() // Kotlin 2.0.0에서는 공통 상위 타입인 Status로 스마트 캐스트되어 오류 없이 실행됩니다.
}
}
공통 상위 타입은 union 타입의 근사치입니다. 코틀린에서는 union 타입을 지원하지 않습니다.
inline 함수
Kotlin 2.0.0에서 K2 컴파일러는 인라인 함수에 대해 다르게 처리하여, 다른 컴파일러 분석과 결합해 스마트 캐스트가 안전한지 판단할 수 있게 합니다.
특히 인라인 함수는 이제 암시적으로 callsInPlace 계약을 가지는 것으로 처리됩니다. 이는 인라인 함수에 전달된 람다 함수가 ‘즉시 호출’된다는 것을 의미합니다. 람다 함수가 즉시 호출되므로, 컴파일러는 람다 함수가 함수 본문 내의 변수에 대한 참조를 외부로 유출할 수 없음을 알게 됩니다.
컴파일러는 이 정보를 다른 분석과 함께 사용하여 캡처된 변수들에 대해 스마트 캐스트가 안전한지 결정합니다. 예를 들어:
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// Kotlin 2.0.0에서는 컴파일러가 processor가 로컬 변수이며,
// inlineAction()이 인라인 함수임을 알고 있으므로,
// processor에 대한 참조가 유출될 수 없다고 판단합니다.
// 따라서 processor를 스마트 캐스트해도 안전합니다.
if (processor != null) {
// 컴파일러는 processor가 null이 아님을 알기에 안전 호출이 필요하지 않습니다.
processor.process()
// Kotlin 1.9.20에서는 안전 호출이 필요합니다:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
함수 타입의 속성
이전 Kotlin 버전에서는 함수 타입을 가진 클래스 속성이 스마트 캐스트되지 않는 버그가 있었습니다. Kotlin 2.0.0과 K2 컴파일러에서 이 동작이 수정되었습니다. 예를 들어:
class Holder(val provider: (() -> Unit)?) {
fun process() {
// Kotlin 2.0.0에서는 provider가 null이 아니면 스마트 캐스트됩니다.
if (provider != null) {
// 컴파일러는 provider가 null이 아님을 인지합니다.
provider()
// 1.9.20에서는 컴파일러가 provider가 null이 아님을 인식하지 못하여 오류가 발생합니다:
// 명시적으로 '?.invoke()'를 사용하여 호출하라는 오류 메시지를 보냅니다.
}
}
}
이 변경 사항은 invoke 연산자를 오버로딩한 경우에도 적용됩니다. 예를 들어:
interface Provider {
operator fun invoke()
}
interface Processor : () -> String
class Holder(val provider: Provider?, val processor: Processor?) {
fun process() {
if (provider != null) {
provider()
// 1.9.20에서는 오류가 발생합니다:
// 'Provider?' 타입을 함수처럼 호출하려면 명시적으로 '?.invoke()'를 사용하라고 경고합니다.
}
}
}
예외 처리
Kotlin 2.0.0에서는 스마트 캐스트 정보를 catch 및 finally 블록에 전달할 수 있도록 예외 처리가 개선되었습니다. 이 변경으로 컴파일러가 객체가 nullable 타입을 가지는지 여부를 더 잘 추적하여 코드의 안정성을 높입니다. 예를 들어:
fun testString() {
var stringInput: String? = null
// stringInput이 String 타입으로 스마트 캐스트됩니다.
stringInput = ""
try {
// 컴파일러는 stringInput이 null이 아님을 알기 때문에
println(stringInput.length)
// 0
// 컴파일러는 이전의 스마트 캐스트 정보를 무효화하여
// stringInput이 String? 타입이 되도록 합니다.
stringInput = null
// 예외 발생
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// Kotlin 2.0.0에서는 stringInput이 null일 수 있음을 컴파일러가 인식합니다.
println(stringInput?.length)
// null
// Kotlin 1.9.20에서는 안전 호출이 필요하지 않다고 인식하지만, 이는 잘못된 판단입니다.
}
}
증감 연산자
Kotlin 2.0.0 이전에는 증가(increment) 및 감소(decrement) 연산자를 사용한 후 객체의 타입이 변경될 수 있다는 것을 컴파일러가 이해하지 못했습니다. 컴파일러가 객체의 타입을 정확하게 추적하지 못해서 코드에서 해결되지 않은 참조 오류가 발생할 수 있었습니다. 하지만 Kotlin 2.0.0에서는 이 문제가 수정되었습니다.
다음은 그 예시입니다:
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Sigma : Rho {
fun sigma() = Unit
}
interface Tau {
fun tau() = Unit
}
fun main(input: Rho) {
var unknownObject: Rho = input
// unknownObject가 Tau 인터페이스를 상속하는지 확인
// unknownObject가 Rho와 Tau 인터페이스 둘 다 상속할 수 있다는 점에 유의하세요.
if (unknownObject is Tau) {
// Rho 인터페이스에서 오버로드된 inc() 연산자를 사용
// Kotlin 2.0.0에서는 unknownObject의 타입이 Sigma로 스마트 캐스트됨
++unknownObject
// Kotlin 2.0.0에서는 컴파일러가 unknownObject의 타입이 Sigma임을 알고,
// sigma() 함수를 성공적으로 호출할 수 있음
unknownObject.sigma()
// Kotlin 1.9.20에서는 inc()가 호출될 때 스마트 캐스트가 수행되지 않아서,
// 컴파일러는 여전히 unknownObject의 타입이 Tau로 간주
// sigma() 함수 호출 시 컴파일 타임 오류가 발생
// Kotlin 2.0.0에서는 컴파일러가 unknownObject의 타입이 Sigma임을 알고,
// tau() 함수 호출 시 컴파일 타임 오류가 발생
unknownObject.tau()
// 'tau'에 대한 해결되지 않은 참조 오류 발생
// Kotlin 1.9.20에서는 컴파일러가 unknownObject의 타입을 Tau로 잘못 인식해서
// tau() 함수 호출이 가능하지만, ClassCastException이 발생
}
}
이 코드에서는 Kotlin 2.0.0에서 스마트 캐스트가 제대로 동작하는 예시를 보여줍니다. 1.9.20에서는 증가 연산자(inc()) 호출 후 타입이 올바르게 캐스트되지 않아 오류가 발생했지만, 2.0.0에서는 올바르게 스마트 캐스트가 이루어지고, 해당 타입에 맞는 메서드들이 정상적으로 호출될 수 있습니다.
Kotlin Multiplatform 개선
Kotlin 2.0.0에서 Kotlin Multiplatform과 관련된 K2 컴파일러 개선 사항은 다음과 같습니다:
컴파일 중 공통 소스와 플랫폼 소스의 분리
이전에는 Kotlin 컴파일러의 설계로 인해 공통 소스와 플랫폼 소스 세트를 컴파일 시간에 분리하는 것이 불가능했습니다. 그 결과, 공통 코드가 플랫폼 코드를 접근할 수 있었고, 이로 인해 플랫폼 간에 동작이 달라지는 문제와 공통 코드에서 사용하는 일부 컴파일러 설정 및 의존성이 플랫폼 코드에 누출되는 문제가 발생했습니다.
Kotlin 2.0.0에서는 K2 컴파일러 구현에 따라 컴파일 방식을 재설계하여 공통 소스와 플랫폼 소스 세트 간의 철저한 분리를 보장했습니다. 이 변화는 주로 expected와 actual 함수 사용 시에 눈에 띄게 나타납니다. 이전에는 공통 코드에서 호출된 함수가 플랫폼 코드의 함수로 해결되는 경우가 있었습니다. 예를 들어:
공통 코드
fun foo(x: Any) = println("common foo")
fun exampleFunction() {
foo(42)
}
플랫폼 코드
JVM:
fun foo(x: Int) = println("platform foo")
JavaScript:
// JavaScript 플랫폼에서는 foo() 함수 오버로드가 없음
이 예시에서, 공통 코드는 실행되는 플랫폼에 따라 동작이 달라집니다:
• JVM 플랫폼에서는 공통 코드에서 foo() 함수를 호출하면, 플랫폼 코드에 정의된 foo() 함수가 호출되어 platform foo가 출력됩니다.
• JavaScript 플랫폼에서는 공통 코드에서 foo() 함수를 호출하면, 플랫폼 코드에 foo() 함수가 없으므로 공통 코드의 foo() 함수가 호출되어 common foo가 출력됩니다.
Kotlin 2.0.0에서는 공통 코드가 플랫폼 코드를 접근할 수 없기 때문에, 두 플랫폼 모두 foo() 함수를 공통 코드의 foo() 함수로 해결하게 되어 common foo가 출력됩니다.
이러한 개선을 통해 플랫폼 간 동작의 일관성이 향상되었고, IntelliJ IDEA나 Android Studio와 컴파일러 간의 충돌하는 동작도 수정되었습니다. 예를 들어, expected와 actual 클래스를 사용할 때 다음과 같은 문제가 있었습니다:
공통 코드
expect class Identity {
fun confirmIdentity(): String
}
fun common() {
// 2.0.0 이전에는 IDE에서만 오류가 발생
Identity().confirmIdentity()
// RESOLUTION_TO_CLASSIFIER : Expected class
// Identity에는 기본 생성자가 없음.
}
플랫폼 코드
actual class Identity {
actual fun confirmIdentity() = "expect class fun: jvm"
}
이 예시에서 expected 클래스인 Identity는 기본 생성자가 없기 때문에 공통 코드에서 호출할 수 없습니다. 이전에는 IDE에서만 오류가 발생했지만, 코드가 JVM에서 컴파일되었고 실행되었으나, 이제 컴파일러는 정확하게 오류를 보고합니다:
Expected class 'expect class Identity : Any' does not have default constructor
해결 방식이 변경되지 않은 경우
현재는 새로운 컴파일 방식을 적용하는 과정에 있으며, 동일한 소스 세트에 포함되지 않은 함수 호출에 대해서는 여전히 기존의 해결 방식이 유지됩니다. 이 차이는 주로 멀티플랫폼 라이브러리에서 공통 코드에서 오버로드된 함수를 사용할 때 나타납니다.
예를 들어, 두 개의 다른 시그니처를 가진 whichFun() 함수가 있는 라이브러리가 있다고 가정해 보겠습니다:
예시 라이브러리
MODULE: common
fun whichFun(x: Any) = println("common function")
MODULE: JVM
fun whichFun(x: Int) = println("platform function")
공통 코드에서 whichFun() 함수를 호출하면, 라이브러리에서 가장 적합한 인자 유형을 가진 함수가 해결됩니다:
JVM 타겟을 사용하는 프로젝트
fun main() {
whichFun(2)
// platform function
}
비교를 위해, whichFun()의 오버로드를 동일한 소스 세트 내에서 선언하면 공통 코드에서 정의된 함수가 해결됩니다:
라이브러리 사용 안함
MODULE: common
fun whichFun(x: Any) = println("common function")
fun main() {
whichFun(2)
// common function
}
MODULE: JVM
fun whichFun(x: Int) = println("platform function")
마찬가지로 멀티플랫폼 라이브러리처럼 commonTest 모듈이 별도의 소스 세트에 있기 때문에, 여전히 플랫폼별 코드에 접근할 수 있습니다. 따라서 commonTest 모듈에서 함수 호출 해결 방식은 이전의 컴파일 방식과 동일합니다.
향후에는 이러한 남은 경우들도 새로운 컴파일 방식에 맞춰 더 일관되게 처리될 예정입니다.
expected와 actual 선언의 다른 가시성 수준
Kotlin 2.0.0 이전에는 Kotlin Multiplatform 프로젝트에서 expected와 actual 선언을 사용할 때, 두 선언의 가시성 수준이 동일해야 했습니다. 그러나 Kotlin 2.0.0에서는 actual 선언이 expected 선언보다 더 허용적인 경우에만 서로 다른 가시성 수준을 지원합니다. 예를 들어:
expect internal class Attribute // 가시성은 internal
actual class Attribute // 가시성은 기본적으로 public, 더 허용적
유사하게, actual 선언에서 타입 별칭을 사용할 때, 기본 타입의 가시성은 expected 선언과 동일하거나 더 허용적이어야 합니다. 예를 들어:
expect internal class Attribute // 가시성은 internal
internal actual typealias Attribute = Expanded
class Expanded // 가시성은 기본적으로 public, 더 허용적
컴파일러 플러그인 지원
현재 Kotlin K2 컴파일러는 다음과 같은 Kotlin 컴파일러 플러그인을 지원합니다:
• all-open
• AtomicFU
• jvm-abi-gen
• js-plain-objects
• kapt
• Lombok
• no-arg
• Parcelize
• SAM with receiver
• serialization
• Power-assert
또한, Kotlin K2 컴파일러는 다음을 지원합니다:
• Jetpack Compose 컴파일러 플러그인 2.0.0, 이제 Kotlin 리포지토리로 이동
• Kotlin Symbol Processing (KSP) 플러그인, KSP2부터 지원
추가적인 컴파일러 플러그인을 사용하는 경우, 해당 플러그인의 문서를 확인하여 K2와의 호환성 여부를 확인하는 것이 좋습니다.
실험적 Kotlin Power-assert 컴파일러 플러그인
Kotlin 2.0.0에서는 실험적인 Power-assert 컴파일러 플러그인이 도입되었습니다. 이 플러그인은 실패 메시지에 컨텍스트 정보를 포함시켜 디버깅을 더 쉽고 효율적으로 만들어 줍니다.
개발자는 종종 효과적인 테스트를 작성하기 위해 복잡한 어설션 라이브러리를 사용해야 합니다. Power-assert 플러그인은 어설션 표현식의 중간 값을 자동으로 생성하여 실패 메시지에 포함시키므로, 테스트가 왜 실패했는지 빠르게 파악할 수 있습니다.
어설션이 실패하면 개선된 오류 메시지는 어설션 내의 모든 변수와 하위 표현식의 값을 보여주어 어떤 조건이 실패를 일으켰는지 명확히 알 수 있게 합니다. 이는 여러 조건을 체크하는 복잡한 어설션에서 특히 유용합니다.
이 플러그인을 프로젝트에 활성화하려면 build.gradle(.kts) 파일에서 다음과 같이 설정하세요:
Kotlin
plugins {
kotlin("multiplatform") version "2.0.0"
kotlin("plugin.power-assert") version "2.0.0"
}
powerAssert {
functions = listOf("kotlin.assert", "kotlin.test.assertTrue")
}
Kotlin Power-assert 플러그인에 대해 더 알아보려면 문서를 참조하세요.
Kotlin K2 컴파일러 활성화 방법
Kotlin 2.0.0부터는 Kotlin K2 컴파일러가 기본적으로 활성화됩니다. 추가적인 작업은 필요하지 않습니다.
Kotlin K2 컴파일러를 Kotlin Playground에서 시도하기
Kotlin Playground는 2.0.0 릴리스를 지원합니다. 확인해 보세요!
IDE에서의 지원
기본적으로 IntelliJ IDEA와 Android Studio는 코드 분석, 코드 완성, 하이라이팅, 기타 IDE 관련 기능에 대해 이전 컴파일러를 사용합니다. Kotlin 2.0의 전체 경험을 IDE에서 사용하려면 K2 모드를 활성화해야 합니다.
IDE에서 Settings | Languages & Frameworks | Kotlin으로 이동한 후 Enable K2 mode 옵션을 선택하세요. 그러면 IDE에서 K2 모드를 사용하여 코드를 분석합니다.
K2 모드는 2024.2부터 베타 상태로 제공됩니다. 안정성 및 코드 분석 개선을 위해 작업 중에 있지만, 아직 모든 IDE 기능이 지원되지는 않습니다.
K2 모드를 활성화한 후, 컴파일러 동작 변경으로 인해 IDE 분석에서 차이를 경험할 수 있습니다. 새로운 K2 컴파일러가 이전 컴파일러와 어떻게 다른지에 대한 마이그레이션 가이드를 참고하세요.
K2 모드에 대해 더 알아보려면 블로그에서 확인하세요.
우리는 K2 모드에 대한 피드백을 적극적으로 수집하고 있으므로, 공용 Slack 채널에서 의견을 공유해 주세요.
새로운 K2 컴파일러에 대한 피드백 남기기
K2 컴파일러에 대해 피드백을 주시면 감사하겠습니다!
새로운 K2 컴파일러에서 발생한 문제는 문제 추적기(issue tracker)를 통해 보고해 주세요.
“Send usage statistics” 옵션을 활성화하여 JetBrains가 K2 사용에 대한 익명 데이터를 수집할 수 있도록 허용할 수 있습니다.
Kotlin/Native
이 버전에서 다음과 같은 변경 사항이 도입되었습니다:
• Apple 플랫폼에서 GC 성능 모니터링 (signposts)
• Objective-C 메서드 충돌 해결
• Kotlin/Native에서 컴파일러 인수의 로그 레벨 변경
• Kotlin/Native에서 표준 라이브러리 및 플랫폼 종속성 명시적 추가
• Gradle 구성 캐시에서 태스크 오류
Apple 플랫폼에서 GC 성능 모니터링 (signposts)
이전에는 Kotlin/Native의 가비지 컬렉터(GC) 성능을 로그를 통해서만 모니터링할 수 있었습니다. 그러나 이 로그는 iOS 앱 성능 문제를 조사하는 데 사용되는 인기 있는 툴킷인 Xcode Instruments와 통합되지 않았습니다.
Kotlin 2.0.0부터는 GC가 일시 중지되는 시점을 Instruments에서 사용할 수 있는 signposts로 보고합니다. Signposts는 앱 내에서 사용자 정의 로그를 생성할 수 있게 해주므로, 이제 iOS 앱 성능을 디버깅할 때 GC 중지 시간이 앱의 멈춤과 일치하는지 확인할 수 있습니다.
GC 성능 분석에 대한 자세한 사항은 문서를 참조하세요.
Objective-C 메서드 충돌 해결
Objective-C 메서드는 이름이 다르지만 매개변수의 수와 타입이 같은 경우가 있을 수 있습니다. 예를 들어, locationManager:didEnterRegion:과 locationManager:didExitRegion:은 이름은 다르지만 동일한 시그니처를 가집니다. Kotlin에서는 이 메서드들이 동일한 시그니처로 처리되므로, 이를 사용하려 할 때 충돌하는 오버로드 오류가 발생합니다.
이전에는 이러한 충돌을 수동으로 억제하여 컴파일 오류를 피해야 했습니다. Kotlin 2.0.0부터는 새로운 @ObjCSignatureOverride 애너테이션을 도입하여 Kotlin과 Objective-C의 상호 운용성을 개선했습니다.
이 애너테이션은 Kotlin 컴파일러에 동일한 인수 타입을 가지지만 인수 이름이 다른 여러 함수가 Objective-C 클래스에서 상속될 때 충돌하는 오버로드를 무시하도록 지시합니다. 이 애너테이션을 사용하는 것이 일반적인 오류 억제보다 더 안전하며, 이는 Objective-C 메서드를 오버라이드할 때만 사용할 수 있습니다. 일반적인 억제는 중요한 오류를 숨기고 코드가 조용히 깨질 수 있습니다.
Kotlin/Native에서 컴파일러 인수의 로그 레벨 변경
이번 릴리스에서는 Kotlin/Native Gradle 태스크(예: compile, link, cinterop)에서 컴파일러 인수의 로그 레벨이 info에서 debug로 변경되었습니다.
debug가 기본값으로 설정되어, 다른 Gradle 컴파일 태스크와 일관되게 자세한 디버깅 정보를 제공하며, 모든 컴파일러 인수가 포함됩니다.
Kotlin/Native에서 표준 라이브러리 및 플랫폼 종속성 명시적 추가
이전에는 Kotlin/Native 컴파일러가 표준 라이브러리와 플랫폼 종속성을 암시적으로 해결했으며, 이로 인해 Kotlin Gradle 플러그인이 다양한 Kotlin 타겟에서 일관되지 않게 동작했습니다.
이제 각 Kotlin/Native Gradle 컴파일은 compileDependencyFiles 컴파일 파라미터를 통해 표준 라이브러리와 플랫폼 종속성을 명시적으로 컴파일 타임 라이브러리 경로에 포함시킵니다.
Gradle 구성 캐시에서 태스크 오류
Kotlin 2.0.0부터는 NativeDistributionCommonizerTask 및 KotlinNativeCompile과 같은 태스크에서 다음과 같은 구성 캐시 오류 메시지가 나타날 수 있습니다:
invocation of Task.project at execution time is unsupported
이 오류는 Gradle 구성 캐시와 호환되지 않는 태스크(예: publish* 태스크)가 있을 때 발생합니다. 이 오류는 근본 원인을 정확히 설명하지 않기 때문에, Gradle 팀은 이 문제를 해결하기 위해 작업 중입니다.
Kotlin/Wasm
Kotlin 2.0.0은 성능과 JavaScript와의 상호 운용성을 향상시킵니다:
• Binaryen을 사용한 프로덕션 빌드 최적화
• 네임드 익스포트 지원
• @JsExport가 있는 함수에서 부호 없는 원시 타입 지원
• Kotlin/Wasm에서 TypeScript 선언 파일 생성
• JavaScript 예외 처리 지원
• 새로운 예외 처리 제안이 옵션으로 지원
• withWasm() 함수가 JS와 WASI 변형으로 분리
Binaryen을 사용한 프로덕션 빌드 최적화
Kotlin/Wasm 도구체인은 이제 모든 프로젝트에서 프로덕션 컴파일 시 Binaryen 도구를 자동으로 적용합니다. 이전에는 이 도구를 수동으로 설정해야 했습니다. 이 변화는 런타임 성능을 향상시키고 바이너리 크기를 줄여줄 것으로 예상됩니다.
이 변경 사항은 프로덕션 컴파일에만 영향을 미치며, 개발 컴파일 프로세스는 변경되지 않습니다.
네임드 익스포트 지원
이전에는 모든 Kotlin/Wasm에서 내보낸 선언이 JavaScript에서 기본 익스포트를 사용하여 임포트되었습니다. 이제 Kotlin에서 @JsExport로 표시된 각 선언을 이름으로 임포트할 수 있습니다.
// Kotlin:
@JsExport
fun add(a: Int, b: Int) = a + b
// JavaScript:
import { add } from "./index.mjs"
네임드 익스포트는 Kotlin과 JavaScript 모듈 간의 코드 공유를 더 쉽게 만들어 주며, 모듈 간 종속성 관리 및 가독성을 향상시킵니다.
@JsExport가 있는 함수에서 부호 없는 원시 타입 지원
Kotlin 2.0.0부터는 @JsExport 애너테이션을 사용하는 외부 선언 및 함수 내에서 부호 없는 원시 타입을 사용할 수 있습니다. 이제 부호 없는 원시 타입을 반환하거나 매개변수 타입으로 사용하는 함수도 JavaScript에서 사용할 수 있게 되었습니다.
Kotlin/Wasm에서 TypeScript 선언 파일 생성
Kotlin 2.0.0에서 Kotlin/Wasm 컴파일러는 @JsExport로 표시된 Kotlin 코드의 모든 선언에서 TypeScript 정의 파일을 생성할 수 있습니다. 이 정의는 IDE와 JavaScript 도구에서 코드 자동 완성, 타입 검사 등을 지원하는 데 사용됩니다.
TypeScript 정의 파일을 생성하려면 build.gradle(.kts) 파일에서 wasmJs {} 블록 안에 generateTypeScriptDefinitions() 함수를 추가하면 됩니다:
kotlin {
wasmJs {
binaries.executable()
browser { }
generateTypeScriptDefinitions()
}
}
JavaScript 예외 처리 지원
이전에는 Kotlin/Wasm 코드에서 JavaScript 예외를 처리할 수 없었습니다. 이제 Kotlin 2.0.0에서는 Kotlin/Wasm에서 JavaScript 예외를 try-catch 블록으로 처리할 수 있습니다. Throwable 또는 JsException 타입을 사용하여 JavaScript 예외를 처리할 수 있습니다.
또한, 예외가 발생하더라도 finally 블록이 올바르게 작동합니다. 하지만 JavaScript 예외가 발생할 때 호출 스택과 같은 추가 정보는 제공되지 않으며, 이를 위한 작업이 진행 중입니다.
새로운 예외 처리 제안이 옵션으로 지원
이번 릴리스에서는 WebAssembly의 새로운 예외 처리 제안을 Kotlin/Wasm에서 옵션으로 지원합니다. 이를 통해 최신 제안 버전을 지원하는 가상 머신에서 Kotlin/Wasm을 사용할 수 있습니다.
이 새로운 예외 처리 제안을 활성화하려면 -Xwasm-use-new-exception-proposal 컴파일러 옵션을 사용하면 됩니다. 이 옵션은 기본적으로 꺼져 있습니다.
withWasm() 함수가 JS와 WASI 변형으로 분리
이전에는 withWasm() 함수가 Wasm 타겟을 위한 계층 템플릿을 제공했으나, 이제는 withWasmJs()와 withWasmWasi() 함수로 대체되었습니다. 이를 통해 WASI와 JS 타겟을 트리 정의 내에서 별도로 구분할 수 있게 되었습니다.
Kotlin/JS
Kotlin 2.0.0은 ES2015 표준에서 더 많은 기능을 지원하며, Kotlin에서 JavaScript 컴파일을 현대화한 여러 변화를 포함하고 있습니다:
• 새로운 컴파일 타겟
• Suspend 함수의 ES2015 제너레이터로 컴파일
• main 함수에 인자 전달
• Kotlin/JS 프로젝트에 대한 파일별 컴파일
• 개선된 컬렉션 상호 운용성
• createInstance() 지원
• 타입 안전한 일반 JavaScript 객체 지원
• npm 패키지 관리자 지원
• 컴파일 작업 변경
• 구식 Kotlin/JS JAR 아티팩트 중단
새로운 컴파일 타겟
Kotlin 2.0.0에서는 Kotlin/JS에 새로운 컴파일 타겟인 es2015를 추가했습니다. 이 새로운 타겟을 사용하면 Kotlin에서 지원하는 모든 ES2015 기능을 한 번에 활성화할 수 있습니다. 이를 설정하려면 build.gradle(.kts) 파일에서 다음과 같이 설정합니다:
kotlin {
js {
compilerOptions {
target.set("es2015")
}
}
}
새로운 타겟은 ES 클래스와 모듈, 그리고 새로 지원하는 ES 제너레이터를 자동으로 활성화합니다.
Suspend 함수의 ES2015 제너레이터로 컴파일
이번 릴리스에서는 ES2015 제너레이터를 사용한 suspend 함수 컴파일을 실험적으로 지원합니다. 제너레이터를 사용하면 상태 기계 대신 더 효율적인 번들 크기를 얻을 수 있습니다. 예를 들어, JetBrains 팀은 Space 프로젝트에서 ES2015 제너레이터를 사용하여 20%의 번들 크기 감소를 달성했습니다.
main 함수에 인자 전달
Kotlin 2.0.0부터는 main() 함수에 인자를 지정할 수 있는 기능이 추가되었습니다. 이를 통해 명령줄 인자를 보다 쉽게 처리할 수 있습니다. build.gradle(.kts) 파일에서 passAsArgumentToMainFunction()을 사용하여 인자를 전달합니다:
kotlin {
js {
binary.executable()
passAsArgumentToMainFunction("Deno.args")
}
}
Node.js 런타임을 사용할 경우, process.argv를 자동으로 args 매개변수로 전달할 수 있는 특별한 별칭을 제공합니다:
kotlin {
js {
binary.executable()
nodejs {
passProcessArgvToMainFunction()
}
}
}
Kotlin/JS 프로젝트에 대한 파일별 컴파일
Kotlin 2.0.0에서는 파일별 컴파일을 통해 각 Kotlin 파일에 대해 하나의 JavaScript 파일을 생성할 수 있게 되었습니다. 이는 최종 번들 크기를 최적화하고 프로그램 로딩 시간을 개선하는 데 도움이 됩니다. 이를 활성화하려면 build.gradle.kts에서 useEsModules()를 추가합니다:
kotlin {
js(IR) {
useEsModules() // ES2015 모듈 지원
browser()
}
}
또는 -Xir-per-file 컴파일러 옵션을 사용할 수 있습니다.
개선된 컬렉션 상호 운용성
Kotlin 2.0.0부터는 Kotlin 컬렉션을 JavaScript 및 TypeScript로 내보낼 수 있게 되었습니다. 예를 들어, List, Set, Map과 그 변경 가능한 버전을 JavaScript에서 사용할 수 있습니다. Kotlin에서 선언에 @JsExport를 추가하면 JavaScript에서 일반적인 배열처럼 사용할 수 있습니다:
// Kotlin
@JsExport
data class User(
val name: String,
val friends: List<User> = emptyList()
)
@JsExport
val me = User(
name = "Me",
friends = listOf(User(name = "Kodee"))
)
// JavaScript
import { User, me, KtList } from "my-module"
const allMyFriendNames = me.friends
.asJsReadonlyArrayView()
.map(x => x.name) // ['Kodee']
createInstance() 지원
Kotlin 2.0.0부터는 Kotlin/JS 타겟에서 createInstance() 함수를 사용할 수 있게 되었습니다. 이 함수는 KClass 인터페이스에서 지정된 클래스를 인스턴스화하는 데 사용됩니다.
타입 안전한 일반 JavaScript 객체 지원
js-plain-objects 플러그인은 실험적이며, 언제든지 변경되거나 제거될 수 있습니다. 이 플러그인은 Kotlin에서 타입 안전한 일반 JavaScript 객체를 만들 수 있도록 도와줍니다. 이 플러그인은 외부 인터페이스에 @JsPlainObject 애너테이션을 추가하고, 객체를 안전하게 생성하고 복사할 수 있는 기능을 제공합니다.
import kotlinx.js.JsPlainObject
@JsPlainObject
external interface User {
var name: String
val age: Int
val email: String?
}
fun main() {
// JavaScript 객체 생성
val user = User(name = "Name", age = 10)
// 객체 복사 후 이메일 추가
val copy = user.copy(age = 11, email = "some@user.com")
println(JSON.stringify(user))
// { "name": "Name", "age": 10 }
println(JSON.stringify(copy))
// { "name": "Name", "age": 11, "email": "some@user.com" }
}
npm 패키지 관리자 지원
이제 npm을 패키지 관리자로 사용할 수 있습니다. 이전에는 Kotlin Multiplatform Gradle 플러그인이 Yarn만 지원했으나, Kotlin 2.0.0에서는 npm도 지원합니다. 이를 통해 설정 도구를 줄일 수 있습니다. npm을 사용하려면 gradle.properties 파일에서 다음을 설정합니다:
kotlin.js.yarn = false
컴파일 작업 변경
Kotlin 2.0.0부터는 webpack 및 distributeResources 컴파일 작업이 별도의 디렉토리를 대상으로 하며, dist 폴더로 출력됩니다. 더 이상 중복 출력이 발생하지 않습니다.
구식 Kotlin/JS JAR 아티팩트 중단
Kotlin 2.0.0부터는 구식 Kotlin/JS 아티팩트(JAR 형식)가 Kotlin 배포에서 제외되었습니다. IR 컴파일러는 이제 .klib 형식을 사용합니다.
Gradle 개선 사항
Kotlin 2.0.0은 Gradle 6.8.3에서 8.5까지 완전히 호환됩니다. 최신 Gradle 릴리스를 사용할 수도 있지만, 이 경우 일부 경고 메시지가 발생하거나 새 Gradle 기능이 제대로 작동하지 않을 수 있습니다.
이번 버전은 다음과 같은 변경 사항을 포함합니다:
• 멀티플랫폼 프로젝트에서 컴파일러 옵션을 위한 새로운 Gradle DSL
• 새로운 Compose 컴파일러 Gradle 플러그인
• 최소 지원 버전 상향
• JVM과 Android로 배포된 라이브러리를 구별하는 새로운 속성
• Kotlin/Native의 CInteropProcess에 대한 개선된 Gradle 의존성 처리
• Gradle에서의 가시성 변경
• Gradle 프로젝트에서 Kotlin 데이터의 새로운 디렉터리
• 필요 시 Kotlin/Native 컴파일러 자동 다운로드
• 구식 컴파일러 옵션 정의 방식의 사용 중단
• 최소 AGP 지원 버전 상향
• 최신 언어 버전을 시도할 수 있는 새로운 Gradle 속성
• 빌드 보고서의 JSON 출력 형식
• kapt 구성은 상위 구성에서 주석 처리기를 상속받음
• Kotlin Gradle 플러그인이 더 이상 deprecated된 Gradle 관습을 사용하지 않음
멀티플랫폼 프로젝트에서 컴파일러 옵션을 위한 새로운 Gradle DSL
이 기능은 실험적입니다. 언제든지 변경되거나 삭제될 수 있으므로 평가 목적으로만 사용하십시오. 이에 대한 피드백을 YouTrack에서 제공해 주시면 감사하겠습니다.
Kotlin 2.0.0 이전에는 Gradle을 사용하여 멀티플랫폼 프로젝트에서 컴파일러 옵션을 구성하는 방법이 매우 낮은 수준(예: 작업, 컴파일, 소스 세트 별)으로만 가능했습니다. 프로젝트에서 컴파일러 옵션을 보다 일반적으로 구성할 수 있도록 Kotlin 2.0.0에서는 새로운 Gradle DSL을 도입했습니다.
이 새로운 DSL을 사용하면 공통 소스 세트(commonMain)와 모든 타겟에 대해 컴파일러 옵션을 확장 수준에서 구성하거나, 특정 타겟에 대해 개별적으로 구성할 수 있습니다.
kotlin {
compilerOptions {
// 모든 타겟과 공유 소스셋에 대해 기본값으로 사용되는 확장 수준의 공통 컴파일러 옵션
allWarningsAsErrors.set(true)
}
jvm {
compilerOptions {
// 이 타겟에서 모든 컴파일에 대해 기본값으로 사용되는 타겟 수준의 JVM 컴파일러 옵션
noJdk.set(true)
}
}
}
전체 프로젝트 구성에는 세 가지 계층이 있습니다. 가장 높은 수준은 확장 수준이고, 그 다음은 타겟 수준이며, 가장 낮은 수준은 컴파일 단위(보통 컴파일 작업)입니다.
더 높은 수준의 설정은 낮은 수준에 대한 규약(기본값)으로 사용됩니다:
• 확장 수준의 컴파일러 옵션 값은 타겟 컴파일러 옵션의 기본값으로 사용됩니다. 여기에는 commonMain, nativeMain, commonTest와 같은 공유 소스셋이 포함됩니다.
• 타겟 컴파일러 옵션 값은 컴파일 단위(작업) 컴파일러 옵션의 기본값으로 사용됩니다. 예를 들어, compileKotlinJvm과 compileTestKotlinJvm 작업이 이에 해당합니다.
반대로, 낮은 수준에서 설정한 구성은 높은 수준의 관련 설정을 덮어씁니다:
• 작업 수준의 컴파일러 옵션은 타겟 또는 확장 수준에서 관련된 구성을 덮어씁니다.
• 타겟 수준의 컴파일러 옵션은 확장 수준에서 관련된 구성을 덮어씁니다.
프로젝트를 구성할 때, 일부 오래된 컴파일러 옵션 설정 방법이 더 이상 권장되지 않는다는 점을 유의하십시오.
새로운 DSL을 다중 플랫폼 프로젝트에서 시도해 보고, 피드백을 YouTrack에 남기시길 권장합니다. 이 DSL은 컴파일러 옵션을 구성하는 권장 방법이 될 예정입니다.
새로운 Compose 컴파일러 Gradle 플러그인
Jetpack Compose 컴파일러는 이제 composable을 Kotlin 코드로 변환하는 역할을 하며, Kotlin 리포지토리와 통합되었습니다. 이를 통해 Compose 프로젝트가 Kotlin 2.0.0으로 전환하는 데 도움이 됩니다. Compose 컴파일러는 이제 항상 Kotlin과 동시에 배포됩니다. 이로 인해 Compose 컴파일러 버전은 2.0.0으로 증가했습니다.
새로운 Compose 컴파일러를 프로젝트에서 사용하려면 build.gradle(.kts) 파일에 org.jetbrains.kotlin.plugin.compose Gradle 플러그인을 적용하고 버전을 Kotlin 2.0.0으로 설정하십시오.
이 변경 사항에 대해 더 알고 싶고 마이그레이션 지침을 보려면 Compose 컴파일러 문서를 참조하십시오.
JVM과 Android에서 배포된 라이브러리를 구분하는 새로운 속성
Kotlin 2.0.0부터 org.gradle.jvm.environment Gradle 속성이 모든 Kotlin 변형과 함께 기본적으로 배포됩니다.
이 속성은 Kotlin Multiplatform 라이브러리의 JVM 및 Android 변형을 구분하는 데 도움을 줍니다. 이 속성은 특정 라이브러리 변형이 특정 JVM 환경에 더 적합하다는 것을 나타냅니다. 대상 환경은 “android”, “standard-jvm” 또는 “no-jvm”일 수 있습니다.
이 속성을 배포하면 JVM 및 Android 대상을 가진 Kotlin Multiplatform 라이브러리를 Java 전용 프로젝트와 같은 비멀티플랫폼 클라이언트에서 소비할 때 더 강력한 처리가 가능합니다.
필요한 경우 속성 배포를 비활성화할 수 있습니다. 이를 위해 gradle.properties 파일에 다음 Gradle 옵션을 추가하십시오:
kotlin.publishJvmEnvironmentAttribute=false
Kotlin/Native에서 CInteropProcess의 Gradle 의존성 처리 개선
이번 릴리스에서는 Kotlin/Native 프로젝트에서 Gradle 작업 의존성 관리를 개선하기 위해 defFile 속성 처리가 향상되었습니다.
이 업데이트 이전에는 defFile 속성이 아직 실행되지 않은 다른 작업의 출력물로 지정된 경우 Gradle 빌드가 실패할 수 있었습니다. 이 문제를 해결하기 위해 해당 작업에 의존성을 추가하는 우회 방법이 필요했습니다:
kotlin {
macosArm64("native") {
compilations.getByName("main") {
cinterops {
val cinterop by creating {
defFileProperty.set(createDefFileTask.flatMap { it.defFile.asFile })
project.tasks.named(interopProcessingTaskName).configure {
dependsOn(createDefFileTask)
}
}
}
}
}
}
이 문제를 해결하기 위해, 이제 definitionFile이라는 새로운 RegularFileProperty 속성이 도입되었습니다. 이제 Gradle은 연결된 작업이 빌드 과정 중 후반에 실행된 후 definitionFile 속성의 존재를 지연 검증합니다. 이 새로운 접근 방식은 추가적인 의존성 추가를 제거합니다.
CInteropProcess 작업과 CInteropSettings 클래스는 defFile과 defFileProperty 대신 definitionFile 속성을 사용합니다:
kotlin {
macosArm64("native") {
compilations.getByName("main") {
cinterops {
val cinterop by creating {
definitionFile.set(project.file("def-file.def"))
}
}
}
}
}
defFile과 defFileProperty 매개변수는 더 이상 사용되지 않습니다.
Gradle에서의 가시성 변경
이 변경 사항은 Kotlin DSL 사용자에게만 영향을 미칩니다.
Kotlin 2.0.0에서는 Kotlin Gradle 플러그인을 수정하여 빌드 스크립트에서 더 나은 제어와 안전성을 제공합니다. 이전에는 특정 Kotlin DSL 함수와 속성이 특정 DSL 컨텍스트에 의도된 대로 사용되지 않고 다른 DSL 컨텍스트로 누출되는 경우가 있었습니다. 이러한 누출은 잘못된 컴파일러 옵션을 사용하는 등의 문제를 일으킬 수 있습니다. 예를 들어:
kotlin {
// Target DSL은 kotlin{} 확장 DSL에서 정의된 메서드와 속성에 접근할 수 없었습니다.
jvm {
// Compilation DSL은 kotlin{} 확장 DSL과 Kotlin jvm{} 타겟 DSL에서 정의된 메서드와 속성에 접근할 수 없었습니다.
compilations.configureEach {
// Compilation task DSL은 kotlin{} 확장, Kotlin jvm{} 타겟 또는 Kotlin compilation DSL에서 정의된 메서드와 속성에 접근할 수 없었습니다.
compileTaskProvider.configure {
// 예시:
explicitApi()
// kotlin{} 확장 DSL에서 정의되어 있으므로 오류 발생
mavenPublication {}
// Kotlin jvm{} 타겟 DSL에서 정의되어 있으므로 오류 발생
defaultSourceSet {}
// Kotlin compilation DSL에서 정의되어 있으므로 오류 발생
}
}
}
}
이 문제를 해결하기 위해, 우리는 @KotlinGradlePluginDsl 어노테이션을 추가하여 Kotlin Gradle 플러그인 DSL 함수와 속성이 의도된 컨텍스트 외부에서 노출되는 것을 방지했습니다. 다음 레벨들이 서로 분리됩니다:
• Kotlin 확장
• Kotlin 타겟
• Kotlin 컴파일
• Kotlin 컴파일 작업
가장 일반적인 경우에 대해서는 잘못 구성된 빌드 스크립트에 대해 수정 방법을 제안하는 컴파일러 경고를 추가했습니다. 예를 들어:
kotlin {
jvm {
sourceSets.getByName("jvmMain").dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3")
}
}
}
이 경우 sourceSets에 대한 경고 메시지는 다음과 같습니다:
[DEPRECATION] 'sourceSets: NamedDomainObjectContainer<KotlinSourceSet>'는 더 이상 권장되지 않습니다. Kotlin 타겟 레벨 DSL에서 'sourceSets' 컨테이너에 접근하는 것은 더 이상 권장되지 않습니다. 'sourceSets'를 Kotlin 확장 레벨에서 구성하는 것을 고려하십시오.
이 변경 사항에 대한 피드백을 주시면 감사하겠습니다! #gradle Slack 채널에서 직접 Kotlin 개발자에게 의견을 남겨주세요. Slack 초대장을 받으려면 여기서 신청할 수 있습니다.
Gradle 프로젝트에서 Kotlin 데이터의 새로운 디렉토리
.kotlin 디렉토리는 버전 관리에 커밋하지 마세요. 예를 들어, Git을 사용 중이라면 프로젝트의 .gitignore 파일에 .kotlin을 추가하십시오.
Kotlin 1.8.20에서 Kotlin Gradle 플러그인은 데이터를 Gradle 프로젝트 캐시 디렉토리인 <project-root-directory>/.gradle/kotlin에 저장하기 시작했습니다. 그러나 .gradle 디렉토리는 Gradle 전용으로 예약되어 있으므로, 이는 미래 지향적인 해결책이 아닙니다.
이를 해결하기 위해, Kotlin 2.0.0부터 기본적으로 Kotlin 데이터를 <project-root-directory>/.kotlin에 저장합니다. 여전히 호환성을 위해 일부 데이터는 .gradle/kotlin 디렉토리에 저장됩니다.
새로 추가된 Gradle 속성은 다음과 같습니다:
Gradle 속성 | 설명 |
kotlin.project.persistent.dir | 프로젝트 수준 데이터를 저장할 위치를 구성합니다. 기본값: /.kotlin |
kotlin.project.persistent.dir.gradle.disableWrite | 디렉토리에 Kotlin 데이터를 쓰지 않도록 제어하는 부울 값. 기본값: false |
이 속성을 프로젝트의 gradle.properties 파일에 추가하면 적용됩니다.
필요할 때 Kotlin/Native 컴파일러 다운로드
Kotlin 2.0.0 이전에는 멀티플랫폼 프로젝트의 Gradle 빌드 스크립트에 Kotlin/Native 타겟이 설정되어 있으면, Gradle이 항상 구성 단계에서 Kotlin/Native 컴파일러를 다운로드했습니다.
이 과정은 Kotlin/Native 타겟을 위한 코드 컴파일 작업이 실행될 계획이 없을 때 특히 비효율적이었습니다. 예를 들어, CI 프로세스의 일부로 JVM 또는 JavaScript 코드만 검사하고 싶은 사용자에게 불필요한 다운로드가 이루어졌습니다.
Kotlin 2.0.0에서는 Kotlin Gradle 플러그인에서 이 동작을 변경하여, Kotlin/Native 컴파일러가 실행 단계에서만 다운로드되며, Kotlin/Native 타겟에 대한 컴파일이 요청된 경우에만 다운로드됩니다.
이와 함께 Kotlin/Native 컴파일러의 의존성은 이제 컴파일러의 일부가 아니라 실행 단계에서 다운로드됩니다.
새로운 동작에 문제가 있을 경우, gradle.properties 파일에 다음 Gradle 속성을 추가하여 이전 동작으로 되돌릴 수 있습니다:
kotlin.native.toolchain.enabled=false
Kotlin 1.9.20-Beta부터 Kotlin/Native 배포본이 Maven Central과 CDN에 게시됩니다.
이를 통해 Kotlin은 필요한 아티팩트를 찾고 다운로드하는 방법을 변경할 수 있었습니다. 기본적으로 CDN 대신 프로젝트에서 지정한 Maven 리포지토리를 사용합니다.
이 동작을 일시적으로 이전 상태로 되돌리려면, gradle.properties 파일에 다음 Gradle 속성을 설정하십시오:
kotlin.native.distribution.downloadFromMaven=false
이 기본 동작을 변경하는 Gradle 속성은 임시적인 것이며, 향후 릴리스에서 제거될 예정입니다.
문제가 발생하면 문제 추적기 YouTrack에 보고해 주세요.
Deprecated: 구식 컴파일러 옵션 정의 방법
이번 릴리스에서는 컴파일러 옵션 설정 방법을 계속해서 개선하고 있습니다. 이 변경 사항은 여러 방법 사이의 모호성을 해소하고 프로젝트 구성을 더 직관적으로 만들어 줍니다.
Kotlin 2.0.0부터는 다음과 같은 컴파일러 옵션을 설정하는 DSL이 더 이상 권장되지 않습니다:
• KotlinCompile 인터페이스의 kotlinOptions DSL: 이 DSL은 모든 Kotlin 컴파일 작업을 구현합니다. 대신 KotlinCompilationTask<CompilerOptions>를 사용하세요.
• KotlinCompilation 인터페이스의 compilerOptions 속성: 이 DSL은 다른 DSL과 일관성이 없었고, KotlinCompilation.compileTaskProvider 컴파일 작업에 정의된 동일한 KotlinCommonCompilerOptions 객체를 사용하여 혼란을 일으켰습니다. 대신, Kotlin 컴파일 작업의 compilerOptions 속성을 사용하는 것이 좋습니다.
kotlin {
js(IR) {
compilations.all {
compileTaskProvider.configure {
compilerOptions.freeCompilerArgs.add("-Xir-minimized-member-names=false")
}
}
}
}
• KotlinCompilation 인터페이스의 kotlinOptions DSL
• KotlinNativeArtifactConfig 인터페이스, KotlinNativeLink 클래스, KotlinNativeLinkArtifactTask 클래스의 kotlinOptions DSL: 대신 toolOptions DSL을 사용하세요.
• KotlinJsDce 인터페이스의 dceOptions DSL: 대신 toolOptions DSL을 사용하세요.
컴파일러 옵션을 Kotlin Gradle 플러그인에서 정의하는 방법에 대한 자세한 정보는 컴파일러 옵션 정의 방법에서 확인할 수 있습니다.
최소 지원 AGP 버전 업그레이드
Kotlin 2.0.0부터는 최소 지원 Android Gradle 플러그인(AGP) 버전이 7.1.3으로 업데이트되었습니다.
최신 언어 버전 시도용 새로운 Gradle 속성
Kotlin 2.0.0 이전에는 새로운 K2 컴파일러를 시도하기 위해 kotlin.experimental.tryK2 Gradle 속성을 사용했습니다. 이제 K2 컴파일러가 기본적으로 활성화되었기 때문에, 이 속성은 프로젝트에서 최신 언어 버전을 시도할 수 있도록 새 형태로 발전했습니다: kotlin.experimental.tryNext.
이 속성을 gradle.properties 파일에 사용하면 Kotlin Gradle 플러그인이 Kotlin 버전의 기본값보다 하나 높은 언어 버전으로 설정됩니다. 예를 들어, Kotlin 2.0.0에서 기본 언어 버전은 2.0이므로, 이 속성은 언어 버전 2.1로 설정됩니다.
이 새로운 Gradle 속성은 이전의 kotlin.experimental.tryK2와 유사한 빌드 보고서 메트릭을 생성합니다. 예시:
##### 'kotlin.experimental.tryNext' results #####
:app:compileKotlin: 2.1 language version
:lib:compileKotlin: 2.1 language version
##### 100% (2/2) tasks have been compiled with Kotlin 2.1 #####
빌드 보고서의 활성화 방법과 그 내용을 알아보려면 추가 정보를 확인하십시오.
빌드 보고서의 새로운 JSON 출력 형식
Kotlin 1.7.0에서 우리는 컴파일러 성능을 추적하기 위해 빌드 보고서를 도입했습니다. 시간이 지나면서 성능 문제를 조사할 때 도움이 될 수 있도록 더 많은 메트릭을 추가했습니다. 이전에는 로컬 파일에 대해 출력 형식이 *.txt 형식만 지원되었습니다. Kotlin 2.0.0에서는 JSON 출력 형식도 지원하여 다른 도구를 사용해 분석하기 쉽게 만들었습니다.
JSON 출력 형식으로 빌드 보고서를 구성하려면, gradle.properties 파일에 다음 속성을 선언하십시오:
kotlin.build.report.output=json
// 빌드 보고서를 저장할 디렉터리
kotlin.build.report.json.directory=my/directory/path
또는 다음 명령어를 실행할 수 있습니다:
./gradlew assemble -Pkotlin.build.report.output=json -Pkotlin.build.report.json.directory="my/directory/path"
구성하면 Gradle은 지정한 디렉터리에서 빌드 보고서를 생성하며, 파일 이름은 ${project_name}-date-time-<sequence_number>.json 형식으로 저장됩니다.
다음은 JSON 출력 형식의 빌드 보고서 예시입니다. 이 보고서에는 빌드 메트릭과 집계된 메트릭이 포함됩니다:
"buildOperationRecord": [
{
"path": ":lib:compileKotlin",
"classFqName": "org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated",
"startTimeMs": 1714730820601,
"totalTimeMs": 2724,
"buildMetrics": {
"buildTimes": {
"buildTimesNs": {
"CLEAR_OUTPUT": 713417,
"SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 19699333,
"IR_TRANSLATION": 281000000,
"NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14088042,
"CALCULATE_OUTPUT_SIZE": 1301500,
"GRADLE_TASK": 2724000000,
"COMPILER_INITIALIZATION": 263000000,
"IR_GENERATION": 74000000,
…
}
}
…
"aggregatedMetrics": {
"buildTimes": {
"buildTimesNs": {
"CLEAR_OUTPUT": 782667,
"SHRINK_AND_SAVE_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION": 22031833,
"IR_TRANSLATION": 333000000,
"NON_INCREMENTAL_LOAD_CURRENT_CLASSPATH_SNAPSHOT": 14890292,
"CALCULATE_OUTPUT_SIZE": 2370750,
"GRADLE_TASK": 3234000000,
"COMPILER_INITIALIZATION": 292000000,
"IR_GENERATION": 89000000,
…
}
}
이렇게 JSON 형식으로 빌드 보고서를 설정하면 성능 분석에 더 유용하게 활용할 수 있습니다.
kapt 구성은 상위 구성에서 주석 처리기를 상속받습니다
Kotlin 2.0.0 이전에는 주석 처리기를 별도의 Gradle 구성에 정의하고 이를 하위 프로젝트의 kapt-specific 구성에 확장하려고 하면, kapt가 주석 처리기를 찾지 못하여 주석 처리를 건너뛰는 문제가 발생했습니다. Kotlin 2.0.0에서는 kapt가 주석 처리기에 대한 간접 의존성을 성공적으로 감지할 수 있게 되었습니다.
예를 들어, Dagger를 사용하는 하위 프로젝트의 경우, build.gradle(.kts) 파일에서 다음과 같은 구성을 사용합니다:
val commonAnnotationProcessors by configurations.creating
configurations.named("kapt") { extendsFrom(commonAnnotationProcessors) }
dependencies {
implementation("com.google.dagger:dagger:2.48.1")
commonAnnotationProcessors("com.google.dagger:dagger-compiler:2.48.1")
}
이 예시에서 commonAnnotationProcessors Gradle 구성은 모든 프로젝트에서 사용하고자 하는 공통 주석 처리기 구성입니다. extendsFrom() 메서드를 사용하여 “commonAnnotationProcessors”를 상위 구성으로 추가합니다. kapt는 commonAnnotationProcessors Gradle 구성이 Dagger 주석 처리기에 의존하고 있다는 것을 감지하고, 이를 kapt의 주석 처리기 구성에 포함시킵니다.
이 구현에 대한 감사는 Christoph Loy에게 전합니다!
Kotlin Gradle 플러그인, 더 이상 사용되지 않는 Gradle 관례 사용하지 않음
Kotlin 2.0.0 이전에는 Gradle 8.2 이상을 사용할 경우, Kotlin Gradle 플러그인이 Gradle 8.2에서 더 이상 사용되지 않는 관례를 잘못 사용하여 Gradle에서 빌드 경고를 발생시켰습니다. Kotlin 2.0.0에서는 Gradle 8.2 이상을 사용할 때 더 이상 이러한 경고가 발생하지 않도록 Kotlin Gradle 플러그인이 업데이트되었습니다.
이 변경으로, Gradle 8.2 이상 버전에서 더 이상 경고가 나타나지 않으며, kapt가 주석 처리기를 성공적으로 감지할 수 있게 되어 더 나은 호환성을 제공합니다.
표준 라이브러리
이번 릴리스는 Kotlin 표준 라이브러리의 안정성을 더욱 강화하고, 기존의 여러 함수들이 모든 플랫폼에서 공통적으로 사용될 수 있도록 합니다:
• enum 클래스 values 제네릭 함수의 안정적인 교체
• 안정적인 AutoCloseable 인터페이스
• 공통 보호 속성 AbstractMutableList.modCount
• 공통 보호 함수 AbstractMutableList.removeRange
• 공통 String.toCharArray(destination)
enum 클래스 values 제네릭 함수의 안정적인 교체
Kotlin 2.0.0에서 enumEntries<T>() 함수가 안정적(Stable)으로 제공됩니다. enumEntries<T>() 함수는 제네릭 enumValues<T>() 함수의 교체 함수입니다. 이 새로운 함수는 주어진 enum 타입 T에 대한 모든 enum 항목을 리스트로 반환합니다. enum 클래스의 entries 속성은 이전에 도입되어 안정화되었으며, 이는 합성된 values() 함수의 교체용입니다. entries 속성에 대한 자세한 내용은 Kotlin 1.8.20에서 무엇이 새로 추가되었는지에서 확인할 수 있습니다.
enumValues<T>() 함수는 여전히 지원되지만, 성능에 미치는 영향이 적은 enumEntries<T>() 함수를 사용하는 것이 좋습니다. enumValues<T>()를 호출할 때마다 새로운 배열이 생성되지만, enumEntries<T>()를 호출할 때는 매번 동일한 리스트가 반환되어 훨씬 효율적입니다.
예시:
enum class RGB { RED, GREEN, BLUE }
inline fun <reified T : Enum<T>> printAllValues() {
print(enumEntries<T>().joinToString { it.name })
}
printAllValues<RGB>()
// RED, GREEN, BLUE
안정적인 AutoCloseable 인터페이스
Kotlin 2.0.0에서 공통 AutoCloseable 인터페이스가 안정적(Stable)으로 제공됩니다. 이를 통해 자원을 쉽게 닫을 수 있으며, 유용한 몇 가지 함수도 포함됩니다:
• use() 확장 함수: 주어진 블록 함수가 선택한 자원에서 실행된 후, 예외가 발생하더라도 자원을 올바르게 닫습니다.
• AutoCloseable() 생성자 함수: AutoCloseable 인터페이스의 인스턴스를 생성합니다.
다음 예시에서는 XMLWriter 인터페이스를 정의하고, 이를 구현한 자원을 사용하는 방법을 보여줍니다. 예를 들어, 이 자원은 파일을 열고 XML 내용을 작성한 후 닫는 역할을 할 수 있습니다.
interface XMLWriter {
fun document(encoding: String, version: String, content: XMLWriter.() -> Unit)
fun element(name: String, content: XMLWriter.() -> Unit)
fun attribute(name: String, value: String)
fun text(value: String)
fun flushAndClose()
}
fun writeBooksTo(writer: XMLWriter) {
val autoCloseable = AutoCloseable { writer.flushAndClose() }
autoCloseable.use {
writer.document(encoding = "UTF-8", version = "1.0") {
element("bookstore") {
element("book") {
attribute("category", "fiction")
element("title") { text("Harry Potter and the Prisoner of Azkaban") }
element("author") { text("J. K. Rowling") }
element("year") { text("1999") }
element("price") { text("29.99") }
}
element("book") {
attribute("category", "programming")
element("title") { text("Kotlin in Action") }
element("author") { text("Dmitry Jemerov") }
element("author") { text("Svetlana Isakova") }
element("year") { text("2017") }
element("price") { text("25.19") }
}
}
}
}
}
공통 보호 속성 AbstractMutableList.modCount
이번 릴리스에서 AbstractMutableList 인터페이스의 modCount 보호 속성이 공통으로 제공됩니다. 이전에는 modCount 속성이 각 플랫폼에서 사용 가능했지만 공통 타겟에서는 사용할 수 없었습니다. 이제는 AbstractMutableList의 사용자 정의 구현을 생성하고 공통 코드에서 이 속성에 접근할 수 있습니다.
modCount 속성은 컬렉션에 대해 수행된 구조적 수정 횟수를 추적합니다. 이는 컬렉션의 크기를 변경하거나 리스트를 수정하여 진행 중인 반복문이 잘못된 결과를 반환할 수 있게 하는 작업을 포함합니다.
modCount 속성을 사용하여 사용자 정의 리스트를 구현할 때 동시 수정 사항을 등록하고 감지할 수 있습니다.
공통 보호 함수 AbstractMutableList.removeRange
이번 릴리스에서 AbstractMutableList 인터페이스의 removeRange() 보호 함수가 공통으로 제공됩니다. 이전에는 각 플랫폼에서만 사용 가능했으며 공통 타겟에서는 사용할 수 없었습니다. 이제는 AbstractMutableList의 사용자 정의 구현을 생성하고 공통 코드에서 이 함수를 오버라이드할 수 있습니다.
이 함수는 지정된 범위에 따라 이 리스트에서 요소를 제거합니다. 이 함수를 오버라이드하여 사용자 정의 구현을 활용하고 리스트 연산 성능을 향상시킬 수 있습니다.
공통 String.toCharArray(destination) 함수
이번 릴리스에서는 공통 String.toCharArray(destination) 함수가 도입됩니다. 이전에는 JVM에서만 사용할 수 있었습니다.
기존의 String.toCharArray() 함수와 비교해 보겠습니다. 기존 함수는 지정된 문자열에서 문자를 포함하는 새로운 CharArray를 생성합니다. 그러나 새로 도입된 공통 String.toCharArray(destination) 함수는 기존의 destination 배열에 문자열 문자를 이동시킵니다. 이 방법은 이미 버퍼를 가지고 있어 이를 채우고 싶을 때 유용합니다.
예시:
fun main() {
val myString = "Kotlin is awesome!"
val destinationArray = CharArray(myString.length)
// 문자열을 변환하여 destinationArray에 저장:
myString.toCharArray(destinationArray)
for (char in destinationArray) {
print("$char ")
// K o t l i n i s a w e s o m e !
}
}
Kotlin 2.0.0 설치
IntelliJ IDEA 2023.3과 Android Studio Iguana (2023.2.1) Canary 15부터 Kotlin 플러그인은 IDE에 번들된 플러그인으로 배포됩니다. 이제 JetBrains Marketplace에서 플러그인을 설치할 수 없습니다.
새로운 Kotlin 버전으로 업데이트하려면, 빌드 스크립트에서 Kotlin 버전을 2.0.0으로 변경하십시오.
원문
'Kotlin > What's new' 카테고리의 다른 글
[Kotlin 번역] What's new in Kotlin 2.0.20 (0) | 2024.11.12 |
---|---|
[Kotlin 번역] What's new in Kotlin 1.9.20 (1) | 2024.11.11 |
[Kotlin 번역] What's new in Kotlin 1.9.0 (53) | 2023.09.17 |
[Kotlin 번역] What's new in Kotlin 1.9.20-Beta (5) | 2023.09.17 |
[Kotlin 번역] What's new in Kotlin 1.8.20 (52) | 2023.09.17 |
댓글