配置 Kotlin Multiplatform 项目的 Compose 版本:共用依赖、平台差异、构建目标选择

当 Compose 进入 Kotlin Multiplatform (KMP) 项目,依赖管理需要同时处理 跨平台共享 与 平台特化 两个维度。本章从构建脚本结构出发,逐一拆解 commonMain 共用依赖、平台差异处理方式以及多目标构建的选择逻辑。

4.1 KMP 项目中的 Compose 依赖层次

KMP 项目目录通常按 commonMain、androidMain、desktopMain、iosMain 等源集组织。Compose 依赖并不完全平铺,而是分层铺设:

  • commonMain:放置不依赖底层渲染的 API,如 compose.runtime、compose.foundation.layout、compose.material3(部分),以及自定义的 expect 声明。
  • androidMain:补充 Android 平台 Compose 实现,如 compose.ui(Android 原生)、compose.material3 的 Android 适配、Activity 集成。
  • desktopMain:使用 compose.desktop.currentOs 系列依赖,包含窗口管理、鼠标事件等。
  • iosMain:目前 Compose Multiplatform 通过 Skia 后端提供 iOS 支持,依赖 compose.ui(skiko 实现)。

共用依赖需要在 commonMain 的 dependencies 块中声明,而平台特定依赖则放在对应源集下。

4.2 共用依赖配置:commonMain 的核心声明

在 build.gradle.kts 的 kotlin.sourceSets.commonMain.dependencies 块内,列出所有跨平台 Compose 模块。典型配置:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3)
            implementation(compose.components.resources)
            // 如需使用 Compose 编译器注解(如 @Preview),需要显式添加:
            implementation(compose.compiler)
        }
    }
}
注意版本一致:compose.runtime、compose.foundation 等坐标来自 org.jetbrains.compose 插件。它们会自动使用插件声明的 Compose 编译器版本,无需手动指定 Kotlin Compose Plugin 版本。

共用依赖的版本号由 org.jetbrains.compose 插件统一管理,无需在每个依赖后填写版本。插件版本与 Kotlin 版本之间有严格兼容矩阵,详见第 2 章。

4.3 平台差异处理:expect/actual 与平台源码集

当同一 API 在不同平台有不同实现时,使用 expect 和 actual 机制。例如在 commonMain 中声明:

// commonMain
expect fun getPlatformName(): String

@Composable
expect fun PlatformSpecificText()

然后在 androidMain 和 desktopMain 分别提供 actual 实现。注意,actual 函数需要标注 @Composable(若原 expect 是 Composable 函数)。

平台特定依赖则直接写入对应源集的 dependencies 块:

androidMain.dependencies {
    implementation(compose.ui)          // Android 原生 UI
    implementation(compose.material3)   // Material3 Android 组件
    implementation("androidx.activity:activity-compose:1.8.2")
}

desktopMain.dependencies {
    implementation(compose.desktop.currentOs)
    implementation(compose.material3)   // Desktop 端 Material3
}

iosMain.dependencies {
    implementation(compose.ui)          // iOS Skiko 后端
    implementation(compose.foundation)
}

4.4 构建目标选择

构建目标通过 kotlin.targets 块配置。以支持 Android、Desktop (JVM) 和 iOS 为例:

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

    jvm("desktop") {
        compilations.all {
            kotlinOptions {
                jvmTarget = "17"
            }
        }
    }

    iosX64()
    iosArm64()
    iosSimulatorArm64()

    sourceSets { /* ...依赖声明... */ }
}

compose {
    kotlinCompilerPlugin.set("1.5.12")  // 显式指定 Compose 编译器版本(可选)
    // 或使用插件默认版本
}

如需添加 Windows 或 Linux 桌面目标,可在 jvm("desktop") 之外再添加 linuxX64、mingwX64。每个平台都需要对应的 compose.desktop 模块支持。

重要:不要混用 compose.desktop.currentOs 与 compose.desktop.windows 等平台特定模块。currentOs 会自动根据构建主机选择对应后端,适合开发阶段;生产构建需要明确指定所有目标平台依赖。

4.5 完整示例:多平台 build.gradle.kts

以下是一个支持 Android、Desktop (JVM)、iOS 的 KMP + Compose 项目典型配置:

