feat(config): support vars in tool versions#6401
Conversation
Enable using template variables (vars) in tool version specifications.
This allows for more dynamic configuration where tool versions can be
defined as variables and reused across the configuration.
Example:
```toml
[vars]
NODE_VERSION = "20.11.0"
PYTHON_VERSION = "3.12"
[tools]
node = "{{vars.NODE_VERSION}}"
python = "{{vars.PYTHON_VERSION}}"
```
This works by adding vars from the config file to the template context
immediately after parsing, making them available when tool versions are
evaluated.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull Request Overview
This PR adds support for using template variables (vars) in tool version specifications, enabling more dynamic and DRY configuration management.
- Modifies the TOML config parser to add vars to the template context immediately after parsing
- Allows tool versions to reference variables defined in the same config file using
{{vars.VARIABLE_NAME}}syntax - Maintains backward compatibility with existing environment variable templating
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/config/config_file/mise_toml.rs | Adds vars from the config file to template context for use in tool versions |
| e2e/config/test_tool_version_vars | Comprehensive end-to-end test covering vars in tool versions and env compatibility |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/config/config_file/mise_toml.rs
Outdated
| if let EnvDirective::Val(k, v, _) = var_directive { | ||
| vars_map.insert(k.clone(), v.clone()); |
There was a problem hiding this comment.
The pattern matching only handles EnvDirective::Val variants but silently ignores other variants. Consider adding a comment explaining why other variants are ignored or handle them explicitly to make the intent clearer.
| if let EnvDirective::Val(k, v, _) = var_directive { | |
| vars_map.insert(k.clone(), v.clone()); | |
| match var_directive { | |
| EnvDirective::Val(k, v, _) => { | |
| vars_map.insert(k.clone(), v.clone()); | |
| } | |
| // Other variants are intentionally ignored here because only Val directives are relevant for context vars. | |
| _ => {} |
The set_context method is not needed since vars are now added to the context directly in the from_str method.
The previous approach didn't handle nested templates (vars that reference other vars). This implementation resolves vars locally within the to_tool_request_set method, doing multiple passes to handle nested template references. This approach avoids the complexity of trying to use the global EnvResults resolution and instead does a simple local resolution specifically for tool version parsing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.17 x -- echo |
19.6 ± 0.3 | 18.6 | 21.2 | 1.01 ± 0.05 |
mise x -- echo |
19.5 ± 0.9 | 18.6 | 33.2 | 1.00 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.17 env |
18.7 ± 0.4 | 18.0 | 24.4 | 1.01 ± 0.03 |
mise env |
18.5 ± 0.2 | 18.1 | 20.1 | 1.00 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.17 hook-env |
18.2 ± 0.2 | 17.6 | 19.6 | 1.00 |
mise hook-env |
18.4 ± 0.3 | 17.8 | 20.6 | 1.01 ± 0.02 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.9.17 ls |
16.6 ± 0.4 | 15.8 | 19.1 | 1.01 ± 0.03 |
mise ls |
16.5 ± 0.4 | 15.8 | 17.5 | 1.00 |
xtasks/test/perf
| Command | mise-2025.9.17 | mise | Variance |
|---|---|---|---|
| install (cached) | 168ms | ✅ 104ms | +61% |
| ls (cached) | 63ms | 63ms | +0% |
| bin-paths (cached) | 69ms | 69ms | +0% |
| task-ls (cached) | 469ms | 477ms | -1% |
✅ Performance improvement: install cached is 61%
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| pub fn maybe_get() -> Option<Arc<Self>> { | ||
| _CONFIG.read().unwrap().as_ref().cloned() | ||
| } |
There was a problem hiding this comment.
The .unwrap() call will panic if the lock is poisoned. Consider using a more descriptive error message or handling the poison error gracefully with .expect("Config lock poisoned") or proper error handling.
src/config/config_file/mise_toml.rs
Outdated
| let version_src = version.to_string(); | ||
| if !version_src.contains("{{") && !version_src.contains("{%") && !version_src.contains("{#") | ||
| { | ||
| return Ok(version_src); | ||
| } |
There was a problem hiding this comment.
The template detection logic is duplicated from the parse_template method. Consider extracting this into a helper function like contains_template_syntax() to avoid code duplication and ensure consistency.
| let vars = vars_results | ||
| .vars | ||
| .iter() | ||
| .map(|(k, (v, _))| (k.clone(), v.clone())) | ||
| .collect::<IndexMap<_, _>>(); | ||
| context.insert("vars", &vars); | ||
| } else if !config.vars.is_empty() { |
There was a problem hiding this comment.
The vars collection is being cloned twice on each call (line 520). Consider caching this transformed vars map or using references where possible to avoid unnecessary allocations, especially if this method is called frequently during tool resolution.
| let vars = vars_results | |
| .vars | |
| .iter() | |
| .map(|(k, (v, _))| (k.clone(), v.clone())) | |
| .collect::<IndexMap<_, _>>(); | |
| context.insert("vars", &vars); | |
| } else if !config.vars.is_empty() { | |
| // Cache the transformed vars map to avoid double cloning | |
| let vars: IndexMap<_, _> = vars_results | |
| .vars | |
| .iter() | |
| .map(|(k, (v, _))| (k.clone(), v.clone())) | |
| .collect(); | |
| context.insert("vars", &vars); | |
| } else if !config.vars.is_empty() { | |
| // Insert a reference to the vars map directly to avoid unnecessary cloning |
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/config/config_file/mise_toml.rs
Outdated
| let version_src = version.to_string(); | ||
| if !Self::contains_template_syntax(&version_src) { | ||
| return Ok(version_src); | ||
| } | ||
|
|
||
| get_tera(self.path.parent()) | ||
| .render_str(&version_src, context) | ||
| .wrap_err_with(|| eyre!("failed to render tool version template: {version_src}")) |
There was a problem hiding this comment.
The render_tool_version method duplicates similar logic from the existing parse_template_with_context method. Consider refactoring to use parse_template_with_context directly instead of duplicating the Tera rendering logic.
| let version_src = version.to_string(); | |
| if !Self::contains_template_syntax(&version_src) { | |
| return Ok(version_src); | |
| } | |
| get_tera(self.path.parent()) | |
| .render_str(&version_src, context) | |
| .wrap_err_with(|| eyre!("failed to render tool version template: {version_src}")) | |
| self.parse_template_with_context(context, &version.to_string()) |
src/config/config_file/mise_toml.rs
Outdated
| let mut trs = ToolRequestSet::new(); | ||
| let tools = self.tools.lock().unwrap(); | ||
| let mut context = self.context.clone(); | ||
| if context.get("env").is_none() { |
There was a problem hiding this comment.
[nitpick] Inserting an empty EnvMap when env is not present may mask issues where environment variables are expected but not available. Consider documenting this behavior or providing a way to distinguish between intentionally empty and missing env context.
| if context.get("env").is_none() { | |
| if context.get("env").is_none() { | |
| // [nitpick] Inserting an empty EnvMap when "env" is not present may mask issues where environment variables are expected but not available. | |
| // Consider handling this case explicitly in downstream code. Here, we document this fallback. | |
| // If logging is available, log a warning: | |
| // log::warn!("'env' context missing; inserting empty EnvMap. This may mask missing environment variables."); |
|
bugbot run |
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| pub fn maybe_get() -> Option<Arc<Self>> { | ||
| _CONFIG.read().unwrap().as_ref().cloned() | ||
| } |
There was a problem hiding this comment.
Consider using expect() with a descriptive message instead of unwrap() to provide better error context if the lock is poisoned.
| let vars = vars_results | ||
| .vars | ||
| .iter() | ||
| .map(|(k, (v, _))| (k.clone(), v.clone())) | ||
| .collect::<IndexMap<_, _>>(); | ||
| context.insert("vars", &vars); |
There was a problem hiding this comment.
This creates unnecessary clones of all keys and values. Consider using references in the IndexMap or restructuring to avoid the clone overhead, especially if vars can be large.
| let vars = vars_results | |
| .vars | |
| .iter() | |
| .map(|(k, (v, _))| (k.clone(), v.clone())) | |
| .collect::<IndexMap<_, _>>(); | |
| context.insert("vars", &vars); | |
| // Avoid unnecessary clones by passing a reference to the existing vars map | |
| context.insert("vars", &vars_results.vars); |
| let mut trs = ToolRequestSet::new(); | ||
| let tools = self.tools.lock().unwrap(); | ||
| let mut context = self.context.clone(); | ||
| if context.get("vars").is_none() { |
There was a problem hiding this comment.
The string literal "vars" is used multiple times in this method. Consider defining it as a constant to avoid magic strings and improve maintainability.
Summary
vars) in tool version specificationsUse Case
This feature enables users to define tool versions as variables and reuse them across their configuration. This is particularly useful for:
Example
Technical Approach
The implementation adds vars from the config file to the template context immediately after parsing the TOML file. This makes them available when tool versions are evaluated during
to_tool_request_set().Test Plan
🤖 Generated with Claude Code