Skip to main content

upstream_rs/services/builder/profiles/
cmake.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use anyhow::{Context, Result, anyhow, bail};
5
6use crate::services::builder::{
7    BuildProfile,
8    profiles::{BuildProfileHandler, emit_line_callback, run_command_with_line_callback},
9};
10
11pub struct CmakeProfile;
12
13impl CmakeProfile {
14    fn binary_name(package_name: &str) -> String {
15        #[cfg(windows)]
16        {
17            format!("{package_name}.exe")
18        }
19        #[cfg(not(windows))]
20        {
21            package_name.to_string()
22        }
23    }
24
25    fn find_project_dir(workspace: &Path) -> Option<PathBuf> {
26        if workspace.join("CMakeLists.txt").is_file() {
27            Some(workspace.to_path_buf())
28        } else {
29            None
30        }
31    }
32}
33
34impl BuildProfileHandler for CmakeProfile {
35    fn profile(&self) -> BuildProfile {
36        BuildProfile::Cmake
37    }
38
39    fn detect(&self, workspace: &Path) -> bool {
40        Self::find_project_dir(workspace).is_some()
41    }
42
43    fn run_build(
44        &self,
45        workspace: &Path,
46        package_name: &str,
47        line_callback: &mut Option<&mut dyn FnMut(&str)>,
48    ) -> Result<PathBuf> {
49        let project_dir = Self::find_project_dir(workspace).ok_or_else(|| {
50            anyhow!(
51                "Could not find CMakeLists.txt in repository root '{}'.",
52                workspace.display()
53            )
54        })?;
55
56        let build_dir = project_dir.join(".upstream-build").join("cmake");
57        std::fs::create_dir_all(&build_dir).context(format!(
58            "Failed to create CMake build directory '{}'",
59            build_dir.display()
60        ))?;
61
62        emit_line_callback(line_callback, "Running cmake configure ...");
63        let configure = run_command_with_line_callback(
64            Command::new("cmake")
65                .arg("-S")
66                .arg(&project_dir)
67                .arg("-B")
68                .arg(&build_dir)
69                .arg("-DCMAKE_BUILD_TYPE=Release")
70                .current_dir(&project_dir),
71            "Failed to run 'cmake -S . -B <build-dir> -DCMAKE_BUILD_TYPE=Release'. Is CMake installed?",
72            line_callback,
73        )?;
74
75        if !configure.success() {
76            bail!("CMake configure failed for '{}'", package_name);
77        }
78
79        emit_line_callback(line_callback, "Running cmake build ...");
80        let build = run_command_with_line_callback(
81            Command::new("cmake")
82                .arg("--build")
83                .arg(&build_dir)
84                .arg("--config")
85                .arg("Release")
86                .current_dir(&project_dir),
87            "Failed to run 'cmake --build <build-dir> --config Release'. Is CMake installed?",
88            line_callback,
89        )?;
90
91        if !build.success() {
92            bail!("CMake build failed for '{}'", package_name);
93        }
94
95        let artifact = build_dir.join(Self::binary_name(package_name));
96
97        if !artifact.exists() {
98            return Err(anyhow!(
99                "CMake build succeeded but artifact was not found at '{}'",
100                artifact.display()
101            ));
102        }
103
104        Ok(artifact)
105    }
106}