xvc_pipeline/pipeline/deps/
generic.rs

1//! A generic dependency that's invalidated when the given command's output has changed.
2use crate::error::Error;
3use crate::{Result, XvcDependency};
4use serde::{Deserialize, Serialize};
5use subprocess::Exec;
6use xvc_core::types::diff::Diffable;
7use xvc_core::{Diff, HashAlgorithm, StdoutDigest};
8use xvc_core::persist;
9
10#[derive(Debug, PartialOrd, Ord, Clone, Eq, PartialEq, Serialize, Deserialize)]
11/// A generic dependency that's invalidated when the given command's output has changed.
12pub struct GenericDep {
13    /// The command that the step runs to check its output
14    pub generic_command: String,
15    /// The output digest collected from the command output
16    pub output_digest: Option<StdoutDigest>,
17}
18
19persist!(GenericDep, "generic-dependency");
20
21impl From<GenericDep> for XvcDependency {
22    fn from(val: GenericDep) -> Self {
23        XvcDependency::Generic(val)
24    }
25}
26
27impl GenericDep {
28    /// Create a new generic dependency with the specified command
29    pub fn new(generic_command: String) -> Self {
30        Self {
31            generic_command,
32            output_digest: None,
33        }
34    }
35
36    /// Run the command and update the output digest
37    pub fn update_output_digest(self) -> Result<Self> {
38        let generic_command = self.generic_command;
39
40        let command_output = Exec::shell(generic_command.clone()).capture()?;
41        let stdout = String::from_utf8(command_output.stdout)?;
42        let stderr = String::from_utf8(command_output.stderr)?;
43        let algorithm = HashAlgorithm::Blake3;
44        let return_code = command_output.exit_status;
45        if !stderr.is_empty() || !return_code.success() {
46            Err(Error::ProcessError { stdout, stderr })
47        } else {
48            Ok(Self {
49                output_digest: Some(StdoutDigest::new(&stdout, algorithm)),
50                generic_command,
51            })
52        }
53    }
54}
55
56impl Diffable for GenericDep {
57    type Item = GenericDep;
58
59    /// Always use the command output for the diff.
60    fn diff_superficial(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
61        Self::diff_thorough(record, actual)
62    }
63
64    /// Compare the command and the output.
65    /// WARN: Self::update_output_digest() must be called before this method.
66    fn diff_thorough(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
67        if record == actual {
68            Diff::Identical
69        } else {
70            Diff::Different {
71                record: record.clone(),
72                actual: actual.clone(),
73            }
74        }
75    }
76}