Kotlin 커스텀 스크립팅 시작하기 – 튜토리얼
Kotlin 커스텀 스크립팅 시작하기 – 튜토리얼 (Get started with Kotlin custom scripting – tutorial)
Kotlin 코드를 미리 컴파일하거나 실행 파일로 패키징하지 않고도 스크립트처럼 실행할 수 있게 해주는 기술, 그것이 바로 Kotlin 스크립팅이에요. 이번 튜토리얼에서는 Maven 의존성을 가진 임의의 Kotlin 코드를 실행하는 Kotlin 스크립팅 프로젝트를 직접 만들어 볼게요. 다만 Kotlin 커스텀 스크립팅은 아직 실험적 기능이라는 점을 미리 알아두세요.
본문
Kotlin 커스텀 스크립팅은 실험적(Experimental) 기능이에요. 언제든 중단되거나 바뀔 수 있으니 평가 목적으로만 사용하세요. YouTrack에 피드백을 남겨 주시면 감사하겠어요.
Kotlin 스크립팅은 Kotlin 코드를 사전 컴파일이나 실행 파일 패키징 없이 스크립트로 실행할 수 있게 해주는 기술이에요.
Kotlin 스크립팅에 대한 개요와 예시를 보려면 KotlinConf'19에서 Rodrigo Oliveira가 발표한 Implementing the Gradle Kotlin DSL 토크를 확인해 보세요.
이 튜토리얼에서는 Maven 의존성을 가진 임의의 Kotlin 코드를 실행하는 Kotlin 스크립팅 프로젝트를 만들 거예요. 이렇게 스크립트를 실행할 수 있게 돼요.
@file:Repository("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.3")
import kotlinx.html.*
import kotlinx.html.stream.*
import kotlinx.html.attributes.*
val addressee = "World"
print(
createHTML().html {
body {
h1 { +"Hello, $addressee!" }
}
}
)
지정된 Maven 의존성(이 예시에서는 kotlinx-html-jvm)은 실행 중에 지정된 Maven 저장소나 로컬 캐시에서 해석되어, 스크립트의 나머지 부분에서 사용돼요.
프로젝트 구조
최소한의 Kotlin 커스텀 스크립팅 프로젝트는 두 부분으로 구성돼요.
- Script definition(스크립트 정의) — 이 스크립트 타입을 어떻게 인식하고 처리하며 컴파일·실행할지를 정의하는 매개변수와 구성의 집합이에요.
- Scripting host(스크립팅 호스트) — 스크립트 컴파일과 실행을 처리하는 애플리케이션 또는 컴포넌트로, 실제로 이 타입의 스크립트를 실행해요.
이 모든 것을 고려하면 프로젝트를 두 모듈로 나누는 게 가장 좋아요.
시작하기 전에
최신 버전의 IntelliJ IDEA를 다운로드해 설치해요.
프로젝트 만들기
- IntelliJ IDEA에서 File | New | Project를 선택해요.
- 왼쪽 패널에서 New Project를 선택해요.
- 새 프로젝트의 이름을 정하고, 필요하면 위치를 변경해요.
- Create Git repository 체크박스를 선택하면 새 프로젝트를 버전 관리 아래에 둘 수 있어요. 나중에 언제든 할 수도 있어요.
- Language 목록에서 Kotlin을 선택해요.
- Gradle 빌드 시스템을 선택해요.
- JDK 목록에서 프로젝트에 사용할 JDK를 선택해요.
- JDK가 컴퓨터에 설치되어 있지만 IDE에 정의되지 않았다면 Add JDK를 선택하고 JDK 홈 디렉터리 경로를 지정해요.
- 필요한 JDK가 컴퓨터에 없다면 Download JDK를 선택해요.
- Gradle DSL에 Kotlin 또는 Gradle 언어를 선택해요.
- Create를 클릭해요.
스크립팅 모듈 추가하기
이제 빈 Kotlin/JVM Gradle 프로젝트가 준비되었어요. 필요한 모듈인 script definition과 scripting host를 추가해요.
- IntelliJ IDEA에서 File | New | Module을 선택해요.
- 왼쪽 패널에서 New Module을 선택해요. 이 모듈이 script definition이 돼요.
- 새 모듈 이름을 정하고, 필요하면 위치를 변경해요.
- Language 목록에서 Java를 선택해요.
- Gradle 빌드 시스템을 선택하고, 빌드 스크립트를 Kotlin으로 쓰고 싶다면 Gradle DSL로 Kotlin을 선택해요.
- 모듈의 부모로 루트 모듈을 선택해요.
- Create를 클릭해요.
- 모듈의
build.gradle(.kts)파일에서 Kotlin Gradle 플러그인의version을 제거해요. 이미 루트 프로젝트의 빌드 스크립트에 있거든요. - scripting host용 모듈을 만들기 위해 앞의 단계를 한 번 더 반복해요.
프로젝트는 다음 구조를 가져야 해요. 이런 프로젝트와 더 많은 Kotlin 스크립팅 예시는 kotlin-script-examples GitHub 저장소에서 확인할 수 있어요.
스크립트 정의 만들기
먼저 스크립트 타입을 정의해요. 즉 개발자가 이 타입의 스크립트에 무엇을 쓸 수 있고, 어떻게 처리될지를 정하는 거예요. 이 튜토리얼에서는 스크립트에서 @Repository와 @DependsOn 애너테이션을 지원하는 것이 포함돼요.
- script definition 모듈의
build.gradle(.kts)에 있는dependencies블록에서 Kotlin 스크립팅 컴포넌트에 대한 의존성을 추가해요. 이 의존성들이 스크립트 정의에 필요한 API를 제공해요.
dependencies {
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies")
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven")
// coroutines dependency is required for this particular definition
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
}
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-scripting-common'
implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm'
implementation 'org.jetbrains.kotlin:kotlin-scripting-dependencies'
implementation 'org.jetbrains.kotlin:kotlin-scripting-dependencies-maven'
// coroutines dependency is required for this particular definition
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0'
}
- 모듈에
src/main/kotlin/디렉터리를 만들고,scriptDef.kt같은 Kotlin 소스 파일을 추가해요. scriptDef.kt에서 클래스를 만들어요. 이것이 이 타입의 스크립트를 위한 슈퍼클래스가 되므로abstract나open으로 선언해요.
// abstract (or open) superclass for scripts of this type
abstract class ScriptWithMavenDeps
이 클래스는 나중에 스크립트 정의에 대한 참조 역할도 해요.
- 클래스를 스크립트 정의로 만들려면
@KotlinScript애너테이션으로 표시해요. 애너테이션에 두 매개변수를 전달해요.fileExtension—.kts로 끝나는 문자열로, 이 타입의 스크립트에 대한 파일 확장자를 정의해요.compilationConfiguration—ScriptCompilationConfiguration을 확장하고 이 스크립트 정의에 대한 컴파일 세부 사항을 정의하는 Kotlin 클래스예요. 다음 단계에서 만들 거예요.
// @KotlinScript annotation marks a script definition class
@KotlinScript(
// File extension for the script type
fileExtension = "scriptwithdeps.kts",
// Compilation configuration for the script type
compilationConfiguration = ScriptWithMavenDepsConfiguration::class
)
abstract class ScriptWithMavenDeps
object ScriptWithMavenDepsConfiguration: ScriptCompilationConfiguration()
이 튜토리얼에서는 Kotlin 스크립팅 API에 대한 설명 없이 동작하는 코드만 제공해요. 같은 코드를 자세한 설명과 함께 GitHub에서 찾아볼 수 있어요.
- 아래와 같이 스크립트 컴파일 구성을 정의해요.
object ScriptWithMavenDepsConfiguration : ScriptCompilationConfiguration(
{
// Implicit imports for all scripts of this type
defaultImports(DependsOn::class, Repository::class)
jvm {
// Extract the whole classpath from context classloader and use it as dependencies
dependenciesFromCurrentContext(wholeClasspath = true)
}
// Callbacks
refineConfiguration {
// Process specified annotations with the provided handler
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
}
}
)
configureMavenDepsOnAnnotations 함수는 다음과 같아요.
// Handler that reconfigures the compilation on the fly
fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
?: return context.compilationConfiguration.asSuccess()
return runBlocking {
resolver.resolveFromScriptSourceAnnotations(annotations)
}.onSuccess {
context.compilationConfiguration.with {
dependencies.append(JvmDependency(it))
}.asSuccess()
}
}
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
전체 코드는 여기에서 확인할 수 있어요.
스크립팅 호스트 만들기
다음 단계는 스크립트 실행을 처리하는 컴포넌트인 scripting host를 만드는 거예요.
- scripting host 모듈의
dependencies블록에 의존성을 추가해요.- scripting host에 필요한 API를 제공하는 Kotlin 스크립팅 컴포넌트
- 앞서 만든 script definition 모듈
dependencies {
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm-host")
implementation(project(":script-definition")) // the script definition module
}
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-scripting-common'
implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm'
implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm-host'
implementation project(':script-definition') // the script definition module
}
- 모듈에
src/main/kotlin/디렉터리를 만들고host.kt같은 Kotlin 소스 파일을 추가해요. - 애플리케이션의
main함수를 정의해요. 그 본문에서 인자가 하나(스크립트 파일 경로)인지 확인하고 스크립트를 실행해요. 스크립트 실행은 다음 단계에서 별도 함수evalFile로 정의할 거니, 지금은 빈 상태로 선언해 두세요.
main은 이렇게 만들 수 있어요.
fun main(vararg args: String) {
if (args.size != 1) {
println("usage: <app> <script file>")
} else {
val scriptFile = File(args[0])
println("Executing script $scriptFile")
evalFile(scriptFile)
}
}
- 스크립트 평가 함수를 정의해요. 여기에서 스크립트 정의를 사용할 거예요. 스크립트 정의 클래스를 타입 매개변수로
createJvmCompilationConfigurationFromTemplate을 호출해 스크립트 정의를 얻어요. 그런 다음BasicJvmScriptingHost().eval을 호출하면서 스크립트 코드와 그 컴파일 구성을 전달해요.eval은ResultWithDiagnostics인스턴스를 반환하므로 함수의 반환 타입으로 설정해요.
fun evalFile(scriptFile: File): ResultWithDiagnostics<EvaluationResult> {
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<ScriptWithMavenDeps>()
return BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), compilationConfiguration, null)
}
main함수를 조정해 스크립트 실행 정보를 출력하게 해요.
fun main(vararg args: String) {
if (args.size != 1) {
println("usage: <app> <script file>")
} else {
val scriptFile = File(args[0])
println("Executing script $scriptFile")
val res = evalFile(scriptFile)
res.reports.forEach {
if (it.severity > ScriptDiagnostic.Severity.DEBUG) {
println(" : ${it.message}" + if (it.exception == null) "" else ": ${it.exception}")
}
}
}
}
전체 코드는 여기에서 확인할 수 있어요.
스크립트 실행하기
스크립팅 호스트가 제대로 동작하는지 확인하려면 실행할 스크립트와 실행 구성을 준비해요.
- 프로젝트 루트 디렉터리에 다음 내용으로
html.scriptwithdeps.kts파일을 만들어요.
@file:Repository("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.3")
import kotlinx.html.*; import kotlinx.html.stream.*; import kotlinx.html.attributes.*
val addressee = "World"
print(
createHTML().html {
body {
h1 { +"Hello, $addressee!" }
}
}
)
이 스크립트는 @DependsOn 애너테이션 인자에서 참조하는 kotlinx-html-jvm 라이브러리의 함수를 사용해요.
- 스크립팅 호스트를 시작하고 이 파일을 실행하는 실행 구성을 만들어요.
host.kt를 열고main함수로 이동해요. 왼쪽에 Run 거터(gutter) 아이콘이 있어요.- 거터 아이콘을 마우스 오른쪽으로 클릭하고 Modify Run Configuration을 선택해요.
- Create Run Configuration 대화상자에서 Program arguments에 스크립트 파일 이름을 추가하고 OK를 클릭해요.
- 만든 구성을 실행해요.
지정된 저장소에서 kotlinx-html-jvm 의존성을 해석하고, 그 함수를 호출한 결과를 출력하면서 스크립트가 실행되는 걸 볼 수 있어요.
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
첫 실행에서는 의존성 해석에 시간이 걸릴 수 있어요. 이후 실행은 로컬 Maven 저장소에서 다운로드된 의존성을 사용하므로 훨씬 빠르게 끝나요.
다음 단계는?
간단한 Kotlin 스크립팅 프로젝트를 만들었다면, 이 주제에 대한 더 많은 정보를 찾아보세요.
- Kotlin scripting KEEP 읽어보기
- 더 많은 Kotlin 스크립팅 예시 살펴보기
- Rodrigo Oliveira의 Implementing the Gradle Kotlin DSL 토크 보기
더 알아보기
- Kotlin 스크립팅 예시 저장소 — 다양한 스크립팅 예시
- Kotlin scripting KEEP — 스크립팅 지원 제안
- Kotlin 언어 기능과 제안 — 실험적 기능의 안정성