관리 메뉴

오늘도 더 나은 코드를 작성하였습니까?

Kotlin Multiplatform project 기본구조 본문

카테고리 없음

Kotlin Multiplatform project 기본구조

hik14 2025. 11. 30. 17:17

https://kotlinlang.org/docs/multiplatform/multiplatform-discover-project.html?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fkotlin-multiplatform%3Fhl%3Den%23article-https%3A%2F%2Fwww.jetbrains.com%2Fhelp%2Fkotlin-multiplatform-dev%2Fmultiplatform-discover-project.html

 

The basics of Kotlin Multiplatform project structure | Kotlin Multiplatform

 

kotlinlang.org

위 링크의 공식 문서를 바탕으로 학습한 내용을 정리합니다. 

 

 

Kotlin Multiplatform을 사용하면 다양한 플랫폼 간에 코드를 공유할 수 있습니다.

- 공유 코드의 제약 조건

- 코드의 공유 부분과 플랫폼별 부분을 구별하는 방법

- 공유 코드가 작동하는 플랫폼을 지정하는 방법

Common code(공유  코드)

여러 플랫폼에서 공유되는 Kotlin 코드는 일반적으로 commonMain 디렉토리에 넣는다.

코드 파일의 위치는 이 코드가 컴파일되는 플랫폼 목록에 영향을 미치므로 중요합니다.

 

Kotlin 컴파일러는 소스 코드를 입력으로 받아 플랫폼별 바이너리 세트를 생성합니다.

멀티플랫폼 프로젝트를 컴파일할 때 동일한 코드에서  플랫폼별 여러 개의 바이너리를 생성할 수 있습니다.

예를 들어, 컴파일러는 동일한 Kotlin 파일에서 JVM .class 파일과 네이티브 실행 파일을 생성할 수 있습니다.

모든 Kotlin 코드를 모든 플랫폼에서 컴파일할 수 있는 것은 아닙니다. Kotlin 컴파일러는 공통 코드에서 특정 플랫폼에서만 사용 가능한, 함수나 클래스를 사용할 수 없도록 합니다. 이런 코드는 다른 플랫폼에서 컴파일될 수 없기 때문입니다.

 

예를 들어, 공통 코드에서는 java.io.File 종속성을 사용할 수 없습니다. 이는 JDK의 일부이지만, 공통 코드는 네이티브 코드로도 컴파일되므로 JDK 클래스를 사용할 수 없습니다.

 
공통 코드에서는 Kotlin Multiplatform 라이브러리를 사용할 수 있습니다. 이러한 라이브러리는 다양한 플랫폼에서 다르게 구현할 수 있는 공통 API를 제공합니다. 이 경우 플랫폼에 특화된 API는 별도의 부분으로 작동되어야 하며, 이러한 API를 공통 코드에서 사용하려고 하면 오류가 발생합니다.

 

플랫폼에 특화된 API, KMP 프로젝트 구조에서 플랫폼별 코드는 공통 코드를 보조하고 플랫폼 고유의 기능만 처리한다는 의미입니다.

예를 들어, 시간 측정 기능이 필요하다고 가정해 봅시다.

  • 공통 코드는 단순히 getCurrentTime()이라는 함수를 호출하고 싶어 합니다.
  • 하지만 Android에서는 System.currentTimeMillis()를 사용해야 하고, iOS에서는 NSDate().timeIntervalSince1970와 같은 Objective-C/Swift 함수를 호출해야 합니다.

이때, Android나 iOS의 네이티브 함수는 공통 코드 입장에서는 접근 불가능한 '특화된' 부분입니다. 하지만 fun CoroutinesDispatcher.asExecutor()와 같이 kotlinx.coroutines 동시 기본형을 JDK 동시 기본형으로 변환하는 플랫폼별 부분도 있습니다. Executor.

 

예를 들어, kotlinx.coroutines는 모든 대상을 지원하는 Kotlin Multiplatform 라이브러리입니다.

un CoroutinesDispatcher.asExecutor(): Executor  (JDK 플랫폼별 부분)은 사용 안됩니다.

