дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
Kotlin line breakpoints never verified through java-debug because jdtls cannot resolve classes for .kt files. Kotlin Application configurations and JDWP attach for modules with Kotlin sources now use the official Kotlin Debug Adapter (installable from the debugger prompt, running on the language-server JDK); pure Java keeps java-debug. The adapter resolves classes from source paths, so sources whose directories don't match their package are mirrored package-aligned outside the project (symlinks, copies where links fail) and paths are mapped back for breakpoints and stack frames; files that can't be mirrored are skipped rather than failing the session. Kotlin applications start with JDWP suspend=y and a session-owned JDWP relay holds the adapter's early resume until attach completes, so breakpoints are installed before the program runs. Limitations (docs/kotlin-debugging.md): @file:JvmName facades are not resolved; package/source-root changes need a new session. Not verified on a real Windows machine. Fixes #374
98 рядки
4.7 KiB
TypeScript
98 рядки
4.7 KiB
TypeScript
/**
|
|
* Debugging Maven / Gradle run configurations (IDEA debugs those too): the
|
|
* build tool starts the JVM with a JDWP agent that waits for a debugger
|
|
* (`spring-boot:run` → `-Dspring-boot.run.jvmArguments`, Surefire →
|
|
* `-Dmaven.surefire.debug`, Gradle → `--debug-jvm`); when the agent reports
|
|
* its port, the Java debugger attaches. Output and the test tree stay in the Run tab.
|
|
* Maven goals that run the application inside Maven's JVM (`tomcat7:run`, `jetty:run`,
|
|
* `exec:java`) get the agent through `MAVEN_OPTS`; any other configuration set to the Java
|
|
* debugger (a script such as `catalina.sh run`) through `JAVA_TOOL_OPTIONS`, which every JVM reads.
|
|
*/
|
|
import { executeAction } from "@/platform/actions";
|
|
import type { RunConfig } from "@/platform/ipc";
|
|
import { isJvmApplication, JDWP_AGENT, jdwpListeningPort } from "@/platform/javaLaunch";
|
|
import { startRun, useRun } from "./store";
|
|
|
|
const isMavenTestGoal = (a: string) =>
|
|
/^(test|verify|integration-test|surefire:test|failsafe:integration-test)$/.test(a);
|
|
const isGradleJvmTask = (a: string) => /(^|:)(test|bootRun|run|integrationTest|check)$/.test(a);
|
|
/** A plugin goal that runs the application in Maven's own JVM (`tomcat7:run`, `jetty:run-war`, `exec:java`). */
|
|
const isMavenInProcessGoal = (a: string) => /^[\w.-]+(:[\w.-]+)*:(run|run-war|run-exploded|java)$/.test(a);
|
|
const JVM_ADAPTERS = ["java-debug", "kotlin-debug-adapter"];
|
|
|
|
function withOption(env: Record<string, string>, key: string, value: string): Record<string, string> {
|
|
const current = env[key]?.trim();
|
|
return { ...env, [key]: current ? `${current} ${value}` : value };
|
|
}
|
|
|
|
function withProperty(args: readonly string[], key: string, value: string): string[] {
|
|
const prefix = `-D${key}=`;
|
|
const i = args.findIndex((a) => a.startsWith(prefix));
|
|
if (i < 0) return [...args, prefix + value];
|
|
const current = args[i]!.slice(prefix.length).trim();
|
|
const out = [...args];
|
|
out[i] = prefix + (current ? `${current} ${value}` : value);
|
|
return out;
|
|
}
|
|
|
|
/** The configuration with a waiting JDWP agent, or null when it starts no debuggable JVM. */
|
|
export function jdwpLaunch(config: RunConfig): RunConfig | null {
|
|
if (config.kind === "tomcat") return { ...config, params: { ...config.params, tomcatDebug: "true" } };
|
|
const args = config.args;
|
|
// A single source file (`java Hi.java`): the agent goes before the file.
|
|
if (config.kind === "java-file") return { ...config, args: [JDWP_AGENT, ...args] };
|
|
if (config.kind === "maven" || /(^|\/)mvnw?(\.cmd)?$/.test(config.command)) {
|
|
if (args.some((a) => a === "spring-boot:run" || /:spring-boot-maven-plugin:[^:]*:run$/.test(a)))
|
|
return { ...config, args: withProperty(args, "spring-boot.run.jvmArguments", JDWP_AGENT) };
|
|
if (config.isTest || args.some(isMavenTestGoal)) {
|
|
const failsafe = args.some((a) => /integration-test|verify|failsafe/.test(a));
|
|
let out = withProperty(args, "maven.surefire.debug", JDWP_AGENT);
|
|
if (failsafe) out = withProperty(out, "maven.failsafe.debug", JDWP_AGENT);
|
|
return { ...config, args: out };
|
|
}
|
|
if (JVM_ADAPTERS.includes(config.debugAdapter ?? "") || args.some(isMavenInProcessGoal))
|
|
return { ...config, env: withOption(config.env, "MAVEN_OPTS", JDWP_AGENT) };
|
|
return null;
|
|
}
|
|
if (config.kind === "gradle" || /(^|\/)gradlew?(\.bat)?$/.test(config.command)) {
|
|
if (!args.some(isGradleJvmTask)) return null;
|
|
return args.includes("--debug-jvm") ? config : { ...config, args: [...args, "--debug-jvm"] };
|
|
}
|
|
// Application / Spring Boot configurations are launched by java-debug itself.
|
|
if (JVM_ADAPTERS.includes(config.debugAdapter ?? "") && !isJvmApplication(config))
|
|
return { ...config, env: withOption(config.env, "JAVA_TOOL_OPTIONS", JDWP_AGENT) };
|
|
return null;
|
|
}
|
|
|
|
/** Runs `config` with a JDWP agent and attaches the Java debugger to every JVM it starts. */
|
|
export async function debugWithAgent(config: RunConfig): Promise<boolean> {
|
|
const launch = jdwpLaunch(config);
|
|
if (!launch) return false;
|
|
let sessionId: string | null = null;
|
|
const waiting: number[] = [];
|
|
const attach = (port: number) => {
|
|
const runId = useRun.getState().sessions.find((s) => s.id === sessionId)?.runId ?? null;
|
|
void executeAction("debug.attach", {
|
|
name: config.name,
|
|
module: config.cwd ?? "",
|
|
hostName: "127.0.0.1",
|
|
port,
|
|
process: runId,
|
|
runConfig: config,
|
|
});
|
|
};
|
|
const session = await startRun(config, undefined, undefined, {
|
|
launch,
|
|
onLine: (line) => {
|
|
const port = jdwpListeningPort(line);
|
|
if (port === null) return;
|
|
if (sessionId) attach(port);
|
|
else waiting.push(port);
|
|
},
|
|
});
|
|
if (!session) return true;
|
|
sessionId = session.id;
|
|
waiting.splice(0).forEach(attach);
|
|
return true;
|
|
}
|