Skip to content

Windows 长 classpath:由插件生成 @argfile,经 run/start 的 temporaryFiles base64 交给宿主 #7

Open

Description

@xiaoyumuxi

要解决的问题

Windows 的 CreateProcess 命令行上限是 32,767 个 UTF-16 码元。依赖多的 Java 项目,classpath 很容易超过这个长度,java 会启动失败。

这件事以前由宿主兜底:

  • 桌面端 desktop/internal/pluginservice/runs.go 在处理每个非调试的 run/start 时,调用 Core 的 execution.planLaunchCommand。
  • 如果启动的是 JDK 9 及以上的 java,并且命令行超长,Core 会把 -cp / -classpath / --class-path / -p / --module-path 的值移进 @argfile。
  • 桌面端再按系统 ANSI 代码页写出这个文件(argfile_encoding_windows.go)。

Core 必须对 Java 零依赖、零特判,所以 Lithe-IDEA/Lithe-core-rust 分支 refactor/core-remove-java-special-cases 已经删除了 execution.planLaunchCommand。桌面端的 launch_plan.go、runargs.go、argfile_encoding_*.go 也会随之删除。之后宿主会原样启动插件给的命令行。

backend/src/run.rs:34 的说明是 “Long command lines are passed as they are”。这句话在 macOS 上成立,但在 Windows 上,长 classpath 的运行和测试会直接失败。

建议方案

由插件后端自己生成参数文件,交给 run/start 的 temporaryFiles。Core 在同一个分支里给 temporaryFiles 增加了 {name, base64} 形式:宿主按给定字节原样写出,不做转码。规则如下:

  • 每项要么是 {name, contents}(UTF-8 文本),要么是 {name, base64}(字节)。同时给出两者或两者都不给,都会被拒绝。
  • 最多 4 个文件,每个不超过 4 MiB。
  • 参数里写 ${temporaryFile:<name>},宿主会替换成实际路径。

插件里已经有可以直接用的部分(origin/HEAD 28628cb):

  • crates/java-domain/src/execution/launch_command.rs 的 plan_launch_command:从 Core 移植过来,负责判断是否需要参数文件、把哪些选项移进文件,以及如何加引号。它已经作为同名插件函数 execution.planLaunchCommand 注册在 backend/src/lib.rs 里。
  • crates/java-domain/src/execution/argfile.rs:读取 JDK 的 release 文件得到主版本号,并写出参数文件。
  • crates/java-domain/src/execution/classpath_jar.rs:给 JDK 8 用的、只含清单的 classpath JAR。

缺的是两件事:

  • 运行和测试流程(backend/src/run.rs)没有调用上面这些代码;
  • 没有按 Windows 代码页编码参数文件。

插件需要:

  1. 在 backend/src/run.rs 生成 run/start 请求之前调用 plan_launch_command。命令行超过 Windows 上限时,把生成的文件放进 temporaryFiles,参数里写 ${temporaryFile:<name>},不再自己选目录写文件。插件现在还没有自己的临时目录,见 argfile.rs 顶部的 TODO(host-api: API-20 storage)。
  2. Windows 上按系统 ANSI 代码页编码参数文件,规则是:调用 GetACP 取得代码页,再用 WideCharToMultiByte 并带上 WC_NO_BEST_FIT_CHARS 转换;代码页里放不下的字符直接报错,不做替换;双字节字符的尾字节如果是 0x5C(\),要再写一个 0x5C;代码页是 65001 时直接写 UTF-8。编码结果以 {name, base64} 交给宿主。可以参考将被删除的 desktop/internal/pluginservice/argfile_encoding_windows.go。
  3. JDK 8 的 classpath JAR 也可以用 {name, base64} 交给宿主,单个文件不能超过 4 MiB。
  4. macOS 和 Linux 上,命令行没有超长时维持现状。如果需要参数文件,用 UTF-8 的 {name, contents}。
  5. 同步修改 backend/src/run.rs:34 的 “Not yet covered” 说明和 README 的日志表。表里的 execution.planLaunchCommand 是插件自己的函数,不是 Core 命令。
  6. 测试至少覆盖:
    • 超长 classpath 会生成参数文件;
    • 未超长时不生成;
    • JDK 8 走 classpath JAR;
    • 非 ASCII 路径;
    • 双字节代码页下的尾字节加倍。

迁移顺序

  • 合并顺序是:Core PR 先合入;桌面端删除兜底、并支持写出 base64 临时文件的 PR 再合入;最后本插件发布支持 argfile 的版本。
  • 在本插件发版之前,Windows 上的长 classpath 运行会失败。目前 Viva 桌面端在 Windows 上仍处于迁移验证阶段。

其他

Core 的模板值 ${field.<runtime>.major} 现在取版本号的第一个数字分量:1.8.0_412 得到 1,17.0.2 得到 17。本插件的 ${field.jdk.major} 只用在 minimumVersion: 17 的 JDK 字段上,不受影响。如果以后要支持 JDK 8 的选择,需要在插件里自己换算 1.x。

Activity

  1. xiaoyumuxi commented on Nov 13, 2026

    @xiaoyumuxi
    ContributorAuthor

    宿主侧的两个 PR 已开(Draft):

    合并顺序:Core #23 → 桌面端 #1859 → 本插件发布 argfile 版本。插件发版前,Windows 上长 classpath 的运行会失败。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions