fix(run): Shorten java command lines too long for the OS (#28) (#45)

Root cause: every java launch inlined the whole classpath into one -cp argument with no "Shorten command line", so long classpaths exceeded CreateProcessW's 32767-char limit (os error 206) before the JVM started.
Fix (IDEA's auto "Shorten command line"): ide-run rewrites a too-long java command line at spawn time — @argfile on JDK 9+ with ASCII paths, otherwise a MANIFEST pathing jar (percent-encoded file: URLs, 72-byte lines); temp files are removed when the process exits. Debug passes the same choice to java-debug's shortenCommandLine.
Siblings covered: Application/Spring Boot, test classpath, Kotlin main, explicit java -cp configs, profiler, PTY launches, Debug.
Verified: unit tests, pathing jar loaded by real JDK 8/11/25, ProcessManager integration test with 3000 entries on JDK 8/17/25. Not verified on Windows.

Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-10-27 10:13:31 +08:00
1 джерело de84a643aa
коміт 1127263ea8
12 files changed
+647 -2

No files matched your search

+5
Переглянути файл
@@ -2810,6 +2810,11 @@ export type JavaLaunchInfo = {
javaHome: IpcPath | null,
/** The `java` launcher to use (filled in by the caller from the installed JDKs). */
javaExec: string | null,
/**
* How a launcher that builds the command line itself must shorten it (java-debug's
* `shortenCommandLine`: `none` | `jarmanifest` | `argfile`; filled in by the caller).
*/
shortenCommandLine: string | null,
/** First compile errors of the workspace when the build had errors. */
errors: JavaCompileError[],
/** All compile errors (`errors` is capped). */
+12
Переглянути файл
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RunConfig } from "@/platform/ipc";
const invoked: Array<[string, unknown[]]> = [];
let shortenCommandLine: string | null = null;
vi.mock("@/platform/ipc", async (orig) => {
const real = await orig<typeof import("@/platform/ipc")>();
const ok = (data: unknown) => Promise.resolve({ status: "ok", data });
@@ -18,6 +19,7 @@ vi.mock("@/platform/ipc", async (orig) => {
modulePaths: [],
javaHome: "/jdk21",
javaExec: "/jdk21/bin/java",
shortenCommandLine,
errors: [],
errorCount: 0,
hints: [],
@@ -49,6 +51,7 @@ const app: RunConfig = {
describe("Java debugging (java-debug through jdtls)", () => {
beforeEach(() => {
invoked.length = 0;
shortenCommandLine = null;
});
it("launches Application configs with the resolved classpath on jdtls' debug server", async () => {
@@ -68,9 +71,18 @@ describe("Java debugging (java-debug through jdtls)", () => {
vmArgs: "-Xmx1g",
args: '--port "8081 x"',
console: "internalConsole",
shortenCommandLine: "none",
});
});
it("lets java-debug shorten a classpath too long for the OS, as Run does", async () => {
for (const how of ["jarmanifest", "argfile"]) {
shortenCommandLine = how;
const l = await javaLaunchConfig(app, "/p");
expect(JSON.parse(l.extraJson!).shortenCommandLine).toBe(how);
}
});
it("attaches to a JDWP agent", async () => {
const l = await javaAttachConfig({ name: "mvn test", module: "", hostName: "127.0.0.1", port: 5005 }, "/p");
expect(l).toMatchObject({ request: "attach", cwd: "/p" });
+2
Переглянути файл
@@ -86,6 +86,8 @@ export async function javaLaunchConfig(rc: RunConfig, root: string): Promise<Lau
javaExec: resolved.command === "java" ? undefined : resolved.command,
vmArgs: joinJavaArgs(parsed.vmArgs),
args: joinJavaArgs(parsed.programArgs),
// java-debug spawns the JVM itself: it must shorten a classpath too long for the OS the same way Run does.
shortenCommandLine: info.shortenCommandLine ?? "none",
console: "internalConsole",
stopOnEntry: false,
}),
+2
Переглянути файл
@@ -179,6 +179,7 @@ describe("java compile errors", () => {
modulePaths: [],
javaHome: null,
javaExec: null,
shortenCommandLine: null,
};
it("lists the first errors, counts the rest and adds hints", () => {
@@ -204,6 +205,7 @@ describe("java command lines", () => {
modulePaths: [],
javaHome: "/jdk21",
javaExec: "/jdk21/bin/java",
shortenCommandLine: "none",
errors: [],
errorCount: 0,
hints: [],