Skip to content

Commit cf8daeb

Browse files
authored
feat: eargerly scan topologically dependencies (#49)
# feat: Add topological dependency scanning to task graph This PR adds automatic topological dependency scanning to the task graph, allowing tasks to depend on tasks with the same name in dependent packages. Key changes include: - Eagerly scan for topological dependencies between tasks based on package dependencies - Add support for dependencies that are both explicit and topological with a new `Both` type - Create a `TaskIx` newtype for better type safety in task graph indices - Wrap `TaskDependencyTypeInner` in `TaskDependencyType` to provide safer API for checking dependency types - Update task graph to use package indices instead of package paths for better performance - Add tests for the new topological dependency scanning functionality
1 parent e4dae7c commit cf8daeb

14 files changed

+589
-147
lines changed

crates/vite_task/src/config/workspace.rs

Lines changed: 57 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ use std::{
55
sync::Arc,
66
};
77

8-
use petgraph::{Graph, graph::NodeIndex, stable_graph::StableDiGraph, visit::IntoNodeReferences};
8+
use petgraph::{
9+
graph::{DiGraph, NodeIndex},
10+
stable_graph::StableDiGraph,
11+
visit::IntoNodeReferences,
12+
};
913
use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePath, RelativePathBuf};
1014
use vite_str::Str;
1115
use vite_workspace::{
12-
DependencyType, PackageInfo, PackageJson, WorkspaceRoot, find_package_root, find_workspace_root,
16+
DependencyType, PackageInfo, PackageIx, PackageJson, PackageNodeIndex, WorkspaceRoot,
17+
find_package_root, find_workspace_root,
1318
};
1419