Common API (공통 API)

  • 코드 위치: commonMain 소스 세트.
  • 포함하는 것: launch, async, Job, Deferred, 그리고 **CoroutineDispatcher**와 같은 핵심 개념들.
  • 특징: 이 코드는 모든 플랫폼(JVM/Android, iOS, Web, Desktop)에서 동일하게 컴파일되고 사용됩니다. 이들은 플랫폼 고유의 코드를 직접 건드리지 않습니다.

Platform-Specific API (플랫폼별 API)

  • 코드 위치: jvmMain, iosMain, jsMain 등 각 플랫폼 전용 소스 세트.
  • 포함하는 것: 해당 플랫폼의 네이티브 시스템과 상호 작용하는 코드.

Targets

target은 Kotlin이 공통 코드를 컴파일하는 플랫폼을 정의합니다. 예를 들어 JVM, JS, Android, iOS 또는 Linux가 될 수 있습니다.

Kotlin target은 컴파일 타겟을 설명하는 식별자입니다. 생성된 바이너리의 형식, 사용 가능한 언어 구문, 그리고 허용되는 종속성을 정의합니다. (target은 platform이라고도 합니다.)

kotlin {
    jvm() // Declares a JVM target
    iosArm64() // Declares a target that corresponds to 64-bit iPhones
}

 

 

멀티플랫폼 프로젝트가 지원되는 target(platform) 집합을 정의한다

 

어떤 코드가 특정 target(platform)에 컴파일될지 알아보자.

target(platform)을 Kotlin 소스 파일에 붙은 라벨로 생각하면 됩니다.

greeting.kt 파일도 .js로 컴파일하려면 JS target(platform)만 선언하면 됩니다. 그러면 commonMain의 코드는 JS 대상에 해당하는 js 라벨을 추가로 받게 되는데, 이 라벨은 Kotlin이 .js 파일을 생성하도록 지시합니다.

 

Kotlin 컴파일러는 선언된 모든 target(platform)으로 컴파일된 공통 코드를 이런 방식으로 처리합니다.

Source sets

코틀린 source setstarget(platform), 종속성 및 컴파일러 옵션을 가진 소스 파일 세트입니다. 이는 다중 플랫폼 프로젝트에서 코드를 공유하는 주요 방법입니다.
 
각 프로젝트에 고유한 이름이 있습니다. 예) commonMain, iosMain, jvmMain
일반적으로, source sets 이름이 있는 디렉토리에 저장되는 파일과 리소스 세트를 포함합니다.
source sets 코드가 컴파일되는 target을 지정합니다. 이러한 target은 이 source sets에서 사용할 수 있는 언어 구문과 종속성에 영향을 미칩니다.

 

자체 종속성과 컴파일러 옵션을 정의합니다

 

Kotlin은 미리 정의된 여러 source sets를 제공합니다. 그중 하나는 commonMain으로, 모든 멀티플랫폼 프로젝트에 존재하며 선언된 모든 대상으로 컴파일됩니다

 

Kotlin Multiplatform 프로젝트에서는 src 내부의 디렉터리를 통해 source sets와 상호 작용합니다. 예를 들어, commonMain, iosMain, jvmMain 소스 세트가 있는 프로젝트의 구조는 다음과 같습니다.

 

Gradle 스크립트에서는 kotlin.sourceSets {} 블록 내에서 이름으로 source sets에 액세스합니다

kotlin {
    // Targets declaration:
    // …

    // Source set declaration:
    sourceSets {
        commonMain {
            // Configure the commonMain source set
        }
    }
}

 

Platform-specific source sets

공통 코드만 있으면 편리하지만 항상 가능한 것은 아닙니다. commonMain에 있는 코드는 선언된 모든 target(platform)으로 컴파일되며, Kotlin에서는 해당 플랫폼별 API를 사용할 수 없습니다

 

네이티브 및 JS target이 있는 멀티플랫폼 프로젝트에서 commonMain의 다음 코드는 컴파일되지 않습니다.

