From d020c0512a71ef31f4966609dfc4d61046750300 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sat, 21 Dec 2024 08:08:12 -0600 Subject: [PATCH] feat: added keep-order task output type (#3763) * chore: release 2024.12.16 (#3720) * feat: add `usage` field to tasks * feat: added keep-order task output type Fixes #2347 --------- Co-authored-by: mise-en-dev --- docs/cli/run.md | 6 ++- docs/cli/tasks/run.md | 6 ++- e2e/tasks/test_task_keep_order | 11 +++++ mise.usage.kdl | 4 +- schema/mise.json | 2 +- settings.toml | 12 +++++ src/cli/mod.rs | 7 +-- src/cli/run.rs | 83 +++++++++++++++++++++++++++++++--- src/cli/tasks/mod.rs | 4 +- src/cmd.rs | 63 ++++++++++++++------------ src/output.rs | 16 ++++--- 11 files changed, 160 insertions(+), 54 deletions(-) create mode 100644 e2e/tasks/test_task_keep_order diff --git a/docs/cli/run.md b/docs/cli/run.md index 137762c1f9..69a1eca238 100644 --- a/docs/cli/run.md +++ b/docs/cli/run.md @@ -93,7 +93,11 @@ Don't show any output except for errors Change how tasks information is output when running tasks -- `prefix` - Print stdout/stderr by line, prefixed with the task's label - `interleave` - Print directly to stdout/stderr instead of by line - `quiet` - Don't show extra output - `silent` - Don't show any output including stdout and stderr from the task except for errors +- `prefix` - Print stdout/stderr by line, prefixed with the task's label +- `interleave` - Print directly to stdout/stderr instead of by line +- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output +- `quiet` - Don't show extra output +- `silent` - Don't show any output including stdout and stderr from the task except for errors Examples: diff --git a/docs/cli/tasks/run.md b/docs/cli/tasks/run.md index be43fc5945..9ca27991ef 100644 --- a/docs/cli/tasks/run.md +++ b/docs/cli/tasks/run.md @@ -107,7 +107,11 @@ Don't show any output except for errors Change how tasks information is output when running tasks -- `prefix` - Print stdout/stderr by line, prefixed with the task's label - `interleave` - Print directly to stdout/stderr instead of by line - `quiet` - Don't show extra output - `silent` - Don't show any output including stdout and stderr from the task except for errors +- `prefix` - Print stdout/stderr by line, prefixed with the task's label +- `interleave` - Print directly to stdout/stderr instead of by line +- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output +- `quiet` - Don't show extra output +- `silent` - Don't show any output including stdout and stderr from the task except for errors Examples: diff --git a/e2e/tasks/test_task_keep_order b/e2e/tasks/test_task_keep_order new file mode 100644 index 0000000000..49a499eb88 --- /dev/null +++ b/e2e/tasks/test_task_keep_order @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +cat <mise.toml +tasks.a = "echo a" +tasks.b = "echo b" +tasks.c = "echo c" +tasks.all.depends = ['a', 'b', 'c'] +EOF +assert "mise run -o keep-order all" "[a] a +[b] b +[c] c" diff --git a/mise.usage.kdl b/mise.usage.kdl index dc69229110..3ef86983f4 100644 --- a/mise.usage.kdl +++ b/mise.usage.kdl @@ -1088,7 +1088,7 @@ The name of the script will be the name of the tasks. flag "-q --quiet" help="Don't show extra output" flag "-S --silent" help="Don't show any output except for errors" flag "-o --output" help="Change how tasks information is output when running tasks" { - long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label - `interleave` - Print directly to stdout/stderr instead of by line - `quiet` - Don't show extra output - `silent` - Don't show any output including stdout and stderr from the task except for errors" + long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label\n- `interleave` - Print directly to stdout/stderr instead of by line\n- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output\n- `quiet` - Don't show extra output\n- `silent` - Don't show any output including stdout and stderr from the task except for errors" arg "" } mount run="mise tasks --usage" @@ -1530,7 +1530,7 @@ The name of the script will be the name of the tasks. flag "-q --quiet" help="Don't show extra output" flag "-S --silent" help="Don't show any output except for errors" flag "-o --output" help="Change how tasks information is output when running tasks" { - long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label - `interleave` - Print directly to stdout/stderr instead of by line - `quiet` - Don't show extra output - `silent` - Don't show any output including stdout and stderr from the task except for errors" + long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label\n- `interleave` - Print directly to stdout/stderr instead of by line\n- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output\n- `quiet` - Don't show extra output\n- `silent` - Don't show any output including stdout and stderr from the task except for errors" arg "" } arg "[TASK]" help="Tasks to run\nCan specify multiple tasks by separating with `:::`\ne.g.: mise run task1 arg1 arg2 ::: task2 arg1 arg2" required=false default="default" diff --git a/schema/mise.json b/schema/mise.json index bcce563ee1..3164afdd35 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -839,7 +839,7 @@ "task_output": { "description": "Change output style when executing tasks.", "type": "string", - "enum": ["prefix", "interleave"] + "enum": ["prefix", "interleave", "keep-order", "quiet", "silent"] }, "task_run_auto_install": { "default": true, diff --git a/settings.toml b/settings.toml index 1619004b61..7ecba0d03c 100644 --- a/settings.toml +++ b/settings.toml @@ -972,6 +972,18 @@ enum = [ [ "interleave", "(default if jobs == 1 or all tasks run sequentially) print output as it comes in" + ], + [ + "keep-order", + "print output from tasks in the order they are defined" + ], + [ + "quiet", + "print only stdout/stderr from tasks and nothing from mise" + ], + [ + "silent", + "print nothing from tasks or mise" ] ] docs = """ diff --git a/src/cli/mod.rs b/src/cli/mod.rs index d5e2bd3a9a..c2aa04d533 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -78,7 +78,7 @@ pub enum LevelFilter { Error, } -#[derive(clap::Parser, Debug)] +#[derive(clap::Parser)] #[clap(name = "mise", about, long_about = LONG_ABOUT, after_long_help = AFTER_LONG_HELP, author = "Jeff Dickey <@jdx>", arg_required_else_help = true)] pub struct Cli { #[clap(subcommand)] @@ -160,7 +160,7 @@ pub struct Cli { pub global_output_flags: CliGlobalOutputFlags, } -#[derive(Debug, clap::Args)] +#[derive(clap::Args)] #[group(multiple = false)] pub struct CliGlobalOutputFlags { /// Sets log level to debug @@ -182,7 +182,7 @@ pub struct CliGlobalOutputFlags { pub verbose: u8, } -#[derive(Debug, Subcommand, strum::Display)] +#[derive(Subcommand, strum::Display)] #[strum(serialize_all = "kebab-case")] pub enum Commands { Activate(activate::Activate), @@ -388,6 +388,7 @@ impl Cli { timings: self.timings, tmpdir: Default::default(), tool: Default::default(), + keep_order_output: Default::default(), })); } else if let Some(cmd) = external::COMMANDS.get(&task) { external::execute( diff --git a/src/cli/run.rs b/src/cli/run.rs index 0720fee8ba..a782342d0f 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -27,6 +27,7 @@ use demand::{DemandOption, Select}; use duct::IntoExecutablePath; use eyre::{bail, ensure, eyre, Result}; use glob::glob; +use indexmap::IndexMap; use itertools::Itertools; #[cfg(unix)] use nix::sys::signal::SIGTERM; @@ -57,7 +58,7 @@ use xx::regex; /// npm run build /// EOF /// $ mise run build -#[derive(Debug, clap::Args)] +#[derive(clap::Args)] #[clap(visible_alias = "r", verbatim_doc_comment, disable_help_flag = true, after_long_help = AFTER_LONG_HELP)] pub struct Run { /// Tasks to run @@ -172,15 +173,21 @@ pub struct Run { /// /// - `prefix` - Print stdout/stderr by line, prefixed with the task's label /// - `interleave` - Print directly to stdout/stderr instead of by line + /// - `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output /// - `quiet` - Don't show extra output /// - `silent` - Don't show any output including stdout and stderr from the task except for errors - #[clap(short, long, env = "MISE_TASK_OUTPUT")] + #[clap(short, long, verbatim_doc_comment, env = "MISE_TASK_OUTPUT")] pub output: Option, #[clap(skip)] pub tmpdir: PathBuf, + + #[clap(skip)] + pub keep_order_output: Mutex>, } +type KeepOrderOutputs = (Vec<(String, String)>, Vec<(String, String)>); + impl Run { pub fn run(mut self) -> Result<()> { if self.task == "-h" { @@ -222,6 +229,12 @@ impl Run { let tasks = Deps::new(tasks)?; for task in tasks.all() { self.validate_task(task)?; + if self.output(Some(task)) == TaskOutput::KeepOrder { + self.keep_order_output + .lock() + .unwrap() + .insert(task.clone(), Default::default()); + } } let num_tasks = tasks.all().count(); @@ -311,7 +324,27 @@ impl Run { eprintln!("{}", style::edim(msg)); }; - time!("paralellize_tasks done"); + if self.output(None) == TaskOutput::KeepOrder { + // TODO: display these as tasks complete in order somehow rather than waiting until everything is done + let output = self.keep_order_output.lock().unwrap(); + for (out, err) in output.values() { + for (prefix, line) in out { + if console::colors_enabled() { + prefix_println!(prefix, "{line}\x1b[0m"); + } else { + prefix_println!(prefix, "{line}"); + } + } + for (prefix, line) in err { + if console::colors_enabled_stderr() { + prefix_eprintln!(prefix, "{line}\x1b[0m"); + } else { + prefix_eprintln!(prefix, "{line}"); + } + } + } + } + time!("parallelize_tasks done"); Ok(()) } @@ -538,7 +571,42 @@ impl Run { .raw(self.raw(Some(task))); cmd.with_pass_signals(); match self.output(Some(task)) { - TaskOutput::Prefix => cmd = cmd.prefix(prefix), + TaskOutput::Prefix => { + cmd = cmd.with_on_stdout(|line| { + if console::colors_enabled() { + prefix_println!(prefix, "{line}\x1b[0m"); + } else { + prefix_println!(prefix, "{line}"); + } + }); + cmd = cmd.with_on_stderr(|line| { + if console::colors_enabled() { + prefix_eprintln!(prefix, "{line}\x1b[0m"); + } else { + prefix_eprintln!(prefix, "{line}"); + } + }); + } + TaskOutput::KeepOrder => { + cmd = cmd.with_on_stdout(|line| { + self.keep_order_output + .lock() + .unwrap() + .get_mut(task) + .unwrap() + .0 + .push((prefix.to_string(), line)); + }); + cmd = cmd.with_on_stderr(|line| { + self.keep_order_output + .lock() + .unwrap() + .get_mut(task) + .unwrap() + .1 + .push((prefix.to_string(), line)); + }); + } TaskOutput::Silent => { cmd = cmd.stdout(Stdio::null()).stderr(Stdio::null()); } @@ -841,12 +909,13 @@ static AFTER_LONG_HELP: &str = color_print::cstr!( serde::Serialize, serde::Deserialize, )] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "kebab-case")] +#[strum(serialize_all = "kebab-case")] pub enum TaskOutput { + Interleave, + KeepOrder, #[default] Prefix, - Interleave, Quiet, Silent, } diff --git a/src/cli/tasks/mod.rs b/src/cli/tasks/mod.rs index 232275de97..9c4b2156f2 100644 --- a/src/cli/tasks/mod.rs +++ b/src/cli/tasks/mod.rs @@ -10,7 +10,7 @@ mod info; mod ls; /// Manage tasks -#[derive(Debug, clap::Args)] +#[derive(clap::Args)] #[clap(visible_alias = "t", alias = "task", verbatim_doc_comment)] pub struct Tasks { #[clap(subcommand)] @@ -23,7 +23,7 @@ pub struct Tasks { ls: ls::TasksLs, } -#[derive(Debug, Subcommand)] +#[derive(Subcommand)] enum Commands { Add(add::TasksAdd), Deps(deps::TasksDeps), diff --git a/src/cmd.rs b/src/cmd.rs index d7553579ea..12ad47a242 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -100,10 +100,11 @@ pub struct CmdLineRunner<'a> { cmd: Command, pr: Option<&'a dyn SingleReport>, stdin: Option, - prefix: String, redactions: IndexSet, raw: bool, pass_signals: bool, + on_stdout: Option>, + on_stderr: Option>, } static OUTPUT_LOCK: Mutex<()> = Mutex::new(()); @@ -127,10 +128,11 @@ impl<'a> CmdLineRunner<'a> { cmd, pr: None, stdin: None, - prefix: String::new(), redactions: Default::default(), raw: false, pass_signals: false, + on_stdout: None, + on_stderr: None, } } @@ -184,8 +186,13 @@ impl<'a> CmdLineRunner<'a> { self } - pub fn prefix(mut self, prefix: impl Into) -> Self { - self.prefix = prefix.into(); + pub fn with_on_stdout(mut self, on_stdout: F) -> Self { + self.on_stdout = Some(Box::new(on_stdout)); + self + } + + pub fn with_on_stderr(mut self, on_stderr: F) -> Self { + self.on_stderr = Some(Box::new(on_stderr)); self } @@ -347,10 +354,18 @@ impl<'a> CmdLineRunner<'a> { for line in rx { match line { ChildProcessOutput::Stdout(line) => { + let line = self + .redactions + .iter() + .fold(line, |acc, r| acc.replace(r, "[redacted]")); self.on_stdout(line.clone()); combined_output.push(line); } ChildProcessOutput::Stderr(line) => { + let line = self + .redactions + .iter() + .fold(line, |acc, r| acc.replace(r, "[redacted]")); self.on_stderr(line.clone()); combined_output.push(line); } @@ -387,35 +402,29 @@ impl<'a> CmdLineRunner<'a> { } } - fn on_stdout(&self, mut line: String) { - line = self - .redactions - .iter() - .fold(line, |acc, r| acc.replace(r, "[redacted]")); + fn on_stdout(&self, line: String) { let _lock = OUTPUT_LOCK.lock().unwrap(); + if let Some(on_stdout) = &self.on_stdout { + on_stdout(line); + return; + } if let Some(pr) = self.pr { if !line.trim().is_empty() { pr.set_message(line) } } else if console::colors_enabled() { - if self.prefix.is_empty() { - println!("{line}\x1b[0m"); - } else { - prefix_println!(self.prefix, "{line}\x1b[0m"); - } - } else if self.prefix.is_empty() { - println!("{line}"); + println!("{line}\x1b[0m"); } else { - prefix_println!(self.prefix, "{line}"); + println!("{line}"); } } - fn on_stderr(&self, mut line: String) { - line = self - .redactions - .iter() - .fold(line, |acc, r| acc.replace(r, "[redacted]")); + fn on_stderr(&self, line: String) { let _lock = OUTPUT_LOCK.lock().unwrap(); + if let Some(on_stderr) = &self.on_stderr { + on_stderr(line); + return; + } match self.pr { Some(pr) => { if !line.trim().is_empty() { @@ -424,15 +433,9 @@ impl<'a> CmdLineRunner<'a> { } None => { if console::colors_enabled_stderr() { - if self.prefix.is_empty() { - eprintln!("{line}\x1b[0m"); - } else { - prefix_eprintln!(self.prefix, "{line}\x1b[0m"); - } - } else if self.prefix.is_empty() { - eprintln!("{line}"); + eprintln!("{line}\x1b[0m"); } else { - prefix_eprintln!(self.prefix, "{line}"); + eprintln!("{line}"); } } } diff --git a/src/output.rs b/src/output.rs index f1e303f9a8..3d511061f5 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,13 +1,16 @@ +use regex::Regex; use std::collections::HashSet; -use std::sync::LazyLock as Lazy; +use std::sync::LazyLock; use std::sync::Mutex; +pub static UNNEST_RE: LazyLock = + LazyLock::new(|| Regex::new(r#"MISE_TASK_UNNEST:(\d+):MISE_TASK_UNNEST (.*)"#).unwrap()); + #[macro_export] macro_rules! prefix_println { ($prefix:expr, $($arg:tt)*) => {{ let msg = format!($($arg)*); - let re = xx::regex!(r#"MISE_TASK_UNNEST:(\d+):MISE_TASK_UNNEST (.*)"#); - if let Some(msg) = re.captures(&msg) { + if let Some(msg) = $crate::output::UNNEST_RE.captures(&msg) { let level = msg.get(1).unwrap().as_str().parse::().unwrap(); if level > 1 { println!("MISE_TASK_UNNEST:{}:MISE_TASK_UNNEST {}", level - 1, msg.get(2).unwrap().as_str()); @@ -23,8 +26,7 @@ macro_rules! prefix_println { macro_rules! prefix_eprintln { ($prefix:expr, $($arg:tt)*) => {{ let msg = format!($($arg)*); - let re = xx::regex!(r#"MISE_TASK_UNNEST:(\d+):MISE_TASK_UNNEST (.*)"#); - if let Some(msg) = re.captures(&msg) { + if let Some(msg) = $crate::output::UNNEST_RE.captures(&msg) { let level = msg.get(1).unwrap().as_str().parse::().unwrap(); if level > 1 { eprintln!("MISE_TASK_UNNEST:{}:MISE_TASK_UNNEST {}", level - 1, msg.get(2).unwrap().as_str()); @@ -149,7 +151,7 @@ macro_rules! warn { }}; } -pub static WARNED_ONCE: Lazy>> = Lazy::new(Default::default); +pub static WARNED_ONCE: LazyLock>> = LazyLock::new(Default::default); macro_rules! warn_once { ($($arg:tt)*) => {{ let msg = format!($($arg)*); @@ -167,7 +169,7 @@ macro_rules! error { }}; } -pub static DEPRECATED: Lazy>> = Lazy::new(Default::default); +pub static DEPRECATED: LazyLock>> = LazyLock::new(Default::default); #[macro_export] macro_rules! deprecated {