mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
The Gradle model came from a static parse of settings.gradle, which cannot follow includeFlat, projectDir assignments, computed include lists or included builds, so those modules were missing from the Gradle tool window, the Project view, indexing and run. Sync now runs an init script that prints every evaluated project (identity path, directory, build directory, source sets with test classification, Java version) without resolving dependencies; the static parser stays as the fallback before the first sync. Modules outside the root become content roots, the Project view lists the build's module group above the project folder, an unsynced Gradle build is imported once when opened, and staleness ignores comments and whitespace in build scripts. Application / Spring Boot configurations of a Gradle module run through a JavaExec task on the source set's runtime classpath by default (Settings › Build Tools › Gradle › Build and run using), with the debugger attached to the forked JVM. The build model reaches run and the Project view through a platform store rather than a cross-feature import. Fixes #737 Fixes #861 Co-authored-by: ntbang0901 <121003585+ntbang0901@users.noreply.github.com>
103 lines
5.1 KiB
TypeScript
103 lines
5.1 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";
|
|
import { DELEGATED_DEBUG, DELEGATED_MAIN } from "./gradleRun";
|
|
|
|
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)) {
|
|
// A delegated Application: its init script adds the agent (port 0) to the JavaExec JVM.
|
|
if (args.some((a) => a.startsWith(DELEGATED_MAIN)))
|
|
return args.includes(DELEGATED_DEBUG) ? config : { ...config, args: [...args, DELEGATED_DEBUG] };
|
|
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,
|
|
// The application's module (a delegated run's working directory is the Gradle build).
|
|
module: config.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;
|
|
}
|