mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
fix(java): resolve application classpaths independently of working directory (#493)
Run and Debug passed the configuration working directory to java_resolve_launch as the module, so changing the working directory also changed which jdtls project supplied the classpath and failed with "references non-existing project". Java application configurations now store their module at detection and creation, keep it when the working directory changes, and clear it when the main class changes. Older saved configurations resolve it from the main class source file. The Kotlin debugger check also uses the module directory. Fixes #435
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
* `${classpath}` (or `${testClasspath}`) and the `java` launcher are resolved
|
||||
* through jdtls when the configuration starts.
|
||||
*/
|
||||
import type { JavaLaunchInfo, RunConfig } from "@/platform/ipc";
|
||||
import { call, commands, type JavaLaunchInfo, type RunConfig } from "@/platform/ipc";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
export const CLASSPATH = "${classpath}";
|
||||
export const TEST_CLASSPATH = "${testClasspath}";
|
||||
@@ -103,3 +104,48 @@ export function jdwpListeningPort(line: string): number | null {
|
||||
const m = /Listening for transport dt_socket at address:\s*(?:[\w.[\]:-]*:)?(\d+)\s*$/.exec(line.trim());
|
||||
return m ? Number(m[1]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module directory (relative to the project root, `""` for the root) of a JVM
|
||||
* source file: the part before `src/{main,test}/…`, else the working directory
|
||||
* of the closest Maven / Gradle configuration.
|
||||
*/
|
||||
export function jvmModuleDir(relPath: string, configs: readonly RunConfig[] = []): string {
|
||||
const m = /^(?:(.*)\/)?src\/(?:main|test|[\w-]+)\/(?:java|kotlin|scala|groovy)\//.exec(relPath);
|
||||
if (m) return m[1] ?? "";
|
||||
const cwds = configs
|
||||
.filter((c) => c.kind === "maven" || c.kind === "gradle")
|
||||
.map((c) => (c.cwd ?? "").replace(/^\.\/?/, "").replace(/\/+$/, ""))
|
||||
.filter((cwd) => !cwd || relPath.startsWith(cwd + "/"))
|
||||
.sort((a, b) => b.length - a.length);
|
||||
return cwds[0] ?? "";
|
||||
}
|
||||
|
||||
/** Recover the classpath module of configurations saved before it was stored separately. */
|
||||
export async function findJavaModule(config: RunConfig): Promise<string | null> {
|
||||
if (config.module != null) return config.module;
|
||||
const main = parseJavaArgs(config.args).mainClass;
|
||||
if (!main) return null;
|
||||
const outer = main.split("$")[0]!;
|
||||
const simple = outer.slice(outer.lastIndexOf(".") + 1);
|
||||
const pkg = outer.slice(0, Math.max(0, outer.lastIndexOf(".")));
|
||||
const names = [`${simple}.java`, `${simple}.kt`, ...(simple.endsWith("Kt") ? [`${simple.slice(0, -2)}.kt`] : [])];
|
||||
const matches = (await Promise.all(names.map((name) => call(commands.findFiles(name, 1000)))))
|
||||
.flat()
|
||||
.filter((file) => names.includes(file.relPath.split("/").pop()!));
|
||||
const configs = await call(commands.runConfigs());
|
||||
const modules = new Set<string>();
|
||||
for (const file of matches) {
|
||||
const text = await call(commands.fsReadText(file.path));
|
||||
const sourcePackage = /^\s*package\s+([\w.]+)\s*[;\r\n]/m.exec(text)?.[1] ?? "";
|
||||
if (sourcePackage === pkg) modules.add(jvmModuleDir(file.relPath, configs));
|
||||
}
|
||||
return modules.size === 1 ? [...modules][0]! : null;
|
||||
}
|
||||
|
||||
export async function resolveJavaModule(config: RunConfig): Promise<string> {
|
||||
const module = await findJavaModule(config);
|
||||
if (module === null)
|
||||
throw new Error(tx`Cannot determine the module for main class '${parseJavaArgs(config.args).mainClass}'`);
|
||||
return module;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user