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 <pluginManagement> 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
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-04 00:18:45 +08:00
1 джерело 5c642819e2
коміт 4eda613c59
2 files changed
+74 -10

No files matched your search

+67 -9
Переглянути файл
@@ -245,7 +245,8 @@ struct RawPom {
plugins: Vec<RawPlugin>,
managed_plugins: Vec<RawPlugin>,
profiles: Vec<RawProfile>,
/// `<build><plugins>` `maven-compiler-plugin` `<release>` / `<source>`.
/// `maven-compiler-plugin` `<release>` / `<source>` of `<build><plugins>`, else of
/// `<pluginManagement>`.
compiler_release: Option<String>,
}
@@ -300,6 +301,16 @@ fn read_build(e: &Element, plugins: &mut Vec<RawPlugin>, managed: &mut Vec<RawPl
managed.extend(e.list("build/pluginManagement/plugins", "plugin").into_iter().filter_map(read_plugin));
}
/// `maven-compiler-plugin` `<release>` / `<source>` among the plugins at `path`.
fn compiler_level(project: &Element, path: &str) -> Option<String> {
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<RawDep>,
plugins: Vec<RawPlugin>,
managed_plugins: Vec<RawPlugin>,
/// The nearest `maven-compiler-plugin` `<release>` / `<source>`: a module usually
/// inherits its compiler level from the parent's (managed) plugin configuration.
compiler_release: Option<String>,
/// `<id>`, `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<PathBuf>) -> 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#"<project><groupId>demo</groupId><artifactId>parent</artifactId><version>1.0</version><packaging>pom</packaging>
<modules><module>core</module><module>app</module></modules>
<properties><jdk>17</jdk></properties>{build}</project>"#
)
};
let child = |name: &str, build: &str| {
format!(
r#"<project><parent><groupId>demo</groupId><artifactId>parent</artifactId><version>1.0</version></parent>
<artifactId>{name}</artifactId>{build}</project>"#
)
};
let compiler = |level: &str| {
format!(
"<plugin><artifactId>maven-compiler-plugin</artifactId><configuration><source>{level}</source></configuration></plugin>"
)
};
let versions = |model: &MavenModel| -> Vec<(String, Option<u32>)> {
model.projects.iter().map(|p| (p.pom.clone(), p.java_version)).collect()
};
let app_build = format!("<build><plugins>{}</plugins></build>", compiler("21"));
write(root, "core/pom.xml", &child("core", ""));
write(root, "app/pom.xml", &child("app", &app_build));
for build in [
format!("<build><pluginManagement><plugins>{}</plugins></pluginManagement></build>", compiler("${jdk}")),
format!("<build><plugins>{}</plugins></build>", 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();
+7 -1
Переглянути файл
@@ -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))