rust/build_system/src/utils.rs

227 lines
6.4 KiB
Rust
Raw Normal View History

2023-09-26 16:09:51 +02:00
use std::collections::HashMap;
2023-08-18 16:06:20 +02:00
use std::ffi::OsStr;
2023-08-19 20:53:47 +02:00
use std::fmt::Debug;
2023-08-18 16:06:20 +02:00
use std::fs;
use std::path::Path;
2023-08-19 20:53:47 +02:00
use std::process::{Command, ExitStatus, Output};
2023-08-18 16:06:20 +02:00
2023-09-26 16:09:51 +02:00
fn get_command_inner(
input: &[&dyn AsRef<OsStr>],
cwd: Option<&Path>,
env: Option<&HashMap<String, String>>,
) -> Command {
2023-08-18 16:06:20 +02:00
let (cmd, args) = match input {
[] => panic!("empty command"),
[cmd, args @ ..] => (cmd, args),
};
let mut command = Command::new(cmd);
command.args(args);
if let Some(cwd) = cwd {
command.current_dir(cwd);
}
2023-09-26 16:09:51 +02:00
if let Some(env) = env {
command.envs(env.iter().map(|(k, v)| (k.as_str(), v.as_str())));
}
2023-08-19 18:24:01 +02:00
command
}
2023-08-19 20:53:47 +02:00
fn check_exit_status(
input: &[&dyn AsRef<OsStr>],
cwd: Option<&Path>,
exit_status: ExitStatus,
) -> Result<(), String> {
if exit_status.success() {
Ok(())
} else {
Err(format!(
"Command `{}`{} exited with status {:?}",
2023-09-26 16:09:51 +02:00
input
.iter()
2023-08-18 16:06:20 +02:00
.map(|s| s.as_ref().to_str().unwrap())
.collect::<Vec<_>>()
.join(" "),
2023-08-19 20:53:47 +02:00
cwd.map(|cwd| format!(" (running in folder `{}`)", cwd.display()))
.unwrap_or_default(),
exit_status.code(),
2023-08-18 16:06:20 +02:00
))
2023-08-19 20:53:47 +02:00
}
}
fn command_error<D: Debug>(input: &[&dyn AsRef<OsStr>], cwd: &Option<&Path>, error: D) -> String {
format!(
"Command `{}`{} failed to run: {error:?}",
2023-09-26 16:09:51 +02:00
input
.iter()
2023-08-19 20:53:47 +02:00
.map(|s| s.as_ref().to_str().unwrap())
.collect::<Vec<_>>()
.join(" "),
cwd.as_ref()
2023-09-26 16:09:51 +02:00
.map(|cwd| format!(" (running in folder `{}`)", cwd.display(),))
2023-08-19 20:53:47 +02:00
.unwrap_or_default(),
)
}
pub fn run_command(input: &[&dyn AsRef<OsStr>], cwd: Option<&Path>) -> Result<Output, String> {
2023-09-26 16:09:51 +02:00
run_command_with_env(input, cwd, None)
}
pub fn run_command_with_env(
input: &[&dyn AsRef<OsStr>],
cwd: Option<&Path>,
env: Option<&HashMap<String, String>>,
) -> Result<Output, String> {
let output = get_command_inner(input, cwd, env)
2023-08-19 20:53:47 +02:00
.output()
.map_err(|e| command_error(input, &cwd, e))?;
check_exit_status(input, cwd, output.status)?;
Ok(output)
2023-08-18 16:06:20 +02:00
}
2023-08-19 18:24:01 +02:00
pub fn run_command_with_output(
input: &[&dyn AsRef<OsStr>],
cwd: Option<&Path>,
2023-09-26 16:09:51 +02:00
env: Option<&HashMap<String, String>>,
2023-08-19 18:24:01 +02:00
) -> Result<(), String> {
2023-09-26 16:09:51 +02:00
let exit_status = get_command_inner(input, cwd, env)
.spawn()
2023-08-19 20:53:47 +02:00
.map_err(|e| command_error(input, &cwd, e))?
2023-08-19 18:24:01 +02:00
.wait()
2023-08-19 20:53:47 +02:00
.map_err(|e| command_error(input, &cwd, e))?;
check_exit_status(input, cwd, exit_status)?;
2023-08-19 18:24:01 +02:00
Ok(())
}
2023-08-18 16:06:20 +02:00
pub fn cargo_install(to_install: &str) -> Result<(), String> {
let output = run_command(&[&"cargo", &"install", &"--list"], None)?;
2023-08-19 20:53:47 +02:00
let to_install_needle = format!("{to_install} ");
2023-08-18 16:06:20 +02:00
// cargo install --list returns something like this:
//
// mdbook-toc v0.8.0:
// mdbook-toc
// rust-reduce v0.1.0:
// rust-reduce
//
// We are only interested into the command name so we only look for lines ending with `:`.
if String::from_utf8(output.stdout)
.unwrap()
.lines()
2023-08-19 20:53:47 +02:00
.any(|line| line.ends_with(':') && line.starts_with(&to_install_needle))
2023-08-18 16:06:20 +02:00
{
return Ok(());
}
2023-08-19 20:53:47 +02:00
// We voluntarily ignore this error.
2023-09-26 16:09:51 +02:00
if run_command_with_output(&[&"cargo", &"install", &to_install], None, None).is_err() {
2023-08-19 20:53:47 +02:00
println!("Skipping installation of `{to_install}`");
}
2023-08-18 16:06:20 +02:00
Ok(())
}
2023-09-26 16:09:51 +02:00
pub fn get_os_name() -> Result<String, String> {
let output = run_command(&[&"uname"], None)?;
let name = std::str::from_utf8(&output.stdout)
.unwrap_or("")
.trim()
.to_owned();
if !name.is_empty() {
Ok(name)
} else {
Err(format!("Failed to retrieve the OS name"))
}
}
pub fn get_rustc_host_triple() -> Result<String, String> {
let output = run_command(&[&"rustc", &"-vV"], None)?;
let content = std::str::from_utf8(&output.stdout).unwrap_or("");
for line in content.split('\n').map(|line| line.trim()) {
if !line.starts_with("host:") {
continue;
}
return Ok(line.split(':').nth(1).unwrap().trim().to_owned());
}
Err("Cannot find host triple".to_owned())
}
pub fn get_gcc_path() -> Result<String, String> {
let content = match fs::read_to_string("gcc_path") {
Ok(c) => c,
Err(_) => {
return Err(
"Please put the path to your custom build of libgccjit in the file \
`gcc_path`, see Readme.md for details"
.into(),
)
}
};
match content
.split('\n')
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.next()
{
Some(gcc_path) => {
let path = Path::new(gcc_path);
if !path.exists() {
Err(format!(
"Path `{gcc_path}` contained in the `gcc_path` file doesn't exist"
))
} else {
Ok(gcc_path.into())
}
}
None => Err("No path found in `gcc_path` file".into()),
}
}
2023-08-18 16:06:20 +02:00
pub struct CloneResult {
pub ran_clone: bool,
pub repo_name: String,
}
pub fn git_clone(to_clone: &str, dest: Option<&Path>) -> Result<CloneResult, String> {
let repo_name = to_clone.split('/').last().unwrap();
let repo_name = match repo_name.strip_suffix(".git") {
Some(n) => n.to_owned(),
None => repo_name.to_owned(),
};
2023-08-19 20:53:47 +02:00
let dest = dest
.map(|dest| dest.join(&repo_name))
.unwrap_or_else(|| Path::new(&repo_name).into());
2023-08-18 16:06:20 +02:00
if dest.is_dir() {
2023-09-26 16:09:51 +02:00
return Ok(CloneResult {
ran_clone: false,
repo_name,
});
2023-08-18 16:06:20 +02:00
}
2023-09-26 16:09:51 +02:00
run_command_with_output(&[&"git", &"clone", &to_clone, &dest], None, None)?;
Ok(CloneResult {
ran_clone: true,
repo_name,
})
2023-08-18 16:06:20 +02:00
}
2023-08-19 20:53:47 +02:00
pub fn walk_dir<P, D, F>(dir: P, mut dir_cb: D, mut file_cb: F) -> Result<(), String>
2023-08-18 16:06:20 +02:00
where
P: AsRef<Path>,
2023-08-19 20:53:47 +02:00
D: FnMut(&Path) -> Result<(), String>,
F: FnMut(&Path) -> Result<(), String>,
2023-08-18 16:06:20 +02:00
{
let dir = dir.as_ref();
2023-09-26 16:09:51 +02:00
for entry in
fs::read_dir(dir).map_err(|e| format!("Failed to read dir `{}`: {e:?}", dir.display()))?
{
let entry =
entry.map_err(|e| format!("Failed to read entry in `{}`: {e:?}", dir.display()))?;
2023-08-18 16:06:20 +02:00
let entry_path = entry.path();
if entry_path.is_dir() {
dir_cb(&entry_path)?;
} else {
file_cb(&entry_path)?;
}
}
Ok(())
}