Kotlin Gradle 플러그인의 컴파일러 옵션

Kotlin Gradle 플러그인의 컴파일러 옵션

Kotlin Gradle 플러그인(KGP)에서 컴파일러 옵션을 어떻게 설정하고 관리하는지 알아볼게요. 이 가이드에서는 옵션을 정의하는 방법부터 kotlinOptions {} 블록의 사용이 deprecated된 뒤 어떻게 compilerOptions {}로 마이그레이션하는지까지, 실제 빌드 스크립트 예시를 곁들여 차근차근 설명해드릴게요.

출처: Compiler options in the Kotlin Gradle plugin

본문

Kotlin의 각 릴리스에는 지원되는 대상(JVM, JavaScript, 그리고 지원 플랫폼용 네이티브 바이너리)을 위한 컴파일러가 포함돼 있어요.

이 컴파일러들은 다음과 같은 경우에 사용돼요:

  • IDE에서 Kotlin 프로젝트의 Compile 또는 Run 버튼을 클릭할 때.
  • 콘솔이나 IDE에서 gradle build를 호출할 때(Gradle).
  • 콘솔이나 IDE에서 mvn compile 또는 mvn test-compile을 호출할 때(Maven).

또한 커맨드라인 컴파일러 사용하기 튜토리얼에서 설명한 대로 Kotlin 컴파일러를 커맨드라인에서 직접 실행할 수도 있어요.

옵션 정의하는 방법

Kotlin 컴파일러에는 컴파일 과정을 세부 조정하기 위한 여러 옵션이 있어요.

Gradle DSL은 컴파일러 옵션을 폭넓게 구성할 수 있게 해줘요. 이 DSL은 Kotlin MultiplatformJVM/Android 프로젝트에서 사용할 수 있어요.

Gradle DSL을 사용하면 빌드 스크립트 안에서 세 가지 레벨로 컴파일러 옵션을 구성할 수 있어요:

Kotlin compiler options levels

더 높은 레벨에서 설정한 값은 더 낮은 레벨의 관례(기본값)로 사용돼요:

  • 확장 레벨에서 설정한 컴파일러 옵션은 commonMain, nativeMain, commonTest 같은 공유 소스셋을 포함해 대상 레벨 옵션의 기본값이 돼요.
  • 대상 레벨에서 설정한 컴파일러 옵션은 compileKotlinJvm, compileTestKotlinJvm 같은 컴파일 유닛(태스크) 레벨 옵션의 기본값이 돼요.

반대로, 더 낮은 레벨에서 한 구성이 더 높은 레벨의 관련 설정을 덮어써요:

  • 태스크 레벨 컴파일러 옵션은 대상 또는 확장 레벨의 관련 구성을 덮어써요.
  • 대상 레벨 컴파일러 옵션은 확장 레벨의 관련 구성을 덮어써요.

어떤 레벨의 컴파일러 인자가 컴파일에 적용되는지 확인하려면 Gradle 로깅DEBUG 레벨을 사용하면 돼요. JVM과 JS/WASM 태스크에서는 로그에서 "Kotlin compiler args:" 문자열을 찾아보고, 네이티브 태스크에서는 "Arguments =" 문자열을 찾아보면 돼요.

확장(Extension) 레벨

최상위 레벨의 compilerOptions {} 블록에서 모든 대상과 공유 소스셋의 공통 컴파일러 옵션을 구성할 수 있어요:

kotlin {
    compilerOptions {
        optIn.add("kotlin.RequiresOptIn")
    }
}

대상(Target) 레벨

target {} 블록 안의 compilerOptions {} 블록에서 JVM/Android 대상의 컴파일러 옵션을 구성할 수 있어요:

kotlin {
    target {
        compilerOptions {
            optIn.add("kotlin.RequiresOptIn")
        }
    }
}

Kotlin Multiplatform 프로젝트에서는 특정 대상 안에서도 컴파일러 옵션을 구성할 수 있어요. 예를 들어 jvm { compilerOptions {}}처럼요. 자세한 내용은 Multiplatform Gradle DSL reference 문서를 참고해주세요.

컴파일 유닛(Compilation unit) 레벨

태스크 구성 안의 compilerOptions {} 블록에서 특정 컴파일 유닛 또는 태스크의 컴파일러 옵션을 구성할 수 있어요:

