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("