1use anyhow::{anyhow, Context};
2use std::collections::HashMap;
3use std::path::PathBuf;
4use std::sync::Arc;
5use url::Url;
6
7use crate::executors::{
8 AsyncTask, AsyncTaskExecutor, BlockingTaskExecutor, CustomCommandExecutor, Executor,
9 HttpRemoteExecDomain, HttpRemoteExecutor, TaskFn, WasiExecutor,
10};
11use crate::metadata::Tag;
12use crate::{ArenaId, FileId, FileType, Razel, ScheduleState};
13
14pub struct Command {
15 pub id: CommandId,
16 pub name: String,
17 pub executables: Vec<FileId>,
19 pub inputs: Vec<FileId>,
21 pub outputs: Vec<FileId>,
22 pub deps: Vec<CommandId>,
24 pub executor: Executor,
25 pub tags: Vec<Tag>,
26 pub is_excluded: bool,
27 pub unfinished_deps: Vec<CommandId>,
29 pub reverse_deps: Vec<CommandId>,
31 pub schedule_state: ScheduleState,
33}
34
35pub type CommandId = ArenaId<Command>;
36
37pub struct CommandBuilder {
38 name: String,
39 args_with_out_paths: Vec<String>,
40 executables: Vec<FileId>,
41 inputs: Vec<FileId>,
42 outputs: Vec<FileId>,
43 stdout_file: Option<PathBuf>,
44 stderr_file: Option<PathBuf>,
45 deps: Vec<CommandId>,
46 executor: Option<Executor>,
47 tags: Vec<Tag>,
48}
49
50impl CommandBuilder {
51 pub fn new(name: String, args: Vec<String>, tags: Vec<Tag>) -> CommandBuilder {
52 CommandBuilder {
53 name,
54 args_with_out_paths: args,
55 executables: vec![],
56 inputs: vec![],
57 outputs: vec![],
58 stdout_file: None,
59 stderr_file: None,
60 deps: vec![],
61 executor: None,
62 tags,
63 }
64 }
65
66 fn map_out_path(&mut self, original: &String, mapped: &str) {
67 self.args_with_out_paths.iter_mut().for_each(|x| {
68 if x == original {
69 *x = mapped.to_owned()
70 }
71 });
72 }
73
74 pub fn input(&mut self, path: &String, razel: &mut Razel) -> Result<PathBuf, anyhow::Error> {
75 razel.input_file(path.clone()).map(|file| {
76 self.map_out_path(path, file.path.to_str().unwrap());
77 self.inputs.push(file.id);
78 file.path.clone()
79 })
80 }
81
82 pub fn inputs(
83 &mut self,
84 paths: &[String],
85 razel: &mut Razel,
86 ) -> Result<Vec<PathBuf>, anyhow::Error> {
87 self.inputs.reserve(paths.len());
88 paths
89 .iter()
90 .map(|path| {
91 let file = razel.input_file(path.clone())?;
92 self.map_out_path(path, file.path.to_str().unwrap());
93 self.inputs.push(file.id);
94 Ok(file.path.clone())
95 })
96 .collect()
97 }
98
99 pub fn output(
100 &mut self,
101 path: &String,
102 file_type: FileType,
103 razel: &mut Razel,
104 ) -> Result<PathBuf, anyhow::Error> {
105 razel.output_file(path, file_type).map(|file| {
106 self.map_out_path(path, file.path.to_str().unwrap());
107 self.outputs.push(file.id);
108 file.path.clone()
109 })
110 }
111
112 pub fn outputs(
113 &mut self,
114 paths: &[String],
115 razel: &mut Razel,
116 ) -> Result<Vec<PathBuf>, anyhow::Error> {
117 self.outputs.reserve(paths.len());
118 paths
119 .iter()
120 .map(|path| {
121 let file = razel.output_file(path, FileType::OutputFile)?;
122 self.map_out_path(path, file.path.to_str().unwrap());
123 self.outputs.push(file.id);
124 Ok(file.path.clone())
125 })
126 .collect()
127 }
128
129 pub fn stdout(&mut self, path: &String, razel: &mut Razel) -> Result<(), anyhow::Error> {
130 let file = razel.output_file(path, FileType::OutputFile)?;
131 self.outputs.push(file.id);
132 self.stdout_file = Some(file.path.clone());
133 Ok(())
134 }
135
136 pub fn stderr(&mut self, path: &String, razel: &mut Razel) -> Result<(), anyhow::Error> {
137 let file = razel.output_file(path, FileType::OutputFile)?;
138 self.outputs.push(file.id);
139 self.stderr_file = Some(file.path.clone());
140 Ok(())
141 }
142
143 pub fn dep(&mut self, command_name: &String, razel: &mut Razel) -> Result<(), anyhow::Error> {
144 let command_id = razel
145 .get_command_by_name(command_name)
146 .with_context(|| anyhow!("unknown command for dep: {command_name}"))?;
147 self.deps.push(command_id.id);
148 Ok(())
149 }
150
151 pub fn custom_command_executor(
152 &mut self,
153 executable: String,
154 env: HashMap<String, String>,
155 razel: &mut Razel,
156 ) -> Result<(), anyhow::Error> {
157 let file = razel.executable(executable)?;
158 self.executables.push(file.id);
159 self.executor = Some(Executor::CustomCommand(CustomCommandExecutor {
160 executable: file.executable_for_command_line(),
161 args: self.args_with_out_paths.clone(),
162 env,
163 stdout_file: self.stdout_file.clone(),
164 stderr_file: self.stderr_file.clone(),
165 timeout: self.tags.iter().find_map(|t| {
166 if let Tag::Timeout(x) = t {
167 Some(*x)
168 } else {
169 None
170 }
171 }),
172 }));
173 Ok(())
174 }
175
176 pub fn wasi_executor(
177 &mut self,
178 executable: String,
179 env: HashMap<String, String>,
180 razel: &mut Razel,
181 ) -> Result<(), anyhow::Error> {
182 let mut read_dirs = vec![];
183 for id in &self.inputs {
184 let dir = razel.get_file_path(*id).parent().unwrap().to_path_buf();
185 if !read_dirs.contains(&dir) {
186 read_dirs.push(dir);
187 }
188 }
189 let file = razel.wasi_module(executable)?;
190 let write_dir = (self.outputs.len()
191 - self.stdout_file.is_some() as usize
192 - self.stderr_file.is_some() as usize)
193 != 0;
194 self.executables.push(file.id);
195 self.executor = Some(Executor::Wasi(WasiExecutor {
196 module: None,
197 module_file_id: Some(file.id),
198 executable: file.executable_for_command_line(),
199 args: self.args_with_out_paths.clone(),
200 env,
201 stdout_file: self.stdout_file.clone(),
202 stderr_file: self.stderr_file.clone(),
203 read_dirs,
204 write_dir,
205 }));
206 Ok(())
207 }
208
209 pub fn async_task_executor(&mut self, task: impl AsyncTask + Send + Sync + 'static) {
210 self.executor = Some(Executor::AsyncTask(AsyncTaskExecutor {
211 task: Arc::new(task),
212 args: self.args_with_out_paths.clone(),
213 }));
214 }
215
216 pub fn blocking_task_executor(&mut self, f: TaskFn) {
217 self.executor = Some(Executor::BlockingTask(BlockingTaskExecutor {
218 f,
219 args: self.args_with_out_paths.clone(),
220 }));
221 }
222
223 pub fn http_remote_executor(
224 &mut self,
225 state: Option<Arc<HttpRemoteExecDomain>>,
226 url: Url,
227 files: Vec<(String, PathBuf)>,
228 ) {
229 self.executor = Some(Executor::HttpRemote(HttpRemoteExecutor {
230 args: self.args_with_out_paths.clone(),
231 state,
232 url,
233 files,
234 }));
235 }
236
237 pub fn build(self, id: CommandId) -> Command {
238 Command {
239 id,
240 name: self.name,
241 executables: self.executables,
242 inputs: self.inputs,
243 outputs: self.outputs,
244 deps: self.deps,
245 executor: self.executor.unwrap(),
246 tags: self.tags,
247 is_excluded: false,
248 unfinished_deps: vec![],
249 reverse_deps: vec![],
250 schedule_state: ScheduleState::New,
251 }
252 }
253}