Kotlin 1.8.0의 새로운 기능(What's new in Kotlin 1.8.0)

Kotlin 1.8.0의 새로운 기능(What's new in Kotlin 1.8.0)

Kotlin 1.8.0이 출시됐어요. 이번 릴리스의 주요 변경점을 정리하면 다음과 같아요.

출처: Kotlin 1.8.0의 새로운 기능

본문

출시일: 2022년 12월 28일

Kotlin 1.8.0이 나왔어요. 여기 그중 가장 큰 변화만 먼저 짚어볼게요.

  • JVM을 위한 새로운 실험용 함수: 디렉터리 내용을 재귀적으로 복사하거나 삭제
  • kotlin-reflect 성능 개선
  • 더 나은 디버깅을 위한 새로운 -Xdebug 컴파일러 옵션
  • kotlin-stdlib-jdk7kotlin-stdlib-jdk8kotlin-stdlib로 통합
  • Objective-C/Swift 상호 운용성 개선
  • Gradle 7.3 호환

Kotlin의 릴리스 주기에 대한 자세한 내용은 Kotlin 릴리스 과정에서 확인할 수 있어요.

IDE 지원

1.8.0을 지원하는 Kotlin 플러그인은 다음 환경에서 사용할 수 있어요.

IDE 지원 버전
IntelliJ IDEA 2021.3, 2022.1, 2022.2
Android Studio Electric Eel (221), Flamingo (222)

IntelliJ IDEA 2022.3에서는 IDE 플러그인을 업데이트하지 않고도 프로젝트를 Kotlin 1.8.0으로 올릴 수 있어요.

IntelliJ IDEA 2022.3에서 기존 프로젝트를 Kotlin 1.8.0으로 마이그레이션하려면 Kotlin 버전을 1.8.0으로 바꾸고 Gradle 또는 Maven 프로젝트를 다시 가져오면 돼요.

Kotlin/JVM

1.8.0부터 컴파일러가 JVM 19에 해당하는 바이트코드 버전의 클래스를 생성할 수 있게 됐어요. 새 언어 버전은 다음 내용도 함께 포함해요.

  • JVM 애너테이션 타깃 생성을 끄는 컴파일러 옵션
  • 최적화를 비활성화하는 새로운 -Xdebug 컴파일러 옵션
  • 기존 백엔드(레거시 백엔드) 제거
  • Lombok의 @Builder 애너테이션 지원

TYPE_USETYPE_PARAMETER 애너테이션 타깃을 생성하지 않기

Kotlin 애너테이션의 Kotlin 타깃에 TYPE이 있으면, 해당 애너테이션은 Java 애너테이션 타깃 목록에서 java.lang.annotation.ElementType.TYPE_USE로 매핑돼요. 이는 TYPE_PARAMETER Kotlin 타깃이 Java 타깃 java.lang.annotation.ElementType.TYPE_PARAMETER로 매핑되는 것과 마찬가지예요. 이는 API 레벨이 26 미만인 Android 클라이언트에서 문제가 되는데, 그 API에는 이런 타깃이 없거든요.

Kotlin 1.8.0부터 새로운 컴파일러 옵션 -Xno-new-java-annotation-targets를 사용하면 TYPE_USETYPE_PARAMETER 애너테이션 타깃을 생성하지 않도록 할 수 있어요.

최적화를 비활성화하는 새로운 컴파일러 옵션

Kotlin 1.8.0에는 더 나은 디버깅 경험을 위해 최적화를 끄는 새로운 -Xdebug 컴파일러 옵션이 추가됐어요. 지금은 이 옵션이 코루틴의 "was optimized out" 기능을 끄는 역할을 해요. 앞으로 최적화가 더 추가되면 이 옵션이 그것들도 비활성화하게 될 거예요.

"was optimized out" 기능은 suspend 함수를 사용할 때 변수를 최적화해요. 그런데 최적화된 변수는 값을 볼 수 없어서 코드를 디버깅하기 어려워져요.

이 옵션은 프로덕션에서 절대 쓰면 안 돼요. -Xdebug로 이 기능을 끄면 메모리 누수가 일어날 수 있으니까요.

기존 백엔드 제거

Kotlin 1.5.0에서 IR 기반 백엔드가 Stable이 됐다고 발표했었어요. 이는 Kotlin 1.4.*의 기존 백엔드가 deprecated됐다는 뜻이었죠. Kotlin 1.8.0에서는 그 기존 백엔드를 완전히 제거했어요. 그에 따라 컴파일러 옵션 -Xuse-old-backend와 Gradle 옵션 useOldBackend도 함께 제거했어요.

Lombok의 @Builder 애너테이션 지원

Kotlin Lombok: Support generated builders (@Builder) YouTrack 이슈에 커뮤니티가 정말 많은 투표를 해줘서, @Builder 애너테이션을 지원하게 됐어요.

@SuperBuilder@Tolerate 애너테이션 지원 계획은 아직 없지만, @SuperBuilder@Tolerate 이슈에 충분히 많은 사람이 투표하면 다시 검토할게요.

Lombok 컴파일러 플러그인 구성 방법도 확인해 보세요.

Kotlin/Native

Kotlin 1.8.0에는 Objective-C/Swift 상호 운용성 변경, Xcode 14.1 지원, CocoaPods Gradle 플러그인 개선이 포함돼 있어요.

  • Xcode 14.1 지원
  • Objective-C/Swift 상호 운용성 개선
  • CocoaPods Gradle 플러그인의 기본 동적 프레임워크

Xcode 14.1 지원

Kotlin/Native 컴파일러가 이제 최신 안정 Xcode 버전인 14.1을 지원해요. 호환성 개선에는 다음 변경이 포함돼요.

  • ARM64 플랫폼의 Apple watchOS를 지원하는 watchOS 타깃용 watchosDeviceArm64 프리셋이 새로 생겼어요.
  • Kotlin CocoaPods Gradle 플러그인은 더 이상 Apple 프레임워크에 bitcode를 기본적으로 포함하지 않아요.
  • Apple 타깃의 Objective-C 프레임워크 변경을 반영하도록 플랫폼 라이브러리가 업데이트됐어요.

Objective-C/Swift 상호 운용성 개선

Kotlin을 Objective-C와 Swift에 더 잘 상호 운용되게 하기 위해 새 애너테이션 세 가지가 추가됐어요.

  • @ObjCName — Kotlin 선언 이름을 바꾸지 않고도 Swift나 Objective-C에서 더 관용적인 이름을 지정할 수 있게 해줘요. 이 애너테이션은 클래스, 프로퍼티, 파라미터 또는 함수에 커스텀 Objective-C와 Swift 이름을 사용하라고 Kotlin 컴파일러에 지시해요.
@ObjCName(swiftName = "MySwiftArray")
class MyKotlinArray {
    @ObjCName("index")
    fun indexOf(@ObjCName("of") element: String): Int = TODO()
}

// Usage with the ObjCName annotations
let array = MySwiftArray()
let index = array.index(of: "element")
  • @HiddenFromObjC — Kotlin 선언을 Objective-C에서 숨길 수 있게 해줘요. 이 애너테이션은 함수나 프로퍼티를 Objective-C, 나아가 Swift로 내보내지 말라고 Kotlin 컴파일러에 지시해요. 이렇게 하면 Kotlin 코드가 Objective-C/Swift 친화적으로 만들어질 수 있어요.
  • @ShouldRefineInSwift — Kotlin 선언을 Swift로 작성한 래퍼로 대체할 때 유용해요. 이 애너테이션은 생성된 Objective-C API에서 함수나 프로퍼티를 swift_private로 표시하라고 Kotlin 컴파일러에 지시해요. 그런 선언에는 __ 접두사가 붙어서 Swift 코드에는 보이지 않게 돼요. Swift 코드에서 이런 선언을 사용해 Swift 친화적인 API를 만들 수는 있지만, 예를 들어 Xcode의 자동 완성에는 추천되지 않아요. Swift에서 Objective-C 선언을 정제(refine)하는 방법에 대한 자세한 내용은 Apple 공식 문서를 참고하세요.

새 애너테이션들은 opt-in이 필요해요.

Kotlin 팀은 이 애너테이션들을 구현해준 Rick Clephas에게 정말 감사하고 있어요.

CocoaPods Gradle 플러그인의 기본 동적 프레임워크

Kotlin 1.8.0부터 CocoaPods Gradle 플러그인이 등록한 Kotlin 프레임워크는 기본적으로 동적(dynamic)으로 링크돼요. 기존의 정적(static) 구현은 Kotlin Gradle 플러그인의 동작과 일관성이 없었거든요.

kotlin {
    cocoapods {
        framework {
            baseName = "MyFramework"
            isStatic = false // Now dynamic by default
        }
    }
}

정적 링크 타입을 쓰는 기존 프로젝트에서 Kotlin 1.8.0으로 업그레이드하거나(또는 링크 타입을 명시적으로 바꾸면) 프로젝트 실행 중 오류가 발생할 수 있어요. 해결하려면 Xcode 프로젝트를 닫고 Podfile 디렉터리에서 pod install을 실행하면 돼요.

더 자세한 내용은 CocoaPods Gradle 플러그인 DSL 참고 문서를 참고하세요.

Kotlin Multiplatform: 새로운 Android 소스 세트 레이아웃

Kotlin 1.8.0에는 여러모로 헷갈리던 기존 디렉터리 이름 체계를 대체하는 새로운 Android 소스 세트 레이아웃이 도입됐어요.

현재 레이아웃으로 만들어진 두 개의 androidTest 디렉터리를 예로 들어볼게요. 하나는 KotlinSourceSets용, 다른 하나는 AndroidSourceSets용이에요.

  • 의미가 달라요: Kotlin의 androidTestunitTest 타입에 속하는 반면, Android의 것은 integrationTest 타입에 속해요.
  • src/androidTest/kotlin에는 UnitTest가, src/androidTest/java에는 InstrumentedTest가 들어 있어서 SourceDirectories 레이아웃이 헷갈리게 만들어져요.
  • KotlinSourceSetsAndroidSourceSets 모두 Gradle 설정에 비슷한 이름 체계를 사용해서, Kotlin과 Android 소스 세트 양쪽의 androidTest 결과 설정(androidTestImplementation, androidTestApi, androidTestRuntimeOnly, androidTestCompileOnly)이 같아져요.

이런 문제와 그 밖의 기존 문제를 해결하기 위해 새로운 Android 소스 세트 레이아웃을 도입했어요. 두 레이아웃의 주요 차이점은 다음과 같아요.

KotlinSourceSet 이름 체계

현재 소스 세트 레이아웃 새 소스 세트 레이아웃
targetName + AndroidSourceSet.name targetName + AndroidVariantType

{AndroidSourceSet.name}은 다음과 같이 {KotlinSourceSet.name}에 매핑돼요.

현재 소스 세트 레이아웃 새 소스 세트 레이아웃
main androidMain androidMain
test androidTest androidUnitTest
androidTest androidAndroidTest androidInstrumentedTest

SourceDirectories

현재 소스 세트 레이아웃 새 소스 세트 레이아웃
레이아웃이 추가적인 /kotlin SourceDirectories를 추가함 src/{AndroidSourceSet.name}/kotlin, src/{KotlinSourceSet.name}/kotlin

{AndroidSourceSet.name}은 다음과 같이 {SourceDirectories included}에 매핑돼요.

현재 소스 세트 레이아웃 새 소스 세트 레이아웃
main src/androidMain/kotlin, src/main/kotlin, src/main/java src/androidMain/kotlin, src/main/kotlin, src/main/java
test src/androidTest/kotlin, src/test/kotlin, src/test/java src/androidUnitTest/kotlin, src/test/kotlin, src/test/java
androidTest src/androidAndroidTest/kotlin, src/androidTest/java src/androidInstrumentedTest/kotlin, src/androidTest/java, src/androidTest/kotlin

AndroidManifest.xml 파일의 위치

현재 소스 세트 레이아웃 새 소스 세트 레이아웃
src/{AndroidSourceSet.name}/AndroidManifest.xml src/{KotlinSourceSet.name}/AndroidManifest.xml

{AndroidSourceSet.name}은 다음과 같이 {AndroidManifest.xml 위치}에 매핑돼요.

현재 소스 세트 레이아웃 새 소스 세트 레이아웃
main src/main/AndroidManifest.xml src/androidMain/AndroidManifest.xml
debug src/debug/AndroidManifest.xml src/androidDebug/AndroidManifest.xml

Android 테스트와 공통 테스트의 관계

새 Android 소스 세트 레이아웃은 Android 기기 테스트(새 레이아웃에서는 androidInstrumentedTest로 이름이 바뀜)와 공통 테스트 사이의 관계를 바꿔요.

예전에는 androidAndroidTestcommonTest 사이에 기본 dependsOn 관계가 있었어요. 실제로는 다음을 의미했죠.

  • commonTest의 코드를 androidAndroidTest에서 사용할 수 있었다.
  • commonTestexpect 선언은 androidAndroidTest에 대응하는 actual 구현이 있어야 했다.
  • commonTest에 선언된 테스트는 Android 계측 테스트로도 실행되었다.

새 Android 소스 세트 레이아웃에서는 dependsOn 관계가 기본으로 추가되지 않아요. 예전 동작을 원한다면 build.gradle.kts 파일에서 직접 이 관계를 선언하면 돼요.

kotlin {
    // ...
    sourceSets {
        val commonTest by getting
        val androidInstrumentedTest by getting {
            dependsOn(commonTest)
        }
    }
}

Android flavor 지원

예전에는 Kotlin Gradle 플러그인이 debug, release 빌드 타입이나 demo, full 같은 커스텀 flavor에 해당하는 Android 소스 세트를 즉시(즉시 생성) 만들어서 val androidDebug by getting { ... } 같은 구조로 접근할 수 있게 했어요.

새 Android 소스 세트 레이아웃에서는 그런 소스 세트가 afterEvaluate 단계에서 생성돼요. 그래서 그런 표현은 유효하지 않게 되고 org.gradle.api.UnknownDomainObjectException: KotlinSourceSet with name 'androidDebug' not found 같은 오류가 생겨요.

이를 해결하려면 build.gradle.kts 파일에서 새 invokeWhenCreated() API를 사용하면 돼요.

kotlin {
    // ...
    sourceSets.invokeWhenCreated("androidFreeDebug") {
        // ...
    }
}

구성 및 설정

새 레이아웃은 향후 릴리스에서 기본값이 될 예정이에요. 지금 바로 사용하려면 다음 Gradle 옵션으로 활성화할 수 있어요.

kotlin.mpp.androidSourceSetLayoutVersion=2

새 레이아웃은 Android Gradle 플러그인 7.0 이상이 필요하고, Android Studio 2022.3 이상에서 지원돼요.

기존 Android 스타일 디렉터리 사용은 이제 권장되지 않아요. Kotlin 1.8.0부터 deprecation 주기가 시작되면서 현재 레이아웃에 대한 경고가 도입됐어요. 경고를 숨기려면 다음 Gradle 프로퍼티를 사용하면 돼요.

kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true

Kotlin/JS

Kotlin 1.8.0은 JS IR 컴파일러 백엔드를 안정화하고 JavaScript 관련 Gradle 빌드 스크립트에 새 기능을 가져와요.

  • 안정적인 JS IR 컴파일러 백엔드
  • yarn.lock이 업데이트됐는지 알려주는 새 설정
  • Gradle 프로퍼티로 브라우저용 테스트 타깃 추가
  • 프로젝트에 CSS 지원을 추가하는 새로운 접근 방식

안정적인 JS IR 컴파일러 백엔드

이 릴리스부터 Kotlin/JS 중간 표현(IR 기반) 컴파일러 백엔드가 Stable이 돼요. 세 백엔드 모두의 인프라를 통합하는 데 시간이 걸렸지만, 이제 셋 다 Kotlin 코드에 같은 IR을 사용해요.

JS IR 컴파일러 백엔드가 안정화된 결과, 기존 백엔드는 이제부터 deprecated돼요.

안정적인 JS IR 컴파일러와 함께 증분 컴파일도 기본으로 활성화돼요.

여전히 기존 컴파일러를 쓰고 있다면 프로젝트를 새 백엔드로 전환하면 돼요.

yarn.lock 업데이트를 알려주는 새 설정

yarn 패키지 매니저를 쓴다면, yarn.lock 파일이 업데이트됐을 때 알려주는 새 Gradle 설정이 세 가지 있어요. 이 설정들은 CI 빌드 과정에서 yarn.lock이 조용히 바뀌었을 때 알림을 받고 싶을 때 유용해요.

세 가지 새 Gradle 프로퍼티는 다음과 같아요.

  • YarnLockMismatchReportyarn.lock 파일 변경 사항을 어떻게 보고할지 지정해요. 다음 값 중 하나를 쓸 수 있어요.
    • FAIL — 해당 Gradle 태스크를 실패시켜요. 기본값이에요.
    • WARNING — 변경 사항 정보를 경고 로그에 기록해요.
    • NONE — 보고를 비활성화해요.
  • reportNewYarnLock — 새로 생성된 yarn.lock 파일에 대해 명시적으로 보고해요. 기본적으로 이 옵션은 비활성화 돼 있어요. 첫 시작 시 새 yarn.lock 파일을 생성하는 게 일반적인 관행이기 때문이에요. 이 옵션으로 파일이 저장소에 커밋됐는지 확인할 수 있어요.
  • yarnLockAutoReplace — Gradle 태스크를 실행할 때마다 yarn.lock을 자동으로 교체해요.

이 옵션들을 사용하려면 build.gradle.kts 빌드 스크립트를 다음과 같이 업데이트하면 돼요.

import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension

rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
    rootProject.the<YarnRootExtension>().yarnLockMismatchReport =
        YarnLockMismatchReport.WARNING // NONE | FAIL
    rootProject.the<YarnRootExtension>().reportNewYarnLock = false // true
    rootProject.the<YarnRootExtension>().yarnLockAutoReplace = false // true
}

Gradle 프로퍼티로 브라우저용 테스트 타깃 추가

Kotlin 1.8.0부터 다양한 브라우저의 테스트 타깃을 Gradle 프로퍼티 파일에서 바로 설정할 수 있어요. 이렇게 하면 모든 타깃을 build.gradle.kts에 쓸 필요가 없어져서 빌드 스크립트 파일 크기가 줄어들어요.

이 프로퍼티로 모든 모듈의 브라우저 목록을 정의하고, 특정 모듈의 빌드 스크립트에서는 특정 브라우저만 추가할 수도 있어요.

예를 들어 Gradle 프로퍼티 파일에 다음 줄을 쓰면 모든 모듈에서 Firefox와 Safari로 테스트를 실행해요.

kotlin.js.browser.karma.browsers=firefox,safari

GitHub에서 이 프로퍼티의 사용 가능한 전체 값 목록을 확인할 수 있어요.

Kotlin 팀은 이 기능을 구현해준 Martynas Petuška에게 정말 감사하고 있어요.

프로젝트에 CSS 지원을 추가하는 새로운 접근 방식

이 릴리스는 프로젝트에 CSS 지원을 추가하는 새로운 방식을 제공해요. 이 변경이 많은 프로젝트에 영향을 줄 것이라고 예상하니, 아래 설명대로 Gradle 빌드 스크립트 파일을 꼭 업데이트해 주세요.

Kotlin 1.8.0 이전에는 cssSupport.enabled 프로퍼티로 CSS 지원을 추가했어요.

kotlin {
    js {
        browser {
            cssSupport.enabled = true
        }
    }
}

Kotlin 1.8.0 이전에는 cssSupport.enabled 프로퍼티로 CSS 지원을 추가했어요.

browser {
    commonWebpackConfig {
        cssSupport.enabled = true
    }
}

이제는 cssSupport {} 블록 안에서 enabled.set() 메서드를 사용해야 해요.

browser {
    commonWebpackConfig {
        cssSupport {
            enabled.set(true)
        }
    }
}

Gradle

  • Kotlin 컴파일러 옵션을 Gradle 지연 프로퍼티(lazy property)로 노출
  • 최소 지원 버전 상향
  • Kotlin daemon 폴백 전략을 비활성화하는 기능
  • 전이적(transitive) 의존성에서 최신 kotlin-stdlib 버전 사용
  • 관련된 Kotlin과 Java 컴파일 태스크의 JVM 타깃 호환성 동일성에 대한 의무 검사
  • Kotlin Gradle 플러그인의 전이적 의존성 해석
  • Deprecation 및 제거

Kotlin 컴파일러 옵션을 Gradle 지연 프로퍼티로 노출

사용 가능한 Kotlin 컴파일러 옵션을 Gradle 지연 프로퍼티로 노출하고 Kotlin 태스크에 더 잘 통합하기 위해 많은 변경을 했어요.

  • 컴파일 태스크에 새 compilerOptions 입력이 생겼어요. 기존 kotlinOptions와 비슷하지만 반환 타입으로 Gradle Properties API의 Property를 사용해요.
tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile::class.java) {
    compilerOptions {
        useK2.set(true)
    }
}
  • Kotlin 도구 태스크 KotlinJsDceKotlinNativeLink에 기존 kotlinOptions 입력과 비슷한 새 toolOptions 입력이 생겼어요.
  • 새 입력에는 @Nested Gradle 애너테이션이 붙어요. 입력 안의 모든 프로퍼티에는 @Input 또는 @Internal 같은 관련 Gradle 애너테이션이 있어요.
  • Kotlin Gradle 플러그인 API 아티팩트에 새 인터페이스 두 개가 추가됐어요.
    • org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTaskcompilerOptions 입력과 compileOptions() 메서드를 가져요. 모든 Kotlin 컴파일 태스크가 이 인터페이스를 구현해요.
    • org.jetbrains.kotlin.gradle.tasks.KotlinToolTasktoolOptions 입력과 toolOptions() 메서드를 가져요. 모든 Kotlin 도구 태스크(KotlinJsDce, KotlinNativeLink, KotlinNativeLinkArtifactTask)가 이 인터페이스를 구현해요.
  • 일부 compilerOptionsString 타입 대신 새 타입을 사용해요.
  • Kotlin Gradle 플러그인 API는 이전 릴리스와 바이너리 호환이 돼요. 다만 kotlin-gradle-plugin 아티팩트에는 일부 소스/ABI 수준의 호환성이 깨지는 변경이 있어요. 대부분은 일부 내부 타입에 추가 제네릭 파라미터가 붙는 변경이에요. 중요한 변경 하나는 KotlinNativeLink 태스크가 더 이상 AbstractKotlinNativeCompile 태스크를 상속하지 않는다는 점이에요.
  • KotlinJsCompilerOptions.outputFile과 관련 KotlinJsOptions.outputFile 옵션이 deprecated됐어요. 대신 Kotlin2JsCompile.outputFileProperty 태스크 입력을 사용하면 돼요.

