mod cargo;
mod driver;
pub use cargo::CargoDriver;
pub use driver::BuildDriver;
use std::path::PathBuf;
use std::process::ExitStatus;
use crate::project::Project;
use crate::target::Target;
#[derive(Debug)]
pub struct Compiler<'a> {
driver: &'a dyn BuildDriver,
}
impl<'a> Compiler<'a> {
pub fn new(driver: &'a dyn BuildDriver) -> Self {
Self { driver }
}
pub fn compile_all(&self, project: &Project, targets: &[Target]) -> Result<(), CompileError> {
for target in targets {
self.driver.build(project, target)?;
let path = target.binary_path(&project.target_directory, &project.bin);
if !path.is_file() {
return Err(CompileError::BinaryMissing {
triple: target.triple.clone(),
path,
});
}
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum CompileError {
#[error("spawning build driver {driver:?}")]
Spawn {
driver: String,
#[source]
source: std::io::Error,
},
#[error("build for {triple} failed: {status}")]
BuildFailed { triple: String, status: ExitStatus },
#[error("binary for {triple} not found after a successful build: {}", path.display())]
BinaryMissing { triple: String, path: PathBuf },
#[error("build driver {driver:?} must be a bare command name on PATH, not a path")]
InvalidDriver { driver: String },
#[error("workspace root does not exist: {}", path.display())]
MissingWorkspaceRoot { path: PathBuf },
}
#[cfg(test)]
mod tests {
use super::{BuildDriver, CompileError, Compiler};
use crate::project::{Project, sample_project};
use crate::target::Target;
use std::fs;
use std::path::PathBuf;
fn scratch(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("npmgen-compile-{}-{tag}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
#[derive(Debug)]
struct NoopDriver;
impl BuildDriver for NoopDriver {
fn build(&self, _project: &Project, _target: &Target) -> Result<(), CompileError> {
Ok(())
}
}
#[derive(Debug)]
struct PlacingDriver;
impl BuildDriver for PlacingDriver {
fn build(&self, project: &Project, target: &Target) -> Result<(), CompileError> {
let path = target.binary_path(&project.target_directory, &project.bin);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, b"binary").unwrap();
Ok(())
}
}
#[test]
fn missing_binary_after_a_successful_build_is_an_error() {
let mut project = sample_project();
project.target_directory = scratch("missing");
project.bin = "tool".to_owned();
let target = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
let error = Compiler::new(&NoopDriver)
.compile_all(&project, std::slice::from_ref(&target))
.unwrap_err();
assert!(matches!(error, CompileError::BinaryMissing { .. }));
let _ = fs::remove_dir_all(&project.target_directory);
}
#[test]
fn verifies_a_placed_binary() {
let mut project = sample_project();
project.target_directory = scratch("placed");
project.bin = "tool".to_owned();
let target = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
Compiler::new(&PlacingDriver)
.compile_all(&project, std::slice::from_ref(&target))
.unwrap();
let _ = fs::remove_dir_all(&project.target_directory);
}
#[test]
fn a_missing_workspace_root_is_an_error() {
use super::CargoDriver;
let mut project = sample_project();
project.workspace_root = PathBuf::from("npmgen-nonexistent-workspace-root-xyz");
let target = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
let error = CargoDriver::new("cargo")
.build(&project, &target)
.unwrap_err();
assert!(matches!(error, CompileError::MissingWorkspaceRoot { .. }));
}
}