plugins {
    kotlin("multiplatform") version "1.9.22"
    id("org.jetbrains.compose") version "1.6.0"
}

kotlin {
    androidTarget {
        compilations.all { kotlinOptions.jvmTarget = "17" }
    }
    jvm("desktop") {
        compilations.all { kotlinOptions.jvmTarget = "17" }
    }
    iosX64(); iosArm64(); iosSimulatorArm64()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(compose.runtime)
                implementation(compose.foundation)
                implementation(compose.material3)
                implementation(compose.components.resources)
            }
        }
        val androidMain by getting {
            dependencies {
                implementation(compose.ui)
                implementation(compose.material3)
                implementation("androidx.activity:activity-compose:1.8.2")
            }
        }
        val desktopMain by getting {
            dependencies {
                implementation(compose.desktop.currentOs)
                implementation(compose.material3)
            }
        }
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
            dependencies {
                implementation(compose.ui)
                implementation(compose.foundation)
            }
        }
    }
}

compose {
    // 可选:指定编译器版本,默认使用插件捆绑版本
    // kotlinCompilerPlugin.set("1.5.12")
}

4.6 平台差异依赖速查表

依赖坐标(via compose.*) commonMain androidMain desktopMain iosMain
compose.runtime ✅ 必须 自动继承 自动继承 自动继承
compose.foundation ✅ 推荐 自动继承 自动继承 自动继承
compose.material3 ✅ 推荐 ✅ 可额外添加 ✅ 可额外添加 ✅ 可额外添加
compose.ui ❌ 不可(平台抽象) ✅ 必须 ✅ 必须 ✅ 必须
compose.desktop.currentOs ❌ ❌ ✅ 必须 ❌
关键原则:commonMain 中只放置不依赖平台特性的 API;平台期望差异通过 expect/actual 解决,不要试图在 commonMain 中引入 compose.ui 或桌面专属依赖。

4.7 常见错误与调试

  • Missing source set:未正确创建 iosMain 或 desktopMain 中间源集会引发编译错误。务必使用 by creating 并建立 dependsOn(commonMain)。
  • Compose compiler version mismatch:若手动设置了 compose.kotlinCompilerPlugin,必须确保其与 Kotlin 版本兼容(如 Kotlin 1.9.22 对应 Compose Compiler 1.5.12)。
  • Desktop 目标找不到 compose.desktop.currentOs:检查是否添加了 compose.desktop.currentOs 依赖,并确保 jvm("desktop") 目标名称与插件期望一致。
  • iOS 构建失败:确认 Xcode 版本和 Kotlin/Native 工具链支持。iOS 目标可能需要显式指定 compose.ui 版本与 macOS 后端一致。
KMP Compose 依赖模块架构 Compose Compiler Compose MPP Plugin commonMain (Shared API) androidMain desktopMain (JVM) iosMain (Skiko) @Composable 编译 注入编译器版本 继承依赖 继承依赖 继承依赖 说明 • 共用依赖(runtime、foundation、material3)在 commonMain 声明,各平台继承。 • 平台特定依赖(desktop.currentOs、android UI)仅在对应平台源码集添加。 • Compose Multiplatform Plugin 自动配置编译器版本,但可通过 kotlinCompilerPlugin 覆盖。 • iOS 目标需额外配置 Skiko 后端,且不自动包含在 commonMain 依赖中。

4.8 版本锁定与冲突排查

当多个依赖间接引入不同版本的 Compose Compiler 时,可在 compose 块中强制指定:

compose {
    kotlinCompilerPlugin.set("1.5.12")
    // 等价于常规 Gradle 配置:kotlinCompilerExtensionVersion
}

若要禁用插件自动注入(罕见场景),可使用:

compose {
    kotlinCompilerPlugin.set(null as String?)
    // 此时需手动添加 compiler 依赖
}

建议始终让插件管理编译器版本,仅在出现编译报错 Compose Compiler requires Kotlin version X.Y.Z 时才手动覆盖。

典型错误:在 commonMain 的 dependencies 中写 implementation("org.jetbrains.compose:compose-ui:1.6.0") 而非使用快捷坐标 compose.ui。这会导致版本不统一,且 Android 依赖也无法正确解析。务必使用 compose.* 快捷访问器,它们由插件自动映射到正确的 Maven 坐标。