use std::collections::BTreeMap;
use haz_domain::action::{ShellType, TaskAction};
use haz_domain::env::EnvVarName;
use haz_domain::name::{ProjectName, TaskName};
use crate::hasher::Hasher;
const TAG_COMMAND: u8 = 0x01;
const TAG_SHELL: u8 = 0x02;
const ENV_ABSENT_MARKER: u8 = 0x00;
pub struct InputFile<'a> {
pub workspace_absolute_path: &'a str,
pub content_hash: [u8; 32],
}
pub struct PredecessorStreams<'a> {
pub project: &'a ProjectName,
pub task: &'a TaskName,
pub stdout_hash: [u8; 32],
pub stderr_hash: [u8; 32],
}
pub struct EnvContribution<'a> {
pub from_host: &'a BTreeMap<EnvVarName, Option<String>>,
pub overrides: &'a BTreeMap<EnvVarName, String>,
}
pub fn contribute_action(hasher: &mut Hasher, action: &TaskAction) {
match action {
TaskAction::Command(argv) => {
hasher.update(&[TAG_COMMAND]);
let count =
u32::try_from(argv.len()).expect("argv length within u32::MAX is structural");
hasher.update(&count.to_be_bytes());
for arg in argv {
write_length_prefixed(hasher, arg.as_bytes());
}
}
TaskAction::Shell { script, shell } => {
hasher.update(&[TAG_SHELL]);
write_length_prefixed(hasher, shell_identifier(shell).as_bytes());
write_length_prefixed(hasher, script.as_bytes());
}
}
}
pub fn contribute_input_files(hasher: &mut Hasher, files: &[InputFile<'_>]) {
let mut sorted: Vec<&InputFile<'_>> = files.iter().collect();
sorted.sort_by(|a, b| {
a.workspace_absolute_path
.as_bytes()
.cmp(b.workspace_absolute_path.as_bytes())
});
let count =
u32::try_from(sorted.len()).expect("input-file count within u32::MAX is structural");
hasher.update(&count.to_be_bytes());
for f in sorted {
hasher.update(&f.content_hash);
write_length_prefixed(hasher, f.workspace_absolute_path.as_bytes());
}
}
pub fn contribute_predecessors(hasher: &mut Hasher, predecessors: &[PredecessorStreams<'_>]) {
let mut sorted: Vec<&PredecessorStreams<'_>> = predecessors.iter().collect();
sorted.sort_by(|a, b| {
let lhs = (
AsRef::<str>::as_ref(a.project.as_ref()).as_bytes(),
AsRef::<str>::as_ref(a.task.as_ref()).as_bytes(),
);
let rhs = (
AsRef::<str>::as_ref(b.project.as_ref()).as_bytes(),
AsRef::<str>::as_ref(b.task.as_ref()).as_bytes(),
);
lhs.cmp(&rhs)
});
let count =
u32::try_from(sorted.len()).expect("predecessor count within u32::MAX is structural");
hasher.update(&count.to_be_bytes());
for p in sorted {
write_length_prefixed(hasher, AsRef::<str>::as_ref(p.project.as_ref()).as_bytes());
write_length_prefixed(hasher, AsRef::<str>::as_ref(p.task.as_ref()).as_bytes());
hasher.update(&p.stdout_hash);
hasher.update(&p.stderr_hash);
}
}
pub fn contribute_env(hasher: &mut Hasher, env: &EnvContribution<'_>) {
let from_host_effective: Vec<(&EnvVarName, &Option<String>)> = env
.from_host
.iter()
.filter(|(name, _)| !env.overrides.contains_key(*name))
.collect();
let from_host_count = u32::try_from(from_host_effective.len())
.expect("env from_host count within u32::MAX is structural");
hasher.update(&from_host_count.to_be_bytes());
for (name, value) in &from_host_effective {
write_length_prefixed(hasher, AsRef::<str>::as_ref(name.as_ref()).as_bytes());
match value {
Some(v) => write_length_prefixed(hasher, v.as_bytes()),
None => hasher.update(&[ENV_ABSENT_MARKER]),
}
}
let override_count = u32::try_from(env.overrides.len())
.expect("env override count within u32::MAX is structural");
hasher.update(&override_count.to_be_bytes());
for (name, value) in env.overrides {
write_length_prefixed(hasher, AsRef::<str>::as_ref(name.as_ref()).as_bytes());
write_length_prefixed(hasher, value.as_bytes());
}
}
fn write_length_prefixed(hasher: &mut Hasher, bytes: &[u8]) {
let len = u32::try_from(bytes.len()).expect("item length within u32::MAX is structural");
hasher.update(&len.to_be_bytes());
hasher.update(bytes);
}
fn shell_identifier(shell: &ShellType) -> &str {
match shell {
ShellType::Sh => "sh",
ShellType::Bash => "bash",
ShellType::Other(name) => AsRef::<str>::as_ref(name.as_ref()),
}
}