greentic_dev/delegate/
packc.rs1use 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}