Skip to content

Commit d5c36fd

Browse files
committed
Preparation for auto-downloading micromamba
We will attempt to download micromamba if we can't find a working one in $PATH. This establishes some of the groundwork for doing that, including the initial check to see if we already have a working `micromamba` command in $PATH, but it does not attempt the download, yet.
1 parent b852bd4 commit d5c36fd

4 files changed

Lines changed: 115 additions & 42 deletions

File tree

src/env.rs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::csmrc::Config;
2-
use crate::util::micromamba;
2+
use crate::micromamba::micromamba;
33

4-
use log::{debug, error, info};
4+
use log::{debug, error};
55
use serde::Deserialize;
66
use std::io::{Error, ErrorKind};
77
use std::path::Component;
@@ -96,7 +96,7 @@ pub fn run(config: Config, subcommand: Subcommand) -> ExitCode {
9696
error!("No environment name could be determined. You can specify one with --name");
9797
return ExitCode::FAILURE;
9898
};
99-
let mut cmd = micromamba(
99+
let result = micromamba(
100100
&config,
101101
vec![
102102
"env",
@@ -108,23 +108,7 @@ pub fn run(config: Config, subcommand: Subcommand) -> ExitCode {
108108
"--yes",
109109
],
110110
);
111-
if config.noop_mode {
112-
info!("Would run: {:?}", cmd);
113-
ExitCode::SUCCESS
114-
} else {
115-
let cmd = cmd.spawn().expect("failed to call micromamba").wait();
116-
match cmd {
117-
// Exit with whatever code micromamba gives us
118-
Ok(exit_status) => exit_status
119-
.code()
120-
.map(|c| ExitCode::from(c as u8))
121-
.unwrap_or(ExitCode::FAILURE),
122-
Err(e) => {
123-
error!("Failed to spawn micromamba: {}", e);
124-
ExitCode::FAILURE
125-
}
126-
}
127-
}
111+
result.exit_code()
128112
}
129113
_ => {
130114
println!("{:?}", config);

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod csmrc;
22
mod env;
3+
mod micromamba;
34
mod robot;
4-
mod util;
55

66
use crate::csmrc::Config;
77
use clap::{Parser, Subcommand};

src/micromamba.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! This module deals with `micromamba` - obtaining it, calling it, etc.
2+
3+
use crate::csmrc::Config;
4+
use log::{debug, error, info};
5+
use std::collections::HashMap;
6+
use std::process::{Command, ExitCode, ExitStatus};
7+
8+
/// The result from trying to shell out to `micromamba`.
9+
///
10+
/// It would be better if we could "accumulate" errors as we try different
11+
/// fallbacks to run `micromamba`, something like Result/Either, but with an
12+
/// accumulating Applicative on the error side, akin to the "validation" package
13+
/// in Haskell. Alas, this does not seem to exist in Rust, so we drop the errors
14+
/// as we try to determine a working `micromamba` and just report whether or not
15+
/// we were able to do so at the end. (Of course, we log along the way in
16+
/// `micromamba()`.)
17+
pub enum MicromambaResult {
18+
/// We were run in no-op mode, so we didn't actually call out to it
19+
Noop,
20+
/// We were able to successfully call it and get a result
21+
Ok(ExitStatus),
22+
/// We were unable to find or create a working `micromamba`
23+
CouldNotRun,
24+
}
25+
26+
impl MicromambaResult {
27+
pub fn exit_code(&self) -> ExitCode {
28+
match self {
29+
Self::Ok(exit_status) => exit_status
30+
.code()
31+
.map(|c| ExitCode::from(c as u8))
32+
.unwrap_or(ExitCode::FAILURE),
33+
Self::Noop => ExitCode::SUCCESS,
34+
Self::CouldNotRun => ExitCode::FAILURE,
35+
}
36+
}
37+
}
38+
39+
/// Return a [`Command`] ready to shell out to `micromamba` with the appropriate
40+
/// environment variables set based on configuration.
41+
pub fn micromamba_at(path: &str, config: &Config, args: Vec<&str>) -> Command {
42+
let mut env_vars: HashMap<&str, String> = HashMap::new();
43+
44+
if let Some(mamba_root_prefix) = &config.mamba_root_prefix {
45+
env_vars.insert("MAMBA_ROOT_PREFIX", mamba_root_prefix.to_string());
46+
}
47+
48+
let mut cmd = Command::new(path);
49+
cmd.args(args);
50+
cmd.envs(env_vars);
51+
if config.noop_mode {
52+
info!("Would run: {:?}", cmd);
53+
} else {
54+
debug!("About to run: {:?}", cmd);
55+
}
56+
cmd
57+
}
58+
59+
/// Run `micromamba` and return the result, if able.
60+
///
61+
/// We need a `micromamba` binary to work with. If one is not present, attempt
62+
/// to download and install `micromamba` into the user's cache directory.
63+
///
64+
/// 1. If there is already a `micromamba` command in $PATH, we use it.
65+
/// 2. Otherwise, download micromamba and install it somewhere in the user
66+
/// cache directory. (We cannot rely on this - it could be that the user's
67+
/// cache directory is mounted noexec or similar, but we try.)
68+
///
69+
/// Alternative approaches that we do not take here currently:
70+
/// - On Linux, we *could* in theory use memfd_create + fexecve to embed the app
71+
/// and run it from memory. This won't work on Windows.
72+
///
73+
/// - We *could* embed the micromamba binary in our binary (Windows or Linux
74+
/// based on compile target) and write it to the user cache directory rather
75+
/// than downloading it. But this inflates our binary size.
76+
pub fn micromamba(config: &Config, args: Vec<&str>) -> MicromambaResult {
77+
let mut cmd = micromamba_at("micromamba", config, args);
78+
79+
if config.noop_mode {
80+
// Do nothing. micromamba_at() already logged what we're about to run.
81+
return MicromambaResult::Noop;
82+
}
83+
84+
// First we try from $PATH
85+
if let Ok(mut child) = cmd.spawn() {
86+
debug!("Used micromamba from $PATH");
87+
match child.wait() {
88+
Ok(exit_status) => return MicromambaResult::Ok(exit_status),
89+
Err(e) => {
90+
// In this case don't try to download one, there is probably a
91+
// bigger issue.
92+
error!("We found a micromamba binary, but failed to wait for it to run");
93+
error!("Error was: {}", e);
94+
return MicromambaResult::CouldNotRun;
95+
}
96+
}
97+
}
98+
99+
// If we weren't successful there, we download micromamba to the user cache
100+
// directory.
101+
102+
// TODO
103+
104+
// Finally, if we couldn't run the downloaded one either, just bail out
105+
error!("Could not find a suitable micromamba binary to run");
106+
error!(
107+
"Please install micromamba manually, ensure it is executable, and place it somewhere in $PATH"
108+
);
109+
MicromambaResult::CouldNotRun
110+
}

src/util.rs

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)