Skip to content

Commit 65504c9

Browse files
authored
Forward CLI extra args only to explicitly requested tasks (#332)
1 parent 3008bff commit 65504c9

File tree

8 files changed

+346
-19
lines changed

8 files changed

+346
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Changelog
22

3+
- **Changed** Arguments passed after a task name (e.g. `vp run test some-filter`) are now forwarded only to that task. Tasks pulled in via `dependsOn` no longer receive them ([#324](https://github.com/voidzero-dev/vite-task/issues/324))
34
- **Fixed** Windows file access tracking no longer panics when a task touches malformed paths that cannot be represented as workspace-relative inputs ([#330](https://github.com/voidzero-dev/vite-task/pull/330))
45
- **Fixed** `vp run --cache` now supports running without a task specifier and opens the interactive task selector, matching bare `vp run` behavior ([#312](https://github.com/voidzero-dev/vite-task/pull/313))
56
- **Fixed** Ctrl-C now prevents future tasks from being scheduled and prevents caching of in-flight task results ([#309](https://github.com/voidzero-dev/vite-task/pull/309))

crates/vite_task_graph/src/query/mod.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,26 @@ use crate::{IndexedTaskGraph, TaskDependencyType, TaskId, TaskNodeIndex};
2626

2727
/// A task execution graph queried from a `TaskQuery`.
2828
///
29-
/// Nodes are `TaskNodeIndex` values into the full `TaskGraph`.
29+
/// Nodes in `graph` are `TaskNodeIndex` values into the full `TaskGraph`.
3030
/// Edges represent the final dependency relationships between tasks (no weights).
31-
pub type TaskExecutionGraph = DiGraphMap<TaskNodeIndex, ()>;
31+
///
32+
/// `requested` is the subset of nodes the user typed on the CLI — i.e. the
33+
/// nodes added by `map_subgraph_to_tasks` (stage 2), not the ones reached
34+
/// only via `dependsOn` expansion in `IndexedTaskGraph::add_dependencies` (stage 3).
35+
///
36+
/// For example, given `test` with `dependsOn: ["build"]` and the command
37+
/// `vp run test some-filter`:
38+
///
39+
/// - `graph` contains both `test` and `build` with an edge between them.
40+
/// - `requested` contains only `test`.
41+
///
42+
/// The planner uses this distinction to forward `some-filter` to `test`
43+
/// while running `build` with no extra args.
44+
#[derive(Debug, Default, Clone)]
45+
pub struct TaskExecutionGraph {
46+
pub graph: DiGraphMap<TaskNodeIndex, ()>,
47+
pub requested: FxHashSet<TaskNodeIndex>,
48+
}
3249

3350
/// A query for which tasks to run.
3451
///
@@ -167,13 +184,17 @@ impl IndexedTaskGraph {
167184
// Map remaining nodes and their edges to task nodes.
168185
// Every node still in `subgraph` is in `pkg_to_task`; the index operator
169186
// panics on a missing key — that would be a bug in the loop above.
187+
//
188+
// All nodes added here are explicitly-requested tasks, so they are
189+
// inserted into both the inner graph and the `requested` set.
170190
for &task_idx in pkg_to_task.values() {
171-
execution_graph.add_node(task_idx);
191+
execution_graph.graph.add_node(task_idx);
192+
execution_graph.requested.insert(task_idx);
172193
}
173194
for (src, dst, ()) in subgraph.all_edges() {
174195
let st = pkg_to_task[&src];
175196
let dt = pkg_to_task[&dst];
176-
execution_graph.add_edge(st, dt, ());
197+
execution_graph.graph.add_edge(st, dt, ());
177198
}
178199
}
179200

@@ -187,9 +208,13 @@ impl IndexedTaskGraph {
187208
execution_graph: &mut TaskExecutionGraph,
188209
mut filter_edge: impl FnMut(TaskDependencyType) -> bool,
189210
) {
190-
let mut frontier: FxHashSet<TaskNodeIndex> = execution_graph.nodes().collect();
211+
let mut frontier: FxHashSet<TaskNodeIndex> = execution_graph.graph.nodes().collect();
191212

192213
// Continue until no new nodes are added to the frontier.
214+
//
215+
// Nodes added here are dependency-only tasks and must NOT be marked as
216+
// `requested` — the planner uses that distinction to decide whether to
217+
// forward CLI extra args to a task.
193218
while !frontier.is_empty() {
194219
let mut next_frontier = FxHashSet::<TaskNodeIndex>::default();
195220

@@ -198,8 +223,8 @@ impl IndexedTaskGraph {
198223
let to_node = edge_ref.target();
199224
let dep_type = *edge_ref.weight();
200225
if filter_edge(dep_type) {
201-
let is_new = !execution_graph.contains_node(to_node);
202-
execution_graph.add_edge(from_node, to_node, ());
226+
let is_new = !execution_graph.graph.contains_node(to_node);
227+
execution_graph.graph.add_edge(from_node, to_node, ());
203228
if is_new {
204229
next_frontier.insert(to_node);
205230
}

crates/vite_task_plan/src/plan.rs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ fn plan_spawn_execution(
653653
/// `vp run build` produces a different query than the script's `vp run -r build`,
654654
/// so the skip rule doesn't fire, but the prune rule catches root in the result).
655655
/// Like the skip rule, extra args don't affect this — only the `TaskQuery` matters.
656+
#[expect(clippy::too_many_lines, reason = "sequential planning steps are clearer in one function")]
656657
pub async fn plan_query_request(
657658
query: Arc<TaskQuery>,
658659
plan_options: PlanOptions,
@@ -696,7 +697,15 @@ pub async fn plan_query_request(
696697

697698
let parallel = plan_options.parallel;
698699

699-
context.set_extra_args(plan_options.extra_args);
700+
// Extra args are applied per-task below, not globally on the context.
701+
// Tasks explicitly requested by the query receive `extra_args`; tasks
702+
// only reached via `dependsOn` expansion receive an empty slice so that
703+
// caller-specific CLI args don't pollute dependency tasks.
704+
// See https://github.com/voidzero-dev/vite-task/issues/324.
705+
let extra_args = plan_options.extra_args;
706+
// Allocated once and shared across every dep-only task's context below,
707+
// instead of calling `Arc::new([])` inside the per-task loop.
708+
let empty_extra_args: Arc<[Str]> = Arc::from([]);
700709
context.set_parent_query(Arc::clone(&query));
701710

702711
// Query matching tasks from the task graph.
@@ -715,35 +724,43 @@ pub async fn plan_query_request(
715724
// This handles cases like root `"build": "vp run build"` — the root's build
716725
// task is in the result but expanding it would recurse, so we remove it and
717726
// reconnect its predecessors directly to its successors.
718-
let pruned_task = context.expanding_task().filter(|t| task_node_index_graph.contains_node(*t));
727+
let pruned_task =
728+
context.expanding_task().filter(|t| task_node_index_graph.graph.contains_node(*t));
719729

720730
let mut execution_node_indices_by_task_index =
721731
FxHashMap::<TaskNodeIndex, ExecutionNodeIndex>::with_capacity_and_hasher(
722-
task_node_index_graph.node_count(),
732+
task_node_index_graph.graph.node_count(),
723733
rustc_hash::FxBuildHasher,
724734
);
725735

726736
// Build the inner DiGraph first, then validate acyclicity at the end.
727737
let mut inner_graph = InnerExecutionGraph::with_capacity(
728-
task_node_index_graph.node_count(),
729-
task_node_index_graph.edge_count(),
738+
task_node_index_graph.graph.node_count(),
739+
task_node_index_graph.graph.edge_count(),
730740
);
731741

732742
// Plan each task node as execution nodes, skipping the pruned task
733-
for task_index in task_node_index_graph.nodes() {
743+
for task_index in task_node_index_graph.graph.nodes() {
734744
if Some(task_index) == pruned_task {
735745
continue;
736746
}
737-
let task_execution = plan_task_as_execution_node(task_index, context.duplicate(), true)
738-
.boxed_local()
739-
.await?;
747+
let mut task_context = context.duplicate();
748+
// Only the explicitly requested tasks receive CLI extra args.
749+
// Dep-only tasks (pulled in via `dependsOn`) run with empty extras.
750+
if task_node_index_graph.requested.contains(&task_index) {
751+
task_context.set_extra_args(Arc::clone(&extra_args));
752+
} else {
753+
task_context.set_extra_args(Arc::clone(&empty_extra_args));
754+
}
755+
let task_execution =
756+
plan_task_as_execution_node(task_index, task_context, true).boxed_local().await?;
740757
execution_node_indices_by_task_index
741758
.insert(task_index, inner_graph.add_node(task_execution));
742759
}
743760

744761
// Add edges between execution nodes according to task dependencies,
745762
// skipping edges involving the pruned task.
746-
for (from_task_index, to_task_index, ()) in task_node_index_graph.all_edges() {
763+
for (from_task_index, to_task_index, ()) in task_node_index_graph.graph.all_edges() {
747764
if Some(from_task_index) == pruned_task || Some(to_task_index) == pruned_task {
748765
continue;
749766
}
@@ -757,9 +774,9 @@ pub async fn plan_query_request(
757774
// Reconnect through the pruned node: wire each predecessor directly to each successor.
758775
if let Some(pruned) = pruned_task {
759776
let preds: Vec<_> =
760-
task_node_index_graph.neighbors_directed(pruned, Direction::Incoming).collect();
777+
task_node_index_graph.graph.neighbors_directed(pruned, Direction::Incoming).collect();
761778
let succs: Vec<_> =
762-
task_node_index_graph.neighbors_directed(pruned, Direction::Outgoing).collect();
779+
task_node_index_graph.graph.neighbors_directed(pruned, Direction::Outgoing).collect();
763780
for &pred in &preds {
764781
for &succ in &succs {
765782
if let (Some(&pe), Some(&se)) = (
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "@test/extra-args-not-forwarded-to-depends-on"
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Tests that extra args (`vt run test some-filter`) are forwarded only to the
2+
# explicitly requested task (`test`), not to `dependsOn` tasks (`build`).
3+
# https://github.com/voidzero-dev/vite-task/issues/324
4+
5+
[[plan]]
6+
name = "extra args only reach requested task"
7+
args = ["run", "test", "some-filter"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
source: crates/vite_task_plan/tests/plan_snapshots/main.rs
3+
expression: "&plan_json"
4+
info:
5+
args:
6+
- run
7+
- test
8+
- some-filter
9+
input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/extra-args-not-forwarded-to-depends-on
10+
---
11+
{
12+
"graph": [
13+
{
14+
"key": [
15+
"<workspace>/",
16+
"build"
17+
],
18+
"node": {
19+
"task_display": {
20+
"package_name": "@test/extra-args-not-forwarded-to-depends-on",
21+
"task_name": "build",
22+
"package_path": "<workspace>/"
23+
},
24+
"items": [
25+
{
26+
"execution_item_display": {
27+
"task_display": {
28+
"package_name": "@test/extra-args-not-forwarded-to-depends-on",
29+
"task_name": "build",
30+
"package_path": "<workspace>/"
31+
},
32+
"command": "vt tool print build",
33+
"and_item_index": null,
34+
"cwd": "<workspace>/"
35+
},
36+
"kind": {
37+
"Leaf": {
38+
"Spawn": {
39+
"cache_metadata": {
40+
"spawn_fingerprint": {
41+
"cwd": "",
42+
"program_fingerprint": {
43+
"OutsideWorkspace": {
44+
"program_name": "vtt"
45+
}
46+
},
47+
"args": [
48+
"print",
49+
"build"
50+
],
51+
"env_fingerprints": {
52+
"fingerprinted_envs": {},
53+
"untracked_env_config": [
54+
"<default untracked envs>"
55+
]
56+
}
57+
},
58+
"execution_cache_key": {
59+
"UserTask": {
60+
"task_name": "build",
61+
"and_item_index": 0,
62+
"extra_args": [],
63+
"package_path": ""
64+
}
65+
},
66+
"input_config": {
67+
"includes_auto": true,
68+
"positive_globs": [],
69+
"negative_globs": []
70+
}
71+
},
72+
"spawn_command": {
73+
"program_path": "<tools>/vtt",
74+
"args": [
75+
"print",
76+
"build"
77+
],
78+
"all_envs": {
79+
"NO_COLOR": "1",
80+
"PATH": "<workspace>/node_modules/.bin:<tools>"
81+
},
82+
"cwd": "<workspace>/"
83+
}
84+
}
85+
}
86+
}
87+
}
88+
]
89+
},
90+
"neighbors": []
91+
},
92+
{
93+
"key": [
94+
"<workspace>/",
95+
"test"
96+
],
97+
"node": {
98+
"task_display": {
99+
"package_name": "@test/extra-args-not-forwarded-to-depends-on",
100+
"task_name": "test",
101+
"package_path": "<workspace>/"
102+
},
103+
"items": [
104+
{
105+
"execution_item_display": {
106+
"task_display": {
107+
"package_name": "@test/extra-args-not-forwarded-to-depends-on",
108+
"task_name": "test",
109+
"package_path": "<workspace>/"
110+
},
111+
"command": "vt tool print test some-filter",
112+
"and_item_index": null,
113+
"cwd": "<workspace>/"
114+
},
115+
"kind": {
116+
"Leaf": {
117+
"Spawn": {
118+
"cache_metadata": {
119+
"spawn_fingerprint": {
120+
"cwd": "",
121+
"program_fingerprint": {
122+
"OutsideWorkspace": {
123+
"program_name": "vtt"
124+
}
125+
},
126+
"args": [
127+
"print",
128+
"test",
129+
"some-filter"
130+
],
131+
"env_fingerprints": {
132+
"fingerprinted_envs": {},
133+
"untracked_env_config": [
134+
"<default untracked envs>"
135+
]
136+
}
137+
},
138+
"execution_cache_key": {
139+
"UserTask": {
140+
"task_name": "test",
141+
"and_item_index": 0,
142+
"extra_args": [
143+
"some-filter"
144+
],
145+
"package_path": ""
146+
}
147+
},
148+
"input_config": {
149+
"includes_auto": true,
150+
"positive_globs": [],
151+
"negative_globs": []
152+
}
153+
},
154+
"spawn_command": {
155+
"program_path": "<tools>/vtt",
156+
"args": [
157+
"print",
158+
"test",
159+
"some-filter"
160+
],
161+
"all_envs": {
162+
"NO_COLOR": "1",
163+
"PATH": "<workspace>/node_modules/.bin:<tools>"
164+
},
165+
"cwd": "<workspace>/"
166+
}
167+
}
168+
}
169+
}
170+
}
171+
]
172+
},
173+
"neighbors": [
174+
[
175+
"<workspace>/",
176+
"build"
177+
]
178+
]
179+
}
180+
],
181+
"concurrency_limit": 4
182+
}

0 commit comments

Comments
 (0)