Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 3b2e6124ed817ecc17289ee72ec18e0f2209fddf
Timestamp: Sun, 28 Jan 2024 19:01:05 +0000 (1 year ago)

+77 -8 +/-3 browse
add option to tee build output for debugging purposes
add option to tee build output for debugging purposes

ayllu-build evaluate now supports a --tee-output flag which will write the
executor output to the caller's stdout/stderr for debugging purposes.
1diff --git a/ayllu-build/src/evaluate.rs b/ayllu-build/src/evaluate.rs
2index 892d03f..b47e2e4 100644
3--- a/ayllu-build/src/evaluate.rs
4+++ b/ayllu-build/src/evaluate.rs
5 @@ -111,6 +111,7 @@ impl Display for Unit {
6 }
7
8 #[derive(Clone)]
9+ #[allow(dead_code)]
10 pub enum Source {
11 // already loaded manifest for testing
12 Manifest(Manifest),
13 @@ -123,6 +124,7 @@ pub enum Source {
14 #[derive(Default)]
15 pub struct RuntimeBuilder {
16 parallelism: bool,
17+ tee_output: bool,
18 log_dir: Option<PathBuf>,
19 source: Option<Source>,
20 db: Option<Database>,
21 @@ -144,6 +146,12 @@ impl RuntimeBuilder {
22 self
23 }
24
25+ pub fn tee_output(mut self, tee_output: bool) -> RuntimeBuilder {
26+ self.tee_output = tee_output;
27+ self
28+ }
29+
30+ #[allow(dead_code)]
31 pub fn enable_parallelism(self, _enabled: bool) -> RuntimeBuilder {
32 todo!();
33 }
34 @@ -151,6 +159,7 @@ impl RuntimeBuilder {
35 pub fn build(&self) -> Runtime {
36 Runtime {
37 parallelism: self.parallelism,
38+ tee_output: self.tee_output,
39 db: self.db.clone(),
40 source: self.source.clone().unwrap(),
41 log_dir: self.log_dir.clone().unwrap_or(String::from("/tmp").into()),
42 @@ -158,8 +167,10 @@ impl RuntimeBuilder {
43 }
44 }
45
46+ #[allow(dead_code)]
47 pub(crate) struct Runtime {
48 parallelism: bool,
49+ tee_output: bool,
50 db: Option<Database>,
51 source: Source,
52 log_dir: PathBuf,
53 @@ -321,6 +332,7 @@ impl Runtime {
54 db.update_step_start(state.id(unit)).await?;
55 let executor = Local {
56 temp_dir: self.log_dir.clone(),
57+ tee_output: self.tee_output,
58 };
59 let mut ctx = Context::default();
60 // TODO: more context
61 diff --git a/ayllu-build/src/executor.rs b/ayllu-build/src/executor.rs
62index bca25f2..56371aa 100644
63--- a/ayllu-build/src/executor.rs
64+++ b/ayllu-build/src/executor.rs
65 @@ -1,11 +1,14 @@
66 use std::collections::HashMap;
67 use std::env;
68 use std::fs::{create_dir_all, metadata, read_to_string, File};
69- use std::io::{Read, Write};
70 use std::mem::take;
71 use std::path::{Path, PathBuf};
72 use std::process::{Command, ExitStatus, Stdio};
73 use std::thread;
74+ use std::{
75+ io,
76+ io::{Read, Write},
77+ };
78
79 use anyhow::Result;
80 use serde::Deserialize;
81 @@ -13,6 +16,24 @@ use tracing::log::{debug, info};
82
83 use crate::models::Step;
84
85+ /// standard stream when logging output
86+ enum Output {
87+ Stdout,
88+ Stderr,
89+ }
90+
91+ impl Output {
92+ fn enabled(self, tee: bool) -> Option<Self> {
93+ if !tee {
94+ return None;
95+ };
96+ match self {
97+ Output::Stdout => Some(Output::Stdout),
98+ Output::Stderr => Some(Output::Stderr),
99+ }
100+ }
101+ }
102+
103 // context exposed to the runtime of individual steps, also used in other
104 // parts of the build process.
105 #[derive(Deserialize, Debug, Clone, Default)]
106 @@ -32,9 +53,14 @@ pub trait Executor {
107 // build executor that runs with the same permissions as the build server.
108 pub struct Local {
109 pub temp_dir: PathBuf,
110+ pub tee_output: bool,
111 }
112
113- fn write_stream(mut stream: impl Read, filename: &Path) -> std::io::Result<()> {
114+ fn write_stream(
115+ mut stream: impl Read,
116+ filename: &Path,
117+ output: Option<Output>,
118+ ) -> std::io::Result<()> {
119 let mut file = File::create(filename)?;
120
121 let mut buf = [0u8; 1024];
122 @@ -45,6 +71,19 @@ fn write_stream(mut stream: impl Read, filename: &Path) -> std::io::Result<()> {
123 }
124
125 let buf = &buf[..num_read];
126+
127+ match output {
128+ Some(Output::Stdout) => {
129+ let mut stdout = io::stdout().lock();
130+ stdout.write_all(buf).expect("failed to copy stdout");
131+ }
132+ Some(Output::Stderr) => {
133+ let mut stderr = io::stderr().lock();
134+ stderr.write_all(buf).expect("failed to copy stderr");
135+ }
136+ None => {}
137+ };
138+
139 file.write_all(buf)?;
140 }
141
142 @@ -87,15 +126,25 @@ impl Executor for Local {
143 let stdout_log_path = temp_dir.clone().join("stdout.log");
144 info!("writing stdout to: {:?}", stdout_log_path);
145 let proc_stdout = take(&mut proc.stdout).expect("cannot get stdout");
146+ let tee_output = self.tee_output;
147 let stdout_thd = thread::spawn(move || {
148- write_stream(proc_stdout, stdout_log_path.as_path()).unwrap();
149+ write_stream(
150+ proc_stdout,
151+ stdout_log_path.as_path(),
152+ Output::Stdout.enabled(tee_output),
153+ )
154+ .unwrap();
155 });
156-
157 let stderr_log_path = temp_dir.clone().join("stderr.log");
158 info!("writing stderr to: {:?}", stderr_log_path);
159 let proc_stderr = take(&mut proc.stderr).expect("cannot get stderr");
160 let stderr_thd = thread::spawn(move || {
161- write_stream(proc_stderr, stderr_log_path.as_path()).unwrap();
162+ write_stream(
163+ proc_stderr,
164+ stderr_log_path.as_path(),
165+ Output::Stderr.enabled(tee_output),
166+ )
167+ .unwrap();
168 });
169
170 stdout_thd.join().unwrap();
171 @@ -119,6 +168,7 @@ mod tests {
172 async fn test_local_executor() {
173 let executor = Local {
174 temp_dir: Path::new("logs").to_path_buf(),
175+ tee_output: true,
176 };
177 let result = executor
178 .execute(
179 diff --git a/ayllu-build/src/main.rs b/ayllu-build/src/main.rs
180index 8d00605..5f5fafe 100644
181--- a/ayllu-build/src/main.rs
182+++ b/ayllu-build/src/main.rs
183 @@ -1,4 +1,4 @@
184- use std::io::{stderr, stdout};
185+ use std::io::stdout;
186 use std::path::{Path, PathBuf};
187 use std::str::FromStr;
188
189 @@ -6,7 +6,7 @@ use anyhow::Result;
190 use clap::{arg, Command, CommandFactory, Parser, Subcommand};
191 use clap_complete::{generate, Generator, Shell};
192 use tokio::task::LocalSet;
193- use tracing::{log::info, Level};
194+ use tracing::Level;
195
196 use ayllu_database::Builder;
197 mod config;
198 @@ -49,6 +49,9 @@ enum Commands {
199 /// Path to your configuration file
200 #[arg(short, long, value_name = "FILE")]
201 alternate_path: Option<PathBuf>,
202+ /// "Tee" output from the executor to your local stdout/stderr
203+ #[arg(short, long, action)]
204+ tee_output: bool,
205 },
206 /// generate an execution plan and output a DOT script
207 Plan {
208 @@ -96,7 +99,10 @@ async fn main() -> Result<()> {
209 .await?;
210 Ok(())
211 }
212- Commands::Evaluate { alternate_path } => {
213+ Commands::Evaluate {
214+ alternate_path,
215+ tee_output,
216+ } => {
217 // TODO fix me
218 let db = Builder::default()
219 .url(&cfg.builder.database.path)
220 @@ -109,6 +115,7 @@ async fn main() -> Result<()> {
221 ))
222 .database(db)
223 .log_dir(Path::new(&cfg.builder.log_path).to_path_buf())
224+ .tee_output(tee_output)
225 .build();
226 rt.eval().await?;
227 Ok(())