use std::path::{Path, PathBuf};
use std::process::Command;
use crate::cli::BuildMode;
use crate::error::{DealerError, DealerResult, process_output_message};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ToolSource {
pub(crate) fn as_metadata_value(self) -> &'static str {
Self::PinnedToolchain => "pinned_toolchain",
Self::DevelopmentFallback => "development_fallback",
fn label(self) -> &'static str {
Self::PinnedToolchain => "pinned xtazy toolchain",
Self::DevelopmentFallback => "development fallback",
pub(crate) struct RustBackend {
pub(crate) cargo_path: PathBuf,
pub(crate) source: ToolSource,
pub(crate) fn pinned_toolchain(path: impl Into<PathBuf>) -> Self {
source: ToolSource::PinnedToolchain,
pub(crate) fn development_system() -> Self {
Self::from_cargo_path("cargo")
pub(crate) fn from_cargo_path(path: impl Into<PathBuf>) -> Self {
source: ToolSource::DevelopmentFallback,
pub(crate) fn build(&self, rust_dir: &Path, mode: BuildMode) -> DealerResult<()> {
let mut command = Command::new(&self.cargo_path);
command.args(["build", "--quiet"]);
if mode == BuildMode::Prod {
command.arg("--release");
let output = command.current_dir(rust_dir).output().map_err(|error| {
DealerError::Backend(format!(
"failed to start {} Cargo backend '{}' in '{}': {error}",
self.cargo_path.display(),
if output.status.success() {
Err(DealerError::Backend(process_output_message(
&format!("{} Cargo backend", self.source.label()),
use crate::test_support::TempProject;
fn development_backend_is_explicit_system_cargo_fallback() {
let backend = RustBackend::development_system();
assert_eq!(backend.cargo_path, PathBuf::from("cargo"));
assert_eq!(backend.source, ToolSource::DevelopmentFallback);
fn backend_reports_missing_cargo_executable() {
let temp = TempProject::new("missing-cargo-backend");
let backend = RustBackend::from_cargo_path(temp.path().join("missing-cargo"));
.build(temp.path(), BuildMode::Dev)
.expect_err("missing cargo executable should fail");
let message = error.to_string();
assert!(message.contains("failed to start development fallback Cargo backend"));
assert!(message.contains("missing-cargo"));
fn backend_reports_rust_build_failure_output() {
let temp = TempProject::new("cargo-build-failure");
fs::create_dir_all(temp.path().join("src")).expect("src dir should be created");
temp.path().join("Cargo.toml"),
"[workspace]\n\n[package]\nname = \"dealer_backend_failure\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
.expect("Cargo.toml should be written");
fs::write(temp.path().join("src/main.rs"), "fn main() { let = ; }\n")
.expect("invalid main.rs should be written");
let error = RustBackend::development_system()
.build(temp.path(), BuildMode::Dev)
.expect_err("invalid generated Rust should fail");
let message = error.to_string();
message.contains("dealer_backend_failure"),
"expected Cargo/Rust error output, got:\n{}",