diff --git a/crates/ide-core/src/process.rs b/crates/ide-core/src/process.rs index 49565b3da..502114de7 100644 --- a/crates/ide-core/src/process.rs +++ b/crates/ide-core/src/process.rs @@ -123,12 +123,14 @@ fn taskkill_tree(child: &Child) { /// inherits them loads the image's (older) libraries and plugins next to its own, e.g. /// `curl: symbol lookup error ... nghttp2_...`. The IDE process keeps its environment /// (WebKit and GTK still need it). Then applies the [`set_env_overlay`] variables and -/// those of a manual HTTP proxy ([`crate::proxy_settings::ProxySettings::child_env`]). -/// Variables the call site has already set are kept. A no-op outside an AppImage -/// without an overlay. +/// those of a manual HTTP proxy ([`crate::proxy_settings::ProxySettings::child_env`]), +/// and the login shell's variables this process lacks ([`shell_env`]: an app started from +/// Finder/Dock doesn't have what rc files export, such as `GOPATH`, `JAVA_HOME` or +/// `MAVEN_OPTS`). Variables the call site has already set are kept. pub fn host_env(cmd: &mut Command) -> &mut Command { let fixes = child_env_changes(); - if fixes.is_empty() { + let shell: Vec<_> = shell_env().collect(); + if fixes.is_empty() && shell.is_empty() { return cmd; } let explicit: Vec = cmd.get_envs().map(|(k, _)| k.to_owned()).collect(); @@ -138,6 +140,9 @@ pub fn host_env(cmd: &mut Command) -> &mut Command { None => cmd.env_remove(key), }; } + for (key, value) in shell.into_iter().filter(|(k, _)| !explicit.contains(k)) { + cmd.env(key, value); + } cmd } @@ -154,12 +159,13 @@ pub fn host_vars() -> impl Iterator { apply_changes(std::env::vars_os().collect(), &child_env_changes()).into_iter() } -/// [`std::env::var_os`] as a child should see it (see [`host_env`]). +/// [`std::env::var_os`] as a child should see it (see [`host_env`]), the login shell's value +/// included when this process lacks the variable. pub fn host_var_os(key: impl AsRef) -> Option { let key = key.as_ref(); match child_env_changes().into_iter().find(|(k, _)| k == key) { Some((_, fixed)) => fixed, - None => std::env::var_os(key), + None => std::env::var_os(key).or_else(|| shell_env().find(|(k, _)| k == key).map(|(_, v)| v)), } } @@ -572,6 +578,15 @@ mod tests { assert_eq!(get("HOME"), Some(None)); let shell: Vec<_> = shell_env().map(|(k, _)| k).collect(); assert_eq!(shell, [OsString::from("OPENIDEA_OVERLAY_B")]); + // Every child gets what the login shell exports and the IDE process lacks (a Finder + // launch: `GOPATH`, `JAVA_HOME`...), unless the call site sets it. + assert_eq!(get("OPENIDEA_OVERLAY_B"), Some(Some("b".into()))); + let mut own = Command::new("true"); + own.env("OPENIDEA_OVERLAY_B", "call site"); + host_env(&mut own); + let value = own.get_envs().find(|(n, _)| *n == "OPENIDEA_OVERLAY_B").and_then(|(_, v)| v); + assert_eq!(value, Some(OsStr::new("call site"))); + assert_eq!(host_var_os("OPENIDEA_OVERLAY_B"), Some("b".into())); assert_eq!(host_var_os("PATH"), Some("/reloaded".into())); assert!(host_vars().all(|(k, _)| k != "HOME")); assert!(host_vars().any(|(k, v)| k == "OPENIDEA_OVERLAY_A" && v == "project")); diff --git a/crates/ide-host/src/shell_env.rs b/crates/ide-host/src/shell_env.rs index 6d2aa62cc..d9f7e3b17 100644 --- a/crates/ide-host/src/shell_env.rs +++ b/crates/ide-host/src/shell_env.rs @@ -177,6 +177,10 @@ pub(crate) fn read_login_env(shell: &str, timeout: std::time::Duration) -> Optio } } } + // Nor what the last read found that this process lacks: children get it, the shell must not. + for (k, _) in ide_core::process::shell_env() { + cmd.env_remove(k); + } ide_core::process::prepare(&mut cmd); let mut child = cmd .args(["-ilc", &format!("printf '{MARK}'; /usr/bin/env -0; printf '{MARK}'")]) @@ -257,6 +261,14 @@ mod tests { std::fs::write(&rc, "export OPENIDEA_TEST_RC='two words'\n").unwrap(); let second = read_login_env(&shell, Duration::from_secs(10)).unwrap(); assert_eq!(get(&second, "OPENIDEA_TEST_RC").as_deref(), Some("two words")); + + // What a read found goes to children, not into the next read: an rc file that no + // longer exports a variable drops it. + ide_core::process::set_shell_env(second); + std::fs::write(&rc, "export OPENIDEA_TEST_SEEN=\"${OPENIDEA_TEST_RC:-unset}\"\n").unwrap(); + let third = read_login_env(&shell, Duration::from_secs(10)).unwrap(); + assert_eq!(get(&third, "OPENIDEA_TEST_RC"), None); + assert_eq!(get(&third, "OPENIDEA_TEST_SEEN").as_deref(), Some("unset")); } #[test]