Skip to main content

upstream_rs/services/builder/profiles/
go.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 GoProfile;
12
13impl GoProfile {
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("go.mod").is_file() {
27            Some(workspace.to_path_buf())
28        } else {
29            None
30        }
31    }
32}
33
34impl BuildProfileHandler for GoProfile {
35    fn profile(&self) -> BuildProfile {
36        BuildProfile::Go
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 go.mod in repository root '{}'.",
52                workspace.display()
53            )
54        })?;
55
56        let artifact = project_dir
57            .join(".upstream-build")
58            .join(Self::binary_name(package_name));
59
60        if let Some(parent) = artifact.parent() {
61            std::fs::create_dir_all(parent).context(format!(
62                "Failed to create go output directory '{}'",
63                parent.display()
64            ))?;
65        }
66
67        emit_line_callback(line_callback, "Running go build -o <artifact> . ...");
68        let root_status = run_command_with_line_callback(
69            Command::new("go")
70                .arg("build")
71                .arg("-o")
72                .arg(&artifact)
73                .arg(".")
74                .current_dir(&project_dir),
75            "Failed to run 'go build -o <artifact> .'. Is Go installed?",
76            line_callback,
77        )?;
78
79        if !root_status.success() {
80            let cmd_target = format!("./cmd/{package_name}");
81            let context = format!(
82                "Failed to run fallback 'go build -o <artifact> {cmd_target}'. Is Go installed?"
83            );
84            emit_line_callback(
85                line_callback,
86                format!("Running go build -o <artifact> {cmd_target} ..."),
87            );
88            let cmd_status = run_command_with_line_callback(
89                Command::new("go")
90                    .arg("build")
91                    .arg("-o")
92                    .arg(&artifact)
93                    .arg(&cmd_target)
94                    .current_dir(&project_dir),
95                &context,
96                line_callback,
97            )?;
98
99            if !cmd_status.success() {
100                bail!(
101                    "Go build failed for '{}': both '.' and './cmd/{}' targets failed",
102                    package_name,
103                    package_name
104                );
105            }
106        }
107
108        if !artifact.exists() {
109            return Err(anyhow!(
110                "Go build succeeded but artifact was not found at '{}'",
111                artifact.display()
112            ));
113        }
114
115        Ok(artifact)
116    }
117}