paralegal_non_rustc_utils/
lib.rsuse std::ffi::OsString;
use std::fmt;
use std::fmt::{Display, Formatter, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use anyhow::{ensure, Result};
use serde::{Deserialize, Serialize};
use tracing::Level;
use tracing_subscriber::filter::Targets;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
pub const FLOW_GRAPH_OUT_NAME: &str = "flow-graph.o";
pub const FLOW_GRAPH_EXT: &str = "fgo";
pub const ARTIFACT_NAME: &str = "paralegal-artifact.json";
pub const STAT_FILE_EXT: &str = "stat.json";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParalegalArtifact {
pub targets: Vec<PathBuf>,
}
pub trait FileSystemStorable: Sized {
fn load(path: impl AsRef<Path>) -> Result<Self>;
fn store(&self, path: impl AsRef<Path>) -> Result<()>;
}
#[macro_export]
macro_rules! fs_storable_default {
($t:ty) => {
impl $crate::FileSystemStorable for $t {
fn load(path: impl AsRef<std::path::Path>) -> anyhow::Result<Self> {
let reader = std::io::BufReader::new(std::fs::File::open(path.as_ref())?);
Ok(serde_json::from_reader(reader)?)
}
fn store(&self, path: impl AsRef<std::path::Path>) -> anyhow::Result<()> {
let file = std::io::BufWriter::new(std::fs::File::create(path)?);
Ok(serde_json::to_writer(file, self)?)
}
}
};
}
fs_storable_default!(ParalegalArtifact);
pub fn write_sep<
E,
I: IntoIterator<Item = E>,
F: FnMut(E, &mut fmt::Formatter<'_>) -> fmt::Result,
>(
fmt: &mut fmt::Formatter<'_>,
sep: &str,
it: I,
mut f: F,
) -> fmt::Result {
let mut first = true;
for e in it {
if first {
first = false;
} else {
fmt.write_str(sep)?;
}
f(e, fmt)?;
}
Ok(())
}
#[derive(Clone)]
pub struct DisplayList<I> {
iter: I,
}
pub fn display_list<I: IntoIterator>(iter: I) -> DisplayList<<I as IntoIterator>::IntoIter> {
DisplayList {
iter: iter.into_iter(),
}
}
impl<E: Display, I: IntoIterator<Item = E> + Clone> Display for DisplayList<I> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_char('[')?;
write_sep(f, ", ", self.iter.clone(), |e, f| e.fmt(f))?;
f.write_char(']')
}
}
pub mod serde_map_via_vec {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
pub fn serialize<S: Serializer, K: Serialize, V: Serialize>(
map: &HashMap<K, V>,
serializer: S,
) -> Result<S::Ok, S::Error> {
map.iter().collect::<Vec<_>>().serialize(serializer)
}
pub fn deserialize<
'de,
D: Deserializer<'de>,
K: Deserialize<'de> + std::cmp::Eq + std::hash::Hash,
V: Deserialize<'de>,
>(
deserializer: D,
) -> Result<HashMap<K, V>, D::Error> {
Ok(Vec::deserialize(deserializer)?.into_iter().collect())
}
}
pub struct TruncatedHumanTime(std::time::Duration);
impl From<std::time::Duration> for TruncatedHumanTime {
fn from(value: std::time::Duration) -> Self {
Self(value)
}
}
impl std::fmt::Display for TruncatedHumanTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const SECS_PER_MIN: u64 = 60;
const MINS_PER_H: u64 = 60;
const H_PER_D: u64 = 24;
const MILLIS_PER_SEC: u128 = 1000;
const MICROS_PER_MILLIS: u128 = 1000;
const NANOS_PER_MICRO: u128 = 1000;
let secs = self.0.as_secs();
let mins = secs / SECS_PER_MIN;
let hs = mins / MINS_PER_H;
let days = hs / H_PER_D;
macro_rules! try_two {
($larger:expr, $letter1:expr, $smaller:expr, $letter2:expr, $multiplier:expr $(,)?) => {
if $larger != 0 {
let small = $smaller - ($larger * $multiplier);
return write!(f, "{}{} {small}{}", $larger, $letter1, $letter2);
}
};
}
try_two!(days, 'd', hs, 'h', H_PER_D);
try_two!(hs, 'h', mins, "min", MINS_PER_H);
try_two!(mins, "min", secs, 's', SECS_PER_MIN);
try_two!(secs as u128, 's', self.0.as_millis(), "ms", MILLIS_PER_SEC);
try_two!(
self.0.as_millis(),
"ms",
self.0.as_micros(),
"μs",
MICROS_PER_MILLIS,
);
try_two!(
self.0.as_micros(),
"μs",
self.0.as_nanos(),
"ns",
NANOS_PER_MICRO,
);
write!(f, "{}ns", self.0.as_nanos())
}
}
pub fn setup_logging() -> anyhow::Result<()> {
tracing_subscriber::registry()
.with(
tracing_subscriber::fmt::layer()
.with_timer(tracing_subscriber::fmt::time::ChronoLocal::new(
"%H:%M:%S".to_string(),
))
.with_writer(std::io::stderr),
)
.with(
tracing_subscriber::filter::EnvFilter::builder()
.with_default_directive(tracing::level_filters::LevelFilter::INFO.into())
.from_env()?,
)
.with(
Targets::new()
.with_target("flowistry", Level::ERROR)
.with_target("rustc_utils", Level::ERROR)
.with_default(Level::TRACE),
)
.try_init()?;
Ok(())
}
pub struct CommandFactory {
path: OsString,
bin: PathBuf,
}
impl CommandFactory {
pub fn make(&self) -> Command {
let mut cmd = Command::new(&self.bin);
cmd.arg("paralegal-flow").env("PATH", &self.path);
cmd
}
}
pub fn prepare_analyzer_command(paralegal_root: &Path) -> anyhow::Result<CommandFactory> {
let paralegal_root = paralegal_root.canonicalize()?;
let success = Command::new("cargo")
.args([
"build",
"--bin",
"paralegal-flow",
"--bin",
"cargo-paralegal-flow",
])
.current_dir(¶legal_root)
.status()
.unwrap()
.success();
ensure!(success, "cargo command failed");
let path = std::env::var_os("PATH").unwrap_or_default();
let bin_dir = paralegal_root.join("target").join("debug");
let cargo_paralegal_flow_path = bin_dir.join("cargo-paralegal-flow");
ensure!(cargo_paralegal_flow_path.exists());
let mut new_path = std::ffi::OsString::with_capacity(
path.len() + cargo_paralegal_flow_path.as_os_str().len() + 1,
);
new_path.push(bin_dir);
if !path.is_empty() {
new_path.push(":");
new_path.push(path);
}
Ok(CommandFactory {
path: new_path,
bin: cargo_paralegal_flow_path,
})
}
pub fn linux_workaround_for_llvm_lib() {
if cfg!(target_os = "linux") {
let rustup_home = std::env::var("RUSTUP_HOME").unwrap();
let rustup_tc = std::env::var("RUSTUP_TOOLCHAIN").unwrap();
let mut rustup_lib: PathBuf = [&rustup_home, "toolchains", &rustup_tc]
.into_iter()
.collect();
rustup_lib.push("lib");
println!("cargo:rustc-link-search=native={}", rustup_lib.display());
}
}