// commonMain/kotlin/common.kt
// Doesn't compile in common code
fun greeting() {
    java.io.File("greeting.txt").writeText("Hello, Multiplatform!")
}

 

이 문제를 해결하기 위해 Kotlin은 Platform specific source sets(플랫폼 소스 세트라고도 함)를 생성합니다.

target에는 해당 target에 대해서만 컴파일되는 해당 Platform specific source sets가 있습니다. 예를 들어, jvm 대상에는 JVM으로만 컴파일되는 해당 jvmMain 소스 세트가 있습니다. Kotlin은 Platform specific source sets에서 플랫폼별 종속성을 사용할 수 있도록 허용합니다. 예를 들어 jvmMain의 JDK는 다음과 같습니다.

// jvmMain/kotlin/jvm.kt
// You can use Java dependencies in the `jvmMain` source set
fun jvmGreeting() {
    java.io.File("greeting.txt").writeText("Hello, Multiplatform!")
}

 

Compilation to a specific target

특정 target의 컴파일은 여러 source sets에서 작동합니다. Kotlin은 다중 플랫폼 프로젝트를 특정 target으로 컴파일할 때, 해당 target으로 라벨이 지정된 모든 source sets를 수집하여 바이너리를 생성합니다.

 

jvm, iosArm64, js target을 사용하는 예를 생각해 보겠습니다. Kotlin은 공통 코드에 대한 commonMain source sets를 생성하고, 특정 target에 대한 해당 jvmMain, iosArm64Main, jsMain source sets를 생성합니다.

JVM으로 컴파일하는 동안 Kotlin은 "JVM"으로 라벨이 지정된 모든 소스 세트, 즉 jvmMain과 commonMain을 선택합니다. 그런 다음 이들을 JVM 클래스 파일로 컴파일합니다.

 

Kotlin은 commonMain과 jvmMain을 함께 컴파일하므로, 결과 바이너리에는 commonMain과 jvmMain의 선언이 모두 포함됩니다.

 

여러 플랫폼으로 구성된 프로젝트를 작업할 때 주의사항

 

1. Kotlin이 특정 플랫폼으로 코드를 컴파일하도록 하려면 해당 target을 선언하세요

 

2. 코드를 저장할 디렉토리나 소스 파일을 선택하려면, 먼저 코드를 공유할 targets을 결정하세요.

  • 코드가 모든 대상에서 공유되는 경우 commonMain에 선언
  • 코드가 단 하나의 에만 사용되는 경우 해당 대상에 대한 Platform specific source sets(예: JVM의 경우 jvmMain)에 정의되어야 합니다.
  •  

3. Platform specific source sets에 작성된 코드는 공통 소스 세트의 선언에 액세스할 수 있습니다. 예를 들어, jvmMain의 코드는 commonMain의 코드를 사용할 수 있습니다. 하지만 그 반대는 성립하지 않습니다. commonMain은 jvmMain의 코드를 사용할 수 없습니다.

 

4. Platform specific source sets로 작성된 코드는 해당 플랫폼 종속성을 사용할 수 있습니다. 예를 들어, jvmMain의 코드는 Guava나 Spring과 같은 Java 전용 라이브러리를 사용할 수 있습니다.

 

Intermediate source sets

간단한 멀티플랫폼 프로젝트는 일반적으로 common과 Platform specific 코드만 포함합니다. commonMain 소스 세트는 선언된 모든 대상에서 공유되는 공통 코드를 나타냅니다. jvmMain과 같은 Platform specific source sets는 해당 대상에만 컴파일된 Platform specific  코드를 나타냅니다.

 

실제로는 더 세부적인 코드 공유가 필요한 경우가 있을수 있습니다.
모든 최신 Apple 기기와 Android 기기를 타겟으로 삼아야 하는 예를 생각해 보겠습니다.
kotlin {
    androidTarget()
    iosArm64()   // 64-bit iPhone devices
    macosArm64() // Modern Apple Silicon-based Macs
    watchosX64() // Modern 64-bit Apple Watch devices
    tvosArm64()  // Modern Apple TV devices
}
그리고 모든 Apple 기기에 대한 UUID를 생성하는 함수를 추가하려면 source sets가 필요합니다.
import platform.Foundation.NSUUID