Kotlin Gradle 플러그인은 여전히 Android 확장에 KotlinJvmOptions DSL을 추가해요.

android { 
    kotlinOptions {
        jvmTarget = "11"
    }
}

이 부분은 이 이슈 범위에서 compilerOptions DSL이 모듈 수준에 추가되면 변경될 예정이에요.

제한 사항

kotlinOptions 태스크 입력과 kotlinOptions{...} 태스크 DSL은 지원 모드에 있고 향후 릴리스에서 deprecated될 예정이에요. 개선은 compilerOptionstoolOptions에만 적용될 거예요.

kotlinOptions에서 setter나 getter를 호출하면 compilerOptions의 관련 프로퍼티에 위임돼요. 이는 다음 제한을 가져와요.

  • compilerOptionskotlinOptions는 태스크 실행 단계에서 변경할 수 없어요(아래 문단의 예외 하나는 예외).
  • freeCompilerArgs는 불변 List<String>을 반환해서, 예를 들어 kotlinOptions.freeCompilerArgs.remove("something")은 실패해요.

kotlin-dslJetpack Compose가 활성화된 Android Gradle 플러그인(AGP)을 포함한 여러 플러그인이 태스크 실행 단계에서 freeCompilerArgs 속성을 수정하려고 해요. Kotlin 1.8.0에서 이들을 위한 해결 방법(workaround)을 추가했어요. 이 해결 방법 덕분에 어떤 빌드 스크립트나 플러그인이든 실행 단계에서 kotlinOptions.freeCompilerArgs를 수정할 수 있지만, 빌드 로그에 경고가 출력돼요. 이 경고를 끄려면 새 Gradle 프로퍼티 kotlin.options.suppressFreeCompilerArgsModificationWarning=true를 사용하면 돼요. Gradle은 kotlin-dsl 플러그인Jetpack Compose가 활성화된 AGP에 대한 수정을 준비 중이에요.