1520
use super::{
@@ -36,7 +41,7 @@ pub struct Workspace {
3641
pub(crate) current_package_path: Option<RelativePathBuf>,
3742
pub(crate) task_cache: TaskCache,
3843
pub(crate) fs: CachedFileSystem,
39-
pub(crate) package_graph: Graph<PackageInfo, DependencyType>,
44+
pub(crate) package_graph: DiGraph<PackageInfo, DependencyType, PackageIx>,
4045
#[expect(unused)]
4146
pub(crate) package_json: PackageJson,
4247
pub(crate) task_graph: StableDiGraph<ResolvedTask, ()>,
@@ -111,7 +116,7 @@ impl Workspace {
111116
};
112117

113118
Ok(Self {
114-
package_graph: Graph::new(),
119+
package_graph: Default::default(),
115120
root_dir: workspace_root.to_absolute_path_buf(),
116121
cwd,
117122
current_package_path,
@@ -162,7 +167,7 @@ impl Workspace {
162167
// Create a map from package name to node index for efficient lookups
163168
// The values are Vecs because multiple packages can have the same name.
164169
let mut package_path_to_node =
165-
HashMap::<Str, Vec<NodeIndex>>::with_capacity(package_graph.node_count());
170+
HashMap::<Str, Vec<PackageNodeIndex>>::with_capacity(package_graph.node_count());
166171
for (package_node_index, package) in package_graph.node_references() {
167172
package_path_to_node
168173
.entry(package.package_json.name.clone())
@@ -398,9 +403,9 @@ impl Workspace {
398403

399404
/// Load tasks from all packages into the task graph builder
400405
fn load_tasks_into_builder(
401-
packages_with_task_jsons: &[(NodeIndex, Option<ViteTaskJson>)],
402-
package_graph: &Graph<PackageInfo, DependencyType>,
403-
package_name_to_node: &HashMap<Str, Vec<NodeIndex>>,
406+
packages_with_task_jsons: &[(PackageNodeIndex, Option<ViteTaskJson>)],
407+
package_graph: &DiGraph<PackageInfo, DependencyType, PackageIx>,
408+
package_name_to_node: &HashMap<Str, Vec<PackageNodeIndex>>,
404409
task_graph_builder: &mut TaskGraphBuilder,
405410
base_dir: &AbsolutePath,
406411
) -> Result<(), Error> {
@@ -425,41 +430,43 @@ impl Workspace {
425430
.map(|task_request| {
426431
let sharp_pos = task_request.find('#');
427432
if sharp_pos == task_request.rfind('#') {
428-
let (dep_package_node_index, dep_task_name): (NodeIndex, Str) =
429-
if let Some(sharp_pos) = sharp_pos {
430-
let package_name = &task_request[..sharp_pos];
431-
let package_node_indexes = package_name_to_node
432-
.get(package_name)
433-
.ok_or_else(|| Error::TaskNotFound {
434-
task_request: task_request.clone(),
435-
})?;
436-
match package_node_indexes.as_slice() {
437-
[] => {
438-
return Err(Error::PackageNotFound(
439-
package_name.into(),
440-
));
441-
}
442-
[package_node_index] => (
443-
*package_node_index,
444-
task_request[sharp_pos + 1..].into(),
445-
),
446-
// Found more than one package with the same name
447-
[package_node_index1, package_node_index2, ..] => {
448-
return Err(Error::DuplicatedPackageName {
449-
name: package_name.into(),
450-
path1: package_graph[*package_node_index1]
451-
.path
452-
.clone(),
453-
path2: package_graph[*package_node_index2]
454-
.path
455-
.clone(),
456-
});
457-
}
433+
let (dep_package_node_index, dep_task_name): (
434+
PackageNodeIndex,
435+
Str,
436+
) = if let Some(sharp_pos) = sharp_pos {
437+
let package_name = &task_request[..sharp_pos];
438+
let package_node_indexes = package_name_to_node
439+
.get(package_name)
440+
.ok_or_else(|| Error::TaskNotFound {
441+
task_request: task_request.clone(),
442+
})?;
443+
match package_node_indexes.as_slice() {
444+
[] => {
445+
return Err(Error::PackageNotFound(
446+
package_name.into(),
447+
));
448+
}
449+
[package_node_index] => (
450+
*package_node_index,
451+
task_request[sharp_pos + 1..].into(),
452+
),
453+
// Found more than one package with the same name
454+
[package_node_index1, package_node_index2, ..] => {
455+
return Err(Error::DuplicatedPackageName {
456+
name: package_name.into(),
457+
path1: package_graph[*package_node_index1]
458+
.path
459+
.clone(),
460+
path2: package_graph[*package_node_index2]
461+
.path
462+
.clone(),
463+
});
458464
}
459-
} else {
460-
// No '#' means it's a local task reference within the same package
461-
(*package_node_index, task_request)
462-
};
465+
}
466+
} else {
467+
// No '#' means it's a local task reference within the same package
468+
(*package_node_index, task_request)
469+
};
463470

464471
Ok(TaskId {
465472
task_group_id: TaskGroupId {
@@ -524,12 +531,12 @@ impl Workspace {
524531
/// Add topological dependencies to the task graph builder
525532
fn add_topological_dependencies(
526533
task_graph_builder: &mut TaskGraphBuilder,
527-
package_graph: &Graph<PackageInfo, DependencyType>,
534+
package_graph: &DiGraph<PackageInfo, DependencyType, PackageIx>,
528535
) {
529536
let package_path_to_node_index = package_graph
530537
.node_references()
531538
.map(|(node_index, package)| (package.path.as_relative_path(), node_index))
532-
.collect::<HashMap<&RelativePath, NodeIndex>>();
539+
.collect::<HashMap<&RelativePath, PackageNodeIndex>>();
533540

534541
// Collect all tasks grouped by task group id
535542
let mut task_ids_by_task_group_id: HashMap<TaskGroupId, Vec<(TaskId, usize)>> =
@@ -602,9 +609,9 @@ impl Workspace {
602609

603610
/// Load vite-task.json files for all packages
604611
fn load_vite_task_jsons(
605-
package_graph: &Graph<PackageInfo, DependencyType>,
612+
package_graph: &DiGraph<PackageInfo, DependencyType, PackageIx>,
606613
base_dir: &AbsolutePath,
607-
) -> Result<Vec<(NodeIndex, Option<ViteTaskJson>)>, Error> {
614+
) -> Result<Vec<(PackageNodeIndex, Option<ViteTaskJson>)>, Error> {
608615
let mut packages_with_task_jsons = Vec::new();
609616

610617
for node_idx in package_graph.node_indices() {
@@ -632,8 +639,8 @@ impl Workspace {
632639
/// Find paths of all transitive dependencies of a package
633640
fn find_transitive_dependencies(
634641
package_path: &RelativePath,
635-
package_graph: &Graph<PackageInfo, DependencyType>,
636-
package_path_to_node_index: &HashMap<&RelativePath, NodeIndex>,
642+
package_graph: &DiGraph<PackageInfo, DependencyType, PackageIx>,
643+
package_path_to_node_index: &HashMap<&RelativePath, PackageNodeIndex>,
637644
) -> Vec<RelativePathBuf> {
638645
let mut result = Vec::new();
639646
let mut visited = HashSet::default();
@@ -651,8 +658,8 @@ fn find_transitive_dependencies(
651658

652659
fn find_transitive_dependencies_recursive<'a>(
653660
package_path: &'a RelativePath,
654-
package_graph: &'a Graph<PackageInfo, DependencyType>,
655-
package_path_to_node: &HashMap<&'a RelativePath, NodeIndex>,
661+
package_graph: &'a DiGraph<PackageInfo, DependencyType, PackageIx>,
662+
package_path_to_node: &HashMap<&'a RelativePath, PackageNodeIndex>,
656663
visited: &mut HashSet<&'a RelativePath>,
657664
result: &mut Vec<RelativePathBuf>,
658665
) {

0 commit comments

Comments
 (0)