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/direnv/envrc.rs b/src/cli/direnv/envrc.rs index 38a5f41dbf..b355590d4c 100644 --- a/src/cli/direnv/envrc.rs +++ b/src/cli/direnv/envrc.rs @@ -48,7 +48,7 @@ impl Envrc { )?; } } - for path in ts.list_final_paths(config, env_results)?.into_iter().rev() { + for path in ts.list_final_paths(config, env_results)?.iter().rev() { writeln!(file, "PATH_add {}", path.to_string_lossy())?; } diff --git a/src/cli/doctor/path.rs b/src/cli/doctor/path.rs index 24f4166302..8e27b36244 100644 --- a/src/cli/doctor/path.rs +++ b/src/cli/doctor/path.rs @@ -21,7 +21,7 @@ impl Path { env::split_paths(&path).collect() } else { let (_env, env_results) = ts.final_env(&config)?; - ts.list_final_paths(&config, env_results)? + ts.list_final_paths(&config, env_results)?.clone() }; for path in paths { println!("{}", path.display()); 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..b252c0084c 100644 --- a/src/cli/exec.rs +++ b/src/cli/exec.rs @@ -82,7 +82,7 @@ impl Exec { }); let (program, mut args) = parse_command(&env::SHELL, &self.command, &self.c); - let env = measure!("env_with_path", { ts.env_with_path(&config)? }); + let env = measure!("env_with_path", { ts.env_with_path(&config)?.clone() }); if program.rsplit('/').next() == Some("fish") { let mut cmd = vec![]; @@ -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/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/config/mod.rs b/src/config/mod.rs index aaa732ecb5..7ca18e833e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,8 @@ use eyre::{bail, eyre, Context, Result}; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; -use once_cell::sync::OnceCell; +use std::sync::LazyLock as Lazy; +use once_cell::sync::{OnceCell}; use rayon::prelude::*; pub use settings::Settings; use std::collections::{BTreeMap, BTreeSet, HashMap}; @@ -9,7 +10,6 @@ use std::fmt::{Debug, Formatter}; use std::iter::once; use std::ops::Deref; use std::path::{Path, PathBuf}; -use std::sync::LazyLock as Lazy; use std::sync::{Arc, Mutex, OnceLock, RwLock}; use std::time::Duration; use walkdir::WalkDir; diff --git a/src/task/mod.rs b/src/task/mod.rs index b9050b8470..0bac7ac698 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -23,6 +23,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::LazyLock as Lazy; use std::{ffi, fmt, path}; +use once_cell::sync::OnceCell; use xx::regex; mod deps; @@ -97,6 +98,9 @@ pub struct Task { // file type #[serde(default)] pub file: Option, + + #[serde(skip)] + pub tera_ctx: OnceCell, } #[derive(Clone, PartialEq, Eq, Deserialize, Serialize)] @@ -382,9 +386,8 @@ 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 dir = tera.render_str(&dir, &tera_ctx)?; + let tera_ctx = self.tera_ctx()?; + let dir = tera.render_str(&dir, tera_ctx)?; let dir = file::replace_path(&dir); if dir.is_absolute() { Ok(Some(dir.to_path_buf())) @@ -398,6 +401,16 @@ impl Task { } } + pub fn tera_ctx(&self) -> Result<&tera::Context> { + self.tera_ctx.get_or_try_init(|| { + 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 +431,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()?.clone(); for a in &mut self.aliases { *a = tera.render_str(a, &tera_ctx)?; } @@ -431,22 +442,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 { @@ -527,6 +529,7 @@ impl Default for Task { file: None, quiet: false, tools: Default::default(), + tera_ctx: Default::default(), } } } diff --git a/src/task/task_dep.rs b/src/task/task_dep.rs index 4175fd40e4..21aa48823d 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,24 @@ 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 +49,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(), }) } } diff --git a/src/task/task_script_parser.rs b/src/task/task_script_parser.rs index 7ace87b08d..ccb27bf553 100644 --- a/src/task/task_script_parser.rs +++ b/src/task/task_script_parser.rs @@ -273,7 +273,9 @@ impl TaskScriptParser { } }); let config = Config::get(); - let mut ctx = config.tera_ctx.clone(); + // TODO: this won't have paths if they're added to the task directly + let ts = config.get_toolset()?; + let mut ctx = ts.tera_ctx()?.clone(); ctx.insert("config_root", config_root); let scripts = scripts .iter() diff --git a/src/toolset/mod.rs b/src/toolset/mod.rs index db925d766a..784f513c84 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).unwrap(); + 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()