use std::{
any::Any,
ffi::OsStr,
fmt,
panic::{RefUnwindSafe, UnwindSafe},
path::Path,
process::{Command, ExitStatus},
sync::Arc,
};
pub trait BuildMessageLogger: Send + Sync + 'static {
fn log(&self, kind: BuildMessageKind, msg: BuildMessage<'_>, extra: &dyn Any);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BuildMessageKind {
GeneralWarning,
StderrForwarding,
#[non_exhaustive]
CommandFailed {
is_detection_cmd: bool,
exit_status: ExitStatus,
},
#[cfg_attr(
not(feature = "compile-commands"),
allow(rustdoc::broken_intra_doc_links)
)]
CompileCommand,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompileCommand {
pub(crate) directory: Box<Path>,
pub(crate) file: Box<Path>,
pub(crate) arguments: Box<[Box<OsStr>]>,
pub(crate) output: Box<Path>,
}
impl CompileCommand {
pub(crate) fn new(cmd: &Command, src: &Path, dst: &Path, directory: &Path) -> Self {
debug_assert!(cmd.get_current_dir().is_none());
Self {
directory: directory.into(),
file: directory.join(src).into(),
arguments: std::iter::once(cmd.get_program())
.chain(cmd.get_args())
.map(Box::from)
.collect(),
output: directory.join(dst).into(),
}
}
pub fn directory(&self) -> &Path {
&self.directory
}
pub fn file(&self) -> &Path {
&self.file
}
pub fn arguments(&self) -> impl ExactSizeIterator<Item = &OsStr> + DoubleEndedIterator {
self.arguments.iter().map(|arg| &**arg)
}
pub fn output(&self) -> &Path {
&self.output
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum BuildMessage<'a> {
Str(&'a str),
}
impl fmt::Display for BuildMessage<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Str(s) => f.write_str(s),
}
}
}
#[derive(Clone)]
pub(crate) struct Logger<L = Arc<dyn BuildMessageLogger>>(pub(crate) L);
impl<L> UnwindSafe for Logger<L> {}
impl<L> RefUnwindSafe for Logger<L> {}
impl Logger {
pub(crate) fn log(&self, kind: BuildMessageKind, msg: &str, extra: &dyn Any) {
self.0.log(kind, BuildMessage::Str(msg), extra);
}
}
impl<L> fmt::Debug for Logger<L> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Logger").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compile_command_has_no_spare_capacity() {
assert_eq!(
std::mem::size_of::<CompileCommand>(),
4 * std::mem::size_of::<Box<[u8]>>()
);
}
#[test]
fn paths_are_joined_to_the_directory() {
let directory = Path::new(if cfg!(windows) { r"C:\crate" } else { "/crate" });
let mut cmd = Command::new("cc");
cmd.args(["-c", "src/foo.c", "-o", "out/foo.o"]);
let command = CompileCommand::new(
&cmd,
Path::new("src/foo.c"),
Path::new("out/foo.o"),
directory,
);
assert_eq!(command.directory(), directory);
assert_eq!(command.file(), directory.join("src/foo.c"));
assert_eq!(command.output(), directory.join("out/foo.o"));
assert!(command
.arguments()
.eq(["cc", "-c", "src/foo.c", "-o", "out/foo.o"]));
let src = directory.join("other").join("bar.c");
let command = CompileCommand::new(&cmd, &src, Path::new("out/bar.o"), directory);
assert_eq!(command.file(), src);
}
}