Skip to main content

dealer/
backend.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use crate::cli::BuildMode;
5use crate::error::{DealerError, DealerResult, process_output_message};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub(crate) enum ToolSource {
9    PinnedToolchain,
10    DevelopmentFallback,
11}
12
13impl ToolSource {
14    pub(crate) fn as_metadata_value(self) -> &'static str {
15        match self {
16            Self::PinnedToolchain => "pinned_toolchain",
17            Self::DevelopmentFallback => "development_fallback",
18        }
19    }
20
21    fn label(self) -> &'static str {
22        match self {
23            Self::PinnedToolchain => "pinned Xtazy toolchain",
24            Self::DevelopmentFallback => "development fallback",
25        }
26    }
27}
28
29#[derive(Debug, Clone)]
30pub(crate) struct RustBackend {
31    pub(crate) cargo_path: PathBuf,
32    pub(crate) source: ToolSource,
33}
34
35impl RustBackend {
36    pub(crate) fn pinned_toolchain(path: impl Into<PathBuf>) -> Self {
37        Self {
38            cargo_path: path.into(),
39            source: ToolSource::PinnedToolchain,
40        }
41    }
42
43    pub(crate) fn development_system() -> Self {
44        Self::from_cargo_path("cargo")
45    }
46
47    pub(crate) fn from_cargo_path(path: impl Into<PathBuf>) -> Self {
48        Self {
49            cargo_path: path.into(),
50            source: ToolSource::DevelopmentFallback,
51        }
52    }
53
54    pub(crate) fn build(&self, rust_dir: &Path, mode: BuildMode) -> DealerResult<()> {
55        let mut command = Command::new(&self.cargo_path);
56        command.args(["build", "--quiet"]);
57        if mode == BuildMode::Prod {
58            command.arg("--release");
59        }
60
61        let output = command.current_dir(rust_dir).output().map_err(|error| {
62            DealerError::Backend(format!(
63                "failed to start {} Cargo backend '{}' in '{}': {error}",
64                self.source.label(),
65                self.cargo_path.display(),
66                rust_dir.display()
67            ))
68        })?;
69
70        if output.status.success() {
71            return Ok(());
72        }
73
74        Err(DealerError::Backend(process_output_message(
75            output,
76            &format!("{} Cargo backend", self.source.label()),
77        )))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::test_support::TempProject;
85    use std::fs;
86
87    #[test]
88    fn development_backend_is_explicit_system_cargo_fallback() {
89        let backend = RustBackend::development_system();
90
91        assert_eq!(backend.cargo_path, PathBuf::from("cargo"));
92        assert_eq!(backend.source, ToolSource::DevelopmentFallback);
93    }
94
95    #[test]
96    fn backend_reports_missing_cargo_executable() {
97        let temp = TempProject::new("missing-cargo-backend");
98        let backend = RustBackend::from_cargo_path(temp.path().join("missing-cargo"));
99
100        let error = backend
101            .build(temp.path(), BuildMode::Dev)
102            .expect_err("missing cargo executable should fail");
103
104        let message = error.to_string();
105        assert!(message.contains("failed to start development fallback Cargo backend"));
106        assert!(message.contains("missing-cargo"));
107    }
108
109    #[test]
110    fn backend_reports_rust_build_failure_output() {
111        let temp = TempProject::new("cargo-build-failure");
112        fs::create_dir_all(temp.path().join("src")).expect("src dir should be created");
113        fs::write(
114            temp.path().join("Cargo.toml"),
115            "[workspace]\n\n[package]\nname = \"dealer_backend_failure\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
116        )
117        .expect("Cargo.toml should be written");
118        fs::write(temp.path().join("src/main.rs"), "fn main() { let = ; }\n")
119            .expect("invalid main.rs should be written");
120
121        let error = RustBackend::development_system()
122            .build(temp.path(), BuildMode::Dev)
123            .expect_err("invalid generated Rust should fail");
124        let message = error.to_string();
125
126        assert!(
127            message.contains("dealer_backend_failure"),
128            "expected Cargo/Rust error output, got:\n{}",
129            error
130        );
131    }
132}