Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Java 项目现在由项目 JDK 直接启动,JDT 解析出的运行时 classpat
`execution.planLaunchCommand` JSON 命令自动采用该策略,不需要 Windows 专属配置。Windows 集成终端对
`cmd.exe` 和 PowerShell 的超长多行输入改用临时脚本调用,脚本执行后自删除,终端关闭时也会清理未执行文件;这不修改系统
Shell 配置。同时启动失败的真实原因必须能显示出来,不能再被兜底文案吞掉。
JDK 8 不认识 argfile,Windows 普通 Run 改用 classpath JAR(只含 `META-INF/MANIFEST.MF`
的临时 JAR,靠 manifest 的 `Class-Path` 列出全部条目);Core 生成 manifest,宿主写入并沿用同一套临时文件生命周期(#955)。

## 问题

Expand Down Expand Up @@ -49,13 +51,29 @@ Shell 配置。同时启动失败的真实原因必须能显示出来,不能
不能因为 JDK 18+ 就写 UTF-8。Windows 宿主按实际 ANSI 代码页转换 Core 返回的
Unicode 文本,并回转校验,不能表示的字符报错,不允许替换成问号。
UTF-8 系统代码页才使用 UTF-8 字节;普通中文系统代码页使用对应的中文编码。
5. **失败必须可见。** 宿主的失败消息带上系统错误、可执行文件和命令长度;
5. **JDK 8 用 classpath JAR,规则同样在 Core。** 已确认 JDK 版本低于 9 时,Windows 宿主调用
`lithe_core::execution::plan_classpath_jar_launch`。它使用同一长度预算,只替换启动器
最终生效的那个 `-cp`/`-classpath` 值(多次出现时最后一个生效),值换成临时 JAR 路径;
JVM 选项、主类和程序参数不动。manifest 规则:
- 每个条目先按工作目录转成绝对路径,因为 manifest 里的相对 URL 以 JAR 所在目录为基准,
而命令行里的相对路径以工作目录为基准;空条目按启动器语义视为工作目录。
- 绝对路径转成 `file:` URL:盘符写成 `file:/C:/...`,UNC 写成 `file:////host/share/...`;
非 `[A-Za-z0-9-._~/:]` 的字节一律按 UTF-8 百分号编码(空格、中文、`#`、`%`、`+` 都包括在内)。
所以 manifest 是纯 ASCII,和 Windows 系统代码页无关,比 argfile 更不容易因中文路径失败。
- 目录条目必须以 `/` 结尾,否则类加载器把它当成 JAR 打开。目录判断是文件系统事实,
由宿主通过回调回答,Core 不读磁盘。
- 每行最多 72 字节,续行以一个空格开头,换行用 CRLF,最后用空行结束主段。
- 不能等价表达的情况保持原命令:`lib/*` 通配符(只在命令行展开)、`C:lib` 这类依赖
每盘当前目录的路径、`-jar` 启动(会忽略 `-cp`)、版本未知。
JAR 只含一个不压缩的 manifest 条目,写在 `temp_dir()/lithe-run/launch-<pid>-<n>.classpath.jar`,
和 argfile 共用 `create_new` 独占创建与 RAII 清理,绝不写进 JDK 或安装目录。
6. **失败必须可见。** 宿主的失败消息带上系统错误、可执行文件和命令长度;
前端把非 `Error` 的拒绝值也转成可读文本,并通过 `frontendTrace` 写日志。
6. **macOS 复用同一份规划。** macOS 组合根把 `RustCoreBridge` 注入
7. **macOS 复用同一份规划。** macOS 组合根把 `RustCoreBridge` 注入
`LitheExecutionModule.RunService` 的 `JavaLaunchArgumentPreparing` 端口。适配器读取 JDK
`release` 版本、调用 `execution.planLaunchCommand`、以 UTF-8 写入自己的临时文件,并把一个
lease 保留到 Java 进程退出;RunService 不直接操作文件系统。
7. **终端只在 IDE 输入边界做短调用转换。** Windows `cmd.exe` 和 PowerShell 的单行输入有独立长度上限。
8. **终端只在 IDE 输入边界做短调用转换。** Windows `cmd.exe` 和 PowerShell 的单行输入有独立长度上限。
终端连接收到超过 7000 个 UTF-16 单元且包含换行的文本时,在系统临时目录使用 `create_new` 创建 `.cmd` 或 `.ps1`,
把原文本写入脚本,再向 PTY 写入短的 `call "路径"` 或 `& '路径'`。短文本、仍处于编辑状态的超长单行文本、二进制输入、
WSL 和 Git Bash 不做转换,避免改变交互式 Shell 的解析语义。脚本自身负责正常执行后的删除,连接关闭或写入失败时由连接对象尽力删除剩余文件。
Expand All @@ -75,16 +93,30 @@ Shell 配置。同时启动失败的真实原因必须能显示出来,不能

- **改用 `CLASSPATH` 环境变量**:被否。Windows 对单个环境变量和整个环境块同样有
32767 的限制,只是把同一个上限换了个地方。
- **生成 pathing jar**(用 manifest 的 `Class-Path` 间接引用):暂不采用。它能兼容
JDK 8,但要处理相对 URL 编码和路径空格,复杂度明显高于 argfile。等真的出现
JDK 8 大工程需求再做。
- **所有 JDK 都用 pathing jar**(classpath JAR):被否。#955 出现 JDK 8 需求后,classpath JAR
只用于 JDK 8。JDK 9+ 继续用 argfile,因为 argfile 还能搬 `--module-path`,
也不会改变 `java.class.path` 系统属性(classpath JAR 下该属性只剩 JAR 本身,
依赖它扫描类路径的少数框架行为会变)。
- **JDK 8 用 `CLASSPATH` 环境变量**:被否,理由同上,环境块同样受 32767 限制。
- **manifest 写相对 URL**:被否。临时 JAR 和项目通常不在同一盘符,相对 URL 无法表达;
绝对 `file:` URL 对所有盘符和 UNC 路径都成立。
- **退回 Maven 启动**:被否。那会把
`.agents/notes/implemented/architecture/2026-09-18-java-project-build-and-launch-boundary.md`
里已经解决的 `ClassNotFoundException` 重新带回来。
- **只延长/忽略长度,靠用户手动缩短依赖**:被否。用户无法控制传递依赖的数量。
- **修改 PowerShell profile、注册表或全局环境变量**:被否。该问题只属于 Lithe 生成的输入,系统级修改会影响 IDE 之外的程序,
也不能可靠消除不同 Shell 的长度和解析差异。

Windows 的目录探测、JDK release 读取、临时文件写入和进程创建在后台阻塞任务中执行,
不能让慢速 UNC 路径冻结 UI。每次启动先登记独立的准备请求,停止会取消该请求,
重新运行会替换它;准备完成后必须在与停止共用的锁内检查归属,再发布进程。
例如 A 准备期间启动 B,即使 A 最后才完成,也必须释放 A 的临时文件而不启动它。
不能只把同步命令改成后台执行而省略归属检查,否则停止后旧进程可能重新出现。

临时 argfile 与 classpath JAR 在 `scripts/worktree-resources.json` 的
`java-launch-temporaries` 中登记为不可复用资源,复用脚本直接拒绝。它们属于单次
执行,没有构建身份 stamp,任何阶段都不能跨工作树复制,也不写发行目录。

## 后果

- 大型多模块 Maven 项目在 Windows 上可以启动,命令行只剩下一个 `@文件` 引用。
Expand All @@ -94,12 +126,18 @@ Shell 配置。同时启动失败的真实原因必须能显示出来,不能
- 每次执行用 `create_new` 独占创建一个参数文件,不按窗口或会话名复用。文件由
RAII 所有者管理(离开作用域时自动清理):写入或启动失败时删除,成功后交给
该进程的退出线程删除;旧执行的清理不能影响替代它的新执行。
JVM 已读取参数后,外部清理文件不影响该进程;后续执行会创建新文件。
- `@argfile` 需要 JDK 9 以上。JDK 8 上的超长 classpath 仍然无解,但那种情况
今天本来就会失败,所以不构成回退。
`@argfile` 在 JVM 启动时读取;classpath JAR 可能在后续类加载时仍被访问,
因此两者统一保留到进程退出,后续执行创建新文件。
- `@argfile` 需要 JDK 9 以上。JDK 8 的普通 Run 现在由 classpath JAR 覆盖。代价:
JDK 8 缩短后 `System.getProperty("java.class.path")` 只返回临时 JAR 路径;
只依赖类加载器的代码不受影响。JDK 8 的 `--module-path` 不存在,所以不需要处理。
macOS 的 `ARG_MAX` 远大于 Windows 上限,JDK 8 直接启动不会超限,因此 macOS 不调用
这条规划,也没有 JSON 命令。
- JDK 8 缩短不覆盖:Java 测试调试(Java Debug Server 自己拉起 JVM)、Maven 目标、
集成终端里手动执行的命令。它们都不经过 `run_start_process`。
- 参数文件使用 Windows 实际系统代码页,JDK 9-17 的中文路径也可在能够无损表示
它们的系统代码页下使用。不支持该字符的系统代码页仍会明确失败;pathing jar
可以作为后续兼容方案。不能把更改 `file.encoding` 当作启动器编码的修复。
它们的系统代码页下使用。不支持该字符的系统代码页仍会明确失败;JDK 9+ 是否也改用
classpath JAR 规避代码页问题,需要先权衡 `java.class.path` 语义变化,本次未做。不能把更改 `file.encoding` 当作启动器编码的修复。
- Windows 集成终端只为 `cmd.exe` 和 PowerShell 的超长多行文本创建一次性脚本;WSL、Git Bash 和普通交互输入保留原始写入。
这解决 IDE 生成命令的 Shell 输入上限,不改变系统 Shell 的全局限制。
- Java **测试**的调试仍由 Java Debug Server 自己拉起 JVM,它的
Expand All @@ -113,8 +151,12 @@ Shell 配置。同时启动失败的真实原因必须能显示出来,不能
以及可配置上限;`execution.planLaunchCommand` 复用同一规划器。
- macOS Swift:`swift build --target Lithe` 覆盖模块边界和组合根注入;临时文件 lease
在启动失败、停止和正常退出路径释放。
- Rust Core:`cargo test --manifest-path rust/Cargo.toml -p lithe-core --lib classpath_jar`
覆盖 JDK 8 触发、JDK 9+/未知版本/短命令保持直接启动、空格中文和保留字符的百分号编码、
目录结尾 `/`、相对/UNC/空条目、72 字节折行、只替换生效的 `-cp`,以及通配符和 `-jar` 不改写。
- Windows 宿主:`cargo test --manifest-path windows/tauri/src-tauri/Cargo.toml run::`
覆盖写入并删除 argfile、普通启动不写文件、失败消息包含系统原因。该 crate 需要
覆盖写入并删除 argfile、普通启动不写文件、失败消息包含系统原因;JDK 8 生成只含 manifest 的
临时 JAR、目录条目带 `/`、所有者释放后删除,JDK 9+ 仍走 argfile,版本未知不写文件。该 crate 需要
Windows 或具备 GTK 依赖的环境才能编译。
- Windows 终端 crate:`cargo test --manifest-path windows/tauri/crates/terminal/Cargo.toml` 覆盖短输入和单行输入不转换、
`cmd.exe`/PowerShell 脚本内容与自删除命令;`cargo check --target x86_64-pc-windows-msvc` 检查 Windows 条件编译。
Expand All @@ -123,7 +165,8 @@ Shell 配置。同时启动失败的真实原因必须能显示出来,不能

## 适用范围

- Rust Core:`rust/lithe-core/src/execution/launch_command.rs`
- Rust Core:`rust/lithe-core/src/execution/launch_command.rs`、
`rust/lithe-core/src/execution/classpath_jar.rs`
- Windows:`windows/tauri/src-tauri/src/run.rs`、
`windows/tauri/src-tauri/src/run/launch_arguments.rs`、
`windows/tauri/src/features/run/stores/run.store.ts`、
Expand Down
8 changes: 8 additions & 0 deletions docs/ci-builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ SHA-256;Cargo、SwiftPM 和 Bun 使用各自的 lockfile、版本与完整性

以下目录不应直接复制或跨工作树共享:

- Java 启动临时文件:`<system-temp>/lithe-run/launch-<pid>-<counter>.argfile`
和同目录的 `.classpath.jar` 由平台启动 adapter 为单次执行独占创建,包含该次
执行的绝对类路径、工作目录、JDK 版本及编码语义,没有可复用的版本、平台、架构
或工具链 identity stamp。准备失败、准备完成前已取消、启动失败或进程退出后由
所有者删除;系统临时目录由平台解析,不写安装包或 JDK,不影响签名或增量更新。
`excludedResources.java-launch-temporaries` 经复用脚本的排除路由直接拒绝,任何
复制阶段都不得共享;内容哈希相同也不能转移进程所有权。

- Agent CLI 的用户级安装与下载缓存:npm 的 global prefix/cache、Homebrew 的
Cellar/Caskroom/cache、用户目录下 `.local/share/claude/versions`。它们由运行时
`PATH` 和原安装器决定,不属于工作树;包版本、平台与架构由原安装器校验,
Expand Down
2 changes: 1 addition & 1 deletion docs/development/platform-parity-matrix.csv
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ lsp-navigation-edits,Java,语言服务,跳转、引用、语义标记与编辑,
run-discovery,运行与调试,运行配置,入口点与运行配置发现,已实现,待验证,已实现,待验证,Run,验证 Spring Boot、Java、Maven、Gradle、npm、Cargo、Go、Python 和 Docker Compose 入口识别;多模块项目把服务工作目录改成模块目录后,服务仍留在列表中并以该目录启动;填写不存在的目录或 ${workspaceFolder} 等变量时,保存被拒绝并提示原因;在 Windows 确认重新扫描服务使用双箭头刷新图标。,,macos/Sources/Lithe/Views/Run; shared/fixtures/run-configuration,windows/tauri/src/features/run; shared/fixtures/run-configuration
run-save-toolchain,运行与调试,运行配置,保存前同步与工具链解析,已实现,待验证,已实现,待验证,Run,修改未保存文件后运行,验证同步、JDK/Maven 选择和版本不匹配诊断。,,macos/Sources/Lithe/Views/Run; macos/Sources/Lithe/Services/Java,windows/tauri/src/features/run; windows/tauri/src/features/maven
run-java-test,运行与调试,运行配置,Java main 与测试运行,已实现,待验证,已实现,待验证,Run,运行 main、单测试和测试类,验证参数、输出、失败状态和终端策略。 Windows Run 运行输出使用 Geist Mono 显示 ====、==>、=>、!=、>=,确认禁用连字后逐字符显示且复制文本与原始输出一致;检查有输出与空态、ANSI 颜色、自动换行切换及横向滚动。,,macos/Sources/Lithe/Views/Run; shared/fixtures/debug,windows/tauri/src/features/run; shared/fixtures/debug; windows/tauri/src/features/run/components/run-pane.tsx; windows/tauri/src/features/run/components/run-output-text.tsx
run-java-long-classpath,运行与调试,运行配置,超长 Java 类路径自动缩短,已实现,待验证,已实现,待验证,Run,使用包含大量依赖的 Java 项目运行 main,确认超长类路径自动写入参数文件、进程可启动且参数文件在退出后清理。,,macos/Sources/Lithe/Platform/MacOS/RunConfiguration/MacJavaLaunchArgumentPreparer.swift; macos/Sources/LitheExecutionModule/Services/RunService.swift; rust/lithe-core/src/execution/launch_command.rs,windows/tauri/src-tauri/src/run/launch_arguments.rs; rust/lithe-core/src/execution/launch_command.rs
run-java-long-classpath,运行与调试,运行配置,超长 Java 类路径自动缩短,已实现,待验证,已实现,待验证,Run,使用包含大量依赖的 Java 项目运行 main,确认超长类路径自动写入参数文件、进程可启动且参数文件在退出后清理。 Windows 上分别用 JDK 8 和 JDK 17 运行同一个依赖很多、路径含空格和中文的项目 main:JDK 8 应在临时目录生成只含 META-INF/MANIFEST.MF 的 .classpath.jar 并以 -cp 引用,JDK 17 仍使用 @argfile;两者都能启动,进程退出或启动失败后临时文件被删除,安装目录与 JDK 目录不新增文件。 在慢速网络盘 classpath 准备期间确认工作台可交互;停止或重新运行后,旧准备结果不得启动进程,旧 executionId 的停止请求不得影响新执行。,,macos/Sources/Lithe/Platform/MacOS/RunConfiguration/MacJavaLaunchArgumentPreparer.swift; macos/Sources/LitheExecutionModule/Services/RunService.swift; rust/lithe-core/src/execution/launch_command.rs,windows/tauri/src-tauri/src/run/launch_arguments.rs; rust/lithe-core/src/execution/launch_command.rs; rust/lithe-core/src/execution/classpath_jar.rs; windows/tauri/src-tauri/src/run.rs
debug-breakpoints,运行与调试,调试器,启动调试与断点,已实现,待验证,部分实现,待验证,Debug,设置、命中、禁用和重新定位断点,确认调试会话生命周期。,Windows 真实调试产品链路仍在 #466 跟进。,macos/Sources/Lithe/Views/Debug; shared/fixtures/debug,windows/tauri/src/features/debugger; shared/fixtures/debug
debug-state,运行与调试,调试器,变量、异常与断开策略,已实现,待验证,部分实现,待验证,Debug,验证变量分页、异常信息、step filters、暂停/继续和 disconnect policy。,Windows 真实调试产品链路仍在 #466 跟进。,macos/Sources/Lithe/Views/Debug; shared/fixtures/debug,windows/tauri/src/features/debugger; shared/fixtures/debug
terminal-shell,工作台,终端,Shell 发现与配置,已实现,待验证,已实现,待验证,Terminal,验证设置页默认 Shell 选项与终端「新建终端」菜单检测到的 Shell 一致、可选 Git Bash 并生效,以及环境变量、工作目录和不可用 Shell 的提示。,,macos/Sources/Lithe/Views/Terminal; macos/Sources/Lithe/Views/App/SettingsView.swift; macos/Sources/Lithe/Services,windows/tauri/src/features/terminal; windows/tauri/src/features/settings/components/macos-settings-panels.tsx; windows/tauri/src/features/settings/lib/default-shell-options.ts; windows/tauri/src-tauri
Expand Down
Loading
Loading