razel/
command.rs

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    /// user specified executable and optionally runtimes, e.g. razel for WASI
18    pub executables: Vec<FileId>,
19    /// input files excluding <Self::executables>
20    pub inputs: Vec<FileId>,
21    pub outputs: Vec<FileId>,
22    /// dependencies on other commands in addition to input files
23    pub deps: Vec<CommandId>,
24    pub executor: Executor,
25    pub tags: Vec<Tag>,
26    pub is_excluded: bool,
27    /// dependencies which are not yet finished successfully
28    pub unfinished_deps: Vec<CommandId>,
29    /// commands which depend on this command
30    pub reverse_deps: Vec<CommandId>,
31    /// TODO remove, Scheduler should keep track of states
32    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}