tasks.named<KotlinJvmCompile>("compileKotlin"){
    compilerOptions {
        optIn.add("kotlin.RequiresOptIn")
    }
}

KotlinCompilation을 통해서도 컴파일 유닛 레벨의 컴파일러 옵션에 접근하고 구성할 수 있어요:

kotlin {
    target {
        val main by compilations.getting {
            compileTaskProvider.configure {
                compilerOptions {
                    optIn.add("kotlin.RequiresOptIn")
                }
            }
        }
    }
}

JVM/Android가 아닌 대상과 Kotlin Multiplatform의 플러그인을 구성하고 싶다면, 해당 Kotlin 컴파일 태스크의 compilerOptions {} 속성을 사용하면 돼요. 아래 예시는 Kotlin DSL과 Groovy DSL 두 가지로 이 구성을 설정하는 방법을 보여줘요:

tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask::class.java) {
    compilerOptions {
        apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
    }
}
tasks.named('compileKotlin', org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class) {
    compilerOptions {
        apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
    }
}

kotlinOptions {}에서 compilerOptions {}로 마이그레이션하기

Kotlin 2.2.0 이전에는 kotlinOptions {} 블록으로 컴파일러 옵션을 구성할 수 있었어요. kotlinOptions {} 블록은 Kotlin 2.0.0부터 deprecated되었으므로, 이 절에서는 빌드 스크립트를 compilerOptions {} 블록을 쓰도록 마이그레이션하기 위한 안내와 권장사항을 다룰게요:

컴파일러 옵션을 중앙화하고 타입 사용하기

가능하다면 컴파일러 옵션을 확장 레벨에서 구성하고, 특정 태스크에 대해서는 컴파일 유닛 레벨에서 덮어쓰는 게 좋아요.

compilerOptions {} 블록에서는 raw 문자열을 사용할 수 없으니, 타입이 있는 값으로 변환해야 해요. 예를 들어 기존에 이렇게 작성했다면:

plugins {
    kotlin("jvm") version "2.4.20"
}

tasks.withType<KotlinCompile>().configureEach {
    kotlinOptions {
        jvmTarget = "17"
        languageVersion = "2.4"
        apiVersion = "2.4"
    }
}
plugins {
    id 'org.jetbrains.kotlin.jvm' version '2.4.20'
}

tasks.withType(KotlinCompile).configureEach {
    kotlinOptions {
        jvmTarget = '17'
        languageVersion = '2.4'
        apiVersion = '2.4'
    }
}

마이그레이션 후에는 이렇게 되어야 해요:

import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

plugins {
    kotlin("jvm") version "2.4.20"
}

kotlin {
    // Extension level
    compilerOptions {
        jvmTarget = JvmTarget.fromTarget("17")
        languageVersion = KotlinVersion.fromVersion("2.4")
        apiVersion = KotlinVersion.fromVersion("2.4")
    }
}

// Example of overriding at compilation unit level
tasks.named<KotlinJvmCompile>("compileKotlin"){
    compilerOptions {
        apiVersion = KotlinVersion.fromVersion("2.4")
    }
}
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

plugins {
    id 'org.jetbrains.kotlin.jvm' version '2.4.20'
}

kotlin {
  // Extension level
    compilerOptions {
        jvmTarget = JvmTarget.fromTarget("17")
        languageVersion = KotlinVersion.fromVersion("2.4")
        apiVersion = KotlinVersion.fromVersion("2.4")
    }
}

// Example of overriding at compilation unit level
tasks.named("compileKotlin", KotlinJvmCompile).configure {
    compilerOptions {
        apiVersion = KotlinVersion.fromVersion("2.4")
    }
}

android.kotlinOptions에서 벗어나기

빌드 스크립트에서 이전에 android.kotlinOptions를 사용했다면, 확장 레벨이나 대상 레벨 중 하나에서 kotlin.compilerOptions로 마이그레이션하면 돼요.

예를 들어 Android 프로젝트가 이렇게 작성돼 있다면:

plugins {
    id("com.android.application")
    kotlin("android")
}

android {
    kotlinOptions {
        jvmTarget = "17"
    }
}
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    kotlinOptions {
        jvmTarget = '17'
    }
}

이렇게 바꿔주세요:

