choir_differential/
choir-differential.rs1use std::path::Path;
9
10use choir_queue::differential::run_merged_vs_parents;
11use choir_queue::differential_ledger::{
12 adjudicate, effective_environment, environment_hash, load_command, record_observation, refresh,
13 Revisions,
14};
15
16fn usage() -> ! {
17 eprintln!(
18 "usage: choir-differential run <command-file> <state-dir> <parent-a-oid> <parent-a-dir> <parent-b-oid> <parent-b-dir> <merged-oid> <merged-dir>\n choir-differential adjudicate <command-file> <state-dir> <observation-id> real|spurious\n choir-differential refresh <command-file> <state-dir>"
19 );
20 std::process::exit(2);
21}
22
23fn run(args: &[String]) -> Result<serde_json::Value, String> {
24 let [command_file, state_dir, parent_a_oid, parent_a_dir, parent_b_oid, parent_b_dir, merged_oid, merged_dir] =
25 args
26 else {
27 usage();
28 };
29 let command = load_command(Path::new(command_file))?;
30 let environment = effective_environment(&command.env);
31 let report = run_merged_vs_parents(
32 &command.program,
33 &command.args,
34 Path::new(parent_a_dir),
35 Path::new(parent_b_dir),
36 Path::new(merged_dir),
37 &environment,
38 command.timeout_seconds.map(std::time::Duration::from_secs),
39 )?;
40 let revisions = Revisions {
41 parent_a: parent_a_oid.clone(),
42 parent_b: parent_b_oid.clone(),
43 merged: merged_oid.clone(),
44 };
45 let recorded = record_observation(
46 Path::new(state_dir),
47 &command.snapshot_hash,
48 &environment_hash(&environment),
49 &revisions,
50 &report,
51 )?;
52 Ok(serde_json::json!({
53 "format_version": 1,
54 "observation_id": recorded.observation_id,
55 "merge": merged_oid,
56 "report": recorded.report.to_json(),
57 "calibration": recorded.calibration,
58 }))
59}
60
61fn adjudicate_one(args: &[String]) -> Result<serde_json::Value, String> {
62 let [command_file, state_dir, observation_id, verdict] = args else {
63 usage();
64 };
65 let command = load_command(Path::new(command_file))?;
66 let observation_id = observation_id
67 .parse::<u64>()
68 .map_err(|_| "observation id must be an integer".to_string())?;
69 let real = match verdict.as_str() {
70 "real" => true,
71 "spurious" => false,
72 _ => return Err("adjudication must be real or spurious".to_string()),
73 };
74 adjudicate(
75 Path::new(state_dir),
76 &command.snapshot_hash,
77 observation_id,
78 real,
79 )
80}
81
82fn refresh_one(args: &[String]) -> Result<serde_json::Value, String> {
83 let [command_file, state_dir] = args else {
84 usage();
85 };
86 let command = load_command(Path::new(command_file))?;
87 refresh(Path::new(state_dir), &command.snapshot_hash)
88}
89
90fn main() {
91 let args: Vec<String> = std::env::args().skip(1).collect();
92 let Some((command, rest)) = args.split_first() else {
93 usage();
94 };
95 let result = match command.as_str() {
96 "run" => run(rest),
97 "adjudicate" => adjudicate_one(rest),
98 "refresh" => refresh_one(rest),
99 _ => usage(),
100 };
101 match result {
102 Ok(value) => println!("{value}"),
103 Err(error) => {
104 eprintln!("choir-differential: {error}");
105 std::process::exit(1);
106 }
107 }
108}