mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 00:07:29 +00:00
fix(java): use the project host classpath separator (#492)
This commit is contained in:
@@ -71,6 +71,8 @@ pub struct JavaLaunchInfo {
|
||||
pub project_root: Option<ide_core::IpcPath>,
|
||||
pub class_paths: Vec<String>,
|
||||
pub module_paths: Vec<String>,
|
||||
/// Path list separator of the host that runs the JVM.
|
||||
pub path_list_separator: String,
|
||||
/// JDK home jdtls compiles the project against.
|
||||
pub java_home: Option<ide_core::IpcPath>,
|
||||
/// The `java` launcher to use (filled in by the caller from the installed JDKs).
|
||||
@@ -351,6 +353,7 @@ impl LspManager {
|
||||
project_root: cp.project_root,
|
||||
class_paths: cp.class_paths,
|
||||
module_paths: cp.module_paths,
|
||||
path_list_separator: if cfg!(windows) { ";" } else { ":" }.into(),
|
||||
java_home: java_home.map(Into::into),
|
||||
java_exec: None,
|
||||
shorten_command_line: None,
|
||||
|
||||
@@ -3679,6 +3679,8 @@ export type JavaLaunchInfo = {
|
||||
projectRoot: IpcPath | null,
|
||||
classPaths: string[],
|
||||
modulePaths: string[],
|
||||
/** Path list separator of the host that runs the JVM. */
|
||||
pathListSeparator: string,
|
||||
/** JDK home jdtls compiles the project against. */
|
||||
javaHome: IpcPath | null,
|
||||
/** The `java` launcher to use (filled in by the caller from the installed JDKs). */
|
||||
|
||||
@@ -32,6 +32,7 @@ vi.mock("@/platform/ipc", async (orig) => {
|
||||
projectRoot: "/p",
|
||||
classPaths: ["/p/target/classes", "/m2/a.jar"],
|
||||
modulePaths: [],
|
||||
pathListSeparator: ":",
|
||||
javaHome: "/jdk21",
|
||||
javaExec: "/jdk21/bin/java",
|
||||
shortenCommandLine,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { JavaLaunchInfo, RunConfig } from "@/platform/ipc";
|
||||
import { resolveJavaCommand } from "@/platform/javaLaunch";
|
||||
|
||||
const ipc = vi.hoisted(() => ({
|
||||
javaResolveLaunch: vi.fn(),
|
||||
javaDebugServer: vi.fn(async () => ({ status: "ok", data: 5005 })),
|
||||
fsReadDir: vi.fn(async () => ({ status: "ok", data: [] })),
|
||||
}));
|
||||
vi.mock("@/platform/ipc", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/platform/ipc")>()),
|
||||
commands: ipc,
|
||||
}));
|
||||
|
||||
import { javaLaunchConfig } from "../java";
|
||||
|
||||
const config: RunConfig = {
|
||||
id: "main",
|
||||
name: "Main",
|
||||
kind: "application",
|
||||
command: "java",
|
||||
args: ["-cp", "${classpath}", "Main"],
|
||||
cwd: null,
|
||||
env: {},
|
||||
detected: false,
|
||||
isTest: false,
|
||||
debugAdapter: null,
|
||||
};
|
||||
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
const hosts = [
|
||||
{ client: "Win32", host: "Linux", separator: ":", paths: ["/p/classes", "/lib/a;b.jar"] },
|
||||
{ client: "MacIntel", host: "Windows", separator: ";", paths: ["C:\\p\\classes", "D:\\lib\\a.jar"] },
|
||||
{ client: "MacIntel", host: "Mac", separator: ":", paths: ["/p/classes", "/lib/a.jar"] },
|
||||
{ client: "Win32", host: "Windows", separator: ";", paths: ["C:\\p\\classes", "D:\\lib\\a.jar"] },
|
||||
{ client: "Linux x86_64", host: "Linux", separator: ":", paths: ["/p/classes", "/lib/a.jar"] },
|
||||
];
|
||||
|
||||
describe.each(hosts)("Java launch on $host from $client", ({ client, separator, paths }) => {
|
||||
const info: JavaLaunchInfo = {
|
||||
build: "succeeded",
|
||||
projectRoot: null,
|
||||
classPaths: paths,
|
||||
modulePaths: paths,
|
||||
pathListSeparator: separator,
|
||||
javaHome: null,
|
||||
javaExec: "java",
|
||||
shortenCommandLine: null,
|
||||
errors: [],
|
||||
errorCount: 0,
|
||||
hints: [],
|
||||
};
|
||||
|
||||
it.each(["${classpath}", "${testClasspath}"])("joins %s and module paths using the host separator", (placeholder) => {
|
||||
vi.spyOn(navigator, "platform", "get").mockReturnValue(client);
|
||||
const result = resolveJavaCommand({ ...config, args: ["-cp", placeholder, "Main"] }, info);
|
||||
expect(result.args).toEqual(["--module-path", paths.join(separator), "-cp", paths.join(separator), "Main"]);
|
||||
});
|
||||
|
||||
it.each(["java-debug", "kotlin-debug-adapter"])(
|
||||
"splits explicit classpaths for %s without damaging paths",
|
||||
async (adapter) => {
|
||||
vi.spyOn(navigator, "platform", "get").mockReturnValue(client);
|
||||
ipc.javaResolveLaunch.mockResolvedValue({ status: "ok", data: info });
|
||||
const result = await javaLaunchConfig(
|
||||
{ ...config, debugAdapter: adapter, args: ["-cp", paths.join(separator), "Main"] },
|
||||
"/p",
|
||||
);
|
||||
expect(JSON.parse(result.extraJson!).classPaths).toEqual(paths);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -107,7 +107,9 @@ export async function javaLaunchConfig(
|
||||
const resolved = resolveJavaCommand(rc, info);
|
||||
const kotlin = rc.debugAdapter === KOTLIN_ADAPTER || (await moduleHasKotlinSources(join(root, module)));
|
||||
preparation?.signal.throwIfAborted();
|
||||
const classPaths = parsed.explicitClasspath ? parsed.explicitClasspath.split(/[:;]/) : info.classPaths;
|
||||
const classPaths = parsed.explicitClasspath
|
||||
? parsed.explicitClasspath.split(info.pathListSeparator)
|
||||
: info.classPaths;
|
||||
if (kotlin) {
|
||||
return {
|
||||
name: rc.name,
|
||||
|
||||
@@ -194,6 +194,7 @@ describe("java compile errors", () => {
|
||||
projectRoot: "/p",
|
||||
classPaths: [],
|
||||
modulePaths: [],
|
||||
pathListSeparator: ":",
|
||||
javaHome: null,
|
||||
javaExec: null,
|
||||
shortenCommandLine: null,
|
||||
@@ -220,6 +221,7 @@ describe("java command lines", () => {
|
||||
projectRoot: "/p",
|
||||
classPaths: ["/p/target/classes", "/m2/a.jar"],
|
||||
modulePaths: [],
|
||||
pathListSeparator: ":",
|
||||
javaHome: "/jdk21",
|
||||
javaExec: "/jdk21/bin/java",
|
||||
shortenCommandLine: "none",
|
||||
@@ -245,10 +247,9 @@ describe("java command lines", () => {
|
||||
const r = resolveJavaCommand({ ...c, args: ["-Dx=1", ...c.args, "arg"] }, info);
|
||||
expect(r.command).toBe("/jdk21/bin/java");
|
||||
expect(r.args).toEqual(["-Dx=1", "-cp", "/p/target/classes:/m2/a.jar", "a.Main", "arg"]);
|
||||
expect(resolveJavaCommand(c, { ...info, modulePaths: ["/m/x.jar"] }, ";").args.slice(0, 2)).toEqual([
|
||||
"--module-path",
|
||||
"/m/x.jar",
|
||||
]);
|
||||
expect(
|
||||
resolveJavaCommand(c, { ...info, modulePaths: ["/m/x.jar"], pathListSeparator: ";" }).args.slice(0, 2),
|
||||
).toEqual(["--module-path", "/m/x.jar"]);
|
||||
});
|
||||
|
||||
it("joins arguments for java-debug and reads JDWP's port", () => {
|
||||
|
||||
@@ -38,6 +38,7 @@ const info: JavaLaunchInfo = {
|
||||
projectRoot: "/p",
|
||||
classPaths: ["/p/target/classes"],
|
||||
modulePaths: [],
|
||||
pathListSeparator: ":",
|
||||
javaHome: null,
|
||||
javaExec: "java",
|
||||
shortenCommandLine: null,
|
||||
@@ -50,7 +51,10 @@ beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
provideNotificationService({ notify });
|
||||
});
|
||||
afterEach(() => resetRuns());
|
||||
afterEach(() => {
|
||||
resetRuns();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("Stop during Java launch resolution", () => {
|
||||
it.each(["cancelled", "success"])("cancels the request and ignores a late %s response", async (response) => {
|
||||
@@ -81,3 +85,26 @@ describe("Stop during Java launch resolution", () => {
|
||||
expect(runtime(session.id)!.resolveRequest).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Java run host separator", () => {
|
||||
it.each([
|
||||
{ client: "Win32", separator: ":", paths: ["/p/classes", "/lib/a.jar"] },
|
||||
{ client: "MacIntel", separator: ";", paths: ["C:\\p\\classes", "D:\\lib\\a.jar"] },
|
||||
{ client: "MacIntel", separator: ":", paths: ["/p/classes", "/lib/a.jar"] },
|
||||
])("uses $separator from the host on $client", async ({ client, separator, paths }) => {
|
||||
vi.spyOn(navigator, "platform", "get").mockReturnValue(client);
|
||||
ipc.javaResolveLaunch.mockResolvedValue({
|
||||
status: "ok",
|
||||
data: { ...info, classPaths: paths, modulePaths: paths, pathListSeparator: separator },
|
||||
});
|
||||
ipc.runStart.mockResolvedValue({ status: "ok", data: 42 });
|
||||
await startRun(config);
|
||||
expect(ipc.runStart.mock.calls[0]![0].args).toEqual([
|
||||
"--module-path",
|
||||
paths.join(separator),
|
||||
"-cp",
|
||||
paths.join(separator),
|
||||
"Main",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -691,8 +691,6 @@ function revealSession(sessionId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const pathSeparator = () => (typeof navigator !== "undefined" && /win/i.test(navigator.platform) ? ";" : ":");
|
||||
|
||||
/**
|
||||
* Builds the module (jdtls workspace build) and resolves `${classpath}` and the
|
||||
* project JDK of a Java application configuration. Null (and a console message) on failure.
|
||||
@@ -737,7 +735,7 @@ async function resolveJvmApplication(sessionId: string, config: RunConfig): Prom
|
||||
say("The classpath is empty: is the module imported by the Java language server?\n", "stderr");
|
||||
return null;
|
||||
}
|
||||
return resolveJavaCommand(config, info, pathSeparator());
|
||||
return resolveJavaCommand(config, info);
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
if (request.cancelled || isCancelled(e)) {
|
||||
|
||||
@@ -72,11 +72,11 @@ export function parseJavaArgs(args: readonly string[]): JavaCommandLine {
|
||||
}
|
||||
|
||||
/** The configuration with its placeholders and launcher resolved. */
|
||||
export function resolveJavaCommand(config: RunConfig, info: JavaLaunchInfo, pathSeparator = ":"): RunConfig {
|
||||
const cp = info.classPaths.join(pathSeparator);
|
||||
export function resolveJavaCommand(config: RunConfig, info: JavaLaunchInfo): RunConfig {
|
||||
const cp = info.classPaths.join(info.pathListSeparator);
|
||||
const args: string[] = [];
|
||||
if (info.modulePaths.length && !config.args.some((a) => a === "-p" || a === "--module-path"))
|
||||
args.push("--module-path", info.modulePaths.join(pathSeparator));
|
||||
args.push("--module-path", info.modulePaths.join(info.pathListSeparator));
|
||||
args.push(...config.args.map((a) => (a === CLASSPATH || a === TEST_CLASSPATH ? cp : a)));
|
||||
const command = config.command === "java" && info.javaExec ? info.javaExec : config.command;
|
||||
return { ...config, command, args };
|
||||
|
||||
Reference in New Issue
Block a user