From ee5d9121571c8ebe57a6159eabfc5758b817a211 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Thu, 5 Nov 2026 13:55:34 +0800 Subject: [PATCH] fix(java): run Application and Spring Boot configs from the project root (#1475) Configurations created for a main class, and the Spring Boot applications detected in a module, set their working directory to the module directory. A relative path in the VM options (such as --module-path=app/server/target/dependency) then resolved against the module and the JVM failed to start. They now leave the working directory empty, so the process starts in the project root, which is what an empty working directory means elsewhere. The module is still stored separately and keeps supplying the classpath and the debugger's sources. Fixes #1197 --- crates/ide-run/src/detect.rs | 5 ++--- crates/ide-run/tests/detect.rs | 8 ++++---- src/features/run/application.ts | 2 +- src/features/run/java.test.ts | 3 ++- src/features/run/producers.test.ts | 7 ++++--- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/ide-run/src/detect.rs b/crates/ide-run/src/detect.rs index acb7e87dc..b7bc88027 100644 --- a/crates/ide-run/src/detect.rs +++ b/crates/ide-run/src/detect.rs @@ -564,9 +564,8 @@ fn spring_boot_apps(root: &Path, build: &Path, out: &mut Vec) { Some(m) => format!("application:{m}:{class}"), None => format!("application:{class}"), }; - let mut app = config(id, simple, "spring-boot", "java", &["-cp", "${classpath}", &class]) - .in_dir(module_rel.as_ref().map(PathBuf::from)) - .debug("java-debug"); + let mut app = + config(id, simple, "spring-boot", "java", &["-cp", "${classpath}", &class]).debug("java-debug"); app.extras.module = Some(module_rel.clone().unwrap_or_default()); out.push(app); } diff --git a/crates/ide-run/tests/detect.rs b/crates/ide-run/tests/detect.rs index 80e54c385..aebe520c2 100644 --- a/crates/ide-run/tests/detect.rs +++ b/crates/ide-run/tests/detect.rs @@ -215,7 +215,7 @@ fn builds_nested_in_a_folder_of_repositories() { assert_eq!(app.name, "DromaraApplication"); assert_eq!(app.kind, "spring-boot"); assert_eq!(cmdline(app), "java -cp ${classpath} org.dromara.DromaraApplication"); - assert_eq!(app.cwd.as_deref(), Some(Path::new("RuoYi-Vue-Plus/ruoyi-admin"))); + assert_eq!(app.cwd, None); assert_eq!(app.extras.module.as_deref(), Some("RuoYi-Vue-Plus/ruoyi-admin")); assert_eq!(app.debug_adapter.as_deref(), Some("java-debug")); let package = find(&c, "maven:package:RuoYi-Vue-Plus"); @@ -258,7 +258,7 @@ fn spring_boot_application_of_a_repository_nested_one_or_two_levels_down() { let module = format!("{prefix}ruoyi-ai/ruoyi-admin"); let app = find(&c, &format!("application:{module}:org.ruoyi.RuoYiAIApplication")); assert_eq!((app.name.as_str(), app.kind.as_str()), ("RuoYiAIApplication", "spring-boot"), "{prefix}"); - assert_eq!(app.cwd.as_deref(), Some(Path::new(&module))); + assert_eq!((app.cwd.as_deref(), app.extras.module.as_deref()), (None, Some(module.as_str())), "{prefix}"); assert!(ids(&c).contains(&format!("maven:spring-boot:run:{prefix}ruoyi-ai").as_str()), "{prefix}"); } } @@ -295,9 +295,9 @@ fn nested_builds_of_every_kind_and_depth() { } assert!(find(&c, "gradle:test:services/api").is_test); let api = find(&c, "application:services/api/app:com.acme.ApiKt"); - assert_eq!(api.cwd.as_deref(), Some(Path::new("services/api/app"))); + assert_eq!((api.cwd.as_deref(), api.extras.module.as_deref()), (None, Some("services/api/app"))); let billing = find(&c, "application:billing:Billing"); - assert_eq!(billing.cwd.as_deref(), Some(Path::new("billing"))); + assert_eq!((billing.cwd.as_deref(), billing.extras.module.as_deref()), (None, Some("billing"))); let builds: Vec<&str> = ids(&c).into_iter().filter(|id| id.starts_with("maven:package") || id.starts_with("gradle:build")).collect(); assert_eq!(builds, ["gradle:build:services/api", "maven:package:billing"]); diff --git a/src/features/run/application.ts b/src/features/run/application.ts index b77cc1f00..8c9ae40c5 100644 --- a/src/features/run/application.ts +++ b/src/features/run/application.ts @@ -34,7 +34,7 @@ export function applicationConfig( kind: target.spring ? "spring-boot" : "application", command: "java", args: ["-cp", isTestSource(relPath) ? TEST_CLASSPATH : CLASSPATH, target.className], - cwd: module || null, + cwd: null, module, env: {}, detected: true, diff --git a/src/features/run/java.test.ts b/src/features/run/java.test.ts index f74d3e7ec..5c74648ba 100644 --- a/src/features/run/java.test.ts +++ b/src/features/run/java.test.ts @@ -224,7 +224,8 @@ describe("application configurations", () => { }); expect(applicationConfig({ className: "a.Tool" }, "m/src/test/java/a/Tool.java")).toMatchObject({ kind: "application", - cwd: "m", + module: "m", + cwd: null, args: ["-cp", "${testClasspath}", "a.Tool"], }); }); diff --git a/src/features/run/producers.test.ts b/src/features/run/producers.test.ts index 21574434e..4c3812cf1 100644 --- a/src/features/run/producers.test.ts +++ b/src/features/run/producers.test.ts @@ -425,7 +425,7 @@ describe("single-file producers (scratches)", () => { }); expect(produceContext(input(), ALL)!.config!.kind).toBe("java-file"); const p = produceContext(input(["/work/billing", svc]), ALL)!; - expect(p.config).toMatchObject({ kind: "spring-boot", module: svc, cwd: svc }); + expect(p.config).toMatchObject({ kind: "spring-boot", module: svc, cwd: null }); expect(p.config!.args).toEqual(["-cp", "${classpath}", "com.example.ShopApplication"]); }); @@ -487,7 +487,8 @@ describe("single-file producers (scratches)", () => { kind: "spring-boot", name: "DromaraApplication", command: "java", - cwd: module, + module, + cwd: null, }); expect(p!.config!.args).toEqual(["-cp", "${classpath}", "org.dromara.DromaraApplication"]); } @@ -503,7 +504,7 @@ describe("single-file producers (scratches)", () => { it("Kotlin and test classes use the build that contains them", () => { const kt = "package demo\n\nfun main() {\n println(1)\n}\n"; const p = produceContext(at("backend/app/src/main/kotlin/demo/App.kt", nested("gradle", "backend"), kt, 3), ALL)!; - expect(p.config).toMatchObject({ kind: "application", cwd: "backend/app" }); + expect(p.config).toMatchObject({ kind: "application", module: "backend/app", cwd: null }); expect(p.config!.args.at(-1)).toBe("demo.AppKt"); const test = "package demo;\nclass AppTest {\n @Test\n void works() {\n }\n}\n"; const t = produceContext(