plugins {
    id("com.android.application")
    kotlin("android")
}

kotlin {
    compilerOptions {
        jvmTarget = JvmTarget.fromTarget("17")
    }
}
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

kotlin {
    compilerOptions {
        jvmTarget = JvmTarget.fromTarget("17")
    }
}

또한 Android 대상을 가진 Kotlin Multiplatform 프로젝트가 이렇게 작성돼 있다면:

plugins {
    kotlin("multiplatform")
    id("com.android.application")
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions.jvmTarget = "17"
        }
    }
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform'
    id 'com.android.application'
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions {
                jvmTarget = '17'
            }
        }
    }
}

이렇게 바꿔주세요:

plugins {
    kotlin("multiplatform")
    id("com.android.application")
}

kotlin {
    androidTarget {
        compilerOptions {
            jvmTarget = JvmTarget.fromTarget("17")
        }
    }
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform'
    id 'com.android.application'
}

kotlin {
    androidTarget {
        compilerOptions {
            jvmTarget = JvmTarget.fromTarget("17")
        }
    }
}

freeCompilerArgs 마이그레이션

  • 모든 += 연산을 add() 또는 addAll() 함수로 바꿔주세요.
  • -opt-in 컴파일러 옵션을 사용한다면, 전용 DSL이 KGP API reference에 이미 있는지 확인하고 있으면 그걸 사용하세요.
  • -progressive 컴파일러 옵션을 사용하는 경우 전용 DSL인 progressiveMode.set(true)로 마이그레이션하세요.
  • -Xjvm-default 컴파일러 옵션을 사용하는 경우 전용 DSLjvmDefault.set()을 쓰도록 마이그레이션하세요. 옵션 매핑은 다음과 같아요:BeforeAfter-Xjvm-default=all-compatibility``jvmDefault.set(JvmDefaultMode.ENABLE)``-Xjvm-default=all``jvmDefault.set(JvmDefaultMode.NO_COMPATIBILITY)``-Xjvm-default=disable``jvmDefault.set(JvmDefaultMode.DISABLE)

예를 들어 기존에 이렇게 작성했다면:

kotlinOptions {
    freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
    freeCompilerArgs += listOf("-Xcontext-receivers", "-Xinline-classes", "-progressive", "-Xjvm-default=all")
}
kotlinOptions {
    freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
    freeCompilerArgs += ["-Xcontext-receivers", "-Xinline-classes", "-progressive", "-Xjvm-default=all"]
}

이렇게 마이그레이션해주세요:

kotlin {
    compilerOptions {
        optIn.add("kotlin.RequiresOptIn")
        freeCompilerArgs.addAll(listOf("-Xcontext-receivers", "-Xinline-classes"))
        progressiveMode.set(true)
        jvmDefault.set(JvmDefaultMode.NO_COMPATIBILITY)
    }
}
kotlin {
    compilerOptions {
        optIn.add("kotlin.RequiresOptIn")
        freeCompilerArgs.addAll(["-Xcontext-receivers", "-Xinline-classes"])
        progressiveMode.set(true)
        jvmDefault.set(JvmDefaultMode.NO_COMPATIBILITY)
    }
}

JVM을 대상으로 할 때

앞에서 설명한 대로, JVM/Android 프로젝트의 컴파일러 옵션은 확장, 대상, 컴파일 유닛(태스크) 레벨에서 정의할 수 있어요.

기본 JVM 컴파일 태스크는 프로덕션 코드용으로 compileKotlin, 테스트 코드용으로 compileTestKotlin이라고 불러요. 커스텀 소스셋의 태스크는 compile<Name>Kotlin 패턴에 따라 이름이 지어져요.

Android 컴파일 태스크 목록은 터미널에서 gradlew tasks --all 명령을 실행하고, Other tasks 그룹에서 compile*Kotlin 태스크 이름을 찾아보면 확인할 수 있어요.

알아두면 좋은 몇 가지 중요한 세부사항이 있어요:

  • kotlin.compilerOptions는 프로젝트의 모든 Kotlin 컴파일 태스크를 구성해요.
  • tasks.named<KotlinJvmCompile>("compileKotlin") { }(또는 tasks.withType<KotlinJvmCompile>().configureEach { }) 방식을 사용해 kotlin.compilerOptions DSL이 적용한 구성을 덮어쓸 수 있어요.

