diff --git a/src/workspace/environment.rs b/src/workspace/environment.rs index 39d4a816e3..f45eaaac4c 100644 --- a/src/workspace/environment.rs +++ b/src/workspace/environment.rs @@ -298,12 +298,21 @@ impl<'p> Environment<'p> { /// The environment variables of all features are combined in the order they /// are defined for the environment. pub(crate) fn activation_env(&self, platform: Option) -> IndexMap { - self.features() - .map(|f| f.activation_env(platform)) - .fold(IndexMap::new(), |mut acc, env| { + // As self.features() would put "default" envs in the last item, but the "default" env priority should be the lowest. + // Here, we use rfold (reverse fold) to ensure later features override earlier features + // for environment variables. Processing features in reverse order means that + // features appearing later in the list will have higher precedence. + // + // Example: If features: [override_feature, user_feature, default] + // - rfold processes as: [default, user_feature, override_feature] + // - Result: override_feature env vars take precedence over all others + self.features().map(|f| f.activation_env(platform)).rfold( + IndexMap::new(), + |mut acc, env| { acc.extend(env.iter().map(|(k, v)| (k.clone(), v.clone()))); acc - }) + }, + ) } /// Validates that the given platform is supported by this environment. @@ -614,6 +623,58 @@ mod tests { ); } + #[test] + fn test_activation_env_priority() { + let manifest = Workspace::from_str( + Path::new("pixi.toml"), + r#" + [project] + name = "foobar" + channels = [] + platforms = ["linux-64", "osx-64"] + + [activation.env] + FOO_VAR = "default" + + [feature.foo.activation.env] + FOO_VAR = "foo" + + [feature.cuda1.activation.env] + FOO_VAR = "cuda1" + + [feature.cuda2.activation.env] + FOO_VAR = "cuda2" + + [environments] + foo = ["foo"] + cuda = ["cuda1", "cuda2"] + "#, + ) + .unwrap(); + + let default_env = manifest.default_environment(); + let foo_env = manifest.environment("foo").unwrap(); + let cuda_env = manifest.environment("cuda").unwrap(); + assert_eq!( + default_env.activation_env(None), + indexmap! { + "FOO_VAR".to_string() => "default", + } + ); + assert_eq!( + foo_env.activation_env(None), + indexmap! { + "FOO_VAR".to_string() => "foo", + } + ); + assert_eq!( + cuda_env.activation_env(None), + indexmap! { + "FOO_VAR".to_string() => "cuda1", + } + ); + } + #[test] fn test_channel_feature_priority() { let manifest = Workspace::from_str( diff --git a/tests/integration_python/test_run_cli.py b/tests/integration_python/test_run_cli.py index e552757b71..36688e07e1 100644 --- a/tests/integration_python/test_run_cli.py +++ b/tests/integration_python/test_run_cli.py @@ -1554,7 +1554,8 @@ def test_run_with_environment_variable_priority( stdout_excludes="outside_env", env={"MY_ENV": "outside_env"}, ) - + + @pytest.mark.skipif( sys.platform == "win32", reason="Signal handling is different on Windows",