From c14ea696c8d855085d10114742624e37240d8abc Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:34:13 -0600 Subject: [PATCH] fix: add full tera context to tasks Fixes #3701 --- e2e/tasks/test_task_run_tmpl | 14 +++++++ src/cli/env.rs | 3 +- src/cli/exec.rs | 2 +- ...file__mise_toml__tests__remove_plugin.snap | 1 + ...e__mise_toml__tests__replace_versions.snap | 1 + src/config/env_directive/mod.rs | 2 +- src/task/mod.rs | 33 +++++++--------- src/task/task_dep.rs | 38 ++++++++++++++----- src/task/task_script_parser.rs | 18 ++++----- src/toolset/mod.rs | 15 +++++++- src/toolset/tool_request_set.rs | 6 ++- 11 files changed, 88 insertions(+), 45 deletions(-) create mode 100644 e2e/tasks/test_task_run_tmpl diff --git a/e2e/tasks/test_task_run_tmpl b/e2e/tasks/test_task_run_tmpl new file mode 100644 index 0000000000..10a707b516 --- /dev/null +++ b/e2e/tasks/test_task_run_tmpl @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +cat <mise.toml +env.BAR = "bar" +tasks.a = "echo {{ env.BAR }}" +EOF +assert "mise run a" "bar" + +cat <mise.toml +env.BAR = "a" +tasks.a = "echo a" +tasks.b.depends = ["{{ env.BAR }}"] +EOF +assert "mise run b" "a" diff --git a/src/cli/env.rs b/src/cli/env.rs index 72c20ec536..564f5f81e6 100644 --- a/src/cli/env.rs +++ b/src/cli/env.rs @@ -125,7 +125,8 @@ impl Env { } fn output_dotenv(&self, config: &Config, ts: Toolset) -> Result<()> { - for (k, v) in ts.final_env(config)?.0 { + let (env, _) = ts.final_env(config)?; + for (k, v) in env { let k = k.to_string(); let v = v.to_string(); miseprint!("{}={}\n", k, v)?; diff --git a/src/cli/exec.rs b/src/cli/exec.rs index 879897fa91..2cf2760546 100644 --- a/src/cli/exec.rs +++ b/src/cli/exec.rs @@ -94,7 +94,7 @@ impl Exec { )); } // TODO: env is being calculated twice with final_env and env_with_path - let env_results = ts.final_env(&config)?.1; + let (_, env_results) = ts.final_env(&config)?; for p in ts.list_final_paths(&config, env_results)? { cmd.push(format!( "fish_add_path -gm {}", diff --git a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__remove_plugin.snap b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__remove_plugin.snap index d1dfd2add0..d862e241ef 100644 --- a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__remove_plugin.snap +++ b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__remove_plugin.snap @@ -6,4 +6,5 @@ snapshot_kind: text Toolset { versions: {}, source: None, + tera_ctx: OnceCell(Uninit), } diff --git a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap index a8f24d1747..b1237b7456 100644 --- a/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap +++ b/src/config/config_file/snapshots/mise__config__config_file__mise_toml__tests__replace_versions.snap @@ -44,4 +44,5 @@ Toolset { "/tmp/.mise.toml", ), ), + tera_ctx: OnceCell(Uninit), } diff --git a/src/config/env_directive/mod.rs b/src/config/env_directive/mod.rs index 45d7de11d4..19d595ed58 100644 --- a/src/config/env_directive/mod.rs +++ b/src/config/env_directive/mod.rs @@ -108,7 +108,7 @@ impl Display for EnvDirective { } } -#[derive(Default)] +#[derive(Default, Clone)] pub struct EnvResults { pub env: IndexMap, pub env_remove: BTreeSet, diff --git a/src/task/mod.rs b/src/task/mod.rs index b9050b8470..f2c2fe821a 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -292,8 +292,7 @@ impl Task { spec.cmd.name = self.name.clone(); (spec, vec![]) } else { - let (scripts, spec) = - TaskScriptParser::new(cwd).parse_run_scripts(&self.config_root, self.run())?; + let (scripts, spec) = TaskScriptParser::new(cwd).parse_run_scripts(self, self.run())?; (spec, scripts) }; spec.name = self.name.clone(); @@ -382,8 +381,7 @@ impl Task { }) { let config_root = self.config_root.clone().unwrap_or_default(); let mut tera = get_tera(Some(&config_root)); - let mut tera_ctx = config.tera_ctx.clone(); - tera_ctx.insert("config_root", &config_root); + let tera_ctx = self.tera_ctx()?; let dir = tera.render_str(&dir, &tera_ctx)?; let dir = file::replace_path(&dir); if dir.is_absolute() { @@ -398,6 +396,14 @@ impl Task { } } + pub fn tera_ctx(&self) -> Result { + let config = Config::get(); + let ts = config.get_toolset()?; + let mut tera_ctx = ts.tera_ctx()?.clone(); + tera_ctx.insert("config_root", &self.config_root); + Ok(tera_ctx) + } + pub fn cf<'a>(&self, config: &'a Config) -> Option<&'a Box> { config.config_files.get(&self.config_source) } @@ -418,10 +424,8 @@ impl Task { } pub fn render(&mut self, config_root: &Path) -> Result<()> { - let config = Config::get(); let mut tera = get_tera(Some(config_root)); - let mut tera_ctx = config.tera_ctx.clone(); - tera_ctx.insert("config_root", &config_root); + let tera_ctx = self.tera_ctx()?; for a in &mut self.aliases { *a = tera.render_str(a, &tera_ctx)?; } @@ -431,22 +435,13 @@ impl Task { } self.outputs.render(&mut tera, &tera_ctx)?; for d in &mut self.depends { - d.task = tera.render_str(&d.task, &tera_ctx)?; - for a in &mut d.args { - *a = tera.render_str(a, &tera_ctx)?; - } + d.render(&mut tera, &tera_ctx)?; } for d in &mut self.depends_post { - d.task = tera.render_str(&d.task, &tera_ctx)?; - for a in &mut d.args { - *a = tera.render_str(a, &tera_ctx)?; - } + d.render(&mut tera, &tera_ctx)?; } for d in &mut self.wait_for { - d.task = tera.render_str(&d.task, &tera_ctx)?; - for a in &mut d.args { - *a = tera.render_str(a, &tera_ctx)?; - } + d.render(&mut tera, &tera_ctx)?; } for v in self.env.values_mut() { if let EitherStringOrIntOrBool(Either::Left(s)) = v { diff --git a/src/task/task_dep.rs b/src/task/task_dep.rs index 4175fd40e4..2ab2aa300f 100644 --- a/src/task/task_dep.rs +++ b/src/task/task_dep.rs @@ -1,5 +1,4 @@ use crate::config::config_file::toml::deserialize_arr; -use itertools::Itertools; use serde::ser::SerializeSeq; use serde::{Deserialize, Deserializer, Serialize}; use std::fmt; @@ -12,6 +11,28 @@ pub struct TaskDep { pub args: Vec, } +impl TaskDep { + pub fn render( + &mut self, + tera: &mut tera::Tera, + tera_ctx: &tera::Context, + ) -> crate::Result<&mut Self> { + self.task = tera.render_str(&self.task, tera_ctx)?; + for a in &mut self.args { + *a = tera.render_str(a, tera_ctx)?; + } + if self.args.is_empty() { + let s = self.task.clone(); + let mut split = s.split_whitespace().map(|s| s.to_string()); + if let Some(task) = split.next() { + self.task = task; + } + self.args = split.collect(); + } + Ok(self) + } +} + impl Display for TaskDep { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.task)?; @@ -32,13 +53,9 @@ impl FromStr for TaskDep { type Err = String; fn from_str(s: &str) -> Result { - let parts = s.split_whitespace().collect_vec(); - if parts.is_empty() { - return Err("Task name is required".to_string()); - } Ok(Self { - task: parts[0].to_string(), - args: parts[1..].iter().map(|s| s.to_string()).collect(), + task: s.to_string(), + args: Default::default(), }) } } @@ -85,9 +102,10 @@ mod tests { assert_eq!(td.task, "task"); assert!(td.args.is_empty()); - let td: TaskDep = "task arg1 arg2".parse().unwrap(); - assert_eq!(td.task, "task"); - assert_eq!(td.args, vec!["arg1", "arg2"]); + // TODO: td.render() + // let td: TaskDep = "task arg1 arg2".parse().unwrap(); + // assert_eq!(td.task, "task"); + // assert_eq!(td.args, vec!["arg1", "arg2"]); } #[test] diff --git a/src/task/task_script_parser.rs b/src/task/task_script_parser.rs index 7ace87b08d..3f26070640 100644 --- a/src/task/task_script_parser.rs +++ b/src/task/task_script_parser.rs @@ -1,4 +1,4 @@ -use crate::config::{Config, SETTINGS}; +use crate::config::SETTINGS; use crate::exit::exit; use crate::shell::ShellType; use crate::task::Task; @@ -27,7 +27,7 @@ impl TaskScriptParser { pub fn parse_run_scripts( &self, - config_root: &Option, + task: &Task, scripts: &[String], ) -> Result<(Vec, usage::Spec)> { let mut tera = self.get_tera(); @@ -272,9 +272,7 @@ impl TaskScriptParser { } } }); - let config = Config::get(); - let mut ctx = config.tera_ctx.clone(); - ctx.insert("config_root", config_root); + let ctx = task.tera_ctx()?; let scripts = scripts .iter() .map(|s| tera.render_str(s.trim(), &ctx).unwrap()) @@ -384,7 +382,7 @@ mod tests { let task = Task::default(); let parser = TaskScriptParser::new(None); let scripts = vec!["echo {{ arg(i=0, name='foo') }}".to_string()]; - let (scripts, spec) = parser.parse_run_scripts(&None, &scripts).unwrap(); + let (scripts, spec) = parser.parse_run_scripts(&task, &scripts).unwrap(); assert_eq!(scripts, vec!["echo MISE_TASK_ARG:foo:MISE_TASK_ARG"]); let arg0 = spec.cmd.args.first().unwrap(); assert_eq!(arg0.name, "foo"); @@ -403,7 +401,7 @@ mod tests { "echo {{ arg(name='foo') }}; echo {{ arg(name='bar') }}; echo {{ arg(name='foo') }}" .to_string(), ]; - let (scripts, spec) = parser.parse_run_scripts(&None, &scripts).unwrap(); + let (scripts, spec) = parser.parse_run_scripts(&task, &scripts).unwrap(); assert_eq!(scripts, vec!["echo MISE_TASK_ARG:foo:MISE_TASK_ARG; echo MISE_TASK_ARG:bar:MISE_TASK_ARG; echo MISE_TASK_ARG:foo:MISE_TASK_ARG"]); let arg0 = spec.cmd.args.first().unwrap(); let arg1 = spec.cmd.args.get(1).unwrap(); @@ -426,7 +424,7 @@ mod tests { let task = Task::default(); let parser = TaskScriptParser::new(None); let scripts = vec!["echo {{ arg(var=true) }}".to_string()]; - let (scripts, spec) = parser.parse_run_scripts(&None, &scripts).unwrap(); + let (scripts, spec) = parser.parse_run_scripts(&task, &scripts).unwrap(); assert_eq!(scripts, vec!["echo MISE_TASK_ARG:0:MISE_TASK_ARG"]); let arg0 = spec.cmd.args.first().unwrap(); assert_eq!(arg0.name, "0"); @@ -446,7 +444,7 @@ mod tests { let task = Task::default(); let parser = TaskScriptParser::new(None); let scripts = vec!["echo {{ flag(name='foo') }}".to_string()]; - let (scripts, spec) = parser.parse_run_scripts(&None, &scripts).unwrap(); + let (scripts, spec) = parser.parse_run_scripts(&task, &scripts).unwrap(); assert_eq!(scripts, vec!["echo MISE_TASK_ARG:foo:MISE_TASK_ARG"]); let flag = spec.cmd.flags.iter().find(|f| &f.name == "foo").unwrap(); assert_eq!(&flag.name, "foo"); @@ -462,7 +460,7 @@ mod tests { let task = Task::default(); let parser = TaskScriptParser::new(None); let scripts = vec!["echo {{ option(name='foo') }}".to_string()]; - let (scripts, spec) = parser.parse_run_scripts(&None, &scripts).unwrap(); + let (scripts, spec) = parser.parse_run_scripts(&task, &scripts).unwrap(); assert_eq!(scripts, vec!["echo MISE_TASK_ARG:foo:MISE_TASK_ARG"]); let option = spec.cmd.flags.iter().find(|f| &f.name == "foo").unwrap(); assert_eq!(&option.name, "foo"); diff --git a/src/toolset/mod.rs b/src/toolset/mod.rs index db925d766a..7a4621fc71 100644 --- a/src/toolset/mod.rs +++ b/src/toolset/mod.rs @@ -23,6 +23,7 @@ use console::truncate_str; use eyre::{eyre, Result, WrapErr}; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; +use once_cell::sync::OnceCell; pub use outdated_info::is_outdated_version; use outdated_info::OutdatedInfo; use rayon::prelude::*; @@ -108,6 +109,7 @@ impl Default for InstallOptions { pub struct Toolset { pub versions: IndexMap, pub source: Option, + tera_ctx: OnceCell, } impl Toolset { @@ -463,7 +465,7 @@ impl Toolset { /// returns env_with_path but also with the existing env vars from the system pub fn full_env(&self) -> Result { let mut env = env::PRISTINE_ENV.clone().into_iter().collect::(); - env.extend(self.env_with_path(&Config::get())?); + env.extend(self.env_with_path(&Config::get())?.clone()); Ok(env) } /// the full mise environment including all tool paths @@ -471,7 +473,7 @@ impl Toolset { let (mut env, env_results) = self.final_env(config)?; let mut path_env = PathEnv::from_iter(env::PATH.clone()); for p in self.list_final_paths(config, env_results)? { - path_env.add(p); + path_env.add(p.clone()); } env.insert(PATH_KEY.to_string(), path_env.to_string()); Ok(env) @@ -585,6 +587,15 @@ impl Toolset { let paths = env_results.env_paths.into_iter().chain(paths).collect(); Ok(paths) } + pub fn tera_ctx(&self) -> Result<&tera::Context> { + self.tera_ctx.get_or_try_init(|| { + let config = Config::get(); + let env = self.env_with_path(&config)?; + let mut ctx = config.tera_ctx.clone(); + ctx.insert("env", &env); + Ok(ctx) + }) + } pub fn which(&self, bin_name: &str) -> Option<(Arc, ToolVersion)> { self.list_current_installed_versions() .into_par_iter() diff --git a/src/toolset/tool_request_set.rs b/src/toolset/tool_request_set.rs index a2df983fc4..7e7535c3bc 100644 --- a/src/toolset/tool_request_set.rs +++ b/src/toolset/tool_request_set.rs @@ -3,7 +3,7 @@ use std::fmt::{Debug, Display}; use indexmap::IndexMap; use itertools::Itertools; - +use crate::backend::backend_type::BackendType; use crate::cli::args::{BackendArg, ToolArg}; use crate::config::{Config, Settings}; use crate::env; @@ -168,6 +168,10 @@ impl ToolRequestSetBuilder { } fn is_disabled(&self, ba: &BackendArg) -> bool { + let backend_type = ba.backend_type(); + if backend_type == BackendType::Unknown || (cfg!(windows) && backend_type == BackendType::Asdf) { + return false; + } !ba.is_os_supported() || self.disable_tools.contains(ba) }