1mod fs_ops;
2mod handlers;
3mod io;
4mod pipe;
5mod state;
6mod steps;
7#[cfg(test)]
8mod tests;
9
10pub use self::io::ExecIo;
11
12use anyhow::Result;
13use oxdock_fs::{GuardedPath, PathResolver, WorkspaceFs};
14use oxdock_parser::{Step, TemplateString};
15use oxdock_process::{
16 BuiltinEnv, CommandContext, ProcessManager, SharedInput, SharedOutput, default_process_manager,
17 expand_command_env,
18};
19
20use std::sync::Arc;
21
22use self::fs_ops::describe_dir;
23use self::io::{StreamHandle, assemble_default_io, teed_stdout};
24use self::state::ExecState;
25use self::steps::execute_steps;
26
27fn expand_template(t: &TemplateString, ctx: &CommandContext) -> String {
28 expand_command_env(&t.0, ctx)
29}
30
31pub fn run_steps(fs_root: &GuardedPath, steps: &[Step]) -> Result<()> {
32 run_steps_with_context(fs_root, fs_root, steps)
33}
34
35pub fn run_steps_with_context(
36 fs_root: &GuardedPath,
37 build_context: &GuardedPath,
38 steps: &[Step],
39) -> Result<()> {
40 run_steps_with_context_result(fs_root, build_context, steps, None, None).map(|_| ())
41}
42
43pub fn run_steps_with_context_result(
45 fs_root: &GuardedPath,
46 build_context: &GuardedPath,
47 steps: &[Step],
48 stdin: Option<SharedInput>,
49 stdout: Option<SharedOutput>,
50) -> Result<GuardedPath> {
51 let io = assemble_default_io(stdin, stdout);
52 run_steps_with_context_result_with_io(fs_root, build_context, steps, io)
53}
54
55pub fn run_steps_with_context_result_with_io(
56 fs_root: &GuardedPath,
57 build_context: &GuardedPath,
58 steps: &[Step],
59 io: ExecIo,
60) -> Result<GuardedPath> {
61 match run_steps_inner(fs_root, build_context, steps, io) {
62 Ok(final_cwd) => Ok(final_cwd),
63 Err(err) => {
64 let chain = err.chain().map(|e| e.to_string()).collect::<Vec<_>>();
66 let mut primary = chain
67 .first()
68 .cloned()
69 .unwrap_or_else(|| "unknown error".into());
70 let rest = if chain.len() > 1 {
71 let first_cause = chain[1].clone();
72 primary = format!("{primary} ({first_cause})");
73 if chain.len() > 2 {
74 let causes = chain
75 .iter()
76 .skip(2)
77 .map(|s| s.as_str())
78 .collect::<Vec<_>>()
79 .join("\n ");
80 format!("\ncauses:\n {}", causes)
81 } else {
82 String::new()
83 }
84 } else {
85 String::new()
86 };
87 let fs = PathResolver::new(fs_root.as_path(), build_context.as_path())?;
88 let tree = describe_dir(&fs, fs_root, 2, 24);
89 let snapshot = format!(
90 "filesystem snapshot (root {}):\n{}",
91 fs_root.display(),
92 tree
93 );
94 let msg = format!("{}{}\n{}", primary, rest, snapshot);
95 Err(anyhow::anyhow!(msg))
96 }
97 }
98}
99
100fn run_steps_inner(
101 fs_root: &GuardedPath,
102 build_context: &GuardedPath,
103 steps: &[Step],
104 io: ExecIo,
105) -> Result<GuardedPath> {
106 let mut resolver = PathResolver::new_guarded(fs_root.clone(), build_context.clone())?;
107 resolver.set_workspace_root(build_context.clone());
108 run_steps_with_fs_with_io(Box::new(resolver), steps, io)
109}
110
111pub fn run_steps_with_fs(
112 fs: Box<dyn WorkspaceFs>,
113 steps: &[Step],
114 stdin: Option<SharedInput>,
115 stdout: Option<SharedOutput>,
116) -> Result<GuardedPath> {
117 let io = assemble_default_io(stdin, stdout);
118 run_steps_with_fs_with_io(fs, steps, io)
119}
120
121pub fn run_steps_with_fs_with_io(
122 fs: Box<dyn WorkspaceFs>,
123 steps: &[Step],
124 io: ExecIo,
125) -> Result<GuardedPath> {
126 run_steps_with_manager(fs, steps, default_process_manager(), io)
127}
128
129fn run_steps_with_manager<P: ProcessManager>(
130 fs: Box<dyn WorkspaceFs>,
131 steps: &[Step],
132 process: P,
133 io: ExecIo,
134) -> Result<GuardedPath> {
135 let fs_root = fs.root().clone();
136 let cwd = fs.root().clone();
137 let build_context = fs.build_context().clone();
138 let envs = Arc::new(BuiltinEnv::collect(&build_context).into_envs());
139 let stdout_log = Arc::new(std::sync::Mutex::new(Vec::new()));
140 let mut state = ExecState {
141 fs,
142 cargo_target_dir: fs_root.join(".cargo-target")?,
143 cwd,
144 envs,
145 bg_children: Vec::new(),
146 scope_stack: Vec::new(),
147 io,
148 stdout_log,
149 };
150
151 let _default_stdout = std::io::stdout();
152 let stdin = state.io.stdin();
153 let stdout = Some(StreamHandle::Stream(teed_stdout(
157 state.io.stdout(),
158 Arc::clone(&state.stdout_log),
159 )));
160 let stderr = state.io.stderr().map(StreamHandle::Stream);
161 let mut proc_mgr = process;
162 execute_steps(
163 &mut state,
164 &mut proc_mgr,
165 steps,
166 stdin,
167 false,
168 stdout,
169 stderr,
170 true,
171 )?;
172
173 Ok(state.cwd)
174}