1use std::{
2 num::NonZeroU32,
3 path::{Path, PathBuf},
4 str::FromStr,
5};
6
7use either::Either;
8use flams_math_archives::{
9 backend::AnyBackend,
10 formats::{BuildSpec, BuildTargetId, TaskDependency, TaskRef},
11};
12use flams_utils::{
13 triomphe::Arc,
14 vecmap::{VecMap, VecSet},
15};
16use ftml_ontology::utils::time::Eta;
17use ftml_uris::{ArchiveId, ArchiveUri, DocumentUri, Language, ModuleUri, UriPath, UriWithArchive};
18use parking_lot::RwLock;
19
20mod queue;
21pub mod queue_manager;
22pub use queue::QueueName;
23mod queueing;
24
25pub(crate) static BUILD_QUEUE_SPAN: std::sync::LazyLock<tracing::Span> = std::sync::LazyLock::new(
26 || tracing::info_span!(target:"build queue",parent:None,"Build Queue"),
27);
28
29#[cfg(all(test, feature = "tokio"))]
30mod tests;
31
32pub use queue::Queue;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
35pub enum TaskState {
36 Running,
37 Queued,
38 Blocked,
39 Done,
40 Failed,
41 None,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum Dependency {
46 Physical {
47 task: TaskRef,
48 strict: bool,
49 },
50 Logical {
51 uri: ModuleUri,
52 strict: bool,
53 },
54 Resolved {
55 task: BuildTask,
56 step: BuildTargetId,
57 strict: bool,
58 },
59}
60impl From<TaskDependency> for Dependency {
61 fn from(value: TaskDependency) -> Self {
62 match value {
63 TaskDependency::Logical { uri, strict } => Self::Logical { uri, strict },
64 TaskDependency::Physical { task, strict } => Self::Physical { task, strict },
65 }
66 }
67}
68
69#[derive(Copy, Clone, Debug, PartialEq, Eq)]
70pub struct BuildTaskId(NonZeroU32);
71impl From<BuildTaskId> for u32 {
72 #[inline]
73 fn from(id: BuildTaskId) -> Self {
74 id.0.get()
75 }
76}
77
78#[derive(Debug, PartialEq, Eq)]
79struct BuildTaskI {
80 id: BuildTaskId,
81 uri: DocumentUri,
82 steps: Box<[BuildStep]>,
83 source: Either<PathBuf, String>,
84 rel_path: UriPath,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct BuildTask(Arc<BuildTaskI>);
89impl BuildTask {
90 #[inline]
91 pub fn new(
93 id: BuildTaskId,
94 archive: ArchiveUri,
95 steps: Box<[BuildStep]>,
96 source: Either<PathBuf, String>,
97 rel_path: UriPath,
98 ) -> eyre::Result<Self> {
99 let uri = DocumentUri::from_archive_relpath(archive, rel_path.as_ref())
100 .map_err(eyre::Report::new)?;
101 Ok(Self(Arc::new(BuildTaskI {
102 uri,
103 id,
104 steps,
105 source,
106 rel_path,
107 })))
108 }
109
110 #[must_use]
111 #[inline]
112 pub fn document_uri(&self) -> &DocumentUri {
113 &self.0.uri
114 }
115
116 #[must_use]
117 pub fn as_build_spec<'a>(&'a self, backend: &'a AnyBackend) -> BuildSpec<'a> {
118 BuildSpec {
119 uri: &self.0.uri,
120 source: self.source(),
121 backend,
122 rel_path: self.rel_path(),
123 }
124 }
125
126 #[must_use]
127 pub fn as_task_ref(&self, target: BuildTargetId) -> TaskRef {
128 TaskRef {
129 archive: self.0.uri.archive_id().clone(),
130 rel_path: self.0.rel_path.clone(),
131 target,
132 }
133 }
134
135 #[inline]
136 #[must_use]
137 pub fn source(&self) -> Either<&Path, &str> {
138 match &self.0.source {
139 Either::Left(p) => Either::Left(p),
140 Either::Right(s) => Either::Right(s),
141 }
142 }
143
144 #[inline]
145 #[must_use]
146 pub fn archive(&self) -> &ArchiveUri {
147 self.0.uri.archive_uri()
148 }
149
150 #[inline]
151 #[must_use]
152 pub fn rel_path(&self) -> &UriPath {
153 &self.0.rel_path
154 }
155
156 #[inline]
157 #[must_use]
158 pub fn steps(&self) -> &[BuildStep] {
159 &self.0.steps
160 }
161
162 #[inline]
163 #[must_use]
164 pub fn get_step(&self, target: BuildTargetId) -> Option<&BuildStep> {
165 self.0.steps.iter().find(|s| s.0.target == target)
166 }
167
168 #[must_use]
169 #[allow(clippy::cast_possible_truncation)]
170 pub fn as_message(&self) -> QueueEntry {
171 QueueEntry {
177 id: self.0.id,
178 archive: self.0.uri.archive_id().clone(),
179 rel_path: self.0.rel_path.clone(),
180 steps: self
181 .steps()
182 .iter()
183 .map(|s| (s.0.target, *s.0.state.read()))
184 .collect(),
185 }
186 }
187}
188
189#[derive(Debug)]
190struct BuildStepI {
191 target: BuildTargetId,
193 state: RwLock<TaskState>,
194 requires: RwLock<VecSet<Dependency>>,
196 dependents: RwLock<Vec<(BuildTaskId, BuildTargetId)>>,
197}
198impl PartialEq for BuildStepI {
199 fn eq(&self, other: &Self) -> bool {
200 self.target == other.target
201 }
202}
203impl Eq for BuildStepI {}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub struct BuildStep(Arc<BuildStepI>);
207impl BuildStep {
208 pub fn add_dependency(&self, dep: Dependency) {
209 self.0.requires.write().insert(dep);
210 }
211 }
218#[derive(Debug, Clone)]
273pub struct QueueEntry {
274 pub id: BuildTaskId,
275 pub archive: ArchiveId,
276 pub rel_path: UriPath,
277 pub steps: VecMap<BuildTargetId, TaskState>,
278}
279
280#[derive(Debug, Clone)]
281pub enum QueueMessage {
282 Idle(Vec<QueueEntry>),
283 Started {
284 running: Vec<QueueEntry>,
285 queue: Vec<QueueEntry>,
286 blocked: Vec<QueueEntry>,
287 failed: Vec<QueueEntry>,
288 done: Vec<QueueEntry>,
289 },
290 Finished {
291 failed: Vec<QueueEntry>,
292 done: Vec<QueueEntry>,
293 },
294 TaskStarted {
295 id: BuildTaskId,
296 target: BuildTargetId,
297 },
298 TaskSuccess {
299 id: BuildTaskId,
300 target: BuildTargetId,
301 eta: Eta,
302 },
303 TaskFailed {
304 id: BuildTaskId,
305 target: BuildTargetId,
306 eta: Eta,
307 },
308}