artifactize_core/project/
status.rs1mod states;
2mod view;
3pub use states::{ArtifactCondition, EvalCondition, VerifyAction};
4#[cfg(test)]
5mod tests;
6
7use std::{
8 collections::{BTreeMap, BTreeSet},
9 path::Path,
10};
11
12use serde::Serialize;
13use tokio_util::sync::CancellationToken;
14
15use crate::{
16 cache,
17 config::{Profile, read_workspace_config},
18 graph::{Evidence, Graph, Readiness},
19 project::{VerifyOptions, selection::select_profiles},
20 remote::Session,
21 store::{self, Claim, LastRequest},
22};
23
24use super::selection::Selection;
25
26#[derive(Debug, Serialize)]
27#[serde(rename_all = "camelCase")]
28pub struct StatusView {
29 pub selection: Selection,
30 pub recursive: bool,
31 pub force: bool,
32 pub selected_eval_ids: Vec<crate::config::EvalId>,
33 pub included_eval_ids: Vec<crate::config::EvalId>,
34 pub satisfied: bool,
35 pub obligations: Vec<crate::config::ArtifactName>,
36 pub artifacts: Vec<ArtifactState>,
37 pub evals: Vec<EvalState>,
38 pub counts: Counts,
39 #[serde(skip_serializing_if = "Vec::is_empty")]
40 pub unreadable: Vec<store::Unreadable>,
41}
42
43#[derive(Debug, Serialize)]
44#[serde(rename_all = "camelCase")]
45pub struct ArtifactState {
46 pub id: crate::config::ArtifactName,
47 pub kind: crate::config::ArtifactKind,
48 #[serde(with = "crate::platform::path_serde")]
49 pub path: std::path::PathBuf,
50 pub tags: Vec<String>,
51 pub state: ArtifactCondition,
52 pub reason: String,
53 pub eval_ids: Vec<crate::config::EvalId>,
54 pub passed: usize,
55 pub total: usize,
56 pub satisfied: bool,
57 pub obligations: Vec<crate::config::ArtifactName>,
58}
59
60#[derive(Debug, Serialize)]
61#[serde(rename_all = "camelCase")]
62pub struct EvalState {
63 pub id: crate::config::EvalId,
64 pub target: crate::config::ArtifactName,
65 pub title: String,
66 pub profile: Profile,
67 pub selected: bool,
68 pub included: bool,
69 pub force: bool,
70 pub state: EvalCondition,
71 pub action: VerifyAction,
72 pub reason: String,
73 pub blocked_by: Vec<crate::store::Blocker>,
74 pub obligations: Vec<crate::config::ArtifactName>,
75 pub eval_def_hash: crate::types::DefinitionHash,
77 pub fingerprint: Option<crate::types::Fingerprint>,
79 pub fingerprints: BTreeMap<crate::config::ArtifactName, Option<crate::types::Fingerprint>>,
81 pub key: Option<crate::types::ReuseKey>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub last: Option<LastRequest>,
85 #[serde(skip_serializing_if = "Option::is_none")]
88 pub changes: Option<cache::Changes>,
89}
90
91#[derive(Debug, Default, Serialize)]
92pub struct Counts {
93 pub artifacts: BTreeMap<ArtifactCondition, usize>,
94 pub evals: BTreeMap<EvalCondition, usize>,
95 pub execute: usize,
96 pub reuse: usize,
97 pub wait: usize,
98 pub blocked: usize,
99}
100
101struct Snapshot<'a> {
103 required: BTreeSet<&'a crate::config::ArtifactName>,
104 selected_eval_ids: Vec<crate::config::EvalId>,
105 included_eval_ids: Vec<crate::config::EvalId>,
106 fingerprints: BTreeMap<&'a crate::config::ArtifactName, cache::PreparedFingerprint>,
107 all_keys: BTreeMap<&'a crate::config::EvalId, cache::Key>,
108 cached: BTreeMap<crate::config::EvalId, Claim>,
109 previous: BTreeMap<(crate::config::EvalId, crate::types::DefinitionHash), store::Execution>,
110 latest: BTreeMap<crate::config::EvalId, LastRequest>,
111 waiting: Vec<store::RequestView>,
112 unreadable: Vec<store::Unreadable>,
113}
114
115pub async fn status(
117 repo: &Path,
118 state_dir: Option<&Path>,
119 selection: &Selection,
120 options: &VerifyOptions,
121 cancellation: CancellationToken,
122) -> Result<StatusView, String> {
123 let parallelism = super::fingerprint_parallelism(options)?;
124 let config = read_workspace_config(repo).map_err(|error| error.to_string())?;
125 let config = select_profiles(
126 config,
127 selection,
128 options.profile.as_ref(),
129 options.recursive,
130 )?;
131 let graph = Graph::new(&config).map_err(|error| error.to_string())?;
132 let selected = selection.resolve(&config)?;
133 let required: BTreeSet<_> = graph
134 .dependency_closure(&selected.roots)
135 .map_err(|error| error.to_string())?
136 .into_iter()
137 .collect();
138 let selected_eval_ids: Vec<_> = selected.evals.iter().map(|eval| eval.id.clone()).collect();
139 let included_eval_ids: Vec<_> = selection
140 .included_evals(&config, options.recursive)?
141 .iter()
142 .map(|eval| eval.id.clone())
143 .collect();
144 let state = store::state_dir(state_dir)?;
145 let latest = store::read_latest_requests(&state, &config.root).await?;
146 let selected_ids: BTreeSet<_> = selected_eval_ids.iter().collect();
147 let fingerprints = cache::prepare(
148 &config,
149 cache::fingerprint_targets(&config, &required),
150 &state,
151 ¶llelism,
152 cancellation.clone(),
153 )
154 .await?;
155 let all_keys = cache::eval_keys(&config, &fingerprints);
156 let keys: BTreeMap<_, _> = all_keys
157 .iter()
158 .filter(|(id, _)| !(options.force && selected_ids.contains(*id)))
159 .map(|(id, key)| (*id, key))
160 .collect();
161 let mut cached = store::read_profiled_executions(
162 &state,
163 config
164 .evals
165 .iter()
166 .filter_map(|eval| {
167 keys.get(&eval.id).map(|key| {
168 (
169 eval.id.clone(),
170 key.value.clone(),
171 eval.declaration.profile().into(),
172 )
173 })
174 })
175 .collect(),
176 )
177 .await?;
178 if !options.force
182 && let Some(remote) = Session::open(Some(&state), Some(&config.root))?
183 {
184 let all: Vec<_> = keys
185 .values()
186 .map(|key| key.value.clone())
187 .collect::<BTreeSet<_>>()
188 .into_iter()
189 .collect();
190 for execution in remote.lookup(&all).await? {
191 let key = execution.key.clone().expect("remote key");
192 for (id, _) in keys.iter().filter(|(_, candidate)| candidate.value == key) {
193 let newer = match cached.get(*id) {
194 Some(Claim::Reuse(local)) => execution.completed_after(local),
195 _ => true,
196 };
197 if newer {
198 cached.insert((*id).clone(), Claim::Reuse(Box::new(execution.clone())));
199 }
200 }
201 }
202 }
203 let claim = |id: &crate::types::EvalId| cached.get(id);
204 let stale: Vec<_> = keys
206 .iter()
207 .filter(|(id, _)| !matches!(claim(id), Some(Claim::Reuse(_))))
208 .map(|(id, key)| ((*id).clone(), key.eval_def_hash.clone()))
209 .collect();
210 let previous = store::read_latest_cached(&state, &stale).await?;
211 let waiting = store::read_waiting(&state, Some(&config.root)).await?;
212 let unreadable = store::read_unreadable(&state, Some(&config.root)).await?;
213 if cancellation.is_cancelled() {
214 return Err("Project preparation was cancelled.".into());
215 }
216 view::assemble(
217 &config,
218 &graph,
219 selection,
220 options,
221 Snapshot {
222 required,
223 selected_eval_ids,
224 included_eval_ids,
225 fingerprints,
226 all_keys,
227 cached,
228 previous,
229 latest,
230 waiting,
231 unreadable,
232 },
233 )
234}
235
236impl Counts {
237 fn action(&mut self, action: VerifyAction) {
238 match action {
239 VerifyAction::Execute => self.execute += 1,
240 VerifyAction::Reuse => self.reuse += 1,
241 VerifyAction::Wait => self.wait += 1,
242 VerifyAction::Blocked => self.blocked += 1,
243 }
244 }
245}