fix(sdk): keep the drive letter of a JDK home on a mapped drive

A JDK on a mapped network drive was canonicalized to \\?\UNC\server\share\…
too, so Maven ran with that JAVA_HOME and mvn.cmd failed with "The system
cannot find the path specified" even with a local Maven home. JDK homes
(configured, JAVA_HOME, the java on PATH) now keep their drive letter like
Maven homes do.
This commit is contained in:
l0ng-ai
2026-11-04 11:29:02 +08:00
parent 3c6a245d2c
commit 30c464f024
3 changed files with 30 additions and 33 deletions
+3 -28
View File
@@ -545,7 +545,7 @@ fn jdk() -> Vec<Sdk> {
/// A JDK as the SDK pickers list it (`Eclipse Adoptium 25.0.1`).
pub fn jdk_sdk(j: &crate::java::Jdk) -> Sdk {
let java_home = std::env::var_os("JAVA_HOME").and_then(|h| dunce::canonicalize(h).ok());
let java_home = std::env::var_os("JAVA_HOME").and_then(|h| crate::launch_path(Path::new(&h)).ok());
let release = std::fs::read_to_string(j.home.join("release")).unwrap_or_default();
let version = release_value(&release, "JAVA_VERSION").unwrap_or_else(|| j.major.to_string());
let name = match release_value(&release, "IMPLEMENTOR") {
@@ -579,31 +579,6 @@ fn maven_version(home: &Path) -> Option<String> {
})
}
/// `path` with symlinks resolved (Homebrew's `bin/mvn` → its Cellar), except that a Windows
/// drive letter is kept: on a mapped network drive or a `subst` drive canonicalizing gives
/// `\\?\UNC\server\share\…` or another drive, and cmd.exe can't start `mvn.cmd` from a
/// `\\?\` path (nor is it the path the user knows).
fn launch_path(path: &Path) -> std::io::Result<PathBuf> {
let real = dunce::canonicalize(path)?;
#[cfg(windows)]
{
use std::path::{Component, Prefix};
let drive = |p: &Path| match p.components().next() {
Some(Component::Prefix(x)) => match x.kind() {
Prefix::Disk(d) | Prefix::VerbatimDisk(d) => Some(d.to_ascii_uppercase()),
_ => None,
},
_ => None,
};
if let Some(d) = drive(path)
&& drive(&real) != Some(d)
{
return std::path::absolute(path);
}
}
Ok(real)
}
/// A Maven home (`bin/mvn` and `lib/`); Homebrew's formula keeps it in `libexec`.
fn maven_home(dir: &Path) -> Option<PathBuf> {
let mvn = if cfg!(windows) { "mvn.cmd" } else { "mvn" };
@@ -620,7 +595,7 @@ fn maven() -> Vec<Sdk> {
} else {
which("mvn")
};
if let Some(mvn) = on_path.and_then(|p| launch_path(&p).ok())
if let Some(mvn) = on_path.and_then(|p| crate::launch_path(&p).ok())
&& let Some(home) = mvn.parent().and_then(Path::parent)
{
found.push((home.to_path_buf(), "path"));
@@ -654,7 +629,7 @@ fn maven() -> Vec<Sdk> {
let mut out = Vec::new();
for (dir, source) in found {
let Some(home) = maven_home(&dir) else { continue };
let home = launch_path(&home).unwrap_or(home);
let home = crate::launch_path(&home).unwrap_or(home);
if !seen.insert(home.clone()) {
continue;
}
+2 -5
View File
@@ -38,10 +38,7 @@ pub fn jdk_at(home: &Path) -> Option<Jdk> {
if !home.join("bin").join(crate::exe("java")).is_file() {
return None;
}
Some(Jdk {
major: parse_release(&release)?,
home: dunce::canonicalize(home).unwrap_or_else(|_| home.to_path_buf()),
})
Some(Jdk { major: parse_release(&release)?, home: crate::launch_path(home).unwrap_or_else(|_| home.to_path_buf()) })
}
fn children(dir: &Path) -> Vec<PathBuf> {
@@ -90,7 +87,7 @@ fn scan() -> Vec<Jdk> {
let path = std::env::var_os("PATH").unwrap_or_default();
if let Some(java) = std::env::split_paths(&path).map(|d| d.join(crate::exe("java"))).find(|j| j.is_file()) {
// Through symlinks (`/usr/bin/java`, Homebrew, SDKMAN `current`) to `<home>/bin/java`.
if let Some(home) = dunce::canonicalize(java).ok().and_then(|j| j.parent()?.parent().map(Path::to_path_buf)) {
if let Some(home) = crate::launch_path(&java).ok().and_then(|j| j.parent()?.parent().map(Path::to_path_buf)) {
homes.push(home);
}
}
+25
View File
@@ -82,6 +82,31 @@ pub(crate) fn exe(name: &str) -> String {
if cfg!(windows) { format!("{name}.exe") } else { name.to_owned() }
}
/// `path` with symlinks resolved (Homebrew's `bin/mvn` → its Cellar), except that a Windows
/// drive letter is kept: on a mapped network drive or a `subst` drive canonicalizing gives
/// `\\?\UNC\server\share\…` or another drive, and cmd.exe can't start a script from a
/// `\\?\` path (`mvn.cmd`, or `mvn.cmd` with such a `JAVA_HOME`), nor is it the path the user knows.
pub(crate) fn launch_path(path: &Path) -> std::io::Result<PathBuf> {
let real = dunce::canonicalize(path)?;
#[cfg(windows)]
{
use std::path::{Component, Prefix};
let drive = |p: &Path| match p.components().next() {
Some(Component::Prefix(x)) => match x.kind() {
Prefix::Disk(d) | Prefix::VerbatimDisk(d) => Some(d.to_ascii_uppercase()),
_ => None,
},
_ => None,
};
if let Some(d) = drive(path)
&& drive(&real) != Some(d)
{
return std::path::absolute(path);
}
}
Ok(real)
}
pub(crate) fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")).map(PathBuf::from)
}