1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::{ffi::OsString, path::PathBuf};
use anyhow::{Context, bail};
use crate::{
artifact::TargetTriple, channel::Channel, manifest::Manifest, toolchain::Toolchain, utils,
};
/// This struct holds contextual information about the environment in which midenup/miden will
/// operate under. This meant to be a *read-only* data structure.
#[derive(Debug)]
pub struct Config {
/// The path to the current working directory in which midenup/miden was called from.
pub working_directory: PathBuf,
/// The path to the midenup's home directory, which holds all the installed toolchains with
/// their respective libraries and executables.
///
/// By default, it will point to `$XDG_DATA_HOME/midenup`; although a custom path can be
/// specified via the `MIDENUP_HOME` environment variable, like so:
///
/// `MIDENUP_HOME=/path/to/custom/home midenup`
pub midenup_home: PathBuf,
/// This represents the upstream manifest, which contains the state of all the available
/// toolchains with their respective components.
///
/// It is usually going to be obtained from `curl`ing the URI present in
/// [`crate::manifest::Manifest::PUBLISHED_MANIFEST_URI`], although it could also be obtained
/// from a different source (be it a local file or a different URL) for debugging purposes. The
/// source can be specified via the `MIDENUP_MANIFEST_URI` environment variable. For example:
///
/// `MIDENUP_MANIFEST_URI=file://your-custom-manifest.json midenup`
///
/// For more information about the Manifest's fields and format, see [Manifest].
pub manifest: Manifest,
/// This flag is used to detect/distinguish when midenup is being used in tests.
///
/// At the time of writing, this is mostly done to install debug builds of the various miden
/// components to speed tests up.
pub debug: bool,
/// The machine's triplet (e.g. `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, etc).
///
/// This is used to determine which artifact to download. If, for whatever reason (which should
/// be rare), we fail to obtain the system's target triple, then we leave it as `None`. In
/// those cases, we will simply install everything from source.
pub target: TargetTriple,
}
impl Config {
pub fn init(
midenup_home: PathBuf,
manifest_uri: impl AsRef<str>,
debug: bool,
) -> anyhow::Result<Config> {
let manifest = Manifest::load_from(manifest_uri)?;
let working_directory =
std::env::current_dir().context("Could not obtain present working directory")?;
let target = {
let target = env!("TARGET");
TargetTriple::Custom(target.to_string())
};
let config = Config {
working_directory,
midenup_home,
manifest,
debug,
target,
};
Ok(config)
}
pub fn update_opt_symlinks(&self, config: &Config) -> anyhow::Result<()> {
let (current_toolchain, _) = Toolchain::current(self)?;
// Directory which point to the directory where symlinks are stored
let opt_dir = self.midenup_home.join("opt");
let Some(active_channel) = self.manifest.get_channel(¤t_toolchain.channel) else {
bail!("channel '{}' doesn't exist or is unavailable", current_toolchain.channel);
};
// If the currently active channel doesn't exist, then there's nothing to update regarding
// the opt/ symlink.
if !active_channel.get_channel_dir(config).exists() {
// However, if the opt directory still exists, then we remove it in order to avoid a
// "dangling symlink". This can happen when an uninstall is issued.
if std::fs::read_link(&opt_dir).is_ok() {
std::fs::remove_file(&opt_dir).context("Couldn't remove 'opt' symlink")?;
}
return Ok(());
}
let update = if let Ok(pointing) = std::fs::read_link(&opt_dir) {
// If it does exist, update it if it's pointing to a non-active toolchain.
pointing
.file_name()
.and_then(|toolchain_name| toolchain_name.to_str())
.is_some_and(|toolchain_name| toolchain_name != active_channel.name.to_string())
} else {
// If the symlink doesn't exist, update it by creating it.
true
};
if update {
if std::fs::read_link(&opt_dir).is_ok() {
std::fs::remove_file(&opt_dir).context("Couldn't remove 'opt' symlink")?;
}
let opt_path = active_channel.get_channel_dir(self).join("opt");
utils::fs::symlink(&opt_dir, &opt_path).with_context(|| {
format!(
"Failed to create opt/ symlink from {} to {}",
opt_dir.display(),
opt_path.display()
)
})?;
}
Ok(())
}
/// Executes a command.
pub fn execute_command(
&self,
active_toolchain: &Channel,
target_exe: &str,
args: &Vec<OsString>,
) -> Result<std::process::Child, std::io::Error> {
let toolchain_name = active_toolchain.name.to_string();
let sysroot = self.midenup_home.join("toolchains").join(&toolchain_name);
let toolchain_opt = sysroot.join("opt");
let path = match std::env::var_os("PATH") {
Some(prev_path) => {
let mut path = OsString::from(format!("{}:", toolchain_opt.display()));
path.push(prev_path);
path
},
None => toolchain_opt.into_os_string(),
};
std::process::Command::new(target_exe)
.env("MIDENUP_HOME", &self.midenup_home)
.env("MIDENUP_TOOLCHAIN", &toolchain_name)
.env("MIDEN_SYSROOT", &sysroot)
.env("PATH", path)
.args(args)
.stderr(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.spawn()
}
}