Skip to content

Commit 3a631c8

Browse files
authored
refactor: remove ExecutionPlan wrapper struct (#157)
## Summary - Remove the `ExecutionPlan` struct which was a trivial single-field wrapper around `ExecutionItemKind` that added no value - Promote `plan_query()` and `plan_synthetic()` to pub free functions in `vite_task_plan` - `plan_synthetic()` now returns `SpawnExecution` directly instead of wrapping/unwrapping through `ExecutionPlan` - Move `Serialize` impl to `ExecutionGraph` directly, removing the `serialize_with` indirection on `ExecutionItemKind::Expanded` - Remove unused methods: `plan()`, `is_empty()`, `root_node()`, `into_root_node()`, `from_execution_graph()`
1 parent 663351e commit 3a631c8

File tree

25 files changed

+1484
-1648
lines changed

25 files changed

+1484
-1648
lines changed

crates/vite_task/src/session/mod.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use vite_task_graph::{
2020
loader::UserConfigLoader,
2121
};
2222
use vite_task_plan::{
23-
ExecutionGraph, ExecutionPlan, TaskGraphLoader,
23+
ExecutionGraph, TaskGraphLoader,
2424
plan_request::{PlanRequest, ScriptCommand, SyntheticPlanRequest},
2525
prepend_path_env,
2626
};
@@ -457,18 +457,12 @@ impl<'a> Session<'a> {
457457
) -> anyhow::Result<ExitStatus> {
458458
// Plan the synthetic execution — returns a SpawnExecution directly
459459
// (synthetic plans are always a single spawned process)
460-
let execution_plan = ExecutionPlan::plan_synthetic(
460+
let spawn_execution = vite_task_plan::plan_synthetic(
461461
&self.workspace_path,
462462
&self.cwd,
463463
synthetic_plan_request,
464464
cache_key,
465465
)?;
466-
let vite_task_plan::ExecutionItemKind::Leaf(vite_task_plan::LeafExecutionKind::Spawn(
467-
spawn_execution,
468-
)) = execution_plan.into_root_node()
469-
else {
470-
unreachable!("plan_synthetic always produces a Leaf(Spawn(..)) node")
471-
};
472466

473467
// Initialize cache (needed for cache-aware execution)
474468
let cache = self.cache()?;
@@ -531,7 +525,7 @@ impl<'a> Session<'a> {
531525
});
532526
}
533527
};
534-
ExecutionPlan::plan_query(
528+
vite_task_plan::plan_query(
535529
query_plan_request,
536530
&self.workspace_path,
537531
&cwd,

crates/vite_task_plan/src/execution_graph.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::ops::Deref;
22

33
use petgraph::graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex};
4+
use serde::{Serialize, Serializer};
45

56
use crate::TaskExecution;
67

@@ -135,3 +136,9 @@ impl Deref for ExecutionGraph {
135136
&self.graph
136137
}
137138
}
139+
140+
impl Serialize for ExecutionGraph {
141+
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
142+
vite_graph_ser::serialize_by_key(&self.graph, serializer)
143+
}
144+
}

crates/vite_task_plan/src/lib.rs

