Skip to content

feat(windows): JDK 8 超长 classpath 自动改用临时 manifest JAR - #1012

Merged
1lck merged 4 commits into
previewfrom
feat/955-jdk8-shorten-classpath
Oct 1, 2026
Merged

1lck merged 4 commits into
previewfrom
feat/955-jdk8-shorten-classpath

Conversation

@1lck

@1lck 1lck commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Refs #955

只实现 #955 中维护者已承诺的 JDK 8 部分,不增加 IDEA 式的手动“缩短命令行”选项。

背景

v0.5.7(#746、#888)已在 Windows 普通 Java Run 中为 JDK 9+ 自动把超长 classpath/module-path 写入临时 @argfile。JDK 8 不支持 @argfile,依赖很多的项目在 JDK 8 下仍会因命令行超过 CreateProcessW 的 32767 字符上限而无法启动。

方案

参考 IDEA 对 JDK 8 的 “JAR manifest” 缩短方式:已确认 JDK 版本 < 9 且命令接近 Windows 上限时,生成一个只含 META-INF/MANIFEST.MF 的临时 classpath JAR,用 Class-Path 列出原 classpath 的全部条目,再用 -cp <该 JAR> 启动。

  • 判断和 manifest 生成是确定性逻辑,放在 Rust Core(与 argfile 规划同一模块目录、同一长度预算);宿主只负责回答“条目是否为目录”、写 JAR、清理。
  • JDK 9+ 行为不变,仍用 @argfile;JDK 版本无法识别时保持原命令(与现状一致)。
  • 临时 JAR 写在 temp_dir()/lithe-run/launch-<pid>-<n>.classpath.jar,create_new 独占创建,复用 argfile 的 LaunchArgumentFile RAII 所有者:写入失败、启动失败时立即删除,启动成功后由该进程的退出线程删除。不写 JDK 目录和安装目录。

manifest 规则:

  • 每个条目先按工作目录转成绝对路径(manifest 中的相对 URL 以 JAR 所在目录为基准,和命令行语义不同);空条目按启动器语义视为工作目录;./.. 按 Windows 词法规则折叠。
  • 盘符路径写成 file:/C:/...,UNC 写成 file:////host/share/...,\\?\C:\ 和 \\?\UNC\ 长路径前缀先还原。
  • 非 [A-Za-z0-9-._~/:] 字节一律按 UTF-8 百分号编码(空格、中文、#、%、+ 等)。manifest 因此是纯 ASCII,和 Windows 系统代码页无关。
  • 已存在的目录条目以 / 结尾,否则 JDK 会把它当 JAR 打开。
  • 每行 ≤ 72 字节,续行以一个空格开头,CRLF 换行,空行结束主段。
  • 只替换启动器最终生效的那个 -cp/-classpath(重复出现时取最后一个,且只看主类之前的启动器选项),JVM 选项、主类、程序参数不动。
  • 不能等价表达时保持原命令:lib/* 通配符、C:lib 这类依赖每盘当前目录的路径、\\.\ 设备路径、-jar 启动。

改动明细

  • rust/lithe-core/src/execution/classpath_jar.rs(新增):plan_classpath_jar_launch、ClasspathJarRequest、ClasspathJarPlan、ClasspathStyle,包括路径绝对化、file: URL 编码、manifest 72 字节折行,以及 8 个单元测试。
  • rust/lithe-core/src/execution/launch_command.rs:COMMAND_LINE_MARGIN 和 command_line_length 改为 pub(super),供 JDK 8 规划复用同一长度预算;模块文档补充 JDK 8 走向。逻辑无变化。
  • rust/lithe-core/src/execution/mod.rs:注册新模块并导出新 API。
  • windows/tauri/src-tauri/src/run/launch_arguments.rs:prepare 新增 working_directory 参数;确认 JDK < 9 时调用新规划并写入只含 manifest 的 JAR(zip crate 已是该 crate 依赖,条目不压缩);临时路径生成函数改名为 next_temporary_path(extension),argfile 与 JAR 共用;新增 3 个测试,原有 2 个测试适配新签名。
  • windows/tauri/src-tauri/src/run.rs:把 working_directory 传给参数规划;目录探测、JDK release 读取、临时文件写入、进程创建及终止移到后台阻塞任务。按窗口/会话登记独立启动请求,停止会使待启动请求失效,重新运行会替换旧请求,准备完成后检查归属并原子发布进程;旧 executionId 的停止请求不能取消新执行。新增 4 个无等待生命周期测试,覆盖停止、重启乱序、多窗口与相同 executionId、所有者清理。
  • scripts/worktree-resources.json、scripts/reuse-worktree-resources.mjs、scripts/test-reuse-worktree-resources.mjs、docs/ci-builds.md:把临时 argfile 与 classpath JAR 登记为不可跨工作树复用的进程私有资源,复用脚本显式拒绝,并测试列表排除与复制拒绝。
  • Agent Note 与功能矩阵补充后台准备、停止/重启归属和临时文件生命周期;矩阵继续保留 Windows 实机验证 pending。
  • shared/contracts/rust-core-api.md:记录 plan_classpath_jar_launch 的契约(仅 Rust API,暂无 JSON 命令及原因)。
  • .agents/notes/implemented/bug-fix/2026-09-20-oversized-java-launch-command.md:新增 JDK 8 classpath JAR 决策、规则、代价(java.class.path 只剩 JAR)、被否方案(所有 JDK 都用 JAR、相对 URL、环境变量)、未覆盖路径和验证命令。
  • shared/platform-feature-matrix.json:run-java-long-classpath 行的 Windows evidence 加入新文件,verification 补充 JDK 8/JDK 17 对照验收步骤;状态仍为 pending(未做 Windows 实机验证)。
  • docs/development/platform-parity-matrix.md、.csv:由生成脚本重新生成。

覆盖范围

  • 已覆盖:Windows 普通 Java Run(run_start_process,包括 main 运行和使用同一启动入口的步骤)。
  • 未覆盖(本 PR 不扩展):
    • 调试:Java 测试调试由 Java Debug Server 自己拉起 JVM,不经过该路径。
    • Maven 目标:classpath 在 Maven 进程内组装,本来不受 Windows 命令行限制。
    • 集成终端里手动执行的命令。
    • macOS:ARG_MAX 远大于 Windows 上限,JDK 8 直接启动不会超限,所以不接入,也没有新增 JSON 命令。
  • 已知代价:JDK 8 缩短后 System.getProperty("java.class.path") 只返回临时 JAR;只经类加载器加载类的代码不受影响,个别直接解析该属性扫描类路径的工具可能表现不同。只有在原本就会因超长而启动失败时才会触发。

最新修复验证(94efa552,Linux)

  • cargo check --manifest-path windows/tauri/src-tauri/Cargo.toml --target x86_64-pc-windows-gnu --tests:通过,包含 Windows 宿主与新增测试的交叉编译检查。
  • 新增 4 个生命周期测试:从当前源码及测试原样提取到临时无依赖 harness,由仓库 run-rust-tests-with-timing.mjs 逐例执行,全部通过,耗时 7–15ms。HTML/JUnit 已生成;此隔离测试不等同于 Windows 完整宿主运行。
  • node --test scripts/test-reuse-worktree-resources.mjs:通过,包含新增 Java 临时文件排除测试与原有资源复用场景。
  • Rust 格式、测试稳定性、Agent Notes、Windows 架构边界、发行目录只读边界、功能矩阵及 PR base/head 变更门禁检查通过。
  • Linux 未启动 Windows 应用;完整 Windows 宿主测试由此提交触发的 CI 执行,Windows 实机 JDK 8/JDK 17 对照验收仍待完成。

原实现验证

本机为 macOS(arm64),以下命令实际运行:

  • cargo test --manifest-path rust/Cargo.toml -p lithe-core --lib:754 passed。其中 execution:: 19 个(新增 8 个 classpath_jar 用例,覆盖 JDK 8 触发、JDK 9+/未知版本/短命令/非 Java 不变、空格/中文/#%+ 编码、目录 /、相对/UNC/空条目/\\?\ 前缀、72 字节折行、只替换生效的 -cp、通配符/-jar/设备路径不改写、POSIX 分隔符)。
  • cargo test --manifest-path windows/tauri/src-tauri/Cargo.toml:199 passed,5 failed。这 5 个(debug::tests::derives_adapter_identifier_from_the_executable_name、project_window_registry::tests::follows_directory_links_without_folding_case_sensitive_names、run::tests::{workspace_relative_paths_use_forward_slashes, write_generated_creates_lithe_documents, toolchain_probe_captures_both_version_streams})在未改动的 origin/preview 上于 macOS 同样失败,属于依赖 Windows 路径语义的既有用例,与本 PR 无关。新增/修改的 5 个 run::launch_arguments 用例全部通过:JDK 8 生成 JAR 于临时目录、manifest 内容与目录 /、释放后删除;JDK 17 仍用 argfile;版本未知不写文件。
  • node .agents/skills/write-stable-tests/scripts/run-rust-tests-with-timing.mjs --manifest windows/tauri/src-tauri/Cargo.toml --keep-going:逐用例计时,变更用例最慢 17ms,失败项同上 5 个。
  • 本地真实 JDK 8 端到端(临时测试,未提交):用 Zulu 1.8.0_492 运行 prepare 生成的 -cp <classpath.jar> demo.Main,类路径含“示例 classes”目录、“lib dir/依赖 a#b%c.jar”以及 700 个不存在的 JAR;JVM 成功加载主类和依赖并输出预期内容,JAR 随后被删除。
  • ./scripts/verify-rust-core-comments.sh:通过
  • ./.agents/skills/write-stable-tests/scripts/verify-test-stability.sh:通过
  • ./scripts/verify-runtime-bundle-immutability.sh:通过
  • ./scripts/verify-windows-boundaries.sh:通过
  • ./scripts/verify-shared-contracts.sh:通过
  • ./scripts/verify-agent-notes.sh:通过
  • ./scripts/verify-platform-feature-matrix-change.sh origin/preview HEAD、./scripts/verify-platform-feature-matrix.sh:通过
  • cargo fmt --check(两个 crate),以及 cargo clippy --all-targets(lithe-core、src-tauri):新增代码无告警
  • ./scripts/verify-rust-core.sh:失败于 scripts/test-rust-core-comments.mjs 的 8 个用例,原因是本机沙箱中 /usr/bin/ruby 找不到 uname,与本改动无关;其中的注释校验步骤单独运行通过。

CI(Windows runner):

  • Test Windows Rust implementation 首次运行失败,失败项是与本 PR 无关的 lithe_core::tests::git::git_write_executes_checkout_preflight_clone_and_validation:它在默认 15s 预算下跑了 21047ms;重跑后同一用例 3058ms 通过,属于 runner 波动。重跑后该 job 通过,Windows 上 8 个 classpath_jar 用例和 6 个 run::launch_arguments 用例(含 cfg(windows) 的 ClasspathStyle::Windows 宿主路径与 native_encoding_handles_chinese_and_rejects_lossy_paths)全部通过。本地 macOS 失败的 5 个既有 Windows 路径用例在 Windows CI 上也通过。
  • 其余 macOS/Windows CI 检查全部通过。

未能验证:

  • 未做 Windows 实机产品验收(没有使用虚拟机)。需要维护者按矩阵 verification 步骤操作:用 JDK 8 与 JDK 17 运行同一个大型项目做对照,覆盖含空格和中文的盘符路径,并确认安装目录与 JDK 目录不新增文件。矩阵状态因此保持 pending。

🤖 Generated with Claude Code

1lck and others added 2 commits October 1, 2026 15:45
JDK 8 不支持 @argfile,Windows 普通 Run 遇到超长 classpath 时
CreateProcessW 直接失败。现在已确认 JDK < 9 时由 Core 规划一个只含
META-INF/MANIFEST.MF 的临时 classpath JAR(Class-Path 写绝对、
百分号编码的 file: URL,目录以 / 结尾,72 字节折行),宿主写到
temp_dir()/lithe-run 并沿用 argfile 的独占创建与 RAII 清理。
JDK 9+ 仍走 argfile,版本未知保持原命令。

Co-Authored-By: Claude <noreply@anthropic.com>
自审发现 \\?\C:\... 和 \\?\UNC\... 形式的条目或工作目录会被当成主机名
为 "?" 的 UNC 路径,生成错误的 file: URL。现在先还原为普通盘符/UNC
路径;其他 \\?\ 与 \\.\ 设备路径无法表示为 file: URL,保持原命令启动。

Co-Authored-By: Claude <noreply@anthropic.com>
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Oct 1, 2026
1lck added 2 commits October 1, 2026 09:26
Guard pending launches against stop/restart races, preserve execution and window ownership, and clean reservations on failure. Register per-execution Java files as non-reusable resources with tests and lifecycle documentation.
@1lck
1lck merged commit 18efd31 into preview Oct 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant