diff --git a/docs/environments/index.md b/docs/environments/index.md index 0a32a52ce8..715b9c051c 100644 --- a/docs/environments/index.md +++ b/docs/environments/index.md @@ -97,7 +97,7 @@ mise en ## Environment in tasks -Its also possible to define environment inside a task +It is also possible to define environment inside a task ```toml [mise.toml] [tasks.print] diff --git a/e2e/tasks/test_task_env_propagation b/e2e/tasks/test_task_env_propagation new file mode 100644 index 0000000000..c072a714ef --- /dev/null +++ b/e2e/tasks/test_task_env_propagation @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +cat <mise.toml +[tasks.echo] +env = { "SUB_TASK" = "sub_task_env" } +run = 'echo "\$SUB_TASK \$MY_VAR"' + + +[tasks.propagation] +run = [{ task = "echo" }] +env = { "MY_VAR" = "my_variable" } +EOF + +assert_contains "mise run echo" "sub_task_env " # with trailing space +assert_contains "mise run propagation" "sub_task_env my_variable" diff --git a/src/cli/run.rs b/src/cli/run.rs index 1c63bec41f..b33fa65b8e 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -13,7 +13,7 @@ use std::time::{Duration, SystemTime}; use super::args::ToolArg; use crate::cli::Cli; use crate::cmd::CmdLineRunner; -use crate::config::{Config, Settings}; +use crate::config::{Config, Settings, env_directive::EnvDirective}; use crate::env_diff::EnvMap; use crate::file::display_path; use crate::task::task_file_providers::TaskFileProvidersBuilder; @@ -687,7 +687,7 @@ impl Run { ts_build_start.elapsed().as_millis() ); let env_render_start = std::time::Instant::now(); - let mut env = task.render_env(config, &ts).await?; + let (mut env, task_env) = task.render_env(config, &ts).await?; trace!( "task {} render_env took {}ms", task.name, @@ -740,8 +740,15 @@ impl Run { .await?; let exec_start = std::time::Instant::now(); - self.exec_task_run_entries(config, task, &env, &prefix, rendered_run_scripts, sched_tx) - .await?; + self.exec_task_run_entries( + config, + task, + (&env, &task_env), + &prefix, + rendered_run_scripts, + sched_tx, + ) + .await?; trace!( "task {} exec_task_run_entries took {}ms (total {}ms)", task.name, @@ -769,11 +776,12 @@ impl Run { &self, config: &Arc, task: &Task, - env: &BTreeMap, + full_env: (&BTreeMap, &[(String, String)]), prefix: &str, rendered_scripts: Vec<(String, Vec)>, sched_tx: Arc>)>>, ) -> Result<()> { + let (env, task_env) = full_env; use crate::task::RunEntry; let mut script_iter = rendered_scripts.into_iter(); for entry in task.run() { @@ -784,11 +792,11 @@ impl Run { } } RunEntry::SingleTask { task: spec } => { - self.inject_and_wait(config, &[spec.to_string()], sched_tx.clone()) + self.inject_and_wait(config, &[spec.to_string()], task_env, sched_tx.clone()) .await?; } RunEntry::TaskGroup { tasks } => { - self.inject_and_wait(config, tasks, sched_tx.clone()) + self.inject_and_wait(config, tasks, task_env, sched_tx.clone()) .await?; } } @@ -800,6 +808,7 @@ impl Run { &self, config: &Arc, specs: &[String], + task_env: &[(String, String)], sched_tx: Arc>)>>, ) -> Result<()> { trace!("inject start: {}", specs.join(", ")); @@ -821,6 +830,8 @@ impl Run { // Pump subgraph into scheduler and signal completion via oneshot when done let (done_tx, done_rx) = oneshot::channel::<()>(); + let task_env_directives: Vec = + task_env.iter().cloned().map(Into::into).collect(); { let sub_deps_clone = sub_deps.clone(); let sched_tx = sched_tx.clone(); @@ -832,6 +843,7 @@ impl Run { match rx.try_recv() { Ok(Some(task)) => { any = true; + let task = task.derive_env(&task_env_directives); trace!("inject initial leaf: {} {}", task.name, task.args.join(" ")); let _ = sched_tx.send((task, sub_deps_clone.clone())); } @@ -862,6 +874,7 @@ impl Run { task.name, task.args.join(" ") ); + let task = task.derive_env(&task_env_directives); let _ = sched_tx.send((task, sub_deps_clone.clone())); } None => { diff --git a/src/task/mod.rs b/src/task/mod.rs index 81636015c9..cb4034d0fb 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,7 +1,7 @@ use crate::cli::version::VERSION; use crate::config::config_file::mise_toml::EnvList; use crate::config::config_file::toml::{TomlParser, deserialize_arr}; -use crate::config::env_directive::{EnvResolveOptions, EnvResults, ToolsFilter}; +use crate::config::env_directive::{EnvDirective, EnvResolveOptions, EnvResults, ToolsFilter}; use crate::config::{self, Config}; use crate::path_env::PathEnv; use crate::task::task_script_parser::{TaskScriptParser, has_any_args_defined}; @@ -232,6 +232,12 @@ impl Task { Ok(task) } + pub fn derive_env(&self, env_directives: &[EnvDirective]) -> Self { + let mut new_task = self.clone(); + new_task.env.0.extend_from_slice(env_directives); + new_task + } + /// prints the task name without an extension pub fn display_name(&self, all_tasks: &BTreeMap) -> String { let display_name = self @@ -563,7 +569,11 @@ impl Task { self.name.replace(':', path::MAIN_SEPARATOR_STR).into() } - pub async fn render_env(&self, config: &Arc, ts: &Toolset) -> Result { + pub async fn render_env( + &self, + config: &Arc, + ts: &Toolset, + ) -> Result<(EnvMap, Vec<(String, String)>)> { let mut tera_ctx = ts.tera_ctx(config).await?.clone(); let mut env = ts.full_env(config).await?; if let Some(root) = &config.project_root { @@ -591,9 +601,9 @@ impl Task { }, ) .await?; - + let task_env = env_results.env.into_iter().map(|(k, (v, _))| (k, v)); // Apply the resolved environment variables - env.extend(env_results.env.into_iter().map(|(k, (v, _))| (k, v))); + env.extend(task_env.clone()); // Remove environment variables that were explicitly unset for key in &env_results.env_remove { @@ -611,7 +621,7 @@ impl Task { env.insert(env::PATH_KEY.to_string(), path_env.to_string()); } - Ok(env) + Ok((env, task_env.collect())) } }