use crate::error::MWError;
use crate::makerule::{MakeRule, path_from_wibo};
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::Stdio;
pub struct Compiler {
pub c_flags: Vec<String>,
pub mwcc_path: PathBuf,
pub use_wibo: bool,
pub wibo_path: PathBuf,
pub gcc_deps: bool,
}
impl Compiler {
pub fn new(
c_flags: Vec<String>,
mwcc_path: PathBuf,
use_wibo: bool,
wibo_path: PathBuf,
) -> Self {
let mut gcc_deps = false;
for flag in &c_flags {
if flag == "-gccdep" || flag == "-gccdepends" {
gcc_deps = true;
}
if flag == "-nogccdep" || flag == "-nogccdepends" {
gcc_deps = false;
}
}
Self {
c_flags,
mwcc_path,
use_wibo,
wibo_path,
gcc_deps,
}
}
pub fn compile_file<P: AsRef<Path>>(
&self,
c_file: P,
display_name: impl AsRef<str> + ToString,
workspace: &Path,
) -> Result<(Vec<u8>, Option<MakeRule>), MWError> {
let o_file = workspace.join("result.o");
let mut cmd_args = vec!["-c".to_string()];
cmd_args.extend(self.c_flags.clone());
cmd_args.extend(vec![
"-o".to_string(),
o_file.to_string_lossy().to_string(),
c_file.as_ref().to_string_lossy().to_string(),
]);
let mut cmd = if self.use_wibo {
let mut c = Command::new(&self.wibo_path);
c.arg(&self.mwcc_path);
c
} else {
Command::new(&self.mwcc_path)
};
cmd.args(cmd_args)
.env("MWCIncludes", ".")
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut process = cmd.spawn()?;
let c_file_path = c_file.as_ref().to_path_buf();
let display_name = display_name.to_string();
let stdout_handle = {
let stdout = process.stdout.take().unwrap();
std::thread::spawn(move || -> std::io::Result<()> {
for line in BufReader::new(stdout).lines() {
eprintln!(
"{}",
filter_diagnostic_line(&line?, &c_file_path, &display_name)
);
}
Ok(())
})
};
let stderr_handle = {
let stderr = process.stderr.take().unwrap();
std::thread::spawn(move || -> std::io::Result<Vec<u8>> {
use std::io::Read;
let mut buf = Vec::new();
BufReader::new(stderr).read_to_end(&mut buf)?;
Ok(buf)
})
};
process.wait()?;
stdout_handle.join().unwrap()?;
let stderr_bytes = stderr_handle.join().unwrap()?;
if !stderr_bytes.is_empty() {
eprint!("{}", String::from_utf8_lossy(&stderr_bytes));
}
if !o_file.exists() {
return Err(MWError::Compiler(format!(
"Compilation failed: {} {}",
c_file.as_ref().to_string_lossy(),
o_file.to_string_lossy(),
)));
}
let obj_bytes = std::fs::read(&o_file)?;
let make_rule = self.handle_dependency_file(c_file, workspace)?;
Ok((obj_bytes, make_rule))
}
fn handle_dependency_file<P1: AsRef<Path>, P2: AsRef<Path>>(
&self,
c_file: P1,
temp_dir: P2,
) -> Result<Option<MakeRule>, MWError> {
if self.gcc_deps {
let d_file = temp_dir.as_ref().join("result.d");
if d_file.exists() {
return Ok(Some(
MakeRule::new(&std::fs::read(d_file)?, self.use_wibo).unwrap(),
));
}
} else if self.c_flags.iter().any(|f| f == "-MD" || f == "-MMD") {
let d_file_name = c_file
.as_ref()
.file_name()
.unwrap()
.to_str()
.unwrap()
.replace(".c", ".d");
let d_file = Path::new(&d_file_name);
if d_file.exists() {
let data = std::fs::read(d_file)?;
std::fs::remove_file(d_file)?;
return Ok(Some(MakeRule::new(&data, self.use_wibo).unwrap()));
}
}
Ok(None)
}
}
pub fn filter_diagnostic_line(line: &str, temp_path: &Path, display_name: &str) -> String {
let (tag, rest) = if let Some(r) = line.strip_prefix("# In: ") {
("# In: ", r)
} else if let Some(r) = line.strip_prefix("# From: ") {
("# From: ", r)
} else if let Some(r) = line.strip_prefix("# File: ") {
("# File: ", r)
} else {
return line.to_string();
};
let posix = path_from_wibo(rest.trim_end());
let posix_str = posix.to_string_lossy();
if tag == "# From: " || tag == "# File: " {
let matches = temp_path
.canonicalize()
.ok()
.zip(posix.canonicalize().ok())
.map(|(a, b)| a == b)
.unwrap_or(false);
let display = if matches || posix_str == temp_path.to_string_lossy() {
display_name.to_string()
} else {
posix_str.into_owned()
};
format!("{tag}{display}")
} else {
format!("{tag}{posix_str}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_compiler_init() {
let compiler = Compiler::new(
vec!["-gccdep".to_string()],
PathBuf::from("mwcc.exe"),
false,
PathBuf::from("wibo"),
);
assert!(compiler.gcc_deps);
}
#[test]
fn test_compiler_flag_detection() {
let compiler_gcc = Compiler::new(
vec!["-gccdep".to_string()],
PathBuf::from("mwcc.exe"),
false,
PathBuf::from("wibo"),
);
assert!(compiler_gcc.gcc_deps);
let compiler_no_gcc = Compiler::new(
vec!["-gccdep".to_string(), "-nogccdep".to_string()],
PathBuf::from("mwcc.exe"),
false,
PathBuf::from("wibo"),
);
assert!(!compiler_no_gcc.gcc_deps);
}
#[test]
fn test_filter_unrelated_line() {
let line = "# variable 'x' is not initialized before being used";
let result = filter_diagnostic_line(line, Path::new("/tmp/foo.c"), "src/foo.c");
assert_eq!(result, line);
}
#[test]
fn test_filter_in_line_backslash() {
let line = "# In: src\\st\\e_grave_keeper.h";
let result = filter_diagnostic_line(line, Path::new("/tmp/foo.c"), "src/foo.c");
assert_eq!(result, "# In: src/st/e_grave_keeper.h");
}
#[test]
fn test_filter_from_line_backslash() {
let line = "# From: src\\st\\are\\foo.c";
let result = filter_diagnostic_line(line, Path::new("/tmp/.tmpXXXX.c"), "src/bar.c");
assert_eq!(result, "# From: src/st/are/foo.c");
}
#[test]
fn test_filter_from_line_temp_substitution() {
let temp = Path::new("/tmp/.tmpJ8qy3h.c");
let line = "# From: /tmp/.tmpJ8qy3h.c";
let result = filter_diagnostic_line(line, temp, "src/st/are/e_grave_keeper.c");
assert_eq!(result, "# From: src/st/are/e_grave_keeper.c");
}
#[test]
fn test_filter_from_line_stdin() {
let temp = Path::new("/tmp/.tmpABCDEF.c");
let line = "# From: /tmp/.tmpABCDEF.c";
let result = filter_diagnostic_line(line, temp, "<stdin>");
assert_eq!(result, "# From: <stdin>");
}
#[test]
fn test_filter_real_file() {
let temp = Path::new("tests/data/assemlber.c");
let line = "# From: tests\\data\\assemlber.c";
let result = filter_diagnostic_line(line, temp, "<stdin>");
assert_eq!(result, "# From: <stdin>");
}
#[test]
fn test_filter_full_diagnostic_block() {
let temp = Path::new("/tmp/.tmpJ8qy3h.c");
let display = "src/st/are/e_grave_keeper.c";
let input = [
"### mwccpsp.exe Compiler:",
"# In: src\\st\\e_grave_keeper.h",
"# From: src\\st\\are\\.tmpJ8qy3h.c",
"# --------------------------------",
"# 135: s32 collisionDetected;",
"# Warning: ^^^^^^^^^^^^^^^^^",
"# variable 'collisionDetected' is not initialized before being used",
];
let input_posix = [
"### mwccpsp.exe Compiler:",
"# In: src\\st\\e_grave_keeper.h",
"# From: /tmp/.tmpJ8qy3h.c",
"# --------------------------------",
"# 135: s32 collisionDetected;",
"# Warning: ^^^^^^^^^^^^^^^^^",
"# variable 'collisionDetected' is not initialized before being used",
];
let expected = [
"### mwccpsp.exe Compiler:",
"# In: src/st/e_grave_keeper.h",
"# From: src/st/are/e_grave_keeper.c",
"# --------------------------------",
"# 135: s32 collisionDetected;",
"# Warning: ^^^^^^^^^^^^^^^^^",
"# variable 'collisionDetected' is not initialized before being used",
];
let _ = input; for (line, want) in input_posix.iter().zip(expected.iter()) {
assert_eq!(&filter_diagnostic_line(line, temp, display), want);
}
let input_posix = [
"### mwccpsp.exe Compiler:",
"# File: src\\st\\e_grave_keeper.h",
"# --------------------------------",
"# 135: s32 collisionDetected;",
"# Warning: ^^^^^^^^^^^^^^^^^",
"# variable 'collisionDetected' is not initialized before being used",
];
let expected = [
"### mwccpsp.exe Compiler:",
"# File: src/st/e_grave_keeper.h",
"# --------------------------------",
"# 135: s32 collisionDetected;",
"# Warning: ^^^^^^^^^^^^^^^^^",
"# variable 'collisionDetected' is not initialized before being used",
];
let _ = input; for (line, want) in input_posix.iter().zip(expected.iter()) {
assert_eq!(&filter_diagnostic_line(line, temp, display), want);
}
}
}