From 1c93bbbb7135222e85e336000b9b6080c221bff5 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 7 Jan 2025 13:59:04 -0500 Subject: [PATCH 1/7] chore(env): simplify framework inference branching --- crates/turborepo-lib/src/task_hash.rs | 96 ++++++++++++--------------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index 5e78ee45e79bc..015e51fcc6032 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -286,70 +286,62 @@ impl<'a> TaskHasher<'a> { let mut explicit_env_var_map = EnvironmentVariableMap::default(); let mut all_env_var_map = EnvironmentVariableMap::default(); let mut matching_env_var_map = EnvironmentVariableMap::default(); - - let framework_slug = if do_framework_inference { - // See if we infer a framework - if let Some(framework) = infer_framework(workspace, is_monorepo) { - debug!("auto detected framework for {}", task_id.package()); - debug!( - "framework: {}, env_prefix: {:?}", - framework.slug(), - framework.env_wildcards() - ); - telemetry.track_framework(framework.slug()); - let mut computed_wildcards = framework - .env_wildcards() - .iter() - .map(|s| s.to_string()) - .collect::>(); - - if let Some(exclude_prefix) = - self.env_at_execution_start.get("TURBO_CI_VENDOR_ENV_KEY") - { - if !exclude_prefix.is_empty() { - let computed_exclude = format!("!{}*", exclude_prefix); - debug!( - "excluding environment variables matching wildcard {}", - computed_exclude - ); - computed_wildcards.push(computed_exclude); - } + // See if we infer a framework + let framework = do_framework_inference + .then(|| infer_framework(workspace, is_monorepo)) + .flatten(); + let framework_slug = framework.map(|f| f.slug().to_string()); + + if let Some(framework) = framework { + debug!("auto detected framework for {}", task_id.package()); + debug!( + "framework: {}, env_prefix: {:?}", + framework.slug(), + framework.env_wildcards() + ); + telemetry.track_framework(framework.slug()); + let mut computed_wildcards = framework + .env_wildcards() + .iter() + .map(|s| s.to_string()) + .collect::>(); + + if let Some(exclude_prefix) = self.env_at_execution_start.get("TURBO_CI_VENDOR_ENV_KEY") + { + if !exclude_prefix.is_empty() { + let computed_exclude = format!("!{}*", exclude_prefix); + debug!( + "excluding environment variables matching wildcard {}", + computed_exclude + ); + computed_wildcards.push(computed_exclude); } + } - let inference_env_var_map = self - .env_at_execution_start - .from_wildcards(&computed_wildcards)?; - - let user_env_var_set = self - .env_at_execution_start - .wildcard_map_from_wildcards_unresolved(&task_definition.env)?; + let inference_env_var_map = self + .env_at_execution_start + .from_wildcards(&computed_wildcards)?; - all_env_var_map.union(&user_env_var_set.inclusions); - all_env_var_map.union(&inference_env_var_map); - all_env_var_map.difference(&user_env_var_set.exclusions); + let user_env_var_set = self + .env_at_execution_start + .wildcard_map_from_wildcards_unresolved(&task_definition.env)?; - explicit_env_var_map.union(&user_env_var_set.inclusions); - explicit_env_var_map.difference(&user_env_var_set.exclusions); + all_env_var_map.union(&user_env_var_set.inclusions); + all_env_var_map.union(&inference_env_var_map); + all_env_var_map.difference(&user_env_var_set.exclusions); - matching_env_var_map.union(&inference_env_var_map); - matching_env_var_map.difference(&user_env_var_set.exclusions); - Some(framework.slug().to_string()) - } else { - all_env_var_map = self - .env_at_execution_start - .from_wildcards(&task_definition.env)?; + explicit_env_var_map.union(&user_env_var_set.inclusions); + explicit_env_var_map.difference(&user_env_var_set.exclusions); - explicit_env_var_map.union(&all_env_var_map); - None - } + matching_env_var_map.union(&inference_env_var_map); + matching_env_var_map.difference(&user_env_var_set.exclusions); } else { all_env_var_map = self .env_at_execution_start .from_wildcards(&task_definition.env)?; explicit_env_var_map.union(&all_env_var_map); - None - }; + } let env_vars = DetailedMap { all: all_env_var_map, From 050b4b7817252b71f9a1b8a4bd1337cd2d59a499 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 7 Jan 2025 14:06:16 -0500 Subject: [PATCH 2/7] chore(env): move debugging lines to famework inference --- crates/turborepo-lib/src/task_hash.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index 015e51fcc6032..8ac3351a62293 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -289,17 +289,19 @@ impl<'a> TaskHasher<'a> { // See if we infer a framework let framework = do_framework_inference .then(|| infer_framework(workspace, is_monorepo)) - .flatten(); + .flatten() + .inspect(|framework| { + debug!("auto detected framework for {}", task_id.package()); + debug!( + "framework: {}, env_prefix: {:?}", + framework.slug(), + framework.env_wildcards() + ); + telemetry.track_framework(framework.slug()); + }); let framework_slug = framework.map(|f| f.slug().to_string()); if let Some(framework) = framework { - debug!("auto detected framework for {}", task_id.package()); - debug!( - "framework: {}, env_prefix: {:?}", - framework.slug(), - framework.env_wildcards() - ); - telemetry.track_framework(framework.slug()); let mut computed_wildcards = framework .env_wildcards() .iter() From dd9e135465af755355d47d554bac5420bbe6e7f2 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 7 Jan 2025 16:09:09 -0500 Subject: [PATCH 3/7] chore(env): simplify inferred env vars --- crates/turborepo-lib/src/task_hash.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index 8ac3351a62293..dea76957ec39e 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -302,22 +302,19 @@ impl<'a> TaskHasher<'a> { let framework_slug = framework.map(|f| f.slug().to_string()); if let Some(framework) = framework { - let mut computed_wildcards = framework - .env_wildcards() - .iter() - .map(|s| s.to_string()) - .collect::>(); + let mut computed_wildcards = framework.env_wildcards().to_vec(); - if let Some(exclude_prefix) = self.env_at_execution_start.get("TURBO_CI_VENDOR_ENV_KEY") + if let Some(exclude_prefix) = self + .env_at_execution_start + .get("TURBO_CI_VENDOR_ENV_KEY") + .filter(|prefix| !prefix.is_empty()) { - if !exclude_prefix.is_empty() { - let computed_exclude = format!("!{}*", exclude_prefix); - debug!( - "excluding environment variables matching wildcard {}", - computed_exclude - ); - computed_wildcards.push(computed_exclude); - } + let computed_exclude = format!("!{}*", exclude_prefix); + debug!( + "excluding environment variables matching wildcard {}", + computed_exclude + ); + computed_wildcards.push(computed_exclude); } let inference_env_var_map = self From 4e9fe2bbf8724d4a699d65cc29af3883bfa2828a Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Wed, 8 Jan 2025 07:35:37 -0500 Subject: [PATCH 4/7] chore(env): refactor and add tests for env resolution --- crates/turborepo-env/src/lib.rs | 56 +++++++++++++++++++++++++++ crates/turborepo-lib/src/task_hash.rs | 42 ++++++-------------- 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/crates/turborepo-env/src/lib.rs b/crates/turborepo-env/src/lib.rs index 29425f9aa3ef0..255e7feb4d255 100644 --- a/crates/turborepo-env/src/lib.rs +++ b/crates/turborepo-env/src/lib.rs @@ -229,6 +229,39 @@ impl EnvironmentVariableMap { self.wildcard_map_from_wildcards(wildcard_patterns) } + + /// Return a detailed map for which environment variables are factored into + /// the task's hash + pub fn hashable_task_env( + &self, + computed_wildcards: &[String], + task_env: &[String], + ) -> Result { + let mut explicit_env_var_map = EnvironmentVariableMap::default(); + let mut all_env_var_map = EnvironmentVariableMap::default(); + let mut matching_env_var_map = EnvironmentVariableMap::default(); + let inference_env_var_map = self.from_wildcards(computed_wildcards)?; + + let user_env_var_set = self.wildcard_map_from_wildcards_unresolved(task_env)?; + + all_env_var_map.union(&user_env_var_set.inclusions); + all_env_var_map.union(&inference_env_var_map); + all_env_var_map.difference(&user_env_var_set.exclusions); + + explicit_env_var_map.union(&user_env_var_set.inclusions); + explicit_env_var_map.difference(&user_env_var_set.exclusions); + + matching_env_var_map.union(&inference_env_var_map); + matching_env_var_map.difference(&user_env_var_set.exclusions); + + Ok(DetailedMap { + all: all_env_var_map, + by_source: BySource { + explicit: explicit_env_var_map, + matching: matching_env_var_map, + }, + }) + } } const WILDCARD: char = '*'; @@ -358,4 +391,27 @@ mod tests { actual.sort(); assert_eq!(actual, expected); } + + #[test_case(&["FOO*"], &["BAR"], &["BAR", "FOO", "FOOBAR", "FOOD"] ; "wildcard")] + #[test_case(&["FOO*", "!FOOBAR"], &["BAR"], &["BAR", "FOO", "FOOD"] ; "omit wild")] + #[test_case(&["FOO*"], &["!FOOBAR"], &["FOO", "FOOD"] ; "omit task")] + fn test_hashable_env(wildcards: &[&str], task: &[&str], expected: &[&str]) { + let env_at_start = EnvironmentVariableMap( + vec![ + ("FOO", "bar"), + ("FOOBAR", "baz"), + ("FOOD", "cheese"), + ("BAR", "nuts"), + ] + .into_iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect(), + ); + let wildcards: Vec<_> = wildcards.iter().map(|s| s.to_string()).collect(); + let task: Vec<_> = task.iter().map(|s| s.to_string()).collect(); + let output = env_at_start.hashable_task_env(&wildcards, &task).unwrap(); + let mut actual: Vec<_> = output.all.keys().map(|s| s.as_str()).collect(); + actual.sort(); + assert_eq!(actual, expected); + } } diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index dea76957ec39e..ea28b3742eb91 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -283,9 +283,6 @@ impl<'a> TaskHasher<'a> { .hashes .get(task_id) .ok_or_else(|| Error::MissingPackageFileHash(task_id.to_string()))?; - let mut explicit_env_var_map = EnvironmentVariableMap::default(); - let mut all_env_var_map = EnvironmentVariableMap::default(); - let mut matching_env_var_map = EnvironmentVariableMap::default(); // See if we infer a framework let framework = do_framework_inference .then(|| infer_framework(workspace, is_monorepo)) @@ -301,7 +298,7 @@ impl<'a> TaskHasher<'a> { }); let framework_slug = framework.map(|f| f.slug().to_string()); - if let Some(framework) = framework { + let env_vars = if let Some(framework) = framework { let mut computed_wildcards = framework.env_wildcards().to_vec(); if let Some(exclude_prefix) = self @@ -317,37 +314,20 @@ impl<'a> TaskHasher<'a> { computed_wildcards.push(computed_exclude); } - let inference_env_var_map = self - .env_at_execution_start - .from_wildcards(&computed_wildcards)?; - - let user_env_var_set = self - .env_at_execution_start - .wildcard_map_from_wildcards_unresolved(&task_definition.env)?; - - all_env_var_map.union(&user_env_var_set.inclusions); - all_env_var_map.union(&inference_env_var_map); - all_env_var_map.difference(&user_env_var_set.exclusions); - - explicit_env_var_map.union(&user_env_var_set.inclusions); - explicit_env_var_map.difference(&user_env_var_set.exclusions); - - matching_env_var_map.union(&inference_env_var_map); - matching_env_var_map.difference(&user_env_var_set.exclusions); + self.env_at_execution_start + .hashable_task_env(&computed_wildcards, &task_definition.env)? } else { - all_env_var_map = self + let all_env_var_map = self .env_at_execution_start .from_wildcards(&task_definition.env)?; - explicit_env_var_map.union(&all_env_var_map); - } - - let env_vars = DetailedMap { - all: all_env_var_map, - by_source: BySource { - explicit: explicit_env_var_map, - matching: matching_env_var_map, - }, + DetailedMap { + all: all_env_var_map.clone(), + by_source: BySource { + explicit: all_env_var_map, + matching: EnvironmentVariableMap::default(), + }, + } }; let hashable_env_pairs = env_vars.all.to_hashable(); From e6008604292a8d998cedc535d97dc51c4df93c8a Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Wed, 8 Jan 2025 17:12:13 -0500 Subject: [PATCH 5/7] chore(env): move pass through env creation to own method --- crates/turborepo-env/src/lib.rs | 49 +++++++++++ crates/turborepo-lib/src/task_hash.rs | 115 +++++++++++++------------- 2 files changed, 106 insertions(+), 58 deletions(-) diff --git a/crates/turborepo-env/src/lib.rs b/crates/turborepo-env/src/lib.rs index 255e7feb4d255..fd88a67a9ccc8 100644 --- a/crates/turborepo-env/src/lib.rs +++ b/crates/turborepo-env/src/lib.rs @@ -262,6 +262,25 @@ impl EnvironmentVariableMap { }, }) } + + /// Constructs an environment map that contains pass through environment + /// variables + pub fn pass_through_env( + &self, + builtins: &[&str], + global_env: &Self, + task_pass_through: &[impl AsRef], + ) -> Result { + let mut pass_through_env = EnvironmentVariableMap::default(); + let default_env_var_pass_through_map = self.from_wildcards(builtins)?; + let task_pass_through_env = self.from_wildcards(task_pass_through)?; + + pass_through_env.union(&default_env_var_pass_through_map); + pass_through_env.union(global_env); + pass_through_env.union(&task_pass_through_env); + + Ok(pass_through_env) + } } const WILDCARD: char = '*'; @@ -414,4 +433,34 @@ mod tests { actual.sort(); assert_eq!(actual, expected); } + + #[test_case(&["FOO*"], &["FOO", "FOOBAR", "FOOD", "PATH"] ; "folds 3 sources")] + #[test_case(&["!FOO"], &["FOO", "PATH"] ; "remove global")] + #[test_case(&["!PATH"], &["FOO", "PATH"] ; "remove builtin")] + fn test_pass_through_env(task: &[&str], expected: &[&str]) { + let env_at_start = EnvironmentVariableMap( + vec![ + ("PATH", "of"), + ("FOO", "bar"), + ("FOOBAR", "baz"), + ("FOOD", "cheese"), + ("BAR", "nuts"), + ] + .into_iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect(), + ); + let global_env = EnvironmentVariableMap( + vec![("FOO", "bar")] + .into_iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect(), + ); + let output = env_at_start + .pass_through_env(&["PATH"], &global_env, task) + .unwrap(); + let mut actual: Vec<_> = output.keys().map(|s| s.as_str()).collect(); + actual.sort(); + assert_eq!(actual, expected); + } } diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index ea28b3742eb91..d5e7fe5bb7ba0 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -433,70 +433,69 @@ impl<'a> TaskHasher<'a> { ) -> Result { match task_env_mode { EnvMode::Strict => { - let mut pass_through_env = EnvironmentVariableMap::default(); - let default_env_var_pass_through_map = - self.env_at_execution_start.from_wildcards(&[ - "HOME", - "USER", - "TZ", - "LANG", - "SHELL", - "PWD", - "CI", - "NODE_OPTIONS", - "COREPACK_HOME", - "LD_LIBRARY_PATH", - "DYLD_FALLBACK_LIBRARY_PATH", - "LIBPATH", - "COLORTERM", - "TERM", - "TERM_PROGRAM", - "DISPLAY", - "TMP", - "TEMP", - // VSCode IDE - https://github.com/microsoft/vscode-js-debug/blob/5b0f41dbe845d693a541c1fae30cec04c878216f/src/targets/node/nodeLauncherBase.ts#L320 - "VSCODE_*", - "ELECTRON_RUN_AS_NODE", - // Docker - https://docs.docker.com/engine/reference/commandline/cli/#environment-variables - "DOCKER_*", - "BUILDKIT_*", - // Docker compose - https://docs.docker.com/compose/environment-variables/envvars/ - "COMPOSE_*", - // Jetbrains IDE - "JB_IDE_*", - "JB_INTERPRETER", - "_JETBRAINS_TEST_RUNNER_RUN_SCOPE_TYPE", - // Vercel specific - "VERCEL", - "VERCEL_*", - "NEXT_*", - "USE_OUTPUT_FOR_EDGE_FUNCTIONS", - "NOW_BUILDER", - // Command Prompt casing of env variables - "APPDATA", - "PATH", - "PROGRAMDATA", - "SYSTEMROOT", - "SYSTEMDRIVE", - ])?; - let tracker_env = self - .task_hash_tracker - .env_vars(task_id) - .ok_or_else(|| Error::MissingEnvVars(task_id.clone().into_owned()))?; - - pass_through_env.union(&default_env_var_pass_through_map); - pass_through_env.union(global_env); - pass_through_env.union(&tracker_env.all); - - let env_var_pass_through_map = self.env_at_execution_start.from_wildcards( + let mut full_task_env = EnvironmentVariableMap::default(); + let builtin_pass_through = &[ + "HOME", + "USER", + "TZ", + "LANG", + "SHELL", + "PWD", + "CI", + "NODE_OPTIONS", + "COREPACK_HOME", + "LD_LIBRARY_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + "LIBPATH", + "COLORTERM", + "TERM", + "TERM_PROGRAM", + "DISPLAY", + "TMP", + "TEMP", + // VSCode IDE - https://github.com/microsoft/vscode-js-debug/blob/5b0f41dbe845d693a541c1fae30cec04c878216f/src/targets/node/nodeLauncherBase.ts#L320 + "VSCODE_*", + "ELECTRON_RUN_AS_NODE", + // Docker - https://docs.docker.com/engine/reference/commandline/cli/#environment-variables + "DOCKER_*", + "BUILDKIT_*", + // Docker compose - https://docs.docker.com/compose/environment-variables/envvars/ + "COMPOSE_*", + // Jetbrains IDE + "JB_IDE_*", + "JB_INTERPRETER", + "_JETBRAINS_TEST_RUNNER_RUN_SCOPE_TYPE", + // Vercel specific + "VERCEL", + "VERCEL_*", + "NEXT_*", + "USE_OUTPUT_FOR_EDGE_FUNCTIONS", + "NOW_BUILDER", + // Command Prompt casing of env variables + "APPDATA", + "PATH", + "PROGRAMDATA", + "SYSTEMROOT", + "SYSTEMDRIVE", + ]; + let pass_through_env_vars = self.env_at_execution_start.pass_through_env( + builtin_pass_through, + global_env, task_definition .pass_through_env .as_deref() .unwrap_or_default(), )?; - pass_through_env.union(&env_var_pass_through_map); - Ok(pass_through_env) + let tracker_env = self + .task_hash_tracker + .env_vars(task_id) + .ok_or_else(|| Error::MissingEnvVars(task_id.clone().into_owned()))?; + + full_task_env.union(&pass_through_env_vars); + full_task_env.union(&tracker_env.all); + + Ok(full_task_env) } EnvMode::Loose => Ok(self.env_at_execution_start.clone()), } From e3d1d08e79794ca930d15584fc1638f0fbc6c452 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Wed, 8 Jan 2025 17:16:46 -0500 Subject: [PATCH 6/7] chore(env): remove need to pass global_env around --- crates/turborepo-lib/src/task_graph/visitor/mod.rs | 9 ++++----- crates/turborepo-lib/src/task_hash.rs | 6 ++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/turborepo-lib/src/task_graph/visitor/mod.rs b/crates/turborepo-lib/src/task_graph/visitor/mod.rs index 2797d693d3de6..b8d2b8b89c52c 100644 --- a/crates/turborepo-lib/src/task_graph/visitor/mod.rs +++ b/crates/turborepo-lib/src/task_graph/visitor/mod.rs @@ -52,7 +52,6 @@ use crate::{ pub struct Visitor<'a> { color_cache: ColorSelector, dry: bool, - global_env: EnvironmentVariableMap, global_env_mode: EnvMode, manager: ProcessManager, run_opts: &'a RunOpts, @@ -146,6 +145,7 @@ impl<'a> Visitor<'a> { run_opts, env_at_execution_start, global_hash, + global_env, ); let sink = Self::sink(run_opts); @@ -172,7 +172,6 @@ impl<'a> Visitor<'a> { sink, task_hasher, color_config, - global_env, ui_sender, is_watch, warnings: Default::default(), @@ -259,9 +258,9 @@ impl<'a> Visitor<'a> { // We do this calculation earlier than we do in Go due to the `task_hasher` // being !Send. In the future we can look at doing this right before // task execution instead. - let execution_env = - self.task_hasher - .env(&info, task_env_mode, task_definition, &self.global_env)?; + let execution_env = self + .task_hasher + .env(&info, task_env_mode, task_definition)?; let task_cache = self.run_cache.task_cache( task_definition, diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index d5e7fe5bb7ba0..18e9c2c44c878 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -242,6 +242,7 @@ pub struct TaskHasher<'a> { hashes: HashMap, String>, run_opts: &'a RunOpts, env_at_execution_start: &'a EnvironmentVariableMap, + global_env: EnvironmentVariableMap, global_hash: &'a str, task_hash_tracker: TaskHashTracker, } @@ -252,6 +253,7 @@ impl<'a> TaskHasher<'a> { run_opts: &'a RunOpts, env_at_execution_start: &'a EnvironmentVariableMap, global_hash: &'a str, + global_env: EnvironmentVariableMap, ) -> Self { let PackageInputsHashes { hashes, @@ -262,6 +264,7 @@ impl<'a> TaskHasher<'a> { run_opts, env_at_execution_start, global_hash, + global_env, task_hash_tracker: TaskHashTracker::new(expanded_hashes), } } @@ -429,7 +432,6 @@ impl<'a> TaskHasher<'a> { task_id: &TaskId, task_env_mode: EnvMode, task_definition: &TaskDefinition, - global_env: &EnvironmentVariableMap, ) -> Result { match task_env_mode { EnvMode::Strict => { @@ -480,7 +482,7 @@ impl<'a> TaskHasher<'a> { ]; let pass_through_env_vars = self.env_at_execution_start.pass_through_env( builtin_pass_through, - global_env, + &self.global_env, task_definition .pass_through_env .as_deref() From ae25499b1e5048189f6ea16df75efa8522194338 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 14 Jan 2025 10:49:16 -0500 Subject: [PATCH 7/7] fix(env): allow for pass through env to negate global --- crates/turborepo-env/src/lib.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/turborepo-env/src/lib.rs b/crates/turborepo-env/src/lib.rs index fd88a67a9ccc8..5b5de1fb5965b 100644 --- a/crates/turborepo-env/src/lib.rs +++ b/crates/turborepo-env/src/lib.rs @@ -218,7 +218,7 @@ impl EnvironmentVariableMap { // that user exclusions have primacy over inferred inclusions. pub fn wildcard_map_from_wildcards_unresolved( &self, - wildcard_patterns: &[String], + wildcard_patterns: &[impl AsRef], ) -> Result { if wildcard_patterns.is_empty() { return Ok(WildcardMaps { @@ -273,11 +273,13 @@ impl EnvironmentVariableMap { ) -> Result { let mut pass_through_env = EnvironmentVariableMap::default(); let default_env_var_pass_through_map = self.from_wildcards(builtins)?; - let task_pass_through_env = self.from_wildcards(task_pass_through)?; + let task_pass_through_env = + self.wildcard_map_from_wildcards_unresolved(task_pass_through)?; pass_through_env.union(&default_env_var_pass_through_map); pass_through_env.union(global_env); - pass_through_env.union(&task_pass_through_env); + pass_through_env.union(&task_pass_through_env.inclusions); + pass_through_env.difference(&task_pass_through_env.exclusions); Ok(pass_through_env) } @@ -435,8 +437,9 @@ mod tests { } #[test_case(&["FOO*"], &["FOO", "FOOBAR", "FOOD", "PATH"] ; "folds 3 sources")] - #[test_case(&["!FOO"], &["FOO", "PATH"] ; "remove global")] - #[test_case(&["!PATH"], &["FOO", "PATH"] ; "remove builtin")] + #[test_case(&["!FOO"], &["PATH"] ; "remove global")] + #[test_case(&["!PATH"], &["FOO"] ; "remove builtin")] + #[test_case(&["FOO*", "!FOOD"], &["FOO", "FOOBAR", "PATH"] ; "mixing negations")] fn test_pass_through_env(task: &[&str], expected: &[&str]) { let env_at_start = EnvironmentVariableMap( vec![