greentic_dev/delegate/
packc.rs

1use std::ffi::OsString;
2
3use anyhow::{Context, Result, anyhow, bail};
4use which::which;
5
6use crate::config::GreenticConfig;
7use crate::util::process::{self, CommandOutput, CommandSpec, StreamMode};
8
9const TOOL_NAME: &str = "packc";
10
11pub struct PackcDelegate {
12    program: OsString,
13}
14
15impl PackcDelegate {
16    pub fn from_config(config: &GreenticConfig) -> Result<Self> {
17        let resolved = resolve_program(config)?;
18        Ok(Self {
19            program: resolved.program,
20        })
21    }
22
23    pub fn program(&self) -> &OsString {
24        &self.program
25    }
26
27    pub fn run_subcommand(&self, subcommand: &str, args: &[String]) -> Result<()> {
28        let mut argv = Vec::with_capacity(args.len() + 1);
29        argv.push(OsString::from(subcommand));
30        argv.extend(args.iter().map(OsString::from));
31        let output = self.exec(argv)?;
32        self.ensure_success(subcommand, &output)
33    }
34
35    fn exec(&self, args: Vec<OsString>) -> Result<CommandOutput> {
36        let mut spec = CommandSpec::new(self.program.clone());
37        spec.args = args;
38        spec.stdout = StreamMode::Inherit;
39        spec.stderr = StreamMode::Inherit;
40        process::run(spec)
41            .with_context(|| format!("failed to spawn `{}`", self.program.to_string_lossy()))
42    }
43
44    fn ensure_success(&self, label: &str, output: &CommandOutput) -> Result<()> {
45        if output.status.success() {
46            return Ok(());
47        }
48        let code = output.status.code().unwrap_or_default();
49        bail!(
50            "`{}` {label} failed with exit code {code}",
51            self.program.to_string_lossy()
52        );
53    }
54}
55
56struct ResolvedProgram {
57    program: OsString,
58}
59
60fn resolve_program(config: &GreenticConfig) -> Result<ResolvedProgram> {
61    if let Some(env_override) = std::env::var_os("GREENTIC_PACKC_PATH") {
62        let path = std::path::PathBuf::from(env_override);
63        if !path.exists() {
64            bail!(
65                "GREENTIC_PACKC_PATH points to `{}` but it does not exist",
66                path.display()
67            );
68        }
69        return Ok(ResolvedProgram {
70            program: path.into_os_string(),
71        });
72    }
73
74    if let Some(custom) = config
75        .tools
76        .packc_path
77        .path
78        .as_ref()
79        .or(config.tools.packc.path.as_ref())
80    {
81        if !custom.exists() {
82            bail!(
83                "configured packc path `{}` does not exist",
84                custom.display()
85            );
86        }
87        return Ok(ResolvedProgram {
88            program: custom.as_os_str().to_os_string(),
89        });
90    }
91
92    match which(TOOL_NAME) {
93        Ok(path) => Ok(ResolvedProgram {
94            program: path.into_os_string(),
95        }),
96        Err(error) => Err(anyhow!(
97            "packc (greentic-pack CLI) is required but was not found ({error}). Install \
98             greentic-pack and ensure `packc` is on your PATH, set GREENTIC_PACKC_PATH, or set \
99             [tools.packc].path in config."
100        )),
101    }
102}