SuperAPI AI 工具集

pseudo-kmp

编程与开发

查看项目

更新于 10-16 09:12

技能介绍

用途

Pseudo-KMP 用于在 IntelliJ 社区单体仓库中创建、迁移或修改 pseudo-KMP 模块。该单体仓库由 JPS 编译,而 JPS 不支持 Kotlin Multiplatform。为模拟 KMP 的 expect/actual 声明,Fleet 团队创建了 expects-compiler-plugin 编译器插件。本技能说明如何设置和使用采用该机制的模块。

主要能力

  • 通过 expects-compiler-plugin 在 JPS 环境下模拟 KMP 的 expect/actual 声明。
  • 支持在公共源码集与平台特定源码集之间组织代码。
  • 提供模块配置清单,确保模块通过结构测试校验。
  • 提供 IML 文件配置方式,注册多平台支持依赖与编译器插件。
  • 提供 expect/actual 的编写模式与平台后缀约定。

模块配置清单

创建或迁移 pseudo-KMP 模块时:

  1. 在结构测试中注册。将模块名加入 com.intellij.ideaProjectStructure.fast.MultiplatformModuleStructureTest 中的 multiplatformModules 列表(路径:tests/ideaProjectStructure/testSrc/com/intellij/ideaProjectStructure/fast/MultiplatformModuleStructureTest.kt)。

  2. 仅使用允许的库依赖。测试强制使用封闭集合:

    • kotlin-stdlib
    • kotlinx-coroutines-core
    • kotlinx-coroutines-debug
    • jetbrains-annotations
    • kotlinx-serialization-core
    • kotlinx-serialization-json
    • kotlin-reflect
    • kotlinx-collections-immutable

    如需新增库,将其加入测试中的 allowedMultiplatformLibraryDependencies。对其他多平台模块(即 multiplatformModules 中的模块)的依赖也是允许的。

  3. 设置源码文件夹:

    • src/ — 公共代码
    • srcJvm/ 或 srcJvmMain/ — JVM 特定代码(Java 代码也可放在此处)
    • srcWasmJs/ 或 srcWasmJsMain/ — WasmJs 特定代码
    • srcJsMain/ — JS 特定代码
    • srcNativeMain/ — Native 特定代码

    重要:不要在 .iml 文件中将 srcWasmJs/srcWasmJsMain 添加为源码根。IDE 会尝试以 JVM 模式编译它。

  4. 在公共源码根添加 .pseudoCommonKotlinSourceSet 标记文件(例如 src/.pseudoCommonKotlinSourceSet)。该空文件告知 Kotlin 插件该源码集为公共代码,防止 Optimize Imports 移除在 JVM 上不必要但在公共代码中必需的导入(例如 @JvmStatic、@JvmField)。

IML 文件设置

.iml 文件需要两项内容:multiplatformSupport 依赖和编译器插件注册。

以 community/platform/util/multiplatform/intellij.platform.util.multiplatform.iml 为参考。要查找当前插件版本,请查看该文件而非硬编码。

  1. 添加支持模块依赖(PROVIDED 作用域):
<orderEntry type="module" module-name="intellij.platform.multiplatformSupport" scope="PROVIDED" />
  1. 添加带 expects-compiler-plugin 的 Kotlin facet:
<facet type="kotlin-language" name="Kotlin">
  <configuration version="5" platform="JVM 1.8" allPlatforms="JVM [1.8]" useProjectSettings="false">
    <compilerSettings>
      <option name="additionalArguments" value="-Xjvm-default=all ..." />
    </compilerSettings>
    <compilerArguments>
      <stringArguments>
        <stringArg name="jvmTarget" arg="1.8" />
        <stringArg name="apiVersion" arg="2.3" />
        <stringArg name="languageVersion" arg="2.3" />
      </stringArguments>
      <arrayArguments>
        <arrayArg name="pluginClasspaths">
          <args>$MAVEN_REPOSITORY$/jetbrains/fleet/expects-compiler-plugin/{VERSION}/expects-compiler-plugin-{VERSION}.jar</args>
        </arrayArg>
      </arrayArguments>
    </compilerArguments>
  </configuration>
</facet>

将 {VERSION} 替换为参考 IML 中的版本。

Expect/Actual 模式

仅支持顶层函数。expect 和 actual 声明必须在同一模块中。

公共侧(位于 src/):使用 linkToActual() 作为函数体:

import fleet.util.multiplatform.linkToActual

internal fun myFunction(param1: String, param2: Int): Boolean = linkToActual()

平台侧(位于 srcJvm/、srcWasmJs/ 等):使用 @Actual 注解。函数名必须是 expect 函数名加平台后缀:

import fleet.util.multiplatform.Actual

@Actual
internal fun myFunctionJvm(param1: String, param2: Int): Boolean {
  // JVM implementation
}

平台后缀:

平台 后缀 源码文件夹
JVM Jvm srcJvm/ 或 srcJvmMain/
WasmJs WasmJs srcWasmJs/ 或 srcWasmJsMain/
JS Js srcJsMain/
Native Native srcNativeMain/
多平台构建 Impl(罕见)

真实示例:

公共侧(community/platform/util/base/multiplatform/src/com/intellij/util/text/CharArrayUtilKmp.kt):

internal fun getCharsPlatformSpecific(
  sequence: CharSequence, srcOffset: Int, dst: CharArray, dstOffset: Int, len: Int
): Boolean = linkToActual()

JVM actual(community/platform/util/base/multiplatform/srcJvmMain/com/intellij/util/text/jvm.kt):

@Actual
internal fun getCharsPlatformSpecificJvm(
  string: CharSequence, srcOffset: Int, dst: CharArray, dstOffset: Int, len: Int
): Boolean {
  if (string is CharBuffer) { /* ... */ }
  return false
}

代码转换注意事项

  • 单体仓库使用 JVM 版 Kotlin 标准库,与公共版不同。需检查公共代码中哪些声明可用。
  • @JvmStatic、@JvmField 等注解在公共代码中不会自动导入,需手动导入。.pseudoCommonKotlinSourceSet 标记可防止 IDE 移除这些导入。
  • Java 代码可保留在平台特定源码集(srcJvm/)中。

编辑之后

  • 修改任何 .iml 文件后运行 ./build/jpsModelToBazel.cmd。
  • 运行 MultiplatformModuleStructureTest 验证模块结构:
./tests.cmd --module tests.ideaProjectStructure --test com.intellij.ideaProjectStructure.fast.MultiplatformModuleStructureTest
  • 使用 mcp__ijproxy__lint_files 对更改的文件进行 lint。

制品发布

单体仓库不直接向 Maven Central 发布多平台制品。具有 Gradle 基础设施的独立仓库会提取模块源码,用真正的 KMP 编译并发布。参见 JetBrains Space 中的 fleet-multiplatform-deps 和 fleet-parser-collection。

适用场景与限制

适用于在 IntelliJ 社区单体仓库中编写跨平台公共代码并保持 JVM 等平台特定实现的开发者。限制包括:仅支持顶层函数;expect 与 actual 必须位于同一模块;库依赖受封闭集合约束;srcWasmJs/srcWasmJsMain 不得作为 .iml 源码根;单体仓库不直接发布多平台制品。