fun randomUuidString(): String {
    // You want to access Apple-specific APIs
    return NSUUID().UUIDString()
}

 

commonMain에 이 기능을 추가할 수 없습니다. commonMain은 Android를 포함한 모든 선언된 대상으로 컴파일됩니다.하지만 platform.Foundation.NSUUID는 Android에서는 사용할 수 없는 Apple 전용 API입니다. Kotlin에서 commonMain에서 NSUUID를 참조하려고 하면 오류가 발생합니다.

 

이 코드를 각 Apple 전용 소스 세트(iosArm64Main, macosArm64Main, watchosX64Main, tvosArm64Main)에 복사하여 붙여넣을 수 있습니다. 하지만 이러한 방식으로 코드를 복제하면 오류가 발생할 가능성이 높으므로 권장하지 않습니다.

 

이 문제를 해결하려면 intermediate source sets를 사용할 수 있습니다.

intermediate source sets는 프로젝트의 모든 targets은 아니지만 일부 targets으로 컴파일되는 Kotlin 소스 세트입니다. intermediate source sets를 계층적 소스 세트 또는 간단히 계층 구조라고도 합니다.

 

Kotlin은 기본적으로 intermediate source sets를 생성합니다. 이 경우, 최종 프로젝트 구조는 다음과 같습니다.

appleMain 블록은 Apple 특정 targets 맞춰 컴파일된 코드를 공유하기 위해 Kotlin이 만든 intermediate source sets입니다.
appleMain source sets는 Apple 타겟으로만 컴파일됩니다. 따라서 Kotlin은 appleMain에서 Apple 전용 API를 사용할 수 있도록 허용하며, 여기에 randomUUID() 함수를 추가할 수 있습니다.

 

특정 targets으로 컴파일하는 동안 Kotlin은 ntermediate source sets를 포함한 모든 소스 세트를 이  targets으로 라벨 지정됩니다.

Integration with tests

 

실제 프로젝트에서도 메인 프로덕션 코드와 함께 테스트가 필요합니다. 이것이 바로 기본적으로 생성되는 모든 소스 세트에 Main과 Test 접미사가 붙는 이유입니다. Main에는 프로덕션 코드가, Test에는 이 코드에 대한 테스트가 포함됩니다.

Main은 프로덕션 코드를 포함하고, Test는 이 코드에 대한 테스트를 포함합니다. 두 코드 간의 연결은 자동으로 설정되며, 테스트는 추가 구성 없이 Main 코드에서 제공하는 API를 사용할 수 있습니다.

 

Test 대응 항목도 Main과 유사한 소스 세트입니다. 예를 들어, commonTest는 commonMain의 대응 항목이며, 선언된 모든 대상으로 컴파일되므로 공통 테스트를 작성할 수 있습니다. jvmTest와 같은 플랫폼별 테스트 소스 세트는 플랫폼별 테스트(예: JVM별 테스트 또는 JVM API가 필요한 테스트)를 작성하는 데 사용됩니다.

 

일반적인 테스트를 작성하기 위한 소스 세트 외에도 다중 플랫폼 테스트 프레임워크도 필요합니다. Kotlin은 @kotlin.Test 주석과 assertEquals, assertTrue와 같은 다양한 어설션 메서드가 포함된 기본 kotlin.test 라이브러리를 제공합니다.

 

각 플랫폼에 대한 일반 테스트처럼 해당 소스 세트에서 플랫폼별 테스트를 작성할 수 있습니다.

메인 코드와 마찬가지로 각 소스 세트에 대해 플랫폼별 종속성을 가질 수 있습니다. 예를 들어 JVM의 경우 JUnit, iOS의 경우 XCTest가 있습니다. 특정 대상에 대한 테스트를 실행하려면 <targetName>Test 작업을 사용하세요.