diff --git a/apps/desktop/src/i18n/backend-errors.ts b/apps/desktop/src/i18n/backend-errors.ts index 389fe4f771..f08427413b 100644 --- a/apps/desktop/src/i18n/backend-errors.ts +++ b/apps/desktop/src/i18n/backend-errors.ts @@ -92,7 +92,7 @@ const patterns: [RegExp, string][] = [ [/^Plugin update blocked by active connections: ([\s\S]+)$/, "pluginPlatform.updateBlockedByConnections"], [/^(.+?) driver is not installed\. Please install it from the Driver Manager\.$/, "connection.driverNotInstalled"], [/^JRE (.+?) runtime is not installed\. Please install it from the Driver Manager\.$/, "connection.jreNotInstalled"], - [/^System Java runtime was not found on PATH\. Please install Java or choose a custom Java executable\.$/, "connection.systemJavaNotFound"], + [/^System Java runtime was not found:/, "connection.systemJavaNotFound"], [/^Custom Java runtime path is empty\. Please choose a Java executable\.$/, "connection.customJavaPathEmpty"], [/^Agent requires Java 21, but DBX started it with an older Java runtime\. Use DBX managed JRE 21 or select a Java 21 executable in Driver Manager\./, "connection.agentJavaTooOld"], [/^JDBC plugin is not installed\. Install the optional JDBC plugin to use this connection\.$/, "connection.jdbcPluginNotInstalled"], diff --git a/apps/desktop/src/i18n/locales/az.ts b/apps/desktop/src/i18n/locales/az.ts index 57126c1779..84092d50fe 100644 --- a/apps/desktop/src/i18n/locales/az.ts +++ b/apps/desktop/src/i18n/locales/az.ts @@ -1090,7 +1090,7 @@ export default withEnglishFallback({ driverNotInstalled: "{driver} drayveri quraşdırılmayıb. Onu drayver menecerindən quraşdırın.", phoenixDriverNotInstalled: "Apache Phoenix JDBC drayveri quraşdırılmayıb. Onu drayver menecerindən quraşdırın, sonra yenidən cəhd edin.", jreNotInstalled: "JRE {jre} icra mühiti quraşdırılmayıb. Onu drayver menecerindən quraşdırın.", - systemJavaNotFound: "Sistemin Java icra mühiti PATH-də tapılmadı. Java quraşdırın və ya xüsusi Java icra faylı seçin.", + systemJavaNotFound: "Sistemin Java icra mühiti JAVA_HOME və ya PATH-də tapılmadı. Java quraşdırın və ya xüsusi Java icra faylı seçin.", customJavaPathEmpty: "Xüsusi Java icra mühitinin yolu boşdur. Java icra faylı seçin.", agentJavaTooOld: "Bu drayver Java 21 tələb edir. DBX tərəfindən idarə olunan JRE 21-dən istifadə edin və ya drayver menecerində Java 21 icra faylı seçin.", agentDriverUpdateConnectionHint: "Bu əlaqə üçün daxili drayver yeniləməsi mövcuddur. Əlaqə xətası köhnəlmiş yerli drayverlə bağlı ola bilər. Drayver menecerində müvafiq drayveri yeniləyin, sonra yenidən cəhd edin.", @@ -8102,7 +8102,7 @@ export default withEnglishFallback({ saving: "Yadda saxlanılır...", customJavaPathPlaceholder: "/path/to/java və ya /path/to/jdk", choose: "Seç", - systemJavaHint: "Cari PATH-dəki java-dan istifadə et.", + systemJavaHint: "Əvvəlcə JAVA_HOME, sonra PATH-dəki java-dan istifadə et.", jreRuntimeTitle: "JRE {jre} icra mühiti", notInstalled: "Quraşdırılmayıb", reinstalling: "Yenidən quraşdırılır", diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 1875be9fc1..84b1b31b8c 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -1244,7 +1244,7 @@ export default { driverNotInstalled: "{driver} driver is not installed. Please install it from the Driver Manager.", phoenixDriverNotInstalled: "The Apache Phoenix JDBC driver is not installed. Install it from the Driver Manager, then retry.", jreNotInstalled: "JRE {jre} runtime is not installed. Please install it from the Driver Manager.", - systemJavaNotFound: "System Java runtime was not found on PATH. Please install Java or choose a custom Java executable.", + systemJavaNotFound: "System Java runtime was not found in JAVA_HOME or PATH. Please install Java or choose a custom Java executable.", customJavaPathEmpty: "Custom Java runtime path is empty. Please choose a Java executable.", agentJavaTooOld: "This driver requires Java 21. Use DBX managed JRE 21 or select a Java 21 executable in Driver Manager.", agentDriverUpdateConnectionHint: "A built-in driver update is available for this connection. The connection failure may be related to an outdated local driver. Update the corresponding driver in Driver Manager, then retry.", @@ -8337,7 +8337,7 @@ export default { saving: "Saving...", customJavaPathPlaceholder: "/path/to/java or /path/to/jdk", choose: "Choose", - systemJavaHint: "Use java from the current PATH.", + systemJavaHint: "Resolve java from JAVA_HOME first, then PATH.", jreRuntimeTitle: "JRE {jre} Runtime", notInstalled: "Not installed", reinstalling: "Reinstalling", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index cbbb0b60df..544b8647f1 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -1010,7 +1010,7 @@ export default withEnglishFallback({ driverNotInstalled: "El controlador {driver} no está instalado. Instálelo desde el Administrador de controladores.", phoenixDriverNotInstalled: "El controlador JDBC de Apache Phoenix no está instalado. Instálelo desde el Administrador de controladores y vuelva a intentarlo.", jreNotInstalled: "El entorno JRE {jre} no está instalado. Instálelo desde el Administrador de controladores.", - systemJavaNotFound: "No se encontró Java en el PATH del sistema. Instale Java o elija un ejecutable personalizado.", + systemJavaNotFound: "No se encontró Java en JAVA_HOME ni en el PATH del sistema. Instale Java o elija un ejecutable personalizado.", customJavaPathEmpty: "La ruta de Java personalizada está vacía. Elija un ejecutable de Java.", agentJavaTooOld: "Este controlador requiere Java 21. Use el JRE 21 administrado por DBX o seleccione un ejecutable Java 21 en el Administrador de controladores.", agentDriverUpdateConnectionHint: @@ -7872,7 +7872,7 @@ export default withEnglishFallback({ saving: "Guardando...", customJavaPathPlaceholder: "/ruta/a/java o /ruta/a/jdk", choose: "Seleccionar", - systemJavaHint: "Usa java del PATH actual.", + systemJavaHint: "Usa java de JAVA_HOME y, si no, del PATH.", jreRuntimeTitle: "Runtime JRE {jre}", notInstalled: "No instalado", reinstalling: "Reinstalando", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index b643cde4b9..f8d5b5ae27 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -1009,7 +1009,7 @@ export default withEnglishFallback({ driverNotInstalled: "Il driver {driver} non è installato. Installalo da Gestione Driver.", phoenixDriverNotInstalled: "Il driver JDBC di Apache Phoenix non è installato. Installalo da Gestione Driver e riprova.", jreNotInstalled: "Il runtime JRE {jre} non è installato. Installalo da Gestione Driver.", - systemJavaNotFound: "Runtime Java di sistema non trovato in PATH. Installa Java o scegli un eseguibile Java personalizzato.", + systemJavaNotFound: "Runtime Java di sistema non trovato in JAVA_HOME o in PATH. Installa Java o scegli un eseguibile Java personalizzato.", customJavaPathEmpty: "Il percorso del runtime Java personalizzato è vuoto. Scegli un eseguibile Java.", agentJavaTooOld: "Questo driver richiede Java 21. Usa il JRE 21 gestito da DBX o seleziona un eseguibile Java 21 in Gestione Driver.", agentDriverUpdateConnectionHint: "È disponibile un aggiornamento del driver integrato per questa connessione. Il problema di connessione potrebbe essere correlato a un driver locale obsoleto. Aggiorna il driver corrispondente in Gestione Driver e riprova.", @@ -7872,7 +7872,7 @@ export default withEnglishFallback({ saving: "Salvataggio...", customJavaPathPlaceholder: "/percorso/per/java o /percorso/per/jdk", choose: "Scegli", - systemJavaHint: "Usa java dal PATH corrente.", + systemJavaHint: "Usa java da JAVA_HOME, poi dal PATH.", jreRuntimeTitle: "Runtime JRE {jre}", notInstalled: "Non installato", reinstalling: "Reinstallazione in corso", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 6cdbac7175..3dcaf5b5b5 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -975,7 +975,7 @@ export default withEnglishFallback({ driverNotInstalled: "{driver} ドライバーがインストールされていません。ドライバーマネージャーからインストールしてください。", phoenixDriverNotInstalled: "Apache Phoenix JDBC ドライバーがインストールされていません。ドライバーマネージャーからインストールして、再試行してください。", jreNotInstalled: "JRE {jre} ランタイムがインストールされていません。ドライバーマネージャーからインストールしてください。", - systemJavaNotFound: "システムJavaランタイムがPATHに見つかりませんでした。Javaをインストールするか、カスタムJava実行ファイルを選択してください。", + systemJavaNotFound: "システムJavaランタイムがJAVA_HOMEまたはPATHに見つかりませんでした。Javaをインストールするか、カスタムJava実行ファイルを選択してください。", customJavaPathEmpty: "カスタムJavaランタイムのパスが空です。Java実行ファイルを選択してください。", agentJavaTooOld: "このドライバーにはJava 21が必要です。ドライバーマネージャーでDBX管理JRE 21を使用するか、Java 21実行ファイルを選択してください。", agentDriverUpdateConnectionHint: "この接続で使用中の内蔵ドライバに更新が利用可能です。接続失敗はローカルドライバの旧版が原因の可能性があります。ドライバマネージャーで該当ドライバを更新してから再試行してください。", @@ -7924,7 +7924,7 @@ export default withEnglishFallback({ saving: "保存中...", customJavaPathPlaceholder: "/path/to/java or /path/to/jdk", choose: "選択", - systemJavaHint: "現在のPATHからJavaを使用します。", + systemJavaHint: "JAVA_HOME を優先し、次に PATH の Java を使用します。", jreRuntimeTitle: "JRE {jre} ランタイム", notInstalled: "未インストール", reinstalling: "再インストール中", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 503cc5ec5e..afbec7deb6 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -1122,7 +1122,7 @@ export default withEnglishFallback({ driverNotInstalled: "{driver} 드라이버가 설치되지 않았습니다. 드라이버 관리자에서 설치하세요.", phoenixDriverNotInstalled: "Apache Phoenix JDBC 드라이버가 설치되지 않았습니다. 드라이버 관리자에서 설치한 후 다시 시도하세요.", jreNotInstalled: "JRE {jre} 런타임이 설치되지 않았습니다. 드라이버 관리자에서 설치하세요.", - systemJavaNotFound: "PATH에서 시스템 Java 런타임을 찾을 수 없습니다. Java를 설치하거나 사용자 지정 Java 실행 파일을 선택하세요.", + systemJavaNotFound: "JAVA_HOME 또는 PATH에서 시스템 Java 런타임을 찾을 수 없습니다. Java를 설치하거나 사용자 지정 Java 실행 파일을 선택하세요.", customJavaPathEmpty: "사용자 지정 Java 런타임 경로가 비어 있습니다. Java 실행 파일을 선택하세요.", agentJavaTooOld: "이 드라이버는 Java 21이 필요합니다. DBX 관리 JRE 21을 사용하거나 드라이버 관리자에서 Java 21 실행 파일을 선택하세요.", agentDriverUpdateConnectionHint: "이 연결에 사용 가능한 내장 드라이버 업데이트가 있습니다. 연결 실패가 오래된 로컬 드라이버와 관련이 있을 수 있습니다. 드라이버 관리자에서 해당 드라이버를 업데이트한 후 다시 시도하세요.", @@ -7604,7 +7604,7 @@ export default withEnglishFallback({ saving: "저장 중...", customJavaPathPlaceholder: "/path/to/java 또는 /path/to/jdk", choose: "선택", - systemJavaHint: "현재 PATH의 java를 사용합니다.", + systemJavaHint: "JAVA_HOME을 우선 사용하고, 없으면 PATH의 java를 사용합니다.", jreRuntimeTitle: "JRE {jre} 런타임", notInstalled: "설치되지 않음", reinstalling: "다시 설치 중", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index b85efa8ad1..2d40a569f1 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -1010,7 +1010,7 @@ export default withEnglishFallback({ driverNotInstalled: "O driver {driver} não está instalado. Instale-o pelo Gerenciador de Drivers.", phoenixDriverNotInstalled: "O driver JDBC do Apache Phoenix não está instalado. Instale-o pelo Gerenciador de Drivers e tente novamente.", jreNotInstalled: "O runtime JRE {jre} não está instalado. Instale-o pelo Gerenciador de Drivers.", - systemJavaNotFound: "O runtime Java do sistema não foi encontrado no PATH. Instale o Java ou escolha um executável Java personalizado.", + systemJavaNotFound: "O runtime Java do sistema não foi encontrado no JAVA_HOME nem no PATH. Instale o Java ou escolha um executável Java personalizado.", customJavaPathEmpty: "O caminho do runtime Java personalizado está vazio. Escolha um executável Java.", agentJavaTooOld: "Este driver requer Java 21. Use o JRE 21 gerenciado pelo DBX ou selecione um executável Java 21 no Gerenciador de Drivers.", agentDriverUpdateConnectionHint: "Há uma atualização de driver integrada disponível para esta conexão. A falha de conexão pode estar relacionada a um driver local desatualizado. Atualize o driver correspondente no Gerenciador de Drivers e tente novamente.", @@ -7874,7 +7874,7 @@ export default withEnglishFallback({ saving: "Salvando...", customJavaPathPlaceholder: "/caminho/para/java ou /caminho/para/jdk", choose: "Escolher", - systemJavaHint: "Usar o java do PATH atual.", + systemJavaHint: "Usar o java do JAVA_HOME e, se não houver, do PATH.", jreRuntimeTitle: "Runtime da JRE {jre}", notInstalled: "Não instalado", reinstalling: "Reinstalando", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index e10e3ea3c5..0b3eb1d71f 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -1092,7 +1092,7 @@ export default withEnglishFallback({ driverNotInstalled: "{driver} sürücüsü kurulu değil. Lütfen Sürücü Yöneticisi'nden kurun.", phoenixDriverNotInstalled: "Apache Phoenix JDBC sürücüsü kurulu değil. Sürücü Yöneticisi'nden kurup tekrar deneyin.", jreNotInstalled: "JRE {jre} çalışma zamanı kurulu değil. Lütfen Sürücü Yöneticisi'nden kurun.", - systemJavaNotFound: "Sistem Java çalışma zamanı PATH üzerinde bulunamadı. Lütfen Java kurun veya özel bir Java çalıştırılabiliri seçin.", + systemJavaNotFound: "Sistem Java çalışma zamanı JAVA_HOME veya PATH üzerinde bulunamadı. Lütfen Java kurun veya özel bir Java çalıştırılabiliri seçin.", customJavaPathEmpty: "Özel Java çalışma zamanı yolu boş. Lütfen bir Java çalıştırılabiliri seçin.", agentJavaTooOld: "Bu sürücü Java 21 gerektirir. DBX tarafından yönetilen JRE 21'i kullanın veya Sürücü Yöneticisi'nde bir Java 21 çalıştırılabiliri seçin.", agentDriverUpdateConnectionHint: "Bu bağlantı için yerleşik bir sürücü güncellemesi mevcut. Bağlantı hatası, güncel olmayan yerel sürücüyle ilgili olabilir. Sürücü Yöneticisi'nde ilgili sürücüyü güncelleyip tekrar deneyin.", @@ -7957,7 +7957,7 @@ export default withEnglishFallback({ saving: "Kaydediliyor...", customJavaPathPlaceholder: "/path/to/java veya /path/to/jdk", choose: "Seç", - systemJavaHint: "Geçerli PATH üzerindeki java'yı kullan.", + systemJavaHint: "Önce JAVA_HOME, sonra PATH üzerindeki java'yı kullan.", jreRuntimeTitle: "JRE {jre} Çalışma Zamanı", notInstalled: "Kurulu değil", reinstalling: "Yeniden kuruluyor", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 06e3d553d3..e8a2353fc3 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -1162,7 +1162,7 @@ export default withEnglishFallback({ driverNotInstalled: "{driver} 驱动未安装,请在驱动管理器中安装。", phoenixDriverNotInstalled: "Apache Phoenix JDBC 驱动未安装,请在驱动管理器中安装后重试。", jreNotInstalled: "JRE {jre} 运行环境未安装,请在驱动管理器中安装。", - systemJavaNotFound: "未在 PATH 中找到系统 Java 运行环境,请安装 Java 或选择自定义 Java 可执行文件。", + systemJavaNotFound: "未在 JAVA_HOME 或 PATH 中找到系统 Java 运行环境,请安装 Java 或选择自定义 Java 可执行文件。", customJavaPathEmpty: "自定义 Java 运行环境路径为空,请选择 Java 可执行文件。", agentJavaTooOld: "该驱动需要 Java 21。请在驱动管理器中使用 DBX 托管 JRE 21,或选择 Java 21 可执行文件。", agentDriverUpdateConnectionHint: "当前连接使用的内置驱动有可用更新,连接失败可能与本地驱动版本过旧有关。请在「驱动管理」中更新对应驱动后重试。", @@ -8309,7 +8309,7 @@ export default withEnglishFallback({ saving: "保存中...", customJavaPathPlaceholder: "/path/to/java 或 /path/to/jdk", choose: "选择", - systemJavaHint: "使用当前环境 PATH 中的 java。", + systemJavaHint: "优先使用 JAVA_HOME,其次为 PATH 中的 java。", jreRuntimeTitle: "JRE {jre} 运行时", notInstalled: "未安装", reinstalling: "重装中", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 5e787a3b10..88b3edb413 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -1019,7 +1019,7 @@ export default withEnglishFallback({ driverNotInstalled: "{driver} 驅動程式未安裝,請在驅動程式管理器中安裝。", phoenixDriverNotInstalled: "Apache Phoenix JDBC 驅動程式未安裝,請在驅動程式管理器中安裝後再試。", jreNotInstalled: "JRE {jre} 執行環境未安裝,請在驅動程式管理器中安裝。", - systemJavaNotFound: "未在 PATH 中找到系統 Java 執行環境,請安裝 Java 或選擇自訂 Java 可執行檔。", + systemJavaNotFound: "未在 JAVA_HOME 或 PATH 中找到系統 Java 執行環境,請安裝 Java 或選擇自訂 Java 可執行檔。", customJavaPathEmpty: "自訂 Java 執行環境路徑為空,請選擇 Java 可執行檔。", agentJavaTooOld: "此驅動程式需要 Java 21。請在驅動程式管理器中使用 DBX 託管 JRE 21,或選擇 Java 21 可執行檔。", agentDriverUpdateConnectionHint: "目前連線使用的內建驅動有可用更新,連線失敗可能與本機驅動版本過舊有關。請在「驅動管理」中更新對應驅動後重試。", @@ -7179,7 +7179,7 @@ export default withEnglishFallback({ saving: "儲存中……", customJavaPathPlaceholder: "/path/to/java 或 /path/to/jdk", choose: "選擇", - systemJavaHint: "使用目前環境 PATH 中的 java。", + systemJavaHint: "優先使用 JAVA_HOME,其次為 PATH 中的 java。", jreRuntimeTitle: "JRE {jre} 執行環境", notInstalled: "未安裝", reinstalling: "重新安裝中", diff --git a/crates/dbx-drivers/src/agent_manager.rs b/crates/dbx-drivers/src/agent_manager.rs index be2092a44c..9d37cf5e52 100644 --- a/crates/dbx-drivers/src/agent_manager.rs +++ b/crates/dbx-drivers/src/agent_manager.rs @@ -297,11 +297,56 @@ mod tests { #[test] fn resolves_system_java_runtime_from_path() { let manager = test_manager("system"); - let system_java = manager.base_dir().join("bin").join(if cfg!(windows) { "java.exe" } else { "java" }); + let system_java = manager.base_dir().join("bin").join(java_executable_name()); touch(&system_java); let path = std::env::join_paths([system_java.parent().unwrap()]).unwrap(); - assert_eq!(resolve_system_java_path(Some(path.as_os_str())).unwrap(), system_java); + assert_eq!(resolve_system_java_path(None, Some(path.as_os_str())).unwrap(), system_java); + } + + #[test] + fn resolves_system_java_runtime_from_java_home() { + let manager = test_manager("system-home"); + let java_home = manager.base_dir().join("jdk"); + let system_java = java_home.join("bin").join(java_executable_name()); + touch(&system_java); + // PATH deliberately points somewhere else: JAVA_HOME is the explicit + // declaration and must win. + let other_bin = manager.base_dir().join("other-bin"); + let other_java = other_bin.join(java_executable_name()); + touch(&other_java); + let path = std::env::join_paths([&other_bin]).unwrap(); + + assert_eq!(resolve_system_java_path(Some(java_home.as_os_str()), Some(path.as_os_str())).unwrap(), system_java); + } + + #[test] + fn stale_java_home_falls_back_to_path() { + let manager = test_manager("system-stale-home"); + let system_java = manager.base_dir().join("bin").join(java_executable_name()); + touch(&system_java); + let path = std::env::join_paths([system_java.parent().unwrap()]).unwrap(); + let dangling = manager.base_dir().join("removed-jdk"); + + assert_eq!(resolve_system_java_path(Some(dangling.as_os_str()), Some(path.as_os_str())).unwrap(), system_java); + assert!(resolve_system_java_path(Some(dangling.as_os_str()), None).is_none()); + assert!(resolve_system_java_path(Some(OsStr::new("")), None).is_none()); + } + + #[test] + fn system_java_missing_error_names_the_checked_sources() { + let dangling = std::env::temp_dir().join("dbx-missing-jdk"); + let path = std::env::join_paths([std::env::temp_dir()]).unwrap(); + + let message = system_java_missing_error(Some(dangling.as_os_str()), Some(path.as_os_str())); + assert!(message.contains(&dangling.display().to_string()), "{message}"); + assert!(message.contains(java_executable_name()), "{message}"); + assert!(message.contains("no java in the 1 PATH entries checked"), "{message}"); + assert!(message.contains("restart dbx"), "{message}"); + + let unset = system_java_missing_error(None, None); + assert!(unset.contains("JAVA_HOME is not set"), "{unset}"); + assert!(unset.contains("PATH is not set"), "{unset}"); } #[tokio::test] @@ -1042,10 +1087,12 @@ impl AgentManager { } Ok(self.jre_java_path(jre_key)) } - JavaRuntimeMode::System => resolve_system_java_path(None).ok_or_else(|| { - "System Java runtime was not found on PATH. Please install Java or choose a custom Java executable." - .to_string() - }), + JavaRuntimeMode::System => { + let java_home = std::env::var_os(JAVA_HOME_ENV); + let path_var = std::env::var_os(PATH_ENV); + resolve_system_java_path(java_home.as_deref(), path_var.as_deref()) + .ok_or_else(|| system_java_missing_error(java_home.as_deref(), path_var.as_deref())) + } JavaRuntimeMode::Custom => { let path = state .java_runtime @@ -1238,32 +1285,81 @@ fn java_executable_name() -> &'static str { } } -fn resolve_custom_java_path(path: &str) -> Result { - let raw = PathBuf::from(path); - if is_executable_file(&raw) { - return Ok(raw); +/// Locate `java` under a JDK/JRE root. Accepts the conventional `bin/java` +/// layout, a macOS bundle (`Contents/Home/bin/java`), and a path that already +/// points at the executable itself. +fn resolve_java_under_root(root: &Path) -> Option { + if is_executable_file(root) { + return Some(root.to_path_buf()); } - let flat = raw.join("bin").join(java_executable_name()); + let flat = root.join("bin").join(java_executable_name()); if is_executable_file(&flat) { - return Ok(flat); + return Some(flat); } - let macos = raw.join("Contents").join("Home").join("bin").join(java_executable_name()); + let macos = root.join("Contents").join("Home").join("bin").join(java_executable_name()); if is_executable_file(&macos) { - return Ok(macos); + return Some(macos); } - Err(format!("Custom Java runtime does not exist or is not a Java executable: {}", raw.display())) + None } -fn resolve_system_java_path(path_var: Option<&OsStr>) -> Option { - let path_var = path_var.map(|p| p.to_owned()).or_else(|| std::env::var_os("PATH"))?; - std::env::split_paths(&path_var) +fn resolve_custom_java_path(path: &str) -> Result { + let raw = PathBuf::from(path); + resolve_java_under_root(&raw) + .ok_or_else(|| format!("Custom Java runtime does not exist or is not a Java executable: {}", raw.display())) +} + +const JAVA_HOME_ENV: &str = "JAVA_HOME"; +const PATH_ENV: &str = "PATH"; + +/// Resolve the "system" Java runtime from the user's own environment: +/// `JAVA_HOME` first (their explicit declaration), then the first `java` on +/// `PATH`. A `JAVA_HOME` that no longer contains a usable `java` is skipped +/// instead of reported, so a stale value cannot shadow a working `PATH` entry +/// (see the dangling-`JAVA_HOME` report in #5517). +/// +/// The caller passes the environment explicitly so the tests stay hermetic. +fn resolve_system_java_path(java_home: Option<&OsStr>, path_var: Option<&OsStr>) -> Option { + if let Some(home) = java_home.filter(|value| !value.is_empty()) { + if let Some(java) = resolve_java_under_root(Path::new(home)) { + return Some(java); + } + } + + let path_var = path_var.filter(|value| !value.is_empty())?; + std::env::split_paths(path_var) + .filter(|dir| !dir.as_os_str().is_empty()) .map(|dir| dir.join(java_executable_name())) .find(|candidate| is_executable_file(candidate)) } +/// Report where the "system" Java runtime was looked for. Users who configured +/// `JAVA_HOME`/`PATH` can still be running with the environment dbx was +/// launched with, so name the exact values that were checked instead of only +/// saying "not found". +fn system_java_missing_error(java_home: Option<&OsStr>, path_var: Option<&OsStr>) -> String { + let java_home_detail = match java_home.filter(|value| !value.is_empty()) { + Some(value) => format!( + "JAVA_HOME={} does not contain {}", + value.to_string_lossy(), + Path::new(value).join("bin").join(java_executable_name()).display() + ), + None => "JAVA_HOME is not set".to_string(), + }; + let path_detail = match path_var.filter(|value| !value.is_empty()) { + Some(value) => format!("no java in the {} PATH entries checked", std::env::split_paths(value).count()), + None => "PATH is not set".to_string(), + }; + format!( + "System Java runtime was not found: {java_home_detail}; {path_detail}. \ + Point JAVA_HOME at a JDK/JRE directory, add java to PATH, or choose a custom Java executable. \ + dbx uses the environment it was started with, so restart dbx after changing environment variables." + ) +} + fn is_executable_file(path: &Path) -> bool { if !path.is_file() { return false;