diff --git a/crates/ark/src/interface.rs b/crates/ark/src/interface.rs index b72894eae..2531a35c4 100644 --- a/crates/ark/src/interface.rs +++ b/crates/ark/src/interface.rs @@ -552,35 +552,41 @@ impl RMain { startup::push_ignore_user_r_profile(&mut r_args); } - let r_home = r_home_setup(); + let r_home = match r_home_setup() { + Ok(r_home) => r_home, + Err(err) => panic!("Can't set up `R_HOME`: {err}"), + }; // `R_HOME` is now defined no matter what and will be used by // `r_command()`. Let's discover the other important environment // variables set by R's shell script frontend. // https://github.com/posit-dev/positron/issues/3637 - if let Ok(output) = - r_command(|command| { - // From https://github.com/rstudio/rstudio/blob/74696236/src/cpp/core/r_util/REnvironmentPosix.cpp#L506-L515 - command.arg("--vanilla").arg("-s").arg("-e").arg( - r#"cat(paste(R.home('share'), R.home('include'), R.home('doc'), sep=';'))"#, - ); - }) - { - if let Ok(vars) = String::from_utf8(output.stdout) { - let vars: Vec<&str> = vars.trim().split(';').collect(); - if vars.len() == 3 { - // Set the R env vars as the R shell script frontend would - unsafe { - std::env::set_var("R_SHARE_DIR", vars[0]); - std::env::set_var("R_INCLUDE_DIR", vars[1]); - std::env::set_var("R_DOC_DIR", vars[2]); - }; + match r_command(|command| { + // From https://github.com/rstudio/rstudio/blob/74696236/src/cpp/core/r_util/REnvironmentPosix.cpp#L506-L515 + command + .arg("--vanilla") + .arg("-s") + .arg("-e") + .arg(r#"cat(paste(R.home('share'), R.home('include'), R.home('doc'), sep=';'))"#); + }) { + Ok(output) => { + if let Ok(vars) = String::from_utf8(output.stdout) { + let vars: Vec<&str> = vars.trim().split(';').collect(); + if vars.len() == 3 { + // Set the R env vars as the R shell script frontend would + unsafe { + std::env::set_var("R_SHARE_DIR", vars[0]); + std::env::set_var("R_INCLUDE_DIR", vars[1]); + std::env::set_var("R_DOC_DIR", vars[2]); + }; + } else { + log::warn!("Unexpected output for R envvars"); + } } else { - log::warn!("Unexpected output for R envvars"); - } - } else { - log::warn!("Could not read stdout for R envvars"); - }; + log::warn!("Could not read stdout for R envvars"); + }; + }, + Err(err) => log::error!("Failed to discover R envvars: {err}"), }; let libraries = RLibraries::from_r_home_path(&r_home); diff --git a/crates/ark/src/version.rs b/crates/ark/src/version.rs index 1418edd84..43184fa39 100644 --- a/crates/ark/src/version.rs +++ b/crates/ark/src/version.rs @@ -32,7 +32,7 @@ pub struct RVersion { } pub fn detect_r() -> anyhow::Result { - let r_home: String = r_home_setup().to_string_lossy().to_string(); + let r_home: String = r_home_setup()?.to_string_lossy().to_string(); let output = r_command(|command| { command diff --git a/crates/harp/src/command.rs b/crates/harp/src/command.rs index 8eb3e5ca4..d85c462d6 100644 --- a/crates/harp/src/command.rs +++ b/crates/harp/src/command.rs @@ -10,6 +10,8 @@ use std::path::PathBuf; use std::process::Command; use std::process::Output; +use anyhow::anyhow; + use crate::sys::command::COMMAND_R_NAMES; /// Execute a `Command` for R, trying multiple names for the R executable @@ -41,28 +43,49 @@ where } /// Use this before calling `r_command()` to ensure that `R_HOME` is set consistently -pub fn r_home_setup() -> PathBuf { - match std::env::var("R_HOME") { +pub fn r_home_setup() -> anyhow::Result { + // Determine candidate path and the string form to set (we will overwrite + // R_HOME in the environment after validation, even if it was already set). + let home = match std::env::var("R_HOME") { Ok(home) => { // Get `R_HOME` from env var, typically set by Positron / CI / kernel specification - PathBuf::from(home) + home.clone() }, + Err(_) => { // Get `R_HOME` from `PATH`, via `R` - let Ok(result) = r_command_from_path(|command| { + let result = r_command_from_path(|command| { command.arg("RHOME"); - }) else { - panic!("Can't find R or `R_HOME`"); - }; + }) + .map_err(|err| anyhow!("Can't find R or `R_HOME`: {err}"))?; - let r_home = String::from_utf8(result.stdout).unwrap(); - let r_home = r_home.trim(); + let home = String::from_utf8(result.stdout) + .map_err(|err| anyhow!("Invalid UTF-8 from R RHOME output: {err}"))?; + home.trim().to_string() + }, + }; + + let path = PathBuf::from(home.clone()); + match path.try_exists() { + Ok(true) => { + // Ensure `R_HOME` is set in the environment after validation. From + // now on, `r_command()` can be used to run exactly the same R as is + // running in Ark. + unsafe { std::env::set_var("R_HOME", &home) }; + + // Check that `R` can be called + r_command(|command| { + command.arg("RHOME"); + }) + .map_err(|err| anyhow!("Can't run R: {err}"))?; - // Now set `R_HOME`. From now on, `r_command()` can be used to - // run exactly the same R as is running in Ark. - unsafe { std::env::set_var("R_HOME", r_home) }; - PathBuf::from(r_home) + Ok(path) }, + Ok(false) => Err(anyhow!( + "The `R_HOME` path '{}' does not exist.", + path.display() + )), + Err(err) => Err(anyhow!("Can't check if `R_HOME` path exists: {err}")), } }