From 78c0c294b08ce3f0af8e05bd2285b90dd58eae9f Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 15:33:19 +0800 Subject: [PATCH 1/5] feat: eargerly scan topologically dependencies --- crates/vite_task/src/config/workspace.rs | 108 +++++++++--------- crates/vite_task_graph/src/lib.rs | 133 ++++++++++++++--------- crates/vite_workspace/src/lib.rs | 35 +++++- 3 files changed, 167 insertions(+), 109 deletions(-) diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index 70c6713f..35c7ff9a 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -5,11 +5,17 @@ use std::{ sync::Arc, }; -use petgraph::{Graph, graph::NodeIndex, stable_graph::StableDiGraph, visit::IntoNodeReferences}; +use petgraph::{ + Graph, + graph::{DiGraph, NodeIndex}, + stable_graph::StableDiGraph, + visit::IntoNodeReferences, +}; use vite_path::{AbsolutePath, AbsolutePathBuf, RelativePath, RelativePathBuf}; use vite_str::Str; use vite_workspace::{ - DependencyType, PackageInfo, PackageJson, WorkspaceRoot, find_package_root, find_workspace_root, + DependencyType, PackageInfo, PackageIx, PackageJson, PackageNodeIndex, WorkspaceRoot, + find_package_root, find_workspace_root, }; use super::{ @@ -36,7 +42,7 @@ pub struct Workspace { pub(crate) current_package_path: Option, pub(crate) task_cache: TaskCache, pub(crate) fs: CachedFileSystem, - pub(crate) package_graph: Graph, + pub(crate) package_graph: DiGraph, #[expect(unused)] pub(crate) package_json: PackageJson, pub(crate) task_graph: StableDiGraph, @@ -111,7 +117,7 @@ impl Workspace { }; Ok(Self { - package_graph: Graph::new(), + package_graph: Default::default(), root_dir: workspace_root.to_absolute_path_buf(), cwd, current_package_path, @@ -162,7 +168,7 @@ impl Workspace { // Create a map from package name to node index for efficient lookups // The values are Vecs because multiple packages can have the same name. let mut package_path_to_node = - HashMap::>::with_capacity(package_graph.node_count()); + HashMap::>::with_capacity(package_graph.node_count()); for (package_node_index, package) in package_graph.node_references() { package_path_to_node .entry(package.package_json.name.clone()) @@ -398,9 +404,9 @@ impl Workspace { /// Load tasks from all packages into the task graph builder fn load_tasks_into_builder( - packages_with_task_jsons: &[(NodeIndex, Option)], - package_graph: &Graph, - package_name_to_node: &HashMap>, + packages_with_task_jsons: &[(PackageNodeIndex, Option)], + package_graph: &DiGraph, + package_name_to_node: &HashMap>, task_graph_builder: &mut TaskGraphBuilder, base_dir: &AbsolutePath, ) -> Result<(), Error> { @@ -425,41 +431,43 @@ impl Workspace { .map(|task_request| { let sharp_pos = task_request.find('#'); if sharp_pos == task_request.rfind('#') { - let (dep_package_node_index, dep_task_name): (NodeIndex, Str) = - if let Some(sharp_pos) = sharp_pos { - let package_name = &task_request[..sharp_pos]; - let package_node_indexes = package_name_to_node - .get(package_name) - .ok_or_else(|| Error::TaskNotFound { - task_request: task_request.clone(), - })?; - match package_node_indexes.as_slice() { - [] => { - return Err(Error::PackageNotFound( - package_name.into(), - )); - } - [package_node_index] => ( - *package_node_index, - task_request[sharp_pos + 1..].into(), - ), - // Found more than one package with the same name - [package_node_index1, package_node_index2, ..] => { - return Err(Error::DuplicatedPackageName { - name: package_name.into(), - path1: package_graph[*package_node_index1] - .path - .clone(), - path2: package_graph[*package_node_index2] - .path - .clone(), - }); - } + let (dep_package_node_index, dep_task_name): ( + PackageNodeIndex, + Str, + ) = if let Some(sharp_pos) = sharp_pos { + let package_name = &task_request[..sharp_pos]; + let package_node_indexes = package_name_to_node + .get(package_name) + .ok_or_else(|| Error::TaskNotFound { + task_request: task_request.clone(), + })?; + match package_node_indexes.as_slice() { + [] => { + return Err(Error::PackageNotFound( + package_name.into(), + )); + } + [package_node_index] => ( + *package_node_index, + task_request[sharp_pos + 1..].into(), + ), + // Found more than one package with the same name + [package_node_index1, package_node_index2, ..] => { + return Err(Error::DuplicatedPackageName { + name: package_name.into(), + path1: package_graph[*package_node_index1] + .path + .clone(), + path2: package_graph[*package_node_index2] + .path + .clone(), + }); } - } else { - // No '#' means it's a local task reference within the same package - (*package_node_index, task_request) - }; + } + } else { + // No '#' means it's a local task reference within the same package + (*package_node_index, task_request) + }; Ok(TaskId { task_group_id: TaskGroupId { @@ -524,12 +532,12 @@ impl Workspace { /// Add topological dependencies to the task graph builder fn add_topological_dependencies( task_graph_builder: &mut TaskGraphBuilder, - package_graph: &Graph, + package_graph: &DiGraph, ) { let package_path_to_node_index = package_graph .node_references() .map(|(node_index, package)| (package.path.as_relative_path(), node_index)) - .collect::>(); + .collect::>(); // Collect all tasks grouped by task group id let mut task_ids_by_task_group_id: HashMap> = @@ -602,9 +610,9 @@ impl Workspace { /// Load vite-task.json files for all packages fn load_vite_task_jsons( - package_graph: &Graph, + package_graph: &DiGraph, base_dir: &AbsolutePath, - ) -> Result)>, Error> { + ) -> Result)>, Error> { let mut packages_with_task_jsons = Vec::new(); for node_idx in package_graph.node_indices() { @@ -632,8 +640,8 @@ impl Workspace { /// Find paths of all transitive dependencies of a package fn find_transitive_dependencies( package_path: &RelativePath, - package_graph: &Graph, - package_path_to_node_index: &HashMap<&RelativePath, NodeIndex>, + package_graph: &DiGraph, + package_path_to_node_index: &HashMap<&RelativePath, PackageNodeIndex>, ) -> Vec { let mut result = Vec::new(); let mut visited = HashSet::default(); @@ -651,8 +659,8 @@ fn find_transitive_dependencies( fn find_transitive_dependencies_recursive<'a>( package_path: &'a RelativePath, - package_graph: &'a Graph, - package_path_to_node: &HashMap<&'a RelativePath, NodeIndex>, + package_graph: &'a DiGraph, + package_path_to_node: &HashMap<&'a RelativePath, PackageNodeIndex>, visited: &mut HashSet<&'a RelativePath>, result: &mut Vec, ) { diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index f990bd79..07e8c8fb 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -7,12 +7,17 @@ use std::{ }; use config::{ResolvedUserTaskConfig, UserConfigFile}; -use petgraph::graph::{DiGraph, NodeIndex}; +use petgraph::{ + graph::{DiGraph, NodeIndex}, + visit::depth_first_search, +}; use serde::Serialize; use vec1::smallvec_v1::SmallVec1; -use vite_path::AbsolutePath; +use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; -use vite_workspace::WorkspaceRoot; +use vite_workspace::{ + DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot, package, +}; /// The type of a desk dependency, explaining why it's introduced. #[derive(Debug, Clone, Copy, Serialize)] @@ -25,16 +30,12 @@ pub enum TaskDependencyType { } /// Uniquely identifies a task, by its name and the path where it's defined. -/// -/// We use package_dir instead of package_name because multiple packages can have the same name in a monorepo. #[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] pub struct TaskId { - /// This is the path of the package where the task is defined. + /// This is the index of the package where the task is defined. /// - /// Note that this is not always the cwd where the command is run, which is stored in `ResolvedUserTaskConfig`. - /// - /// `package_dir` is declared from `task_name` to make the `PartialOrd` implementation group tasks in same packages together. - pub package_dir: Arc, + /// `package_index` is declared from `task_name` to make the `PartialOrd` implementation group tasks in same packages together. + pub package_index: PackageNodeIndex, /// For user defined tasks, this is the name of the script or the entry in `vite-task.json`. /// @@ -75,11 +76,12 @@ pub enum TaskGraphLoadError { package_path: Arc, }, - #[error("Failed to resolve task config for task {0} at {1:?}: {2}", task_id.task_name, task_id.package_dir, error)] + #[error("Failed to resolve task config for task {0}#{1}: {2}", package_name, task_name, error)] ResolveConfigError { #[source] error: crate::config::ResolveTaskError, - task_id: TaskId, + package_name: Str, + task_name: Str, }, #[error("Failed to lookup dependency '{specifier}' of task {0} at {1:?}: {error}", origin_task_id.task_name, origin_task_id.task_name)] @@ -94,16 +96,14 @@ pub enum TaskGraphLoadError { #[derive(Debug, thiserror::Error)] pub enum SpecifierLookupError { - #[error( - "Package name '{package_name}' is ambiguous among multiple packages: {package_paths:?}" - )] - AmbiguousPackageName { package_name: Str, package_paths: Box<[Arc]> }, + #[error("Package '{package_name}' is ambiguous among multiple packages: {package_paths:?}")] + AmbiguousPackageName { package_name: Str, package_paths: Box<[RelativePathBuf]> }, - #[error("Package name '{package_name}' not found")] + #[error("Package '{package_name}' not found")] PackageNameNotFound { package_name: Str }, - #[error("Task name '{0}' not found in package {1:?}", task_id.task_name, task_id.package_dir)] - TaskNameNotFound { task_id: TaskId }, + #[error("Task '{task_name}' not found in package {package_name}")] + TaskNameNotFound { package_name: Str, task_name: Str }, } /// Full task graph of a workspace. @@ -113,9 +113,11 @@ pub enum SpecifierLookupError { pub struct TaskGraph { graph: DiGraph, - /// Grouping package dirs by their package names. + package_graph: Arc>, + + /// Grouping package indices by their package names. /// Due to rare but possible name conflicts in monorepos, we use `SmallVec1` to store multiple dirs for same name. - package_dirs_by_name: HashMap; 1]>>, + package_indices_by_name: HashMap>, /// task indices by task id for quick lookup node_indices_by_task_id: HashMap, @@ -131,10 +133,13 @@ impl TaskGraph { let package_graph = vite_workspace::load_package_graph(&workspace_root)?; - let mut dependency_specifiers_with_node_indices: Vec<(Arc<[Str]>, NodeIndex)> = Vec::new(); + // Record dependency specifiers for each task node to add explicit dependencies later + let mut dependency_specifiers_with_task_node_indices: Vec<(Arc<[Str]>, NodeIndex)> = + Vec::new(); // Load task nodes into `task_graph` - for package in package_graph.node_weights() { + for package_index in package_graph.node_indices() { + let package = &package_graph[package_index]; let package_dir: Arc = workspace_root.path.join(&package.path).into(); // Collect package.json scripts into a mutable map for draining lookup. @@ -155,8 +160,7 @@ impl TaskGraph { // For each task defined in vite.config.*, look up the corresponding package.json script (if any) let package_json_script = package_json_scripts.remove(task_name.as_str()); - let task_id = - TaskId { task_name: task_name.clone(), package_dir: Arc::clone(&package_dir) }; + let task_id = TaskId { task_name: task_name.clone(), package_index }; let dependency_specifiers = Arc::clone(&task_user_config.depends_on); @@ -168,7 +172,8 @@ impl TaskGraph { ) .map_err(|err| TaskGraphLoadError::ResolveConfigError { error: err, - task_id: task_id.clone(), + package_name: package.package_json.name.clone(), + task_name: task_name.clone(), })?; let task_node = TaskNode { @@ -178,15 +183,13 @@ impl TaskGraph { }; let node_index = task_graph.add_node(task_node); - dependency_specifiers_with_node_indices.push((dependency_specifiers, node_index)); + dependency_specifiers_with_task_node_indices + .push((dependency_specifiers, node_index)); } // For remaining package.json scripts not defined in vite.config.*, create tasks with default config for (script_name, package_json_script) in package_json_scripts.drain() { - let task_id = TaskId { - task_name: Str::from(script_name), - package_dir: Arc::clone(&package_dir), - }; + let task_id = TaskId { task_name: Str::from(script_name), package_index }; let resolved_config = ResolvedUserTaskConfig::resolve_package_json_script( &package_dir, package_json_script, @@ -214,30 +217,37 @@ impl TaskGraph { } // Grouping package dirs by their package names. - let mut package_dirs_by_name: HashMap; 1]>> = + let mut package_dirs_by_name: HashMap> = HashMap::new(); - for package in package_graph.node_weights() { - let package_dir: Arc = workspace_root.path.join(&package.path).into(); + for package_index in package_graph.node_indices() { + let package = &package_graph[package_index]; match package_dirs_by_name.entry(package.package_json.name.clone()) { Entry::Vacant(vacant) => { - vacant.insert(SmallVec1::new(package_dir)); + vacant.insert(SmallVec1::new(package_index)); } Entry::Occupied(occupied) => { - occupied.into_mut().push(package_dir); + occupied.into_mut().push(package_index); } } } + let package_graph = Arc::new(package_graph); // Construct `Self` with task_graph with all task nodes ready and indexed, but no edges. - let mut me = Self { graph: task_graph, node_indices_by_task_id, package_dirs_by_name }; + let mut me = Self { + graph: task_graph, + package_graph: Arc::clone(&package_graph), + node_indices_by_task_id, + package_indices_by_name: package_dirs_by_name, + }; // Add explicit dependencies - for (dependency_specifiers, from_node_index) in dependency_specifiers_with_node_indices { + for (dependency_specifiers, from_node_index) in dependency_specifiers_with_task_node_indices + { let from_task_id = me.graph[from_node_index].task_id.clone(); for specifier in dependency_specifiers.iter().cloned() { let to_node_index = me - .get_task_index_by_specifier(&specifier, &from_task_id.package_dir) + .get_task_index_by_specifier(&specifier, from_task_id.package_index) .map_err(|error| TaskGraphLoadError::DependencySpecifierLookupError { error, specifier, @@ -247,7 +257,12 @@ impl TaskGraph { } } - // TODO: Add topological dependencies based on package dependencies + // Add topological dependencies based on package dependencies + // for (package_index, task_node_index) in package_indices_with_task_node_indices { + // // For every task, + // // DFS starting from the containing package + // depth_first_search(&package_graph, Some(package_index), |event| {}); + // } Ok(me) } @@ -258,30 +273,36 @@ impl TaskGraph { fn get_task_index_by_specifier( &self, specifier: &str, - package_origin: &Arc, + package_origin: PackageNodeIndex, ) -> Result { - let (package_dir, task_name): (Arc, Str) = + let (package_index, task_name): (PackageNodeIndex, Str) = if let Some((package_name, task_name)) = specifier.rsplit_once('#') { // Lookup package path by the package name from '#' - let Some(package_paths) = self.package_dirs_by_name.get(package_name) else { + let Some(package_indices) = self.package_indices_by_name.get(package_name) else { return Err(SpecifierLookupError::PackageNameNotFound { package_name: package_name.into(), }); }; - if package_paths.len() > 1 { + if package_indices.len() > 1 { return Err(SpecifierLookupError::AmbiguousPackageName { package_name: package_name.into(), - package_paths: package_paths.iter().cloned().collect(), + package_paths: package_indices + .iter() + .map(|package_index| self.package_graph[*package_index].path.clone()) + .collect(), }); }; - (Arc::clone(package_paths.first()), task_name.into()) + (*package_indices.first(), task_name.into()) } else { // No '#', so the specifier only contains task name, look up in the origin path package - (Arc::clone(&package_origin), specifier.into()) + (package_origin, specifier.into()) }; - let task_id = TaskId { task_name, package_dir }; + let task_id = TaskId { task_name, package_index }; let Some(node_index) = self.node_indices_by_task_id.get(&task_id) else { - return Err(SpecifierLookupError::TaskNameNotFound { task_id }); + return Err(SpecifierLookupError::TaskNameNotFound { + package_name: self.package_graph[package_index].package_json.name.clone(), + task_name: task_id.task_name.clone(), + }); }; Ok(*node_index) } @@ -298,10 +319,13 @@ impl TaskGraph { task_name: Str, } impl TaskIdSnapshot { - fn from_task_id(task_id: &TaskId, base_dir: &AbsolutePath) -> Self { + fn from_task_id( + task_id: &TaskId, + package_graph: &DiGraph, + ) -> Self { Self { task_name: task_id.task_name.clone(), - package_dir: task_id.package_dir.strip_prefix(base_dir).unwrap().unwrap(), + package_dir: package_graph[task_id.package_index].path.clone(), } } } @@ -323,12 +347,15 @@ impl TaskGraph { .map(|edge| { use petgraph::visit::EdgeRef as _; let target_node = &self.graph[edge.target()]; - (TaskIdSnapshot::from_task_id(&target_node.task_id, base_dir), *edge.weight()) + ( + TaskIdSnapshot::from_task_id(&target_node.task_id, &self.package_graph), + *edge.weight(), + ) }) .collect(); depends_on.sort_unstable_by(|a, b| a.0.cmp(&b.0)); node_snapshots.push(TaskNodeSnapshot { - id: TaskIdSnapshot::from_task_id(&node.task_id, base_dir), + id: TaskIdSnapshot::from_task_id(&node.task_id, &self.package_graph), command: node.resolved_config.command.clone(), cwd: node.resolved_config.cwd.strip_prefix(base_dir).unwrap().unwrap(), depends_on, diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index 89a895c7..536332ec 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -4,7 +4,10 @@ mod package_manager; use std::{fs, io}; -use petgraph::{Graph, graph::NodeIndex}; +use petgraph::{ + Graph, + graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, +}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use serde::{Deserialize, Serialize}; use vite_glob::GlobPatternSet; @@ -103,10 +106,10 @@ pub struct PackageInfo { #[derive(Default)] struct PackageGraphBuilder { - id_and_deps_by_path: HashMap)>, + id_and_deps_by_path: HashMap)>, // Only for packages with a name name_to_path: HashMap, - graph: Graph, + graph: DiGraph, } impl PackageGraphBuilder { @@ -138,7 +141,7 @@ impl PackageGraphBuilder { Ok(()) } - fn build(mut self) -> Graph { + fn build(mut self) -> DiGraph { for (id, deps) in self.id_and_deps_by_path.values() { for (dep_name, dep_type) in deps { // Skip dependencies on nameless packages (empty string) @@ -160,10 +163,30 @@ impl PackageGraphBuilder { } } +/// newtype of `DefaultIx` for indices in package graphs +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PackageIx(DefaultIx); +unsafe impl petgraph::graph::IndexType for PackageIx { + fn new(x: usize) -> Self { + Self(DefaultIx::new(x)) + } + + fn index(&self) -> usize { + self.0.index() + } + + fn max() -> Self { + Self(::max()) + } +} + +pub type PackageNodeIndex = NodeIndex; +pub type PackageEdgeIndex = EdgeIndex; + /// Discover the workspace from cwd and load the package graph. pub fn discover_package_graph( cwd: impl AsRef, -) -> Result, Error> { +) -> Result, Error> { let workspace_root = find_workspace_root(cwd.as_ref())?; load_package_graph(&workspace_root) } @@ -171,7 +194,7 @@ pub fn discover_package_graph( /// Load the package graph from a discovered workspace. pub fn load_package_graph( workspace_root: &WorkspaceRoot<'_>, -) -> Result, Error> { +) -> Result, Error> { let mut graph_builder = PackageGraphBuilder::default(); let workspaces = match &workspace_root.workspace_file { WorkspaceFile::PnpmWorkspaceYaml(file) => { From de702192ec89815142b26f85b9f9f368f180fcd6 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 16:15:59 +0800 Subject: [PATCH 2/5] scan topo deps --- crates/vite_task_graph/src/lib.rs | 74 +++++--- ...__task graph@comprehensive-task-graph.snap | 159 ++++++++++++++++-- ...pshots__task graph@empty-package-test.snap | 17 +- ...s__task graph@explicit-deps-workspace.snap | 47 +++++- ...graph@recursive-topological-workspace.snap | 57 ++++++- ...graph@transitive-dependency-workspace.snap | 10 +- 6 files changed, 323 insertions(+), 41 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 07e8c8fb..ee78ee96 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -9,7 +9,7 @@ use std::{ use config::{ResolvedUserTaskConfig, UserConfigFile}; use petgraph::{ graph::{DiGraph, NodeIndex}, - visit::depth_first_search, + visit::{Control, DfsEvent, depth_first_search}, }; use serde::Serialize; use vec1::smallvec_v1::SmallVec1; @@ -111,9 +111,11 @@ pub enum SpecifierLookupError { /// It's immutable after created. The task nodes contain resolved task configurations and their dependencies. /// External factors (e.g. additional args from cli, current working directory, environmental variables) are not stored here. pub struct TaskGraph { - graph: DiGraph, + task_graph: DiGraph, - package_graph: Arc>, + /// Preserve the package graph for displaying purpose: + /// `self.task_graph` refers packages via PackageNodeIndex. To display package names and paths, we need to lookup them in package_graph. + package_graph: DiGraph, /// Grouping package indices by their package names. /// Due to rare but possible name conflicts in monorepos, we use `SmallVec1` to store multiple dirs for same name. @@ -231,11 +233,10 @@ impl TaskGraph { } } - let package_graph = Arc::new(package_graph); // Construct `Self` with task_graph with all task nodes ready and indexed, but no edges. let mut me = Self { - graph: task_graph, - package_graph: Arc::clone(&package_graph), + task_graph, + package_graph, node_indices_by_task_id, package_indices_by_name: package_dirs_by_name, }; @@ -243,7 +244,7 @@ impl TaskGraph { // Add explicit dependencies for (dependency_specifiers, from_node_index) in dependency_specifiers_with_task_node_indices { - let from_task_id = me.graph[from_node_index].task_id.clone(); + let from_task_id = me.task_graph[from_node_index].task_id.clone(); for specifier in dependency_specifiers.iter().cloned() { let to_node_index = me @@ -253,17 +254,51 @@ impl TaskGraph { specifier, origin_task_id: from_task_id.clone(), })?; - me.graph.update_edge(from_node_index, to_node_index, TaskDependencyType::Explicit); + me.task_graph.update_edge( + from_node_index, + to_node_index, + TaskDependencyType::Explicit, + ); } } // Add topological dependencies based on package dependencies - // for (package_index, task_node_index) in package_indices_with_task_node_indices { - // // For every task, - // // DFS starting from the containing package - // depth_first_search(&package_graph, Some(package_index), |event| {}); - // } - + let mut topological_edges = Vec::<(NodeIndex, NodeIndex)>::new(); + for task_index in me.task_graph.node_indices() { + let task_id = &me.task_graph[task_index].task_id; + let task_name = &task_id.task_name; + let package_index = task_id.package_index; + // For every task, + // DFS starting from the package where it's defined. + depth_first_search(&me.package_graph, Some(package_index), |event| { + let DfsEvent::TreeEdge(_, dependency_package_index) = event else { + return Control::<()>::Continue; + }; + // If a direct or indirect dependency has a task with the same name + if let Some(dependency_task_index) = me.node_indices_by_task_id.get(&TaskId { + package_index: dependency_package_index, + task_name: task_name.clone(), + }) { + // form an edge from the current task to that task + topological_edges.push((task_index, *dependency_task_index)); + // And stop searching further down this branch + // If there is a task with the same name in a deeper dependency, it will be connected via that nearer dependency's task. + Control::Prune + } else { + Control::Continue + } + }); + } + for (from_node_index, to_node_index) in topological_edges { + // Avoid duplicating edges if an explicit dependency already exists + if me.task_graph.find_edge(from_node_index, to_node_index).is_none() { + me.task_graph.update_edge( + from_node_index, + to_node_index, + TaskDependencyType::Topological, + ); + } + } Ok(me) } @@ -338,15 +373,16 @@ impl TaskGraph { depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)>, } - let mut node_snapshots = Vec::::with_capacity(self.graph.node_count()); - for a in self.graph.node_indices() { - let node = &self.graph[a]; + let mut node_snapshots = + Vec::::with_capacity(self.task_graph.node_count()); + for a in self.task_graph.node_indices() { + let node = &self.task_graph[a]; let mut depends_on: Vec<(TaskIdSnapshot, TaskDependencyType)> = self - .graph + .task_graph .edges_directed(a, petgraph::Direction::Outgoing) .map(|edge| { use petgraph::visit::EdgeRef as _; - let target_node = &self.graph[edge.target()]; + let target_node = &self.task_graph[edge.target()]; ( TaskIdSnapshot::from_task_id(&target_node.task_id, &self.package_graph), *edge.weight(), diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap index 81726275..18d8e441 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@comprehensive-task-graph.snap @@ -11,7 +11,22 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Generate schemas && echo Compile TypeScript && echo Bundle API && echo Copy assets", "cwd": "packages/api", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/config", + "task_name": "build" + }, + "Topological" + ], + [ + { + "package_dir": "packages/shared", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -38,7 +53,15 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Testing API", "cwd": "packages/api", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/shared", + "task_name": "test" + }, + "Topological" + ] + ] }, { "id": { @@ -47,7 +70,36 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Clean dist && echo Build client && echo Build server && echo Generate manifest && echo Optimize assets", "cwd": "packages/app", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/api", + "task_name": "build" + }, + "Topological" + ], + [ + { + "package_dir": "packages/pkg#special", + "task_name": "build" + }, + "Topological" + ], + [ + { + "package_dir": "packages/shared", + "task_name": "build" + }, + "Topological" + ], + [ + { + "package_dir": "packages/ui", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -65,7 +117,15 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Running dev server", "cwd": "packages/app", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/api", + "task_name": "dev" + }, + "Topological" + ] + ] }, { "id": { @@ -83,7 +143,36 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Unit tests && echo Integration tests", "cwd": "packages/app", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/api", + "task_name": "test" + }, + "Topological" + ], + [ + { + "package_dir": "packages/pkg#special", + "task_name": "test" + }, + "Topological" + ], + [ + { + "package_dir": "packages/shared", + "task_name": "test" + }, + "Topological" + ], + [ + { + "package_dir": "packages/ui", + "task_name": "test" + }, + "Topological" + ] + ] }, { "id": { @@ -110,7 +199,15 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Building package with hash", "cwd": "packages/pkg#special", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/shared", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -119,7 +216,15 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Testing package with hash", "cwd": "packages/pkg#special", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/shared", + "task_name": "test" + }, + "Topological" + ] + ] }, { "id": { @@ -173,7 +278,15 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Validating", "cwd": "packages/tools", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/config", + "task_name": "validate" + }, + "Topological" + ] + ] }, { "id": { @@ -182,7 +295,15 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Compile styles && echo Build components && echo Generate types", "cwd": "packages/ui", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/shared", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -191,7 +312,15 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Linting UI", "cwd": "packages/ui", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/shared", + "task_name": "lint" + }, + "Topological" + ] + ] }, { "id": { @@ -209,6 +338,14 @@ input_file: crates/vite_task_graph/tests/fixtures/comprehensive-task-graph }, "command": "echo Testing UI", "cwd": "packages/ui", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/shared", + "task_name": "test" + }, + "Topological" + ] + ] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap index 1e072f5b..b1414a3a 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@empty-package-test.snap @@ -19,6 +19,13 @@ input_file: crates/vite_task_graph/tests/fixtures/empty-package-test }, "Explicit" ], + [ + { + "package_dir": "packages/normal-package", + "task_name": "build" + }, + "Topological" + ], [ { "package_dir": "packages/normal-package", @@ -68,7 +75,15 @@ input_file: crates/vite_task_graph/tests/fixtures/empty-package-test }, "command": "echo 'Testing another-empty package'", "cwd": "packages/another-empty", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/normal-package", + "task_name": "test" + }, + "Topological" + ] + ] }, { "id": { diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap index 12da1ba8..a4c34726 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@explicit-deps-workspace.snap @@ -11,7 +11,15 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Building @test/app'", "cwd": "packages/app", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/utils", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -60,7 +68,15 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Testing @test/app'", "cwd": "packages/app", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/utils", + "task_name": "test" + }, + "Topological" + ] + ] }, { "id": { @@ -113,7 +129,15 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Building @test/utils'", "cwd": "packages/utils", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/core", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -130,6 +154,13 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "Explicit" ], + [ + { + "package_dir": "packages/core", + "task_name": "lint" + }, + "Topological" + ], [ { "package_dir": "packages/utils", @@ -146,6 +177,14 @@ input_file: crates/vite_task_graph/tests/fixtures/explicit-deps-workspace }, "command": "echo 'Testing @test/utils'", "cwd": "packages/utils", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/core", + "task_name": "test" + }, + "Topological" + ] + ] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap index c10c59b8..941231f8 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@recursive-topological-workspace.snap @@ -11,7 +11,22 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Building @test/web'", "cwd": "apps/web", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/app", + "task_name": "build" + }, + "Topological" + ], + [ + { + "package_dir": "packages/core", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -29,7 +44,15 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Building @test/app'", "cwd": "packages/app", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/utils", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -38,7 +61,15 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Testing @test/app'", "cwd": "packages/app", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/utils", + "task_name": "test" + }, + "Topological" + ] + ] }, { "id": { @@ -65,7 +96,15 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Preparing @test/utils' && echo 'Building @test/utils' && echo 'Done @test/utils'", "cwd": "packages/utils", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/core", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { @@ -74,6 +113,14 @@ input_file: crates/vite_task_graph/tests/fixtures/recursive-topological-workspac }, "command": "echo 'Testing @test/utils'", "cwd": "packages/utils", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/core", + "task_name": "test" + }, + "Topological" + ] + ] } ] diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap index 623fd5de..24cf5297 100644 --- a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@transitive-dependency-workspace.snap @@ -11,7 +11,15 @@ input_file: crates/vite_task_graph/tests/fixtures/transitive-dependency-workspac }, "command": "echo Building A", "cwd": "packages/a", - "depends_on": [] + "depends_on": [ + [ + { + "package_dir": "packages/c", + "task_name": "build" + }, + "Topological" + ] + ] }, { "id": { From 6a4b620f4b1e5c7df92277a0109a9ba8099d1149 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 16:24:37 +0800 Subject: [PATCH 3/5] Handle both type --- crates/vite_task_graph/src/lib.rs | 22 ++++++++++--- .../package.json | 2 ++ .../packages/a/package.json | 7 ++++ .../packages/a/vite.config.json | 8 +++++ .../packages/b/package.json | 6 ++++ .../pnpm-workspace.yaml | 2 ++ ...aph@dependency-both-topo-and-explicit.snap | 33 +++++++++++++++++++ 7 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/package.json create mode 100644 crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json create mode 100644 crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json create mode 100644 crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json create mode 100644 crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml create mode 100644 crates/vite_task_graph/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index ee78ee96..d7c6cc5e 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -23,10 +23,11 @@ use vite_workspace::{ #[derive(Debug, Clone, Copy, Serialize)] pub enum TaskDependencyType { /// The dependency is explicitly declared by user in `dependsOn`. - /// If a dependency is both explicit and topological, `TaskDependencyType::Explicit` takes precedenc Explicit, /// The dependency is added due to topological ordering based on package dependencies. Topological, + /// The dependency is explicitly declared by user in `dependsOn` and also added due to topological ordering. + Both, } /// Uniquely identifies a task, by its name and the path where it's defined. @@ -290,9 +291,22 @@ impl TaskGraph { }); } for (from_node_index, to_node_index) in topological_edges { - // Avoid duplicating edges if an explicit dependency already exists - if me.task_graph.find_edge(from_node_index, to_node_index).is_none() { - me.task_graph.update_edge( + if let Some(existing_edge_index) = + me.task_graph.find_edge(from_node_index, to_node_index) + { + let existing_edge = &mut me.task_graph[existing_edge_index]; + match *existing_edge { + TaskDependencyType::Explicit => { + // upgrade to Both + *existing_edge = TaskDependencyType::Both; + } + TaskDependencyType::Topological | TaskDependencyType::Both => { + // already has topological dependency, do nothing + } + } + } else { + // add new topological edge if not exists + me.task_graph.add_edge( from_node_index, to_node_index, TaskDependencyType::Topological, diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/package.json b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/package.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/package.json @@ -0,0 +1,2 @@ +{ +} diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json new file mode 100644 index 00000000..7b44c34d --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/a", + "version": "1.0.0", + "dependencies": { + "@test/b": "workspace:*" + } +} diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json new file mode 100644 index 00000000..6e52713b --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/a/vite.config.json @@ -0,0 +1,8 @@ +{ + "tasks": { + "build": { + "command": "build a", + "dependsOn": ["@test/b#build"] + } + } +} diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json new file mode 100644 index 00000000..2d4981fc --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/packages/b/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/b", + "scripts": { + "build": "build b" + } +} diff --git a/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml new file mode 100644 index 00000000..18ec407e --- /dev/null +++ b/crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/crates/vite_task_graph/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap new file mode 100644 index 00000000..71a4231f --- /dev/null +++ b/crates/vite_task_graph/tests/snapshots/snapshots__task graph@dependency-both-topo-and-explicit.snap @@ -0,0 +1,33 @@ +--- +source: crates/vite_task_graph/tests/snapshots.rs +expression: task_graph_snapshot +input_file: crates/vite_task_graph/tests/fixtures/dependency-both-topo-and-explicit +--- +[ + { + "id": { + "package_dir": "packages/a", + "task_name": "build" + }, + "command": "build a", + "cwd": "packages/a", + "depends_on": [ + [ + { + "package_dir": "packages/b", + "task_name": "build" + }, + "Both" + ] + ] + }, + { + "id": { + "package_dir": "packages/b", + "task_name": "build" + }, + "command": "build b", + "cwd": "packages/b", + "depends_on": [] + } +] From 99d21ca92b88d57486f88e237e695b4190a04163 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 16:28:58 +0800 Subject: [PATCH 4/5] add TaskIx newtype --- crates/vite_task/src/config/workspace.rs | 1 - crates/vite_task_graph/src/lib.rs | 40 +++++++++++++++++------- crates/vite_workspace/src/lib.rs | 5 +-- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/vite_task/src/config/workspace.rs b/crates/vite_task/src/config/workspace.rs index 35c7ff9a..0f91d82f 100644 --- a/crates/vite_task/src/config/workspace.rs +++ b/crates/vite_task/src/config/workspace.rs @@ -6,7 +6,6 @@ use std::{ }; use petgraph::{ - Graph, graph::{DiGraph, NodeIndex}, stable_graph::StableDiGraph, visit::IntoNodeReferences, diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index d7c6cc5e..397b8b21 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -8,16 +8,14 @@ use std::{ use config::{ResolvedUserTaskConfig, UserConfigFile}; use petgraph::{ - graph::{DiGraph, NodeIndex}, + graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, visit::{Control, DfsEvent, depth_first_search}, }; use serde::Serialize; use vec1::smallvec_v1::SmallVec1; use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; -use vite_workspace::{ - DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot, package, -}; +use vite_workspace::{DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot}; /// The type of a desk dependency, explaining why it's introduced. #[derive(Debug, Clone, Copy, Serialize)] @@ -107,12 +105,32 @@ pub enum SpecifierLookupError { TaskNameNotFound { package_name: Str, task_name: Str }, } +/// newtype of `DefaultIx` for indices in package graphs +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TaskIx(DefaultIx); +unsafe impl IndexType for TaskIx { + fn new(x: usize) -> Self { + Self(DefaultIx::new(x)) + } + + fn index(&self) -> usize { + self.0.index() + } + + fn max() -> Self { + Self(::max()) + } +} + +pub type TaskNodeIndex = NodeIndex; +pub type TaskEdgeIndex = EdgeIndex; + /// Full task graph of a workspace. /// /// It's immutable after created. The task nodes contain resolved task configurations and their dependencies. /// External factors (e.g. additional args from cli, current working directory, environmental variables) are not stored here. pub struct TaskGraph { - task_graph: DiGraph, + task_graph: DiGraph, /// Preserve the package graph for displaying purpose: /// `self.task_graph` refers packages via PackageNodeIndex. To display package names and paths, we need to lookup them in package_graph. @@ -123,7 +141,7 @@ pub struct TaskGraph { package_indices_by_name: HashMap>, /// task indices by task id for quick lookup - node_indices_by_task_id: HashMap, + node_indices_by_task_id: HashMap, } impl TaskGraph { @@ -132,12 +150,12 @@ impl TaskGraph { workspace_root: WorkspaceRoot<'_>, config_loader: impl loader::UserConfigLoader, ) -> Result { - let mut task_graph = DiGraph::::new(); + let mut task_graph = DiGraph::::default(); let package_graph = vite_workspace::load_package_graph(&workspace_root)?; // Record dependency specifiers for each task node to add explicit dependencies later - let mut dependency_specifiers_with_task_node_indices: Vec<(Arc<[Str]>, NodeIndex)> = + let mut dependency_specifiers_with_task_node_indices: Vec<(Arc<[Str]>, TaskNodeIndex)> = Vec::new(); // Load task nodes into `task_graph` @@ -206,7 +224,7 @@ impl TaskGraph { } // index tasks by ids - let mut node_indices_by_task_id: HashMap = + let mut node_indices_by_task_id: HashMap = HashMap::with_capacity(task_graph.node_count()); for node_index in task_graph.node_indices() { let task_node = &task_graph[node_index]; @@ -264,7 +282,7 @@ impl TaskGraph { } // Add topological dependencies based on package dependencies - let mut topological_edges = Vec::<(NodeIndex, NodeIndex)>::new(); + let mut topological_edges = Vec::<(TaskNodeIndex, TaskNodeIndex)>::new(); for task_index in me.task_graph.node_indices() { let task_id = &me.task_graph[task_index].task_id; let task_name = &task_id.task_name; @@ -323,7 +341,7 @@ impl TaskGraph { &self, specifier: &str, package_origin: PackageNodeIndex, - ) -> Result { + ) -> Result { let (package_index, task_name): (PackageNodeIndex, Str) = if let Some((package_name, task_name)) = specifier.rsplit_once('#') { // Lookup package path by the package name from '#' diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index 536332ec..cf920573 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -4,10 +4,7 @@ mod package_manager; use std::{fs, io}; -use petgraph::{ - Graph, - graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}, -}; +use petgraph::graph::{DefaultIx, DiGraph, EdgeIndex, IndexType, NodeIndex}; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use serde::{Deserialize, Serialize}; use vite_glob::GlobPatternSet; From 5c411b2a9e4ddb79566b1832737c9075c7ff5357 Mon Sep 17 00:00:00 2001 From: branchseer Date: Fri, 5 Dec 2025 16:36:33 +0800 Subject: [PATCH 5/5] wrap TaskDependencyTypeInner --- crates/vite_task_graph/src/lib.rs | 32 +++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 397b8b21..d1537ae2 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -17,9 +17,8 @@ use vite_path::{AbsolutePath, RelativePathBuf}; use vite_str::Str; use vite_workspace::{DependencyType, PackageInfo, PackageIx, PackageNodeIndex, WorkspaceRoot}; -/// The type of a desk dependency, explaining why it's introduced. #[derive(Debug, Clone, Copy, Serialize)] -pub enum TaskDependencyType { +enum TaskDependencyTypeInner { /// The dependency is explicitly declared by user in `dependsOn`. Explicit, /// The dependency is added due to topological ordering based on package dependencies. @@ -28,6 +27,23 @@ pub enum TaskDependencyType { Both, } +/// The type of a task dependency, explaining why it's introduced. +#[derive(Debug, Clone, Copy, Serialize)] +#[serde(transparent)] +pub struct TaskDependencyType(TaskDependencyTypeInner); + +// It hides `TaskDependencyTypeInner` and only expose `is_explicit`/`is_topological` +// to avoid incorrectly matching only Explicit variant to check if it's explicit. +impl TaskDependencyType { + pub fn is_explicit(&self) -> bool { + matches!(self.0, TaskDependencyTypeInner::Explicit | TaskDependencyTypeInner::Both) + } + + pub fn is_topological(&self) -> bool { + matches!(self.0, TaskDependencyTypeInner::Topological | TaskDependencyTypeInner::Both) + } +} + /// Uniquely identifies a task, by its name and the path where it's defined. #[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] pub struct TaskId { @@ -276,7 +292,7 @@ impl TaskGraph { me.task_graph.update_edge( from_node_index, to_node_index, - TaskDependencyType::Explicit, + TaskDependencyType(TaskDependencyTypeInner::Explicit), ); } } @@ -313,12 +329,12 @@ impl TaskGraph { me.task_graph.find_edge(from_node_index, to_node_index) { let existing_edge = &mut me.task_graph[existing_edge_index]; - match *existing_edge { - TaskDependencyType::Explicit => { + match existing_edge.0 { + TaskDependencyTypeInner::Explicit => { // upgrade to Both - *existing_edge = TaskDependencyType::Both; + existing_edge.0 = TaskDependencyTypeInner::Both; } - TaskDependencyType::Topological | TaskDependencyType::Both => { + TaskDependencyTypeInner::Topological | TaskDependencyTypeInner::Both => { // already has topological dependency, do nothing } } @@ -327,7 +343,7 @@ impl TaskGraph { me.task_graph.add_edge( from_node_index, to_node_index, - TaskDependencyType::Topological, + TaskDependencyType(TaskDependencyTypeInner::Topological), ); } }