upstream-rs 2.5.0

Fetch package updates directly from the source.
Documentation
use std::path::{Path, PathBuf};
use std::process::Command;

use anyhow::{Context, Result, anyhow, bail};

use crate::routines::builder::{
    BuildProfile,
    profiles::{BuildProfileHandler, emit_line_callback, run_command_with_line_callback},
};

pub struct GoProfile;

impl GoProfile {
    fn binary_name(package_name: &str) -> String {
        #[cfg(windows)]
        {
            format!("{package_name}.exe")
        }
        #[cfg(not(windows))]
        {
            package_name.to_string()
        }
    }

    fn find_project_dir(workspace: &Path) -> Option<PathBuf> {
        if workspace.join("go.mod").is_file() {
            Some(workspace.to_path_buf())
        } else {
            None
        }
    }
}

impl BuildProfileHandler for GoProfile {
    fn profile(&self) -> BuildProfile {
        BuildProfile::Go
    }

    fn detect(&self, workspace: &Path) -> bool {
        Self::find_project_dir(workspace).is_some()
    }

    fn run_build(
        &self,
        workspace: &Path,
        package_name: &str,
        line_callback: &mut Option<&mut dyn FnMut(&str)>,
    ) -> Result<PathBuf> {
        let project_dir = Self::find_project_dir(workspace).ok_or_else(|| {
            anyhow!(
                "Could not find go.mod in repository root '{}'.",
                workspace.display()
            )
        })?;

        let artifact = project_dir
            .join(".upstream-build")
            .join(Self::binary_name(package_name));

        if let Some(parent) = artifact.parent() {
            std::fs::create_dir_all(parent).context(format!(
                "Failed to create go output directory '{}'",
                parent.display()
            ))?;
        }

        emit_line_callback(line_callback, "Running go build -o <artifact> . ...");
        let root_status = run_command_with_line_callback(
            Command::new("go")
                .arg("build")
                .arg("-o")
                .arg(&artifact)
                .arg(".")
                .current_dir(&project_dir),
            "Failed to run 'go build -o <artifact> .'. Is Go installed?",
            line_callback,
        )?;

        if !root_status.success() {
            let cmd_target = format!("./cmd/{package_name}");
            let context = format!(
                "Failed to run fallback 'go build -o <artifact> {cmd_target}'. Is Go installed?"
            );
            emit_line_callback(
                line_callback,
                format!("Running go build -o <artifact> {cmd_target} ..."),
            );
            let cmd_status = run_command_with_line_callback(
                Command::new("go")
                    .arg("build")
                    .arg("-o")
                    .arg(&artifact)
                    .arg(&cmd_target)
                    .current_dir(&project_dir),
                &context,
                line_callback,
            )?;

            if !cmd_status.success() {
                bail!(
                    "Go build failed for '{}': both '.' and './cmd/{}' targets failed",
                    package_name,
                    package_name
                );
            }
        }

        if !artifact.exists() {
            return Err(anyhow!(
                "Go build succeeded but artifact was not found at '{}'",
                artifact.display()
            ));
        }

        Ok(artifact)
    }
}