use anyhow::{bail, Context, Result};
use console::style;
use indicatif::{ProgressBar, ProgressStyle};
use std::path::{Path, PathBuf};
const ADOPTIUM_API: &str = "https://api.adoptium.net/v3";
pub fn ensure_jdk(version: &str, vendor: Option<&str>, auto_download: bool) -> Result<PathBuf> {
if let Ok(java_home) = std::env::var("JAVA_HOME") {
let java_home = PathBuf::from(&java_home);
if java_home.join("bin").join("javac").exists()
|| java_home.join("bin").join("javac.exe").exists()
{
return Ok(java_home);
}
}
if which_javac().is_some() {
return Ok(PathBuf::from("system"));
}
let jdk_dir = jdk_cache_dir();
let cached = find_cached_jdk(&jdk_dir, version);
if let Some(path) = cached {
return Ok(path);
}
if !auto_download {
bail!(
"JDK {} not found. Install it or set jvm.autoDownload: true in package.json",
version
);
}
download_jdk(version, vendor.unwrap_or("temurin"), &jdk_dir)
}
pub fn which_javac() -> Option<PathBuf> {
let cmd = if cfg!(windows) { "where" } else { "which" };
std::process::Command::new(cmd)
.arg("javac")
.output()
.ok()
.and_then(|o| {
if o.status.success() {
let path = String::from_utf8_lossy(&o.stdout).trim().to_string();
if !path.is_empty() {
Some(PathBuf::from(path))
} else {
None
}
} else {
None
}
})
}
fn jdk_cache_dir() -> PathBuf {
crate::home_dir().join(".ym").join("jdks")
}
fn find_cached_jdk(jdk_dir: &Path, version: &str) -> Option<PathBuf> {
if !jdk_dir.exists() {
return None;
}
if let Ok(entries) = std::fs::read_dir(jdk_dir) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.contains(version) {
let path = entry.path();
if path.join("bin").join("javac").exists() {
return Some(path);
}
if let Ok(inner) = std::fs::read_dir(&path) {
for inner_entry in inner.flatten() {
if inner_entry.path().join("bin").join("javac").exists() {
return Some(inner_entry.path());
}
}
}
}
}
}
None
}
fn download_jdk(version: &str, _vendor: &str, jdk_dir: &Path) -> Result<PathBuf> {
let os = if cfg!(target_os = "linux") {
"linux"
} else if cfg!(target_os = "macos") {
"mac"
} else if cfg!(target_os = "windows") {
"windows"
} else {
bail!("Unsupported OS for JDK auto-download");
};
let arch = if cfg!(target_arch = "x86_64") {
"x64"
} else if cfg!(target_arch = "aarch64") {
"aarch64"
} else {
bail!("Unsupported architecture for JDK auto-download");
};
let ext = if cfg!(target_os = "windows") {
"zip"
} else {
"tar.gz"
};
let url = format!(
"{}/binary/latest/{}/ga/{}/{}/jdk/hotspot/normal/eclipse?project=jdk",
ADOPTIUM_API, version, os, arch
);
println!(
" {} downloading JDK {}...",
style("➜").green(),
version
);
let client = reqwest::blocking::Client::builder()
.user_agent(concat!("ym/", env!("CARGO_PKG_VERSION")))
.timeout(std::time::Duration::from_secs(300))
.build()?;
let response = client.get(&url).send()?;
if !response.status().is_success() {
bail!(
"Failed to download JDK {}: HTTP {}",
version,
response.status()
);
}
let total_size = response.content_length().unwrap_or(0);
let pb = if crate::is_progress_quiet() {
ProgressBar::hidden()
} else {
let pb = ProgressBar::new(total_size);
pb.set_style(
ProgressStyle::default_bar()
.template(" [{bar:40.cyan/dim}] {bytes}/{total_bytes} ({eta})")
.unwrap()
.progress_chars("=> "),
);
pb
};
let bytes = response.bytes()?;
pb.finish_and_clear();
std::fs::create_dir_all(jdk_dir)?;
let archive_path = jdk_dir.join(format!("jdk-{}.{}", version, ext));
std::fs::write(&archive_path, &bytes)?;
println!(
" {} extracting JDK {}...",
style("➜").green(),
version
);
if ext == "tar.gz" {
let status = std::process::Command::new("tar")
.arg("xf")
.arg(&archive_path)
.arg("-C")
.arg(jdk_dir)
.status()
.context("Failed to extract JDK archive")?;
if !status.success() {
bail!("Failed to extract JDK archive");
}
} else {
let status = std::process::Command::new("unzip")
.arg("-q")
.arg(&archive_path)
.arg("-d")
.arg(jdk_dir)
.status()
.context("Failed to extract JDK archive")?;
if !status.success() {
bail!("Failed to extract JDK archive");
}
}
let _ = std::fs::remove_file(&archive_path);
let java_home = find_cached_jdk(jdk_dir, version)
.context("JDK extracted but couldn't find JAVA_HOME")?;
println!(
" {} JDK {} installed at {}",
style("✓").green(),
version,
java_home.display()
);
Ok(java_home)
}