최소 지원 버전 상향

Kotlin 1.8.0부터 최소 지원 Gradle 버전은 6.8.3, 최소 지원 Android Gradle 플러그인 버전은 4.1.3이에요.

문서에서 Kotlin Gradle 플러그인과 사용 가능한 Gradle 버전의 호환성을 확인할 수 있어요.

Kotlin daemon 폴백 전략을 비활성화하는 기능

새 Gradle 프로퍼티 kotlin.daemon.useFallbackStrategy가 생겼고, 기본값은 true예요. 값이 false면 daemon 시작 또는 통신 문제 시 빌드가 실패해요. Kotlin 컴파일 태스크에는 useDaemonFallbackStrategy 프로퍼티도 새로 추가됐는데, 둘 다 사용하면 이 프로퍼티가 Gradle 프로퍼티보다 우선해요. 컴파일을 실행할 메모리가 부족하면 로그에서 그에 대한 메시지를 볼 수 있어요.

Kotlin 컴파일러의 폴백 전략은 daemon이 어떤 이유로든 실패하면 Kotlin daemon 밖에서 컴파일을 실행하는 거예요. Gradle daemon이 켜져 있으면 컴파일러는 "In process" 전략을, 꺼져 있으면 "Out of process" 전략을 사용해요. 자세한 내용은 컴파일러 실행 전략을 참고하세요. 다른 전략으로 조용히 폴백되면 시스템 리소스를 많이 소모하거나 비결정적 빌드로 이어질 수 있다는 점을 유의하세요. 자세한 내용은 이 YouTrack 이슈를 참고하세요.

