use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
use whitaker_installer::toolchain::parse_toolchain_channel;
pub fn workspace_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("installer crate is not at workspace root")
.to_path_buf()
}
pub fn pinned_toolchain_channel() -> String {
let toolchain_path = workspace_root().join("rust-toolchain.toml");
let contents =
std::fs::read_to_string(&toolchain_path).expect("failed to read rust-toolchain.toml");
parse_toolchain_channel(&contents).expect("failed to parse rust-toolchain.toml")
}
pub fn is_toolchain_installed(channel: &str) -> bool {
Command::new("rustup")
.args(["run", channel, "rustc", "--version"])
.env("RUSTUP_AUTO_INSTALL", "0")
.env_remove("RUSTUP_TOOLCHAIN")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn is_toolchain_installed_in_env(
channel: &str,
rustup_home: &TempDir,
cargo_home: &TempDir,
) -> bool {
Command::new("rustup")
.args(["run", channel, "rustc", "--version"])
.env("RUSTUP_HOME", rustup_home.path())
.env("CARGO_HOME", cargo_home.path())
.env("RUSTUP_AUTO_INSTALL", "0")
.env_remove("RUSTUP_TOOLCHAIN")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub struct IsolatedRustupEnv {
pub rustup_home: TempDir,
pub cargo_home: TempDir,
}
fn init_isolated_rustup(rustup_home: &Path, cargo_home: &Path) {
let init_output = Command::new("rustup")
.arg("show")
.current_dir(rustup_home) .env("RUSTUP_HOME", rustup_home)
.env("CARGO_HOME", cargo_home)
.env("RUSTUP_AUTO_INSTALL", "0")
.env_remove("RUSTUP_TOOLCHAIN")
.output()
.expect("failed to initialise isolated rustup environment");
assert!(
init_output.status.success(),
"failed to initialise isolated rustup: {}",
String::from_utf8_lossy(&init_output.stderr)
);
let self_update_output = Command::new("rustup")
.args(["set", "auto-self-update", "disable"])
.current_dir(rustup_home)
.env("RUSTUP_HOME", rustup_home)
.env("CARGO_HOME", cargo_home)
.env("RUSTUP_AUTO_INSTALL", "0")
.env_remove("RUSTUP_TOOLCHAIN")
.output()
.expect("failed to disable rustup self-update in isolated environment");
assert!(
self_update_output.status.success(),
"failed to disable rustup self-update: {}",
String::from_utf8_lossy(&self_update_output.stderr)
);
}
fn parse_rustup_location_output(output: &std::process::Output) -> String {
String::from_utf8_lossy(&output.stdout)
.lines()
.next()
.expect("rustup not found in PATH")
.trim()
.to_string()
}
#[cfg(unix)]
fn find_system_rustup() -> String {
let output = Command::new("which")
.arg("rustup")
.output()
.expect("failed to run which rustup");
parse_rustup_location_output(&output)
}
#[cfg(windows)]
fn find_system_rustup() -> String {
let output = Command::new("where")
.arg("rustup")
.output()
.expect("failed to run where rustup");
parse_rustup_location_output(&output)
}
#[cfg(unix)]
fn install_rustup_to_cargo_bin(rustup_path: &str, cargo_bin: &Path) {
std::os::unix::fs::symlink(rustup_path, cargo_bin.join("rustup"))
.expect("failed to symlink rustup to CARGO_HOME/bin");
}
#[cfg(windows)]
fn install_rustup_to_cargo_bin(rustup_path: &str, cargo_bin: &Path) {
std::fs::copy(rustup_path, cargo_bin.join("rustup.exe"))
.expect("failed to copy rustup to CARGO_HOME/bin");
}
pub fn setup_isolated_rustup() -> IsolatedRustupEnv {
let rustup_home = TempDir::new().expect("failed to create RUSTUP_HOME temp dir");
let cargo_home = TempDir::new().expect("failed to create CARGO_HOME temp dir");
init_isolated_rustup(rustup_home.path(), cargo_home.path());
let cargo_bin = cargo_home.path().join("bin");
std::fs::create_dir_all(&cargo_bin).expect("failed to create CARGO_HOME/bin");
let rustup_path = find_system_rustup();
install_rustup_to_cargo_bin(&rustup_path, &cargo_bin);
IsolatedRustupEnv {
rustup_home,
cargo_home,
}
}