JavaScript를 대상으로 할 때

JavaScript 컴파일 태스크는 프로덕션 코드용으로 compileKotlinJs, 테스트 코드용으로 compileTestKotlinJs, 커스텀 소스셋용으로 compile<Name>KotlinJs라고 불러요.

단일 태스크를 구성하려면 그 이름을 사용하면 돼요:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
// ...

val compileKotlin: KotlinCompilationTask<*> by tasks

compileKotlin.compilerOptions.suppressWarnings.set(true)
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
// ...

tasks.named('compileKotlin', KotlinCompilationTask) {
    compilerOptions {
        suppressWarnings = true
    }
}

Gradle Kotlin DSL에서는 프로젝트의 tasks에서 태스크를 먼저 가져와야 한다는 점에 유의하세요.

JS와 공통(common) 대상에는 각각 Kotlin2JsCompileKotlinCompileCommon 타입을 사용해요.

JavaScript 컴파일 태스크 목록은 터미널에서 gradlew tasks --all 명령을 실행하고, Other tasks 그룹에서 compile*KotlinJS 태스크 이름을 찾아보면 확인할 수 있어요.

모든 Kotlin 컴파일 태스크

프로젝트의 모든 Kotlin 컴파일 태스크를 구성하는 것도 가능해요:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
// ...

tasks.named<KotlinCompilationTask<*>>("compileKotlin").configure {
    compilerOptions { /*...*/ }
}
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
// ...

tasks.named('compileKotlin', KotlinCompilationTask) {
    compilerOptions { /*...*/ }
}

모든 컴파일러 옵션

여기 Gradle 컴파일러의 전체 옵션 목록이 있어요:

공통 속성

Name Description Possible values Default value
optIn opt-in 컴파일러 인자 목록을 구성하는 속성 listOf( /* opt-ins */ ) emptyList()
progressiveMode 프로그레시브 컴파일러 모드 활성화 true, false false
extraWarnings true일 때 경고를 내는 추가 선언·표현식·타입 컴파일 검사 활성화 true, false false

JVM 전용 속성

Name Description Possible values Default value
javaParameters 메서드 파라미터에 대한 Java 1.8 리플렉션용 메타데이터 생성 false
jvmTarget 생성되는 JVM 바이트코드의 대상 버전 "1.8", "9", "10", ..., "25", 26". 컴파일러 옵션 타입도 참고 "1.8"
noJdk Java 런타임을 classpath에 자동으로 포함하지 않기 false
jvmTargetValidationMode Kotlin과 Java 사이의 JVM 대상 호환성 검증. KotlinCompile 타입 태스크의 속성. WARNING, ERROR, IGNORE ERROR
jvmDefault 인터페이스에 선언된 함수를 JVM 기본(default) 메서드로 어떻게 컴파일할지 제어 ENABLE, NO_COMPATIBILITY, DISABLE ENABLE

JVM과 JavaScript 공통 속성

Name Description Possible values Default value
allWarningsAsErrors 경고가 있으면 오류로 보고 false
suppressWarnings 경고를 생성하지 않기 false
verbose 상세 로깅 출력 활성화. Gradle debug 로그 레벨이 활성화되어 있을 때만 동작 false
freeCompilerArgs 추가 컴파일러 인자 목록. 여기에 실험적 -X 인자도 사용할 수 있어요. 예시 참고 []
apiVersion 코드에서 사용할 수 있는 Kotlin API를 제어. 자세한 내용은 -api-version 참고 "2.0", "2.1", "2.2", "2.3", "2.4", "2.5" (EXPERIMENTAL)
languageVersion 컴파일 중 사용 가능한 Kotlin 언어 기능과 문법을 제어. 자세한 내용은 -language-version 참고 "2.0", "2.1", "2.2", "2.3", "2.4", "2.5" (EXPERIMENTAL)

freeCompilerArgs로 추가 인자 사용 예시

freeCompilerArgs 속성을 사용해 (실험적 인자를 포함한) 추가 컴파일러 인자를 제공할 수 있어요. 이 속성에 단일 인자를 추가하거나 인자 목록을 추가할 수 있어요:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
// ...