전이적 의존성에서 최신 kotlin-stdlib 버전 사용

의존성에 Kotlin 버전 1.8.0 이상을 명시적으로 적으면, 예를 들어 implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.0")처럼, Kotlin Gradle 플러그인은 전이적 kotlin-stdlib-jdk7kotlin-stdlib-jdk8 의존성에 그 Kotlin 버전을 사용해요. 이는 서로 다른 stdlib 버전으로 인한 클래스 중복을 피하기 위해서예요(kotlin-stdlib-jdk7kotlin-stdlib-jdk8kotlin-stdlib로 통합하는 방법은 여기에서 자세히 알아볼 수 있어요). 이 동작은 kotlin.stdlib.jdk.variants.version.alignment Gradle 프로퍼티로 비활성화할 수 있어요.

kotlin.stdlib.jdk.variants.version.alignment=false

버전 정렬에 문제가 생기면 빌드 스크립트에서 kotlin-bom에 플랫폼 의존성을 선언해 Kotlin BOM으로 모든 버전을 정렬하면 돼요.

implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))

다른 사례와 권장 해결 방법은 문서에서 알아볼 수 있어요.

관련된 Kotlin과 Java 컴파일 태스크의 JVM 타깃 의무 검사

이 섹션은 소스 파일이 Kotlin만 있고 Java를 사용하지 않더라도 JVM 프로젝트에 적용돼요.

