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
154
155
156
157
use std::path::PathBuf;
use std::sync::Arc;
use dashmap::DashMap;
use eyre::{Result, WrapErr};
use std::sync::LazyLock as Lazy;
use crate::config::Config;
use crate::config::env_directive::EnvResults;
use crate::toolset::Toolset;
use crate::uv;
use itertools::Itertools;
// Cache Toolset::list_paths results across identical toolsets within a process.
// Keyed by project_root plus sorted list of backend@requested=version pairs currently installed.
pub(super) static LIST_PATHS_CACHE: Lazy<DashMap<String, Vec<PathBuf>>> = Lazy::new(DashMap::new);
impl Toolset {
/// Enumerate install dependency paths without swallowing backend errors.
///
/// This deliberately mirrors `list_paths` ordering while leaving that public,
/// forgiving API unchanged for existing callers.
pub(crate) async fn list_paths_strict(
&self,
config: &Arc<Config>,
dependent: &crate::toolset::ToolRequest,
) -> Result<Vec<PathBuf>> {
let mut installed = self.list_current_installed_versions(config);
Self::sort_by_overrides(&mut installed)?;
let mut paths = Vec::new();
for (backend, dependency) in installed {
let new_paths = backend
.list_bin_paths(config, &dependency)
.await
.wrap_err_with(|| {
format!(
"failed to list bin paths for install dependency '{}' of '{}'",
dependency.request, dependent
)
})?;
paths.extend(new_paths);
}
Ok(paths
.into_iter()
.filter(|path| path.parent().is_some())
.collect())
}
pub(crate) async fn list_paths(&self, config: &Arc<Config>) -> Vec<PathBuf> {
// Build a stable cache key based on project_root and current installed versions
let mut key_parts = vec![];
if let Some(root) = &config.project_root {
key_parts.push(root.to_string_lossy().to_string());
}
let mut installed = self.list_current_installed_versions(config);
let installed_strs: Vec<String> = installed
.iter()
.map(|(p, tv)| format!("{}@{}={}", p.id(), tv.request.version(), tv.version))
.sorted()
.collect();
key_parts.extend(installed_strs);
let cache_key = key_parts.join("|");
if let Some(entry) = LIST_PATHS_CACHE.get(&cache_key) {
trace!("toolset.list_paths hit cache");
return entry.clone();
}
Self::sort_by_overrides(&mut installed).unwrap();
let mut paths: Vec<PathBuf> = Vec::new();
for (p, tv) in installed {
let start = std::time::Instant::now();
let new_paths = p.list_bin_paths(config, &tv).await.unwrap_or_else(|e| {
warn!("Error listing bin paths for {tv}: {e:#}");
Vec::new()
});
trace!(
"toolset.list_paths {}@{} list_bin_paths took {}ms",
p.id(),
tv.version,
start.elapsed().as_millis()
);
paths.extend(new_paths);
}
LIST_PATHS_CACHE.insert(cache_key, paths.clone());
paths
.into_iter()
.filter(|p| p.parent().is_some()) // TODO: why?
.collect()
}
/// same as list_paths but includes config.list_paths, venv paths, and MISE_ADD_PATHs from self.env()
pub(crate) async fn list_final_paths(
&self,
config: &Arc<Config>,
env_results: EnvResults,
) -> Result<Vec<PathBuf>> {
let mut paths = Vec::new();
// Match the tera_env PATH ordering from final_env():
// 1. Original system PATH is handled by PathEnv::from_iter() in env_with_path()
// 2. Config path dirs
paths.extend(config.path_dirs().await?.clone());
// 3. UV venv path (if any) - ensure project venv takes precedence over tool and tool_add_paths
if let Some(venv) = uv::uv_venv(config, self).await? {
paths.push(venv.venv_path.clone());
}
// 4. tool_add_paths (MISE_ADD_PATH from tools)
paths.extend(env_results.tool_add_paths);
// 5. Tool paths
paths.extend(self.list_paths(config).await);
// 6. env_results.env_paths (from load_post_env like _.path directives) - these go at the front
let paths = env_results.env_paths.into_iter().chain(paths).collect();
Ok(paths)
}
/// Returns paths separated by their source: (user_configured_paths, tool_paths)
/// User-configured paths should never be filtered, while tool paths should be filtered
/// if they duplicate entries in the original PATH.
pub(crate) async fn list_final_paths_split(
&self,
config: &Arc<Config>,
env_results: EnvResults,
) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
// User-configured paths from env._.path directives
// IMPORTANT: There are TWO sources of env paths:
// 1. config.path_dirs() - from config.env_results() (cached, no tera context)
// 2. env_results.env_paths - from ts.final_env() (fresh, with tera context applied)
// env_results.env_paths must come FIRST for highest precedence
let mut user_paths = env_results.env_paths;
user_paths.extend(config.path_dirs().await?.clone());
// Tool paths start empty
let mut tool_paths = Vec::new();
// UV venv path (if any) - these are tool-managed paths
if let Some(venv) = uv::uv_venv(config, self).await? {
tool_paths.push(venv.venv_path.clone());
}
// tool_add_paths (MISE_ADD_PATH from tools)
tool_paths.extend(env_results.tool_add_paths);
// Tool installation paths
tool_paths.extend(self.list_paths(config).await);
Ok((user_paths, tool_paths))
}
}