Skip to content

Commit 61e99a4

Browse files
committed
Be able to capture, instead of stream, micromamba
In some commands, e.g. `csm env create` we don't want to always show the output. Now we capture it and only show it if there was an error.
1 parent bc5119b commit 61e99a4

5 files changed

Lines changed: 79 additions & 28 deletions

File tree

src/csmrc.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ pub struct Config {
2020

2121
/// If false, skip downloading micromamba even if needed (for testing).
2222
pub download_micromamba: bool,
23+
24+
/// (Internal) If true, the program is being run in verbose mode.
25+
/// We do not support this being set from the configuration file, because
26+
/// the configuration file is parsed after logging is initialized. The user
27+
/// can technically do it, we won't error, but it won't have much effect.
28+
pub verbose: bool,
2329
}
2430

2531
#[allow(clippy::derivable_impls)]
@@ -30,6 +36,7 @@ impl Default for Config {
3036
noop_mode: false,
3137
cache_dir: None,
3238
download_micromamba: true,
39+
verbose: false,
3340
}
3441
}
3542
}

src/env.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::csmrc::Config;
2-
use crate::micromamba::micromamba;
2+
use crate::micromamba::{MicromambaResult, micromamba};
33
use crate::shell::SupportedShell;
44

55
use log::{debug, error, info};
@@ -112,6 +112,10 @@ pub fn run(config: Config, subcommand: Subcommand) -> ExitCode {
112112
error!("No environment name could be determined. You can specify one with --name");
113113
return ExitCode::FAILURE;
114114
};
115+
info!(
116+
"Creating environment '{}' - this may take some time...",
117+
env_name
118+
);
115119
let result = micromamba(
116120
&config,
117121
vec![
@@ -123,19 +127,32 @@ pub fn run(config: Config, subcommand: Subcommand) -> ExitCode {
123127
&env_name,
124128
"--yes",
125129
],
130+
config.verbose,
126131
);
127-
result.exit_code()
132+
let rc = result.exit_code();
133+
match result {
134+
MicromambaResult::CapturedOutput(output) if rc != ExitCode::SUCCESS => {
135+
error!("Got a non-zero exit code from micromamba, dumping output:");
136+
error!("micromamba stdout:");
137+
println!("{}", String::from_utf8_lossy(&output.stdout));
138+
error!("micromamba stderr:");
139+
println!("{}", String::from_utf8_lossy(&output.stderr));
140+
}
141+
MicromambaResult::CapturedOutput(_) if rc == ExitCode::SUCCESS => info!("Done."),
142+
_ => {}
143+
}
144+
rc
128145
}
129-
Subcommand::List => micromamba(&config, vec!["env", "list"]).exit_code(),
130-
Subcommand::Info => micromamba(&config, vec!["info"]).exit_code(),
146+
Subcommand::List => micromamba(&config, vec!["env", "list"], true).exit_code(),
147+
Subcommand::Info => micromamba(&config, vec!["info"], true).exit_code(),
131148
Subcommand::Run(args) => {
132149
let Some(env_name) = determine_env_name(args.name) else {
133150
error!("No environment name could be determined. You can specify one with --name");
134151
return ExitCode::FAILURE;
135152
};
136153
let mut micromamba_args = vec!["run", "--name", &env_name, &args.command];
137154
micromamba_args.extend(args.arguments.iter().map(|s| s.as_str()));
138-
micromamba(&config, micromamba_args).exit_code()
155+
micromamba(&config, micromamba_args, true).exit_code()
139156
}
140157
Subcommand::Activate => {
141158
// TODO: handle env name similar to run/create

src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ fn main() -> ExitCode {
7676
config.noop_mode = true;
7777
}
7878

79+
if cli.verbose {
80+
config.verbose = true;
81+
}
82+
7983
let Some(home) = std::env::home_dir() else {
8084
error!("Failed to determine home directory");
8185
return ExitCode::FAILURE;

src/micromamba.rs

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::collections::HashMap;
66
use std::fs;
77
use std::io;
88
use std::path::{Path, PathBuf};
9-
use std::process::{Command, ExitCode, ExitStatus};
9+
use std::process::{Command, ExitCode, ExitStatus, Output};
1010

1111
/// The result from trying to shell out to `micromamba`.
1212
///
@@ -20,8 +20,11 @@ use std::process::{Command, ExitCode, ExitStatus};
2020
pub enum MicromambaResult {
2121
/// We were run in no-op mode, so we didn't actually call out to it
2222
Noop,
23-
/// We were able to successfully call it and get a result
24-
Ok(ExitStatus),
23+
/// We were able to successfully call it and get a result, though we streamed
24+
/// the output and did not save it.
25+
StreamedOutput(ExitStatus),
26+
/// We were able to successfully call it and get a result, capturing output.
27+
CapturedOutput(Output),
2528
/// We were unable to find or create a working `micromamba`
2629
NotFound,
2730
/// We found a micromamba binary, but could not run it
@@ -30,11 +33,16 @@ pub enum MicromambaResult {
3033

3134
impl MicromambaResult {
3235
pub fn exit_code(&self) -> ExitCode {
33-
match self {
34-
Self::Ok(exit_status) => exit_status
36+
let to_code = |status: &ExitStatus| {
37+
status
3538
.code()
3639
.map(|c| ExitCode::from(c as u8))
37-
.unwrap_or(ExitCode::FAILURE),
40+
.unwrap_or(ExitCode::FAILURE)
41+
};
42+
43+
match self {
44+
Self::StreamedOutput(exit_status) => to_code(exit_status),
45+
Self::CapturedOutput(output) => to_code(&output.status),
3846
Self::Noop => ExitCode::SUCCESS,
3947
_ => ExitCode::FAILURE,
4048
}
@@ -102,7 +110,7 @@ fn block_on_child_exit(child: &mut std::process::Child) -> MicromambaResult {
102110
match child.wait() {
103111
Ok(exit_status) => {
104112
debug!("micromamba exited with status: {}", exit_status);
105-
MicromambaResult::Ok(exit_status)
113+
MicromambaResult::StreamedOutput(exit_status)
106114
}
107115
Err(e) => {
108116
error!("We found a micromamba binary, but failed to wait for it to run");
@@ -112,17 +120,32 @@ fn block_on_child_exit(child: &mut std::process::Child) -> MicromambaResult {
112120
}
113121
}
114122

115-
fn exec_micromamba(cmd: &mut Command) -> MicromambaResult {
116-
match cmd.spawn() {
117-
Ok(mut child) => block_on_child_exit(&mut child),
118-
Err(e) if e.kind() == io::ErrorKind::NotFound => {
119-
debug!("Could not run micromamba at specified path: {}", e);
120-
MicromambaResult::NotFound
123+
fn exec_micromamba(cmd: &mut Command, stream_output: bool) -> MicromambaResult {
124+
if stream_output {
125+
match cmd.spawn() {
126+
Ok(mut child) => block_on_child_exit(&mut child),
127+
Err(e) if e.kind() == io::ErrorKind::NotFound => {
128+
debug!("Could not run micromamba at specified path: {}", e);
129+
MicromambaResult::NotFound
130+
}
131+
Err(e) => {
132+
error!("We found a micromamba binary, but failed to run it");
133+
error!("Error was: {}", e);
134+
MicromambaResult::CouldNotRun
135+
}
121136
}
122-
Err(e) => {
123-
error!("We found a micromamba binary, but failed to run it");
124-
error!("Error was: {}", e);
125-
MicromambaResult::CouldNotRun
137+
} else {
138+
match cmd.output() {
139+
Ok(output) => MicromambaResult::CapturedOutput(output),
140+
Err(e) if e.kind() == io::ErrorKind::NotFound => {
141+
debug!("Could not run micromamba at specified path: {}", e);
142+
MicromambaResult::NotFound
143+
}
144+
Err(e) => {
145+
error!("We found a micromamba binary, but failed to run it");
146+
error!("Error was: {}", e);
147+
MicromambaResult::CouldNotRun
148+
}
126149
}
127150
}
128151
}
@@ -144,7 +167,7 @@ fn exec_micromamba(cmd: &mut Command) -> MicromambaResult {
144167
/// - We *could* embed the micromamba binary in our binary (Windows or Linux
145168
/// based on compile target) and write it to the user cache directory rather
146169
/// than downloading it. But this inflates our binary size.
147-
pub fn micromamba(config: &Config, args: Vec<&str>) -> MicromambaResult {
170+
pub fn micromamba(config: &Config, args: Vec<&str>, stream_output: bool) -> MicromambaResult {
148171
let mut cmd = micromamba_at("micromamba", config, &args);
149172

150173
if config.noop_mode {
@@ -154,8 +177,8 @@ pub fn micromamba(config: &Config, args: Vec<&str>) -> MicromambaResult {
154177

155178
// If we were able to get a result using micromamba found in $PATH, then
156179
// we're done.
157-
match exec_micromamba(&mut cmd) {
158-
ok @ MicromambaResult::Ok(_) => {
180+
match exec_micromamba(&mut cmd, stream_output) {
181+
ok @ (MicromambaResult::StreamedOutput(_) | MicromambaResult::CapturedOutput(_)) => {
159182
debug!("Ran micromamba found in $PATH");
160183
return ok;
161184
}
@@ -179,8 +202,8 @@ pub fn micromamba(config: &Config, args: Vec<&str>) -> MicromambaResult {
179202
}
180203
};
181204
let mut cmd = micromamba_at(&downloaded_path.to_string_lossy(), config, &args);
182-
match exec_micromamba(&mut cmd) {
183-
ok @ MicromambaResult::Ok(_) => {
205+
match exec_micromamba(&mut cmd, stream_output) {
206+
ok @ (MicromambaResult::StreamedOutput(_) | MicromambaResult::CapturedOutput(_)) => {
184207
debug!(
185208
"Ran downloaded/cached micromamba at {}",
186209
downloaded_path.display()

tests/env.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ fn csm_env_create(csm: &mut common::Csm, name: &str) -> Result<(), Error> {
1111
.current_dir(common::tests_dir().join("micromamba-minimal"))
1212
.assert()
1313
.success()
14-
.stdout(predicate::str::contains("Transaction finished"));
14+
.stderr(predicate::str::contains("Done."));
1515
Ok(())
1616
}
1717

0 commit comments

Comments
 (0)