From 48ebbe15e799e55e76b58dccf4cb7b0b18953391 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:47:42 +0800 Subject: [PATCH] fix(java): Apply the Maven settings to jdtls and Lombok, find more Maven installs Review fixes for the Project JDK / Maven settings: - jdtls gets the user settings.xml as java.configuration.maven.userSettings, so the editor resolves dependencies through the same mirrors and servers as sync and builds. - The configured local repository (else of the chosen settings.xml) is where the static POM model looks for parent POMs / BOMs and where the Lombok agent is looked for. - Maven discovery finds mvn.cmd on PATH on Windows and the homes Maven Wrapper 3.3+ downloads (dists// without a nested folder). - A Project JDK pointing at the JDK's bin directory is accepted. - Detected run configurations reload when language settings change, so Maven and Gradle runs use the new JDK, Maven home and options. - Chinese text for the new settings. --- crates/ide-build/examples/model.rs | 2 +- crates/ide-build/src/compile/mod.rs | 4 ++-- crates/ide-build/src/lib.rs | 11 ++++++----- crates/ide-build/src/maven.rs | 2 +- crates/ide-sdk/src/apply.rs | 25 +++++++++++++++++++++++-- crates/ide-sdk/src/discover.rs | 12 ++++++++++-- crates/ide-sdk/src/lombok.rs | 18 ++++++++++++------ src-tauri/src/commands/build.rs | 2 +- src-tauri/src/commands/java.rs | 3 ++- src-tauri/src/state.rs | 1 + src/features/run/index.ts | 4 ++++ src/platform/locales/text/zh.json | 15 +++++++++++++++ 12 files changed, 78 insertions(+), 21 deletions(-) diff --git a/crates/ide-build/examples/model.rs b/crates/ide-build/examples/model.rs index 327238adc..8237e79ab 100644 --- a/crates/ide-build/examples/model.rs +++ b/crates/ide-build/examples/model.rs @@ -19,7 +19,7 @@ fn main() { eprintln!("synced in {:?}: {r:?}", started.elapsed()); } let started = std::time::Instant::now(); - let model = ide_build::load(&root, &cache); + let model = ide_build::load(&root, &cache, None); println!("{}", serde_json::to_string_pretty(&model).unwrap()); eprintln!("loaded in {:?}", started.elapsed()); } diff --git a/crates/ide-build/src/compile/mod.rs b/crates/ide-build/src/compile/mod.rs index 68049c9c1..8d62d8e76 100644 --- a/crates/ide-build/src/compile/mod.rs +++ b/crates/ide-build/src/compile/mod.rs @@ -672,7 +672,7 @@ mod tests { ); write(&root, "core/pom.xml", "core"); write(&root, "core/src/main/java/A.java", "class A {}"); - let model = crate::load(&root, &tmp.path().join("cache")); + let model = crate::load(&root, &tmp.path().join("cache"), None); let p = plan(&root, &model, BuildKind::Build, None).unwrap(); assert_eq!(p.system, BuildSystem::Maven); assert_eq!(p.title, "demo"); @@ -686,7 +686,7 @@ mod tests { let g = tmp.path().join("g"); write(&g, "settings.gradle", "include 'app'"); write(&g, "app/build.gradle", "apply plugin: 'java'"); - let model = crate::load(&g, &tmp.path().join("cache2")); + let model = crate::load(&g, &tmp.path().join("cache2"), None); let p = plan(&g, &model, BuildKind::Build, Some(&g.join("app/src/A.java"))).unwrap(); assert_eq!(p.system, BuildSystem::Gradle); assert_eq!(p.steps[0].args, ["--console=plain", ":app:classes", ":app:testClasses"]); diff --git a/crates/ide-build/src/lib.rs b/crates/ide-build/src/lib.rs index 5f4ec5ba1..726aa7b75 100644 --- a/crates/ide-build/src/lib.rs +++ b/crates/ide-build/src/lib.rs @@ -18,7 +18,7 @@ pub use sync::{SyncOptions, SyncProgress, sync_gradle, sync_maven}; use serde::{Deserialize, Serialize}; use specta::Type; -use std::path::Path; +use std::path::{Path, PathBuf}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)] #[serde(rename_all = "camelCase")] @@ -36,9 +36,10 @@ pub struct BuildModel { } /// Static model of the builds under `root`, merged with the sync results cached in `cache_dir`. -pub fn load(root: &Path, cache_dir: &Path) -> BuildModel { +/// Parent POMs are looked up in `maven_repo` (the configured local repository), else the default one. +pub fn load(root: &Path, cache_dir: &Path, maven_repo: Option) -> BuildModel { let maven = maven::is_maven_project(root).then(|| { - let mut m = maven::load(root); + let mut m = maven::load_with_repo(root, maven_repo.or_else(maven::default_local_repo)); sync::apply_maven_cache(root, cache_dir, &mut m); m }); @@ -68,10 +69,10 @@ mod tests { let cache = tmp.path().join("cache"); let root = tmp.path().join("p"); std::fs::create_dir_all(root.join("svc")).unwrap(); - assert_eq!(load(&root, &cache), BuildModel::default()); + assert_eq!(load(&root, &cache, None), BuildModel::default()); std::fs::write(root.join("svc/pom.xml"), "svc").unwrap(); std::fs::write(root.join("build.gradle"), "").unwrap(); - let m = load(&root, &cache); + let m = load(&root, &cache, None); assert_eq!(m.maven.unwrap().projects[0].pom, "svc/pom.xml"); assert_eq!(m.gradle.unwrap().projects[0].path, ":"); assert!(affects_build(Path::new("a/pom.xml")) && affects_build(Path::new("settings.gradle.kts"))); diff --git a/crates/ide-build/src/maven.rs b/crates/ide-build/src/maven.rs index d40e7440b..6a8c29c4e 100644 --- a/crates/ide-build/src/maven.rs +++ b/crates/ide-build/src/maven.rs @@ -412,7 +412,7 @@ struct Resolver { } /// `~/.m2/repository`, or `` from `~/.m2/settings.xml`. -fn default_local_repo() -> Option { +pub(crate) fn default_local_repo() -> Option { let m2 = dirs::home_dir()?.join(".m2"); if let Some(settings) = std::fs::read_to_string(m2.join("settings.xml")).ok().and_then(|t| xml::parse(&t)) && let Some(repo) = settings.text_of("localRepository") diff --git a/crates/ide-sdk/src/apply.rs b/crates/ide-sdk/src/apply.rs index 38226526c..1bd753b63 100644 --- a/crates/ide-sdk/src/apply.rs +++ b/crates/ide-sdk/src/apply.rs @@ -56,10 +56,10 @@ fn goroot(root: &Path, s: &Settings) -> Option { setting_path(root, &s.languages.go.goroot) } -/// The project JDK (`languages.java.jdk`): a JDK home, or its `bin/java`. +/// The project JDK (`languages.java.jdk`): a JDK home, its `bin` or its `bin/java`. pub fn project_jdk(root: &Path, s: &Settings) -> Option { let p = setting_path(root, &s.languages.java.jdk)?; - let jdk = crate::java::jdk_at(&p).or_else(|| crate::java::jdk_at(p.parent()?.parent()?)); + let jdk = p.ancestors().take(3).find_map(crate::java::jdk_at); if jdk.is_none() { tracing::warn!("project JDK {} is not a JDK home", p.display()); } @@ -92,6 +92,19 @@ pub fn maven_options(root: &Path, s: &Settings) -> Vec { out } +/// The Maven local repository from settings: the configured one, else +/// `` of the configured user `settings.xml`; `None` = Maven's default. +pub fn maven_local_repository(root: &Path, s: &Settings) -> Option { + let m = &s.languages.java.maven; + setting_path(root, &m.local_repository).or_else(|| { + let text = std::fs::read_to_string(setting_path(root, &m.user_settings)?).ok()?; + let repo = crate::java::xml_value(&crate::lombok::strip_xml_comments(&text), "localRepository")?; + let home = crate::home_dir().map(|h| h.to_string_lossy().into_owned()).unwrap_or_default(); + let repo = repo.replace("${user.home}", &home); + (!repo.is_empty() && !repo.contains("${")).then(|| PathBuf::from(repo)) + }) +} + /// TypeScript `lib` directory for the selected version; `None` = the server's bundled one. pub fn tsdk(root: &Path, s: &Settings) -> Option { match s.languages.typescript.version.trim() { @@ -206,6 +219,14 @@ pub fn configure_server(cfg: &mut LanguageServerConfig, root: &Path, server_root // The jdtls launcher forwards `--jvm-arg=` values to the JVM it starts. let args = s.languages.java.jdtls_vm_args.split_whitespace().map(|a| format!("--jvm-arg={a}")); cfg.args.extend(args); + // m2e (the Maven import) reads mirrors, servers and `` from it. + if let Some(file) = setting_path(root, &s.languages.java.maven.user_settings) { + let file = file.to_string_lossy(); + merge_init_options( + cfg, + json!({ "settings": { "java": { "configuration": { "maven": { "userSettings": file } } } } }), + ); + } } if let Some(lib) = tsdk(server_root, s).or_else(|| tsdk(root, s)) { diff --git a/crates/ide-sdk/src/discover.rs b/crates/ide-sdk/src/discover.rs index 20bea151b..903682d31 100644 --- a/crates/ide-sdk/src/discover.rs +++ b/crates/ide-sdk/src/discover.rs @@ -437,7 +437,13 @@ fn maven_home(dir: &Path) -> Option { fn maven() -> Vec { let mut found: Vec<(PathBuf, &'static str)> = Vec::new(); - if let Some(mvn) = which("mvn").and_then(|p| dunce::canonicalize(p).ok()) + // Windows' Maven ships `mvn.cmd` only (`which` looks for `mvn.exe`). + let on_path = if cfg!(windows) { + std::env::split_paths(&search_path()).map(|d| d.join("mvn.cmd")).find(|p| p.is_file()) + } else { + which("mvn") + }; + if let Some(mvn) = on_path.and_then(|p| dunce::canonicalize(p).ok()) && let Some(home) = mvn.parent().and_then(Path::parent) { found.push((home.to_path_buf(), "path")); @@ -454,10 +460,12 @@ fn maven() -> Vec { for d in subdirs(&home.join(".sdkman/candidates/maven")) { found.push((d, "sdkman")); } - // Distributions the Maven wrapper downloaded: `dists/apache-maven-3.9.6-bin//apache-maven-3.9.6`. + // Distributions the Maven wrapper downloaded: `dists/apache-maven-3.9.6-bin//apache-maven-3.9.6`, + // or since wrapper 3.3 the home itself, `dists/apache-maven-3.9.6/`. for dist in subdirs(&home.join(".m2/wrapper/dists")) { for hash in subdirs(&dist) { found.extend(subdirs(&hash).into_iter().map(|d| (d, "wrapper"))); + found.push((hash, "wrapper")); } } } diff --git a/crates/ide-sdk/src/lombok.rs b/crates/ide-sdk/src/lombok.rs index e812e3cd4..e9f7ca1af 100644 --- a/crates/ide-sdk/src/lombok.rs +++ b/crates/ide-sdk/src/lombok.rs @@ -64,8 +64,14 @@ pub fn agent_in_args<'a>(args: impl IntoIterator) -> Option, managed: Option) -> LombokAgent { +/// options) wins, then `managed` (the IDE-installed jar), then the build caches +/// (`maven_repo`, the configured Maven local repository, else the default one). +pub fn lombok_agent( + root: &Path, + own: Option, + managed: Option, + maven_repo: Option, +) -> LombokAgent { if let Some(jar) = own { let version = jar .file_stem() @@ -82,7 +88,7 @@ pub fn lombok_agent(root: &Path, own: Option, managed: Option) } let home = home_dir(); let mut dirs = Vec::new(); - if let Some(repo) = maven_repository(home.as_deref()) { + if let Some(repo) = maven_repo.or_else(|| maven_repository(home.as_deref())) { dirs.push(repo.join("org").join("projectlombok").join("lombok")); } if let Some(gradle) = @@ -137,7 +143,7 @@ fn maven_repository(home: Option<&Path>) -> Option { Some(custom.unwrap_or_else(|| m2.join("repository"))) } -fn strip_xml_comments(text: &str) -> String { +pub(crate) fn strip_xml_comments(text: &str) -> String { let mut out = String::with_capacity(text.len()); let mut rest = text; while let Some(i) = rest.find("