kotlin {
    compilerOptions {
        // Specifies the version of the Kotlin API and the JVM target
        apiVersion.set(KotlinVersion.KOTLIN_2_4)
        jvmTarget.set(JvmTarget.JVM_1_8)
        
        // Single experimental argument
        freeCompilerArgs.add("-Xexport-kdoc")

        // Single additional argument
        freeCompilerArgs.add("-Xno-param-assertions")

        // List of arguments
        freeCompilerArgs.addAll(
            listOf(
                "-Xno-receiver-assertions",
                "-Xno-call-assertions"
            )
        ) 
    }
}
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
// ...

tasks.named('compileKotlin', KotlinCompilationTask) {
    compilerOptions {
        // Specifies the version of the Kotlin API and the JVM target
        apiVersion = KotlinVersion.KOTLIN_2_4
        jvmTarget = JvmTarget.JVM_1_8
        
        // Single experimental argument
        freeCompilerArgs.add("-Xexport-kdoc")
        
        // Single additional argument, can be a key-value pair
        freeCompilerArgs.add("-Xno-param-assertions")
        
        // List of arguments
        freeCompilerArgs.addAll(["-Xno-receiver-assertions", "-Xno-call-assertions"])
    }
}

languageVersion 설정 예시

언어 버전을 설정하려면 다음 문법을 사용하면 돼요:

kotlin {
    compilerOptions {
        languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_4)
    }
}
tasks
    .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
    .configureEach {
        compilerOptions.languageVersion =
            org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_4
    }

컴파일러 옵션 타입도 함께 참고해주세요.

JavaScript 전용 속성

Name Description Possible values Default value
friendModulesDisabled internal 선언 내보내기 비활성화 false
main 실행 시 main 함수를 호출할지 여부 지정 JsMainFunctionExecutionMode.CALL, JsMainFunctionExecutionMode.NO_CALL JsMainFunctionExecutionMode.CALL
moduleKind 컴파일러가 생성하는 JS 모듈의 종류 JsModuleKind.MODULE_AMD, JsModuleKind.MODULE_PLAIN, JsModuleKind.MODULE_ES, JsModuleKind.MODULE_COMMONJS, JsModuleKind.MODULE_UMD null
sourceMap 소스 맵 생성 false
sourceMapEmbedSources 소스 파일을 소스 맵에 포함 JsSourceMapEmbedMode.SOURCE_MAP_SOURCE_CONTENT_INLINING, JsSourceMapEmbedMode.SOURCE_MAP_SOURCE_CONTENT_NEVER, JsSourceMapEmbedMode.SOURCE_MAP_SOURCE_CONTENT_ALWAYS null
sourceMapNamesPolicy Kotlin 코드에서 선언한 변수·함수 이름을 소스 맵에 추가. 동작에 대한 자세한 내용은 컴파일러 레퍼런스 참고 JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_FQ_NAMES, JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_SIMPLE_NAMES, JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_NO null
sourceMapPrefix 소스 맵의 경로에 지정된 프리픽스 추가 null
target 특정 ECMA 버전용 JS 파일 생성 "es5", "es2015" "es5"
useEsClasses 생성된 JavaScript 코드가 ES2015 클래스를 사용하도록 허용. ES2015 대상 사용 시 기본적으로 활성화 null

컴파일러 옵션 타입

일부 compilerOptionsString 타입 대신 새로운 타입을 사용해요:

Option Type Example
jvmTarget JvmTarget compilerOptions.jvmTarget.set(JvmTarget.JVM_11)
apiVersion and languageVersion KotlinVersion compilerOptions.languageVersion.set(KotlinVersion.KOTLIN_2_4)
main JsMainFunctionExecutionMode compilerOptions.main.set(JsMainFunctionExecutionMode.NO_CALL)
moduleKind JsModuleKind compilerOptions.moduleKind.set(JsModuleKind.MODULE_ES)
sourceMapEmbedSources JsSourceMapEmbedMode compilerOptions.sourceMapEmbedSources.set(JsSourceMapEmbedMode.SOURCE_MAP_SOURCE_CONTENT_INLINING)
sourceMapNamesPolicy JsSourceMapNamesPolicy compilerOptions.sourceMapNamesPolicy.set(JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_FQ_NAMES)

다음 단계는?

다음 내용들을 더 알아볼 수 있어요:

더 알아보기 (Learn more)