From 72b39d819f4c2ad7326f3d9a375994d68e740e3b Mon Sep 17 00:00:00 2001 From: Qing Date: Sun, 8 Nov 2026 00:47:41 +0800 Subject: [PATCH] fix(run): give each Maven module its own Spring Boot scan budget (#2021) Spring Boot detection shared one 400-file scan budget across all modules, so a large common library before the application modules used it up and later apps' launch configurations were never detected. Each module applying the Spring Boot plugin now gets its own budget. Co-authored-by: qtvbwfn <5848026+qtvbwfn@users.noreply.github.com> --- crates/ide-run/src/detect.rs | 6 ++---- crates/ide-run/tests/detect.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/ide-run/src/detect.rs b/crates/ide-run/src/detect.rs index 7f7c714fe..438ccf6ba 100644 --- a/crates/ide-run/src/detect.rs +++ b/crates/ide-run/src/detect.rs @@ -571,7 +571,6 @@ const SPRING_SCAN_FILES: usize = 400; /// modules of the build in `build` that apply the Spring Boot plugin: /// `java -cp ${classpath} ` in the module, the classpath resolved at launch. fn spring_boot_apps(root: &Path, build: &Path, out: &mut Vec) { - let mut budget = SPRING_SCAN_FILES; let mut modules = vec![build.to_path_buf()]; let mut level = vec![build.to_path_buf()]; for _ in 0..ide_core::project_roots::SEARCH_DEPTH { @@ -579,12 +578,11 @@ fn spring_boot_apps(root: &Path, build: &Path, out: &mut Vec) { modules.extend(level.iter().cloned()); } for module in modules { - if budget == 0 { - break; - } if !applies_spring_boot(&module) { continue; } + // A budget per module: a large common library must not starve later app modules. + let mut budget = SPRING_SCAN_FILES; let module_rel = (module != root).then(|| relative(root, &module).to_string_lossy().replace('\\', "/")); for class in spring_boot_classes(&module, &mut budget) { let simple = class.rsplit(['.', '$']).next().unwrap_or(&class).to_owned(); diff --git a/crates/ide-run/tests/detect.rs b/crates/ide-run/tests/detect.rs index 4d6766cf1..73e5d3dc4 100644 --- a/crates/ide-run/tests/detect.rs +++ b/crates/ide-run/tests/detect.rs @@ -279,6 +279,32 @@ fn spring_boot_application_of_a_repository_nested_one_or_two_levels_down() { } } +#[test] +fn a_large_common_module_does_not_starve_later_app_modules() { + // More source files than one scan budget in `a-common` (no app class): the app in `b-app` must still be found. + let mut files: Vec<(String, String)> = vec![ + ("pom.xml".into(), "a-commonb-app".into()), + ("a-common/pom.xml".into(), BOOT_POM.into()), + ("b-app/pom.xml".into(), BOOT_POM.into()), + ( + "b-app/src/main/java/demo/App.java".into(), + "package demo;\n@SpringBootApplication\npublic class App {\n public static void main(String[] args) {}\n}\n".into(), + ), + ]; + for i in 0..401 { + files.push((format!("a-common/src/main/java/demo/C{i}.java"), format!("package demo;\nclass C{i} {{}}\n"))); + } + let dir = tempfile::tempdir().unwrap(); + for (path, content) in &files { + let p = dir.path().join(path); + fs::create_dir_all(p.parent().unwrap()).unwrap(); + fs::write(p, content).unwrap(); + } + let configs = detect(dir.path()); + let app = find(&configs, "application:b-app:demo.App"); + assert_eq!(app.kind, "spring-boot"); +} + #[test] fn nested_builds_of_every_kind_and_depth() { let dir = project(&[