이 릴리스부터, Gradle 8.0 이상을 쓰는 프로젝트에서는 kotlin.jvm.target.validation.mode 프로퍼티의 기본값이 error이고(이 Gradle 버전은 아직 릴리스되지 않았어요), JVM 타깃이 호환되지 않으면 플러그인이 빌드를 실패시켜요.

기본값이 warning에서 error로 바뀐 것은 Gradle 8.0으로의 부드러운 마이그레이션을 위한 준비 단계예요. 이 프로퍼티를 error로 설정하고 툴체인을 구성하거나 JVM 버전을 수동으로 정렬하는 걸 권장해요.

타깃 호환성을 확인하지 않으면 어떤 일이 생길 수 있는지에 대해 더 알아보세요.

Kotlin Gradle 플러그인의 전이적 의존성 해석

Kotlin 1.7.0에서 Gradle 플러그인 변형 지원을 도입했어요. 이런 플러그인 변형 때문에 빌드 클래스패스에 어떤 의존성, 보통은 kotlin-gradle-plugin-api의 서로 다른 버전에 의존하는 서로 다른 버전의 Kotlin Gradle 플러그인이 있을 수 있어요. 이로 인해 해석(resolution) 문제가 생길 수 있는데, kotlin-dsl 플러그인을 예로 들어 해결 방법을 제안할게요.

