Skip to content

Commit 4845014

Browse files
committed
Standardize CMD execution and display
Making progress towards closing #762. Doesn't quite do everything, but does a lot.
1 parent 3b140fc commit 4845014

File tree

6 files changed

+54
-185
lines changed

6 files changed

+54
-185
lines changed

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

buildpacks/maven/src/main.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -210,19 +210,9 @@ impl Buildpack for MavenBuildpack {
210210
let internal_maven_options = vec![String::from("-B")];
211211

212212
output::print_section("Running Maven build");
213-
output::print_subsection(BuildpackOutputText::new(vec![
214-
BuildpackOutputTextSection::regular("Running "),
215-
BuildpackOutputTextSection::command(format!(
216-
"{} {} {}",
217-
mvn_executable.to_string_lossy(),
218-
shell_words::join(&maven_options),
219-
shell_words::join(&maven_goals)
220-
)),
221-
]));
222213

223-
output::track_timing(|| {
214+
{
224215
let mut command = Command::new(&mvn_executable);
225-
226216
command
227217
.current_dir(&context.app_dir)
228218
.args(
@@ -238,8 +228,8 @@ impl Buildpack for MavenBuildpack {
238228
false,
239229
MavenBuildpackError::MavenBuildIoError,
240230
|output| MavenBuildpackError::MavenBuildUnexpectedExitCode(output.status),
241-
)
242-
})?;
231+
)?
232+
};
243233

244234
output::print_section(BuildpackOutputText::new(vec![
245235
BuildpackOutputTextSection::regular("Running "),

buildpacks/sbt/src/main.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ use libherokubuildpack::error::on_error as on_buildpack_error;
2323
use std::process::Command;
2424

2525
use buildpacks_jvm_shared::output;
26-
use buildpacks_jvm_shared::output::{BuildpackOutputText, BuildpackOutputTextSection};
2726
#[cfg(test)]
2827
use buildpacks_jvm_shared_test as _;
2928
#[cfg(test)]
@@ -105,12 +104,8 @@ impl Buildpack for SbtBuildpack {
105104

106105
let tasks = sbt::tasks::from_config(&buildpack_configuration);
107106

108-
output::track_timing(|| {
107+
{
109108
output::print_section("Running sbt build");
110-
output::print_subsection(BuildpackOutputText::new(vec![
111-
BuildpackOutputTextSection::regular("Running "),
112-
BuildpackOutputTextSection::command(format!("sbt {}", shell_words::join(&tasks))),
113-
]));
114109

115110
let mut command = Command::new("sbt");
116111
command.current_dir(&context.app_dir).args(tasks).envs(&env);
@@ -126,7 +121,7 @@ impl Buildpack for SbtBuildpack {
126121
)
127122
},
128123
)
129-
})?;
124+
}?;
130125

131126
BuildResultBuilder::new().build()
132127
}

shared/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ workspace = true
1010
indoc = "2"
1111
java-properties = "2"
1212
libherokubuildpack = { version = "=0.26.0", default-features = false, features = ["log", "command", "write"] }
13+
bullet_stream = "0.4.0"
14+
fun_run = { version = "0.2.0" }

shared/src/output.rs

Lines changed: 28 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
use libherokubuildpack::command::CommandExt;
2-
use libherokubuildpack::write::line_mapped;
1+
use bullet_stream::global::print;
2+
use bullet_stream::style;
3+
use fun_run::CommandWithName;
34
use std::process::{Command, Output};
45
use std::time::{Duration, Instant};
56

67
pub fn print_buildpack_name(buildpack_name: impl AsRef<str>) {
7-
let buildpack_name = buildpack_name.as_ref();
8-
print!("\n{ANSI_BUILDPACK_NAME_CODE}# {buildpack_name}{ANSI_RESET_CODE}\n\n");
8+
print::h2(buildpack_name);
99
}
1010

1111
pub fn print_section(text: impl Into<BuildpackOutputText>) {
12-
let text = text.into().to_ansi_string();
13-
println!("{ANSI_RESET_CODE}- {text}");
12+
print::bullet(text.into().to_ansi_string());
1413
}
1514

1615
pub fn print_subsection(text: impl Into<BuildpackOutputText>) {
17-
let text = text.into().to_ansi_string();
18-
println!("{ANSI_RESET_CODE} - {text}");
16+
print::sub_bullet(text.into().to_ansi_string());
1917
}
2018

2119
pub fn print_timing_done_subsection(duration: &Duration) {
@@ -24,42 +22,19 @@ pub fn print_timing_done_subsection(duration: &Duration) {
2422

2523
pub fn print_warning(title: impl AsRef<str>, body: impl Into<BuildpackOutputText>) {
2624
let title = title.as_ref();
27-
28-
let mut sections = vec![BuildpackOutputTextSection::regular(format!(
29-
"WARNING: {title}\n\n"
30-
))];
31-
32-
let mut body = body.into();
33-
sections.append(&mut body.sections);
34-
35-
let text = BuildpackOutputText {
36-
default_code: Some(String::from(ANSI_YELLOW_CODE)),
37-
line_prefix: Some(String::from("! ")),
38-
sections,
39-
..BuildpackOutputText::default()
40-
};
41-
42-
eprintln!("{}", text.to_ansi_string());
25+
print::warning(format!(
26+
"WARNING: {title}\n\n{}",
27+
body.into().to_ansi_string()
28+
));
4329
}
4430

4531
pub fn print_error(title: impl AsRef<str>, body: impl Into<BuildpackOutputText>) {
4632
let title = title.as_ref();
4733

48-
let mut sections = vec![BuildpackOutputTextSection::regular(format!(
49-
"ERROR: {title}\n\n"
50-
))];
51-
52-
let mut body = body.into();
53-
sections.append(&mut body.sections);
54-
55-
let text = BuildpackOutputText {
56-
default_code: Some(String::from(ANSI_RED_CODE)),
57-
line_prefix: Some(String::from(ERROR_WARNING_LINE_PREFIX)),
58-
sections,
59-
..BuildpackOutputText::default()
60-
};
61-
62-
eprintln!("{}", text.to_ansi_string());
34+
print::error(format!(
35+
"ERROR: {title}\n\n{}",
36+
body.into().to_ansi_string()
37+
));
6338
}
6439

6540
pub fn run_command<E, F: FnOnce(std::io::Error) -> E, F2: FnOnce(Output) -> E>(
@@ -68,47 +43,25 @@ pub fn run_command<E, F: FnOnce(std::io::Error) -> E, F2: FnOnce(Output) -> E>(
6843
io_error_fn: F,
6944
exit_status_fn: F2,
7045
) -> Result<Output, E> {
71-
let child = if quiet {
72-
command.output_and_write_streams(std::io::sink(), std::io::sink())
46+
let title = format!("Running {}", style::value(command.name()));
47+
if quiet {
48+
let _timer = print::sub_start_timer(title);
49+
command.named_output()
7350
} else {
74-
const SPACE_ASCII: u8 = 0x20;
75-
let prefix = vec![SPACE_ASCII; 6];
76-
77-
println!();
78-
79-
let output = command.output_and_write_streams(
80-
line_mapped(std::io::stdout(), add_prefix_to_non_empty(prefix.clone())),
81-
line_mapped(std::io::stderr(), add_prefix_to_non_empty(prefix)),
82-
);
83-
84-
println!();
85-
86-
output
87-
};
88-
89-
child.map_err(io_error_fn).and_then(|output| {
90-
if output.status.success() {
91-
Ok(output)
92-
} else {
93-
Err(exit_status_fn(output))
51+
print::sub_stream_with(&title, |stdout, stderr| {
52+
command.stream_output(stdout, stderr)
53+
})
54+
}
55+
.map(Into::<Output>::into)
56+
.map_err(|o| match o {
57+
fun_run::CmdError::SystemError(_, error) => io_error_fn(error),
58+
fun_run::CmdError::NonZeroExitNotStreamed(named_output)
59+
| fun_run::CmdError::NonZeroExitAlreadyStreamed(named_output) => {
60+
exit_status_fn(Into::<Output>::into(named_output))
9461
}
9562
})
9663
}
9764

98-
fn add_prefix_to_non_empty<P: Into<Vec<u8>>>(prefix: P) -> impl Fn(Vec<u8>) -> Vec<u8> {
99-
let prefix = prefix.into();
100-
101-
move |mut input| {
102-
if input.is_empty() {
103-
vec![]
104-
} else {
105-
let mut result = prefix.clone();
106-
result.append(&mut input);
107-
result
108-
}
109-
}
110-
}
111-
11265
#[derive(Clone, Debug)]
11366
pub struct BuildpackOutputText {
11467
pub line_prefix: Option<String>,
@@ -297,94 +250,5 @@ fn format_duration(duration: &Duration) -> String {
297250
const VALUE_DELIMITER_CHAR: char = '`';
298251
const ANSI_RESET_CODE: &str = "\u{1b}[0m";
299252
const ANSI_VALUE_CODE: &str = "\u{1b}[0;33m";
300-
const ANSI_YELLOW_CODE: &str = "\u{1b}[0;33m";
301-
const ANSI_RED_CODE: &str = "\u{1b}[0;31m";
302-
const ANSI_BUILDPACK_NAME_CODE: &str = "\u{1b}[1;35m";
303253
const ANSI_URL_CODE: &str = "\u{1b}[0;34m";
304254
const ANSI_COMMAND_CODE: &str = "\u{1b}[1;36m";
305-
const ERROR_WARNING_LINE_PREFIX: &str = "! ";
306-
307-
#[cfg(test)]
308-
mod test {
309-
use super::*;
310-
311-
#[test]
312-
fn test_prefixing() {
313-
const DEFAULT_CODE: &str = "\x1B[0;33m";
314-
315-
let text = BuildpackOutputText {
316-
default_code: Some(String::from(DEFAULT_CODE)),
317-
sections: vec![
318-
BuildpackOutputTextSection::regular("Hello\n"),
319-
BuildpackOutputTextSection::value("World"),
320-
BuildpackOutputTextSection::regular("\n"),
321-
BuildpackOutputTextSection::regular("How\nare you?"),
322-
],
323-
line_prefix: Some(String::from(ERROR_WARNING_LINE_PREFIX)),
324-
..Default::default()
325-
};
326-
327-
assert_eq!(text.to_ansi_string(), "\u{1b}[0m\u{1b}[0;33m! Hello\u{1b}[0m\n\u{1b}[0m\u{1b}[0;33m! `\u{1b}[0;33mWorld\u{1b}[0m`\u{1b}[0;33m\u{1b}[0m\n\u{1b}[0m\u{1b}[0;33m! How\u{1b}[0m\n\u{1b}[0m\u{1b}[0;33m! are you?");
328-
}
329-
330-
#[test]
331-
fn test_prefixing_with_value() {
332-
let text = BuildpackOutputText {
333-
default_code: Some(String::from(ANSI_YELLOW_CODE)),
334-
sections: vec![
335-
BuildpackOutputTextSection::regular("Intro\n"),
336-
BuildpackOutputTextSection::value("With\nNewline"),
337-
BuildpackOutputTextSection::regular("\nOutro"),
338-
],
339-
line_prefix: Some(String::from("! ")),
340-
..Default::default()
341-
};
342-
343-
assert_eq!(
344-
text.to_ansi_string(),
345-
"\u{1b}[0m\u{1b}[0;33m! Intro\u{1b}[0m\n\u{1b}[0m\u{1b}[0;33m! `\u{1b}[0;33mWith\u{1b}[0m\n\u{1b}[0m\u{1b}[0;33m! \u{1b}[0;33mNewline\u{1b}[0m`\u{1b}[0;33m\u{1b}[0m\n\u{1b}[0m\u{1b}[0;33m! Outro"
346-
);
347-
}
348-
349-
#[test]
350-
fn test_display_duration() {
351-
let duration = Duration::ZERO;
352-
assert_eq!(format_duration(&duration), "< 0.1s");
353-
354-
let duration = Duration::from_millis(99);
355-
assert_eq!(format_duration(&duration), "< 0.1s");
356-
357-
let duration = Duration::from_millis(100);
358-
assert_eq!(format_duration(&duration), "0.1s");
359-
360-
let duration = Duration::from_millis(210);
361-
assert_eq!(format_duration(&duration), "0.2s");
362-
363-
let duration = Duration::from_millis(1100);
364-
assert_eq!(format_duration(&duration), "1.1s");
365-
366-
let duration = Duration::from_millis(9100);
367-
assert_eq!(format_duration(&duration), "9.1s");
368-
369-
let duration = Duration::from_millis(10100);
370-
assert_eq!(format_duration(&duration), "10.1s");
371-
372-
let duration = Duration::from_millis(52100);
373-
assert_eq!(format_duration(&duration), "52.1s");
374-
375-
let duration = Duration::from_millis(60 * 1000);
376-
assert_eq!(format_duration(&duration), "1m 0s");
377-
378-
let duration = Duration::from_millis(60 * 1000 + 2000);
379-
assert_eq!(format_duration(&duration), "1m 2s");
380-
381-
let duration = Duration::from_millis(60 * 60 * 1000 - 1);
382-
assert_eq!(format_duration(&duration), "59m 59s");
383-
384-
let duration = Duration::from_millis(60 * 60 * 1000);
385-
assert_eq!(format_duration(&duration), "1h 0m 0s");
386-
387-
let duration = Duration::from_millis(75 * 60 * 1000 - 1);
388-
assert_eq!(format_duration(&duration), "1h 14m 59s");
389-
}
390-
}

0 commit comments

Comments
 (0)