roc/crates/utils/command/src/lib.rs

142 lines
4.3 KiB
Rust

use std::{
env::{self, VarError},
path::PathBuf,
process::Command,
};
#[cfg(windows)]
use std::path::Path;
/// On windows, the path is prefixed with `\\?\`, the "verbatim" prefix.
/// Such a path does not works as an argument to `zig` and other command line tools,
/// and there seems to be no good way to strip it. So we resort to some string manipulation.
#[cfg(windows)]
pub fn strip_windows_prefix(path_buf: &Path) -> PathBuf {
let path_str = path_buf.display().to_string();
Path::new(path_str.trim_start_matches(r"\\?\")).to_path_buf()
}
/// get the Path of the root of the repository
pub fn root_dir() -> PathBuf {
let mut path = env::current_exe().ok().unwrap();
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06
path.pop();
// If we're in deps/ get rid of deps/ in target/debug/deps/
if path.ends_with("deps") {
path.pop();
}
// Get rid of target/debug/ so we're back at the project root
path.pop();
path.pop();
// running cargo with --target will put us in the target dir
if path.ends_with("target") {
path.pop();
}
path
}
/// Gives a friendly error if cargo is not installed.
/// Also makes it easy to track where we use cargo in the codebase.
pub fn cargo() -> Command {
let command_str = "cargo";
if check_command_available(command_str) {
Command::new(command_str)
} else {
panic!("I could not find the cargo command.\nVisit https://rustup.rs/ to install cargo.",)
}
}
/// Gives a friendly error if rustup is not installed.
/// Also makes it easy to track where we use rustup in the codebase.
pub fn rustup() -> Command {
// on windows, we need the version of cargo installed by rustup. The meaning of `cargo` is
// different within a process that runs rust. So we need to explicitly refer to where
// rustup put the binary
let command_str = "rustup";
if check_command_available(command_str) {
Command::new(command_str)
} else {
panic!("I could not find the rustup command.\nVisit https://rustup.rs/ to install rustup.",)
}
}
/// Gives a friendly error if clang is not installed.
/// Also makes it easy to track where we use clang in the codebase.
pub fn clang() -> Command {
let command_str = "clang";
if check_command_available(command_str) {
Command::new(command_str)
} else {
panic!("I could not find the clang command.\nPlease install clang.",)
//TODO detect OS and provide detailed install instructions
}
}
/// Gives a friendly error if zig is not installed.
/// Also makes it easy to track where we use zig in the codebase.
pub fn zig() -> Command {
let command_str = match std::env::var("ROC_ZIG") {
Ok(path) => path,
Err(_) => "zig".into(),
};
if check_command_available(&command_str) {
Command::new(command_str)
} else {
panic!("I could not find the zig command.\nPlease install zig, see instructions at https://ziglang.org/learn/getting-started/.",)
}
}
fn check_command_available(command_name: &str) -> bool {
if cfg!(target_family = "unix") {
let unparsed_path = match std::env::var("PATH") {
Ok(var) => var,
Err(VarError::NotPresent) => return false,
Err(VarError::NotUnicode(_)) => {
panic!("found PATH, but it included invalid unicode data!")
}
};
std::env::split_paths(&unparsed_path).any(|dir| dir.join(command_name).exists())
} else if cfg!(target = "windows") {
let mut cmd = Command::new("Get-Command");
cmd.args([command_name]);
let cmd_str = format!("{:?}", cmd);
let cmd_out = cmd.output().unwrap_or_else(|err| {
panic!(
"Failed to execute `{}` to check if {} is available:\n {}",
cmd_str, command_name, err
)
});
cmd_out.status.success()
} else {
// We're in uncharted waters, best not to panic if
// things may end up working out down the line.
true
}
}
pub fn pretty_command_string(command: &Command) -> std::ffi::OsString {
let mut command_string = std::ffi::OsString::new();
command_string.push(command.get_program());
for arg in command.get_args() {
command_string.push(" ");
command_string.push(arg);
}
command_string
}