Gradle 7.6의 kotlin-dsl 플러그인은 org.jetbrains.kotlin.plugin.sam.with.receiver:1.7.10 플러그인에 의존하고, 이 플러그인은 kotlin-gradle-plugin-api:1.7.10에 의존해요. 여기에 org.jetbrains.kotlin.gradle.jvm:1.8.0 플러그인을 추가하면, 이 kotlin-gradle-plugin-api:1.7.10 전이적 의존성이 버전(1.8.01.7.10)과 변형 속성의 org.gradle.plugin.api-version 값이 일치하지 않아 의존성 해석 오류를 일으킬 수 있어요. 해결 방법으로 버전을 정렬하는 constraint를 추가하면 돼요. 이 해결 방법은 계획 중인 Kotlin Gradle Plugin libraries alignment platform이 구현될 때까지 필요할 수 있어요.

dependencies {
    constraints {
        implementation("org.jetbrains.kotlin:kotlin-sam-with-receiver:1.8.0")
    }
}

이 constraint는 전이적 의존성용 빌드 클래스패스에 org.jetbrains.kotlin:kotlin-sam-with-receiver:1.8.0 버전이 사용되도록 강제해요. Gradle 이슈 트래커의 비슷한 사례를 더 알아볼 수 있어요.

Deprecation 및 제거

Kotlin 1.8.0에서는 다음 프로퍼티와 메서드에 대한 deprecation 주기가 계속돼요.

표준 라이브러리

Kotlin 1.8.0은:

  • JVM 컴파일 타깃을 업데이트해요.
  • Java와 Kotlin 간의 TimeUnit 변환, cbrt(), Java Optionals 확장 함수 등 여러 함수를 안정화해요.
  • 비교·뺄셈 가능한 TimeMarks에 대한 프리뷰를 제공해요.
  • java.nio.file.path용 실험용 확장 함수를 포함해요.
  • 개선된 kotlin-reflect 성능을 제공해요.

JVM 컴파일 타깃 업데이트

