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 cargo_toml = match fs::read_to_string(&cargo_toml_path) {
37            Ok(contents) => contents,
38            Err(_) => return false,
39        };
40
41        let parsed = match toml::Value::from_str(&cargo_toml) {
42            Ok(value) => value,
43            Err(_) => return false,
44        };
45
46        parsed
47            .get("bin")
48            .and_then(toml::Value::as_array)
49            .is_some_and(|bins| bins.len() > 1)
50    }
51}
52
53impl BuildProfileHandler for RustProfile {
54    fn profile(&self) -> BuildProfile {
55        BuildProfile::Rust
56    }
57
58    fn detect(&self, workspace: &Path) -> bool {
59        Self::find_project_dir(workspace).is_some()
60    }
61
62    fn run_build(
63        &self,
64        workspace: &Path,
65        package_name: &str,
66        line_callback: &mut Option<&mut dyn FnMut(&str)>,
67    ) -> Result<PathBuf> {
68        let project_dir = Self::find_project_dir(workspace).ok_or_else(|| {
69            anyhow!(
70                "Could not find Cargo.toml in repository root '{}'.",
71                workspace.display()
72            )
73        })?;
74
75        let status = if Self::has_multiple_declared_bins(&project_dir) {
76            emit_line_callback(line_callback, "Running cargo build --release --bin ...");
77            run_command_with_line_callback(
78                Command::new("cargo")
79                    .arg("build")
80                    .arg("--release")
81                    .arg("--bin")
82                    .arg(package_name)
83                    .current_dir(&project_dir),
84                "Failed to run 'cargo build --release --bin <name>'. Is Cargo installed?",
85                line_callback,
86            )?
87        } else {
88            emit_line_callback(line_callback, "Running cargo build --release ...");
89            run_command_with_line_callback(
90                Command::new("cargo")
91                    .arg("build")
92                    .arg("--release")
93                    .current_dir(&project_dir),
94                "Failed to run 'cargo build --release'. Is Cargo installed?",
95                line_callback,
96            )?
97        };
98
99        if !status.success() {
100            bail!("Cargo build failed for '{}'", package_name);
101        }
102
103        let candidate = project_dir
104            .join("target")
105            .join("release")
106            .join(Self::binary_name(package_name));
107
108        if !candidate.exists() {
109            return Err(anyhow!(
110                "Rust build succeeded but artifact was not found at '{}'",
111                candidate.display()
112            ));
113        }
114
115        Ok(candidate)
116    }
117}