diff --git a/crates/vite_task_plan/src/context.rs b/crates/vite_task_plan/src/context.rs index 119d68af..8422128f 100644 --- a/crates/vite_task_plan/src/context.rs +++ b/crates/vite_task_plan/src/context.rs @@ -28,7 +28,13 @@ pub struct PlanContext<'a> { cwd: Arc, /// The environment variables for the current execution context. - envs: FxHashMap, Arc>, + /// + /// `Arc`-shared with copy-on-write semantics: [`duplicate`](Self::duplicate) + /// and downstream consumers (`ScriptCommand::envs`) share the map; + /// mutations ([`add_envs`](Self::add_envs), + /// [`prepend_path`](Self::prepend_path)) clone only when the map is + /// currently shared. + envs: Arc, Arc>>, /// The callbacks for loading task graphs and parsing commands. callbacks: &'a mut (dyn PlanRequestParser + 'a), @@ -54,7 +60,7 @@ impl<'a> PlanContext<'a> { pub fn new( workspace_path: &'a Arc, cwd: Arc, - envs: FxHashMap, Arc>, + envs: Arc, Arc>>, callbacks: &'a mut (dyn PlanRequestParser + 'a), indexed_task_graph: &'a IndexedTaskGraph, resolved_global_cache: ResolvedGlobalCacheConfig, @@ -73,7 +79,7 @@ impl<'a> PlanContext<'a> { } } - pub const fn envs(&self) -> &FxHashMap, Arc> { + pub const fn envs(&self) -> &Arc, Arc>> { &self.envs } @@ -108,15 +114,22 @@ impl<'a> PlanContext<'a> { } pub fn prepend_path(&mut self, path_to_prepend: &AbsolutePath) -> Result<(), JoinPathsError> { - prepend_path_env(&mut self.envs, path_to_prepend) + prepend_path_env(Arc::make_mut(&mut self.envs), path_to_prepend) } pub fn add_envs( &mut self, new_envs: impl Iterator, impl AsRef)>, ) { + // Don't touch the Arc for an empty iterator — `make_mut` would clone + // the shared map for nothing (most commands have no prefix envs). + let mut new_envs = new_envs.peekable(); + if new_envs.peek().is_none() { + return; + } + let envs = Arc::make_mut(&mut self.envs); for (key, value) in new_envs { - self.envs.insert(Arc::from(key.as_ref()), Arc::from(value.as_ref())); + envs.insert(Arc::from(key.as_ref()), Arc::from(value.as_ref())); } } @@ -154,7 +167,7 @@ impl<'a> PlanContext<'a> { PlanContext { workspace_path: self.workspace_path, cwd: Arc::clone(&self.cwd), - envs: self.envs.clone(), + envs: Arc::clone(&self.envs), callbacks: self.callbacks, task_call_stack: self.task_call_stack.clone(), indexed_task_graph: self.indexed_task_graph, diff --git a/crates/vite_task_plan/src/lib.rs b/crates/vite_task_plan/src/lib.rs index c68a19d5..abd66861 100644 --- a/crates/vite_task_plan/src/lib.rs +++ b/crates/vite_task_plan/src/lib.rs @@ -200,7 +200,7 @@ pub async fn plan_query( query_plan_request: QueryPlanRequest, workspace_path: &Arc, cwd: &Arc, - envs: &FxHashMap, Arc>, + envs: &Arc, Arc>>, plan_request_parser: &mut (dyn PlanRequestParser + '_), task_graph_loader: &mut (dyn TaskGraphLoader + '_), ) -> Result { @@ -216,7 +216,7 @@ pub async fn plan_query( let context = PlanContext::new( workspace_path, Arc::clone(cwd), - envs.clone(), + Arc::clone(envs), plan_request_parser, indexed_task_graph, resolved_global_cache, diff --git a/crates/vite_task_plan/src/plan.rs b/crates/vite_task_plan/src/plan.rs index ad729e07..84faa171 100644 --- a/crates/vite_task_plan/src/plan.rs +++ b/crates/vite_task_plan/src/plan.rs @@ -211,7 +211,7 @@ async fn plan_task_as_execution_node( }; // Try to parse the args of an and_item to a plan request like `run -r build` - let envs: Arc, Arc>> = context.envs().clone().into(); + let envs = Arc::clone(context.envs()); let mut script_command = ScriptCommand { program: and_item.program.clone(), args: args.into(), @@ -597,12 +597,14 @@ fn plan_spawn_execution( execution_cache_key: Option, prefix_envs: &BTreeMap, resolved_task_options: &ResolvedTaskOptions, - envs: &FxHashMap, Arc>, + envs: &Arc, Arc>>, program_path: Arc, args: Arc<[Str]>, ) -> Result { - // all envs available in the current context - let mut all_envs = envs.clone(); + // The child env starts from the full context and is filtered in place by + // `EnvFingerprints::resolve` below — this clone is the one place the map's + // contents are actually copied per spawn. + let mut all_envs = (**envs).clone(); let cwd = Arc::clone(&resolved_task_options.cwd); let mut resolved_cache_metadata = None;