Kotlin 1.8.0에서 표준 라이브러리(kotlin-stdlib, kotlin-reflect, kotlin-script-*)가 JVM 타깃 1.8로 컴파일돼요. 예전에는 표준 라이브러리가 JVM 타깃 1.6으로 컴파일됐어요.

Kotlin 1.8.0은 더 이상 JVM 타깃 1.6과 1.7을 지원하지 않아요. 결과적으로 이 아티팩트들의 내용이 kotlin-stdlib에 통합됐기 때문에 빌드 스크립트에서 kotlin-stdlib-jdk7kotlin-stdlib-jdk8을 따로 선언할 필요가 없어져요.

빌드 스크립트에 kotlin-stdlib-jdk7kotlin-stdlib-jdk8을 의존성으로 명시적으로 선언했다면, kotlin-stdlib로 교체하면 돼요.

서로 다른 stdlib 아티팩트 버전을 섞으면 클래스 중복이나 클래스 누락이 생길 수 있다는 점을 유의하세요. 이를 피하려면 Kotlin Gradle 플러그인이 stdlib 버전 정렬을 도와줄 수 있어요.

cbrt()

double이나 float의 실수 세제곱근을 계산할 수 있는 cbrt() 함수가 이제 Stable이 됐어요.

import kotlin.math.*

fun main() {
    val num = 27
    val negNum = -num

    println("The cube root of ${num.toDouble()} is: " +
            cbrt(num.toDouble()))
    println("The cube root of ${negNum.toDouble()} is: " +
            cbrt(negNum.toDouble()))
}

Java와 Kotlin 사이의 TimeUnit 변환

kotlin.timetoTimeUnit()toDurationUnit() 함수가 이제 Stable이 됐어요. Kotlin 1.6.0에서 Experimental로 도입된 이 함수들은 Kotlin과 Java 사이의 상호 운용성을 개선해요. 이제 Java java.util.concurrent.TimeUnit과 Kotlin kotlin.time.DurationUnit 사이를 쉽게 변환할 수 있어요. 이 함수들은 JVM에서만 지원돼요.

import kotlin.time.*

// For use from Java
fun wait(timeout: Long, unit: TimeUnit) {
    val duration: Duration = timeout.toDuration(unit.toDurationUnit())
    ...
}

비교·뺄셈 가능한 TimeMark

TimeMarks의 새 기능은 Experimental이고, 사용하려면 @OptIn(ExperimentalTime::class) 또는 @ExperimentalTime으로 opt-in해야 해요.

Kotlin 1.8.0 이전에는 여러 TimeMark와 현재 시점의 시간 차이를 계산하고 싶을 때 한 번에 하나의 TimeMark에서만 elapsedNow()를 호출할 수 있었어요. 두 번의 elapsedNow() 호출은 정확히 같은 시점에 실행될 수 없으니 결과를 비교하기 어려웠죠.

이를 해결하기 위해 Kotlin 1.8.0에서 같은 시간 소스(time source)의 TimeMark를 서로 뺄셈하고 비교할 수 있게 됐어요. 이제 현재 시점을 나타내는 새 TimeMark 인스턴스를 만들고 다른 TimeMark를 거기서 뺄 수 있어요. 이렇게 하면 이 계산에서 얻은 결과들이 항상 서로에 대해 상대적임이 보장돼요.

