npmgen_core/compile/
mod.rs1mod cargo;
9mod driver;
10
11pub use cargo::CargoDriver;
12pub use driver::BuildDriver;
13
14use std::path::PathBuf;
15use std::process::ExitStatus;
16
17use crate::project::Project;
18use crate::target::Target;
19
20#[derive(Debug)]
22pub struct Compiler<'a> {
23 driver: &'a dyn BuildDriver,
24}
25
26impl<'a> Compiler<'a> {
27 pub fn new(driver: &'a dyn BuildDriver) -> Self {
28 Self { driver }
29 }
30
31 pub fn compile_all(&self, project: &Project, targets: &[Target]) -> Result<(), CompileError> {
34 for target in targets {
35 self.driver.build(project, target)?;
36 let path = target.binary_path(&project.target_directory, &project.bin);
37 if !path.is_file() {
38 return Err(CompileError::BinaryMissing {
39 triple: target.triple.clone(),
40 path,
41 });
42 }
43 }
44 Ok(())
45 }
46}
47
48#[derive(Debug, thiserror::Error)]
50pub enum CompileError {
51 #[error("spawning build driver {driver:?}")]
52 Spawn {
53 driver: String,
54 #[source]
55 source: std::io::Error,
56 },
57
58 #[error("build for {triple} failed: {status}")]
59 BuildFailed { triple: String, status: ExitStatus },
60
61 #[error("binary for {triple} not found after a successful build: {}", path.display())]
62 BinaryMissing { triple: String, path: PathBuf },
63
64 #[error("build driver {driver:?} must be a bare command name on PATH, not a path")]
65 InvalidDriver { driver: String },
66
67 #[error("workspace root does not exist: {}", path.display())]
68 MissingWorkspaceRoot { path: PathBuf },
69}
70
71#[cfg(test)]
72mod tests {
73 use super::{BuildDriver, CompileError, Compiler};
74 use crate::project::{Project, sample_project};
75 use crate::target::Target;
76 use std::fs;
77 use std::path::PathBuf;
78
79 fn scratch(tag: &str) -> PathBuf {
80 let dir = std::env::temp_dir().join(format!("npmgen-compile-{}-{tag}", std::process::id()));
81 let _ = fs::remove_dir_all(&dir);
82 fs::create_dir_all(&dir).unwrap();
83 dir
84 }
85
86 #[derive(Debug)]
88 struct NoopDriver;
89 impl BuildDriver for NoopDriver {
90 fn build(&self, _project: &Project, _target: &Target) -> Result<(), CompileError> {
91 Ok(())
92 }
93 }
94
95 #[derive(Debug)]
97 struct PlacingDriver;
98 impl BuildDriver for PlacingDriver {
99 fn build(&self, project: &Project, target: &Target) -> Result<(), CompileError> {
100 let path = target.binary_path(&project.target_directory, &project.bin);
101 fs::create_dir_all(path.parent().unwrap()).unwrap();
102 fs::write(&path, b"binary").unwrap();
103 Ok(())
104 }
105 }
106
107 #[test]
108 fn missing_binary_after_a_successful_build_is_an_error() {
109 let mut project = sample_project();
110 project.target_directory = scratch("missing");
111 project.bin = "tool".to_owned();
112 let target = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
113
114 let error = Compiler::new(&NoopDriver)
115 .compile_all(&project, std::slice::from_ref(&target))
116 .unwrap_err();
117 assert!(matches!(error, CompileError::BinaryMissing { .. }));
118 let _ = fs::remove_dir_all(&project.target_directory);
119 }
120
121 #[test]
122 fn verifies_a_placed_binary() {
123 let mut project = sample_project();
124 project.target_directory = scratch("placed");
125 project.bin = "tool".to_owned();
126 let target = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
127
128 Compiler::new(&PlacingDriver)
129 .compile_all(&project, std::slice::from_ref(&target))
130 .unwrap();
131 let _ = fs::remove_dir_all(&project.target_directory);
132 }
133
134 #[test]
135 fn a_missing_workspace_root_is_an_error() {
136 use super::CargoDriver;
137 let mut project = sample_project();
138 project.workspace_root = PathBuf::from("npmgen-nonexistent-workspace-root-xyz");
139 let target = Target::from_triple("x86_64-unknown-linux-gnu").unwrap();
140 let error = CargoDriver::new("cargo")
141 .build(&project, &target)
142 .unwrap_err();
143 assert!(matches!(error, CompileError::MissingWorkspaceRoot { .. }));
144 }
145}