Skip to main content

upstream_rs/services/builder/profiles/
rust.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3use std::{fs, str::FromStr};
4
5use anyhow::{Result, anyhow, bail};
6
7use crate::services::builder::{
8    BuildProfile,
9    profiles::{BuildProfileHandler, emit_line_callback, run_command_with_line_callback},
10};
11
12pub struct RustProfile;
13
14impl RustProfile {
15    fn binary_name(package_name: &str) -> String {
16        #[cfg(windows)]
17        {
18            format!("{package_name}.exe")
19        }
20        #[cfg(not(windows))]
21        {
22            package_name.to_string()
23        }
24    }
25
26    fn find_project_dir(workspace: &Path) -> Option<PathBuf> {
27        if workspace.join("Cargo.toml").is_file() {
28            Some(workspace.to_path_buf())
29        } else {
30            None
31        }
32    }
33
34    fn has_multiple_declared_bins(project_dir: &Path) -> bool {
35        let cargo_toml_path = project_dir.join("Cargo.toml");
36        let Ok(cargo_toml) = fs::read_to_string(&cargo_toml_path) else {
37            return false;
38        };
39
40        let Ok(parsed) = toml::Value::from_str(&cargo_toml) else {
41            return false;
42        };
43
44        parsed
45            .get("bin")
46            .and_then(toml::Value::as_array)
47            .is_some_and(|bins| bins.len() > 1)
48    }
49}
50
51impl BuildProfileHandler for RustProfile {
52    fn profile(&self) -> BuildProfile {
53        BuildProfile::Rust
54    }
55
56    fn detect(&self, workspace: &Path) -> bool {
57        Self::find_project_dir(workspace).is_some()
58    }
59
60    fn run_build(
61        &self,
62        workspace: &Path,
63        package_name: &str,
64        line_callback: &mut Option<&mut dyn FnMut(&str)>,
65    ) -> Result<PathBuf> {
66        let project_dir = Self::find_project_dir(workspace).ok_or_else(|| {
67            anyhow!(
68                "Could not find Cargo.toml in repository root '{}'.",
69                workspace.display()
70            )
71        })?;
72
73        let status = if Self::has_multiple_declared_bins(&project_dir) {
74            emit_line_callback(line_callback, "Running cargo build --release --bin ...");
75            run_command_with_line_callback(
76                Command::new("cargo")
77                    .arg("build")
78                    .arg("--release")
79                    .arg("--bin")
80                    .arg(package_name)
81                    .current_dir(&project_dir),
82                "Failed to run 'cargo build --release --bin <name>'. Is Cargo installed?",
83                line_callback,
84            )?
85        } else {
86            emit_line_callback(line_callback, "Running cargo build --release ...");
87            run_command_with_line_callback(
88                Command::new("cargo")
89                    .arg("build")
90                    .arg("--release")
91                    .current_dir(&project_dir),
92                "Failed to run 'cargo build --release'. Is Cargo installed?",
93                line_callback,
94            )?
95        };
96
97        if !status.success() {
98            bail!("Cargo build failed for '{}'", package_name);
99        }
100
101        let candidate = project_dir
102            .join("target")
103            .join("release")
104            .join(Self::binary_name(package_name));
105
106        if !candidate.exists() {
107            return Err(anyhow!(
108                "Rust build succeeded but artifact was not found at '{}'",
109                candidate.display()
110            ));
111        }
112
113        Ok(candidate)
114    }
115}