import kotlin.time.*
fun main() {
//sampleStart
    val timeSource = TimeSource.Monotonic
    val mark1 = timeSource.markNow()
    Thread.sleep(500) // Sleep 0.5 seconds
    val mark2 = timeSource.markNow()

    // Before 1.8.0
    repeat(4) { n ->
        val elapsed1 = mark1.elapsedNow()
        val elapsed2 = mark2.elapsedNow()

        // Difference between elapsed1 and elapsed2 can vary depending 
        // on how much time passes between the two elapsedNow() calls
        println("Measurement 1.${n + 1}: elapsed1=$elapsed1, " +
                "elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
    }
    println()

    // Since 1.8.0
    repeat(4) { n ->
        val mark3 = timeSource.markNow()
        val elapsed1 = mark3 - mark1
        val elapsed2 = mark3 - mark2

        // Now the elapsed times are calculated relative to mark3, 
        // which is a fixed value
        println("Measurement 2.${n + 1}: elapsed1=$elapsed1, " +
                "elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
    }
    // It's also possible to compare time marks with each other
    // This is true, as mark2 was captured later than mark1
    println(mark2 > mark1)
//sampleEnd
}

이 새 기능은 서로 다른 프레임을 나타내는 여러 TimeMark의 차이를 계산하거나 비교하고 싶은 애니메이션 계산에서 특히 유용해요.

디렉터리 재귀 복사 또는 삭제

java.nio.file.path용 이 새 함수들은 Experimental이에요. 사용하려면 @OptIn(kotlin.io.path.ExperimentalPathApi::class) 또는 @kotlin.io.path.ExperimentalPathApi로 opt-in해야 해요. 또는 컴파일러 옵션 -opt-in=kotlin.io.path.ExperimentalPathApi를 사용할 수도 있어요.

java.nio.file.Path용 확장 함수 두 개, copyToRecursively()deleteRecursively()를 도입했어요. 이들은 재귀적으로 다음을 할 수 있게 해줘요.

  • 디렉터리와 그 내용을 다른 대상으로 복사하기
  • 디렉터리와 그 내용 삭제하기

이 함수들은 백업 과정의 일부로 아주 유용할 수 있어요.

오류 처리

copyToRecursively()를 사용할 때 onError 람다 함수를 오버로드해서 복사 중 예외가 발생하면 어떻게 할지를 정의할 수 있어요.

sourceRoot.copyToRecursively(destinationRoot, followLinks = false,
    onError = { source, target, exception ->
        logger.logError(exception, "Failed to copy $source to $target")
        OnErrorResult.TERMINATE
    })

deleteRecursively()를 사용할 때 파일이나 폴더를 삭제하는 중 예외가 발생하면 그 파일이나 폴더는 건너뛰어져요. 삭제가 완료되면 deleteRecursively()는 발생한 모든 예외를 suppressed exception으로 포함하는 IOException을 던져요.

파일 덮어쓰기

copyToRecursively()가 대상 디렉터리에 이미 존재하는 파일을 발견하면 예외가 발생해요. 대신 파일을 덮어쓰려면 overwrite를 인자로 받는 오버로드를 사용해서 true로 설정하면 돼요.

fun setUpEnvironment(projectDirectory: Path, fixtureName: String) {
    fixturesRoot.resolve(COMMON_FIXTURE_NAME)
        .copyToRecursively(projectDirectory, followLinks = false)
    fixturesRoot.resolve(fixtureName)
        .copyToRecursively(projectDirectory, followLinks = false,
            overwrite = true) // patches the common fixture
}
커스텀 복사 동작

복사를 위한 커스텀 로직을 정의하려면 copyAction을 추가 인자로 받는 오버로드를 사용하면 돼요. copyAction으로 선호하는 동작을 담은 람다 함수를 제공할 수 있어요.

sourceRoot.copyToRecursively(destinationRoot, followLinks = false) { source, target ->
    if (source.name.startsWith(".")) {
        CopyActionResult.SKIP_SUBTREE
    } else {
        source.copyToIgnoringExistingDirectory(target, followLinks = false)
        CopyActionResult.CONTINUE
    }
}

이 확장 함수들에 대한 자세한 내용은 API 참고 문서를 참고하세요.

Java Optionals 확장 함수

Kotlin 1.7.0에서 도입한 확장 함수들이 이제 Stable이 됐어요. 이 함수들은 Java의 Optional 클래스를 다루는 작업을 단순화해요. JVM에서 Optional 객체를 풀고 변환하며, Java API를 더 간결하게 다룰 수 있게 해줘요. 자세한 내용은 Kotlin 1.7.0의 새로운 기능을 참고하세요.

kotlin-reflect 성능 개선

kotlin-reflect가 이제 JVM 타깃 1.8로 컴파일되는 점을 활용해 내부 캐시 메커니즘을 Java의 ClassValue로 마이그레이션했어요. 예전에는 KClass만 캐시했지만, 이제는 KTypeKDeclarationContainer도 캐시해요. 이런 변경으로 typeOf() 호출 시 성능이 크게 개선됐어요.

문서 업데이트

Kotlin 문서에 주목할 만한 변경이 몇 가지 있었어요.

개편 및 신규 페이지

  • Gradle 개요 — Gradle 빌드 시스템으로 Kotlin 프로젝트를 구성·빌드하는 방법, 사용 가능한 컴파일러 옵션, 컴파일과 Kotlin Gradle 플러그인의 캐시에 대해 알아볼 수 있어요.
  • Java와 Kotlin의 Nullability — null 가능성 있는 변수를 다루는 Java와 Kotlin의 접근 방식 차이를 볼 수 있어요.
  • Lincheck 가이드 — JVM에서 동시성 알고리즘을 테스트하는 Lincheck 프레임워크를 설정하고 사용하는 방법을 알아볼 수 있어요.

신규 및 업데이트된 튜토리얼

Kotlin 1.8.0 설치

IntelliJ IDEA 2021.3, 2022.1, 2022.2는 Kotlin 플러그인을 1.8.0으로 업데이트하도록 자동으로 제안해요. IntelliJ IDEA 2022.3은 곧 나올 마이너 업데이트에 1.8.0 버전의 Kotlin 플러그인을 번들로 포함할 예정이에요.

IntelliJ IDEA 2022.3에서 기존 프로젝트를 Kotlin 1.8.0으로 마이그레이션하려면 Kotlin 버전을 1.8.0으로 바꾸고 Gradle 또는 Maven 프로젝트를 다시 가져오면 돼요.

Android Studio Electric Eel (221)과 Flamingo (222)의 경우 1.8.0 버전의 Kotlin 플러그인이 다가오는 Android Studio 업데이트와 함께 배포돼요. 새 커맨드라인 컴파일러는 GitHub 릴리스 페이지에서 다운로드할 수 있어요.

Kotlin 1.8.0 호환성 가이드

Kotlin 1.8.0은 기능(feature) 릴리스이므로 이전 버전의 언어로 작성된 코드와 호환되지 않는 변경이 생길 수 있어요. 이러한 변경의 상세 목록은 Kotlin 1.8.0 호환성 가이드에서 확인할 수 있어요.

더 알아보기