Lines changed: 50 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub use path_env::{get_path_env, prepend_path_env};
1818
use plan::{ParentCacheConfig, plan_query_request, plan_synthetic_request};
1919
use plan_request::{PlanRequest, QueryPlanRequest, SyntheticPlanRequest};
2020
use rustc_hash::FxHashMap;
21-
use serde::{Serialize, Serializer, ser::SerializeMap as _};
21+
use serde::{Serialize, ser::SerializeMap as _};
2222
use vite_path::AbsolutePath;
2323
use vite_str::Str;
2424
use vite_task_graph::{TaskGraphLoadError, display::TaskDisplay};
@@ -142,18 +142,11 @@ pub enum LeafExecutionKind {
142142
///
143143
/// `vite_graph_ser::serialize_by_key` expects `&DiGraph<N, E, Ix>`, so we call `.inner()`
144144
/// to get the underlying `DiGraph` reference.
145-
fn serialize_execution_graph_by_key<S: Serializer>(
146-
graph: &ExecutionGraph,
147-
serializer: S,
148-
) -> Result<S::Ok, S::Error> {
149-
vite_graph_ser::serialize_by_key(graph.inner(), serializer)
150-
}
151-
152145
/// An execution item, from a split subcommand in a task's command (`item1 && item2 && ...`).
153146
#[derive(Debug, Serialize)]
154147
pub enum ExecutionItemKind {
155148
/// Expanded from a known vp subcommand, like `vp run ...` or a synthesized task.
156-
Expanded(#[serde(serialize_with = "serialize_execution_graph_by_key")] ExecutionGraph),
149+
Expanded(ExecutionGraph),
157150
/// A normal execution that spawns a child process, like `tsc --noEmit`.
158151
Leaf(LeafExecutionKind),
159152
}
@@ -186,127 +179,54 @@ pub trait TaskGraphLoader {
186179
) -> Result<&vite_task_graph::IndexedTaskGraph, TaskGraphLoadError>;
187180
}
188181

189-
#[derive(Debug, Serialize)]
190-
pub struct ExecutionPlan {
191-
root_node: ExecutionItemKind,
182+
/// Plan a query execution: load the task graph, query it, and build the execution graph.
183+
///
184+
/// # Errors
185+
/// Returns an error if task graph loading, query, or execution planning fails.
186+
#[expect(clippy::future_not_send, reason = "PlanRequestParser and TaskGraphLoader are !Send")]
187+
#[expect(clippy::implicit_hasher, reason = "FxHashMap is the only hasher used in this codebase")]
188+
pub async fn plan_query(
189+
query_plan_request: QueryPlanRequest,
190+
workspace_path: &Arc<AbsolutePath>,
191+
cwd: &Arc<AbsolutePath>,
192+
envs: &FxHashMap<Arc<OsStr>, Arc<OsStr>>,
193+
plan_request_parser: &mut (dyn PlanRequestParser + '_),
194+
task_graph_loader: &mut (dyn TaskGraphLoader + '_),
195+
) -> Result<ExecutionGraph, Error> {
196+
let indexed_task_graph = task_graph_loader.load_task_graph().await?;
197+
198+
let context = PlanContext::new(
199+
workspace_path,
200+
Arc::clone(cwd),
201+
envs.clone(),
202+
plan_request_parser,
203+
indexed_task_graph,
204+
);
205+
plan_query_request(query_plan_request, context).await
192206
}
193207

194-
impl ExecutionPlan {
195-
#[must_use]
196-
pub const fn root_node(&self) -> &ExecutionItemKind {
197-
&self.root_node
198-
}
199-
200-
#[must_use]
201-
pub fn into_root_node(self) -> ExecutionItemKind {
202-
self.root_node
203-
}
204-
205-
/// Returns `true` if the plan contains no tasks to execute.
206-
#[must_use]
207-
pub fn is_empty(&self) -> bool {
208-
match &self.root_node {
209-
ExecutionItemKind::Expanded(graph) => graph.node_count() == 0,
210-
ExecutionItemKind::Leaf(_) => false,
211-
}
212-
}
213-
214-
/// Create an execution plan from an execution graph.
215-
#[must_use]
216-
pub const fn from_execution_graph(execution_graph: ExecutionGraph) -> Self {
217-
Self { root_node: ExecutionItemKind::Expanded(execution_graph) }
218-
}
219-
220-
/// Plan a query execution: load the task graph, query it, and build the execution graph.
221-
///
222-
/// # Errors
223-
/// Returns an error if task graph loading, query, or execution planning fails.
224-
#[expect(clippy::future_not_send, reason = "PlanRequestParser and TaskGraphLoader are !Send")]
225-
pub async fn plan_query(
226-
query_plan_request: QueryPlanRequest,
227-
workspace_path: &Arc<AbsolutePath>,
228-
cwd: &Arc<AbsolutePath>,
229-
envs: &FxHashMap<Arc<OsStr>, Arc<OsStr>>,
230-
plan_request_parser: &mut (dyn PlanRequestParser + '_),
231-
task_graph_loader: &mut (dyn TaskGraphLoader + '_),
232-
) -> Result<ExecutionGraph, Error> {
233-
let indexed_task_graph = task_graph_loader.load_task_graph().await?;
234-
235-
let context = PlanContext::new(
236-
workspace_path,
237-
Arc::clone(cwd),
238-
envs.clone(),
239-
plan_request_parser,
240-
indexed_task_graph,
241-
);
242-
plan_query_request(query_plan_request, context).await
243-
}
244-
245-
/// Plan an execution from a plan request.
246-
///
247-
/// # Errors
248-
/// Returns an error if task graph loading, query, or execution planning fails.
249-
#[expect(clippy::future_not_send, reason = "PlanRequestParser and TaskGraphLoader are !Send")]
250-
pub async fn plan(
251-
plan_request: PlanRequest,
252-
workspace_path: &Arc<AbsolutePath>,
253-
cwd: &Arc<AbsolutePath>,
254-
envs: &FxHashMap<Arc<OsStr>, Arc<OsStr>>,
255-
plan_request_parser: &mut (dyn PlanRequestParser + '_),
256-
task_graph_loader: &mut (dyn TaskGraphLoader + '_),
257-
) -> Result<Self, Error> {
258-
let root_node = match plan_request {
259-
PlanRequest::Query(query_plan_request) => {
260-
let execution_graph = Self::plan_query(
261-
query_plan_request,
262-
workspace_path,
263-
cwd,
264-
envs,
265-
plan_request_parser,
266-
task_graph_loader,
267-
)
268-
.await?;
269-
ExecutionItemKind::Expanded(execution_graph)
270-
}
271-
PlanRequest::Synthetic(synthetic_plan_request) => {
272-
let execution = plan_synthetic_request(
273-
workspace_path,
274-
&BTreeMap::default(),
275-
synthetic_plan_request,
276-
None,
277-
cwd,
278-
ParentCacheConfig::None,
279-
)?;
280-
ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(execution))
281-
}
282-
};
283-
Ok(Self { root_node })
284-
}
285-
286-
/// Plan a synthetic task execution, returning the resolved [`SpawnExecution`] directly.
287-
///
288-
/// Unlike `plan_query` which returns a full execution graph, synthetic executions
289-
/// are always a single spawned process. The caller can execute it directly using
290-
/// `execute_spawn`.
291-
///
292-
/// # Errors
293-
/// Returns an error if the program is not found or path fingerprinting fails.
294-
#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")]
295-
pub fn plan_synthetic(
296-
workspace_path: &Arc<AbsolutePath>,
297-
cwd: &Arc<AbsolutePath>,
298-
synthetic_plan_request: SyntheticPlanRequest,
299-
cache_key: Arc<[Str]>,
300-
) -> Result<Self, Error> {
301-
let execution_cache_key = cache_metadata::ExecutionCacheKey::ExecAPI(cache_key);
302-
let execution = plan_synthetic_request(
303-
workspace_path,
304-
&BTreeMap::default(),
305-
synthetic_plan_request,
306-
Some(execution_cache_key),
307-
cwd,
308-
ParentCacheConfig::None,
309-
)?;
310-
Ok(Self { root_node: ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(execution)) })
311-
}
208+
/// Plan a synthetic task execution, returning the resolved [`SpawnExecution`] directly.
209+
///
210+
/// Unlike [`plan_query`] which returns a full execution graph, synthetic executions
211+
/// are always a single spawned process. The caller can execute it directly using
212+
/// `execute_spawn`.
213+
///
214+
/// # Errors
215+
/// Returns an error if the program is not found or path fingerprinting fails.
216+
#[expect(clippy::result_large_err, reason = "Error is large for diagnostics")]
217+
pub fn plan_synthetic(
218+
workspace_path: &Arc<AbsolutePath>,
219+
cwd: &Arc<AbsolutePath>,
220+
synthetic_plan_request: SyntheticPlanRequest,
221+
cache_key: Arc<[Str]>,
222+
) -> Result<SpawnExecution, Error> {
223+
let execution_cache_key = cache_metadata::ExecutionCacheKey::ExecAPI(cache_key);
224+
plan_synthetic_request(
225+
workspace_path,
226+
&BTreeMap::default(),
227+
synthetic_plan_request,
228+
Some(execution_cache_key),
229+
cwd,
230+
ParentCacheConfig::None,
231+
)
312232
}

0 commit comments

Comments
 (0)