From 4eda613c5975483f3cc0485afe163988b333462a Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:18:45 +0800 Subject: [PATCH] fix(maven): inherit the compiler level from parent POMs and pluginManagement (#1064) A module whose maven-compiler-plugin release/source is configured in a parent POM or in had no Java level in the build model, so its JDK fell back to the newest installed one. The level is now read from build/plugins and build/pluginManagement/plugins and inherited down the parent chain. Launching a Java application without a project JDK also takes the module level from the build model. Refs #753 --- crates/ide-build/src/maven.rs | 76 +++++++++++++++++++++++++++++---- crates/ide-host/src/ops/java.rs | 8 +++- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/crates/ide-build/src/maven.rs b/crates/ide-build/src/maven.rs index 35a60701d..1dc1c8d88 100644 --- a/crates/ide-build/src/maven.rs +++ b/crates/ide-build/src/maven.rs @@ -245,7 +245,8 @@ struct RawPom { plugins: Vec, managed_plugins: Vec, profiles: Vec, - /// `` `maven-compiler-plugin` `` / ``. + /// `maven-compiler-plugin` `` / `` of ``, else of + /// ``. compiler_release: Option, } @@ -300,6 +301,16 @@ fn read_build(e: &Element, plugins: &mut Vec, managed: &mut Vec` / `` among the plugins at `path`. +fn compiler_level(project: &Element, path: &str) -> Option { + project + .list(path, "plugin") + .into_iter() + .find(|p| p.text_of("artifactId").as_deref() == Some("maven-compiler-plugin")) + .and_then(|p| p.child("configuration")) + .and_then(|c| c.text_of("release").or_else(|| c.text_of("source"))) +} + fn read_raw(project: &Element) -> RawPom { let parent = project.child("parent").and_then(|p| { Some(ParentRef { @@ -328,13 +339,8 @@ fn read_raw(project: &Element) -> RawPom { ..RawPom::default() }; read_build(project, &mut pom.plugins, &mut pom.managed_plugins); - if let Some(c) = pom.plugins.iter().position(|p| p.artifact_id == "maven-compiler-plugin") { - let plugin = - project.list("build/plugins", "plugin").into_iter().filter(|p| p.text_of("artifactId").is_some()).nth(c); - if let Some(cfg) = plugin.and_then(|p| p.child("configuration")) { - pom.compiler_release = cfg.text_of("release").or_else(|| cfg.text_of("source")); - } - } + pom.compiler_release = + compiler_level(project, "build/plugins").or_else(|| compiler_level(project, "build/pluginManagement/plugins")); for p in project.list("profiles", "profile") { let mut profile = RawProfile { id: p.text_of("id").unwrap_or_else(|| "default".into()), @@ -411,6 +417,9 @@ struct Inherited { managed: Vec, plugins: Vec, managed_plugins: Vec, + /// The nearest `maven-compiler-plugin` `` / ``: a module usually + /// inherits its compiler level from the parent's (managed) plugin configuration. + compiler_release: Option, /// ``, `activeByDefault`: profiles of the POM and its ancestors — /// Maven lets a child enable a parent's profiles with `-P`. profiles: Vec<(String, bool)>, @@ -538,6 +547,9 @@ impl Resolver { merge_by(&mut ctx.deps, &pom.deps, RawDep::key); merge_plugins(&mut ctx.plugins, &pom.plugins); merge_plugins(&mut ctx.managed_plugins, &pom.managed_plugins); + if pom.compiler_release.is_some() { + ctx.compiler_release = pom.compiler_release.clone(); + } for p in pom.profiles.iter().filter(|p| p.active_by_default) { merge_by(&mut ctx.properties, &p.properties, |(k, _)| k.clone()); merge_by(&mut ctx.deps, &p.deps, RawDep::key); @@ -1123,7 +1135,7 @@ pub(crate) fn load_with_repo(root: &Path, local_repo: Option) -> MavenM let jv = ["maven.compiler.release", "java.version", "maven.compiler.source", "maven.compiler.target"] .iter() .find_map(|k| props.get(*k).map(|v| i(v))) - .or_else(|| pom.compiler_release.as_deref().map(i)) + .or_else(|| ctx.compiler_release.as_deref().map(i)) .and_then(|v| java_version(&v)); // The project can activate the profiles of its own POM and its ancestors'. @@ -1524,6 +1536,52 @@ mod tests { assert!(model.projects.iter().any(|p| p.pom == "core/pom.xml" && p.aggregated)); } + #[test] + fn modules_inherit_the_compiler_level_of_the_parent_plugin_configuration() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let parent = |build: &str| { + format!( + r#"demoparent1.0pom + coreapp + 17{build}"# + ) + }; + let child = |name: &str, build: &str| { + format!( + r#"demoparent1.0 + {name}{build}"# + ) + }; + let compiler = |level: &str| { + format!( + "maven-compiler-plugin{level}" + ) + }; + let versions = |model: &MavenModel| -> Vec<(String, Option)> { + model.projects.iter().map(|p| (p.pom.clone(), p.java_version)).collect() + }; + let app_build = format!("{}", compiler("21")); + write(root, "core/pom.xml", &child("core", "")); + write(root, "app/pom.xml", &child("app", &app_build)); + for build in [ + format!("{}", compiler("${jdk}")), + format!("{}", compiler("1.8")), + ] { + write(root, "pom.xml", &parent(&build)); + let expected = if build.contains("1.8") { 8 } else { 17 }; + assert_eq!( + versions(&load_with_repo(root, None)), + [ + ("pom.xml".to_owned(), Some(expected)), + ("core/pom.xml".to_owned(), Some(expected)), + ("app/pom.xml".to_owned(), Some(21)), + ], + "{build}" + ); + } + } + #[test] fn multi_module_parent_properties_and_managed_versions() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/ide-host/src/ops/java.rs b/crates/ide-host/src/ops/java.rs index 098c1bab7..984e4881c 100644 --- a/crates/ide-host/src/ops/java.rs +++ b/crates/ide-host/src/ops/java.rs @@ -96,7 +96,13 @@ async fn resolve_launch( let project_jdk = ide_sdk::apply::project_jdk(&project.root, &settings); let from_jdtls = info.java_home.as_deref().map(ide_sdk::java::java_executable).filter(|p| p.is_file()); let java = project_jdk.map(|j| ide_sdk::java::java_executable(&j.home)).or(from_jdtls).or_else(|| { - let release = ide_sdk::java::project_java_version(&module); + // The build model's level includes what the module inherits from its parent POMs. + let release = model + .maven + .as_ref() + .and_then(|m| m.projects.iter().find(|p| project.root.join(&p.dir) == module)) + .and_then(|p| p.java_version) + .or_else(|| ide_sdk::java::project_java_version(&module)); let jdks = ide_sdk::java::installed_jdks(); info.hints.extend(ide_sdk::java::missing_jdk_message(&jdks, release)); ide_sdk::java::pick_jdk(&jdks, release).map(|j| ide_sdk::java::java_executable(&j.home))