Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

- **Perf** `vp run` starts faster in workspaces whose tasks share cache `input` and `output` patterns: each distinct pattern is now parsed once instead of once for every task that uses it. In a 138-package workspace, this saves about 2 seconds of CPU on every run ([#887](https://github.com/voidzero-dev/vite-task/pull/887)).
- **Perf** `vp run` starts faster in workspaces whose tasks share cache `input` and `output` patterns: each distinct pattern is now parsed once instead of once for every task that uses it. In a 138-package workspace, this saves about 2 seconds of CPU on every run ([#887](https://github.com/voidzero-dev/vite-task/pull/887), [#890](https://github.com/voidzero-dev/vite-task/pull/890)).
- **Perf** When tasks miss the cache, `vp run` no longer waits for the disk after saving each task's result, so the run finishes sooner. If the computer loses power or the operating system crashes during `vp run`, the cache entries that the run saved can be lost, and those tasks run again next time instead of hitting the cache ([#871](https://github.com/voidzero-dev/vite-task/pull/871)).
- **Fixed** Running `vp run` in a package that sits inside a workspace folder but isn't listed in its `pnpm-workspace.yaml` `packages` or `package.json` `workspaces` now runs that package's own tasks, instead of treating the enclosing workspace as its root. When both configurations exist, `pnpm-workspace.yaml` takes precedence ([#859](https://github.com/voidzero-dev/vite-task/pull/859)).
- **Fixed** A mistyped or misplaced setting in a task now gets an error that names it. For example, `cache: { outputs: [...] }` now fails with an `unknown field` error that names `outputs` and lists the accepted settings, instead of `data did not match any variant of untagged enum UserTaskDefinition` ([vite-plus#2635](https://github.com/voidzero-dev/vite-plus/issues/2635), [#815](https://github.com/voidzero-dev/vite-task/pull/815)).
Expand Down
29 changes: 23 additions & 6 deletions crates/vt/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod execute;
pub(crate) mod reporter;

// Re-export types that are part of the public API
use std::{ffi::OsStr, fmt::Debug, io::IsTerminal, sync::Arc};
use std::{ffi::OsStr, fmt::Debug, io::IsTerminal, rc::Rc, sync::Arc};

use cache::ExecutionCache;
pub use cache::{CacheMiss, FingerprintMismatch};
Expand All @@ -19,8 +19,10 @@ use reporter::{
use rustc_hash::FxHashMap;
use vt_casefold::EnvName;
use vt_graph::{
IndexedTaskGraph, TaskGraph, TaskGraphLoadError, config::user::UserCacheConfig,
loader::UserConfigLoader, query::TaskQuery,
IndexedTaskGraph, TaskGraph, TaskGraphLoadError,
config::{GlobResolver, user::UserCacheConfig},
loader::UserConfigLoader,
query::TaskQuery,
};
use vt_path::{AbsolutePath, AbsolutePathBuf};
use vt_plan::{
Expand Down Expand Up @@ -57,7 +59,11 @@ where

#[derive(Debug)]
enum LazyTaskGraph<'a> {
Uninitialized { workspace_root: WorkspaceRoot, config_loader: &'a dyn UserConfigLoader },
Uninitialized {
workspace_root: WorkspaceRoot,
config_loader: &'a dyn UserConfigLoader,
glob_resolver: Rc<GlobResolver>,
},
Initialized(IndexedTaskGraph),
}

Expand All @@ -66,8 +72,9 @@ impl TaskGraphLoader for LazyTaskGraph<'_> {
async fn load_task_graph(&mut self) -> Result<&vt_graph::IndexedTaskGraph, TaskGraphLoadError> {
let _span = tracing::debug_span!("load_task_graph").entered();
Ok(match self {
Self::Uninitialized { workspace_root, config_loader } => {
let graph = IndexedTaskGraph::load(workspace_root, *config_loader).await?;
Self::Uninitialized { workspace_root, config_loader, glob_resolver } => {
let graph =
IndexedTaskGraph::load(workspace_root, *config_loader, glob_resolver).await?;
*self = Self::Initialized(graph);
match self {
Self::Initialized(graph) => &*graph,
Expand Down Expand Up @@ -158,6 +165,9 @@ pub struct Session<'a> {
/// The task graph is loaded on-demand and cached for future use.
lazy_task_graph: LazyTaskGraph<'a>,

/// Shared by task graph loading and every plan, so each cache glob pattern is parsed once.
glob_resolver: Rc<GlobResolver>,

envs: Arc<FxHashMap<EnvName<Arc<OsStr>>, Arc<OsStr>>>,
cwd: Arc<AbsolutePath>,

Expand Down Expand Up @@ -240,13 +250,17 @@ impl<'a> Session<'a> {
let workspace_node_modules_bin = workspace_root.path.join("node_modules").join(".bin");
prepend_path_env(&mut envs, &workspace_node_modules_bin)?;

let glob_resolver = Rc::new(GlobResolver::default());

// Cache is lazily initialized on first access to avoid SQLite race conditions
Ok(Self {
workspace_path: Arc::clone(&workspace_root.path),
lazy_task_graph: LazyTaskGraph::Uninitialized {
workspace_root,
config_loader: config.user_config_loader,
glob_resolver: Rc::clone(&glob_resolver),
},
glob_resolver,
envs: Arc::new(envs),
cwd,
plan_request_parser: PlanRequestParser { command_handler: config.command_handler },
Expand Down Expand Up @@ -713,6 +727,7 @@ impl<'a> Session<'a> {
&self.cwd,
synthetic_plan_request,
cache_key,
&self.glob_resolver,
)?;

// Initialize cache (needed for cache-aware execution)
Expand Down Expand Up @@ -808,6 +823,7 @@ impl<'a> Session<'a> {
&self.envs,
&mut self.plan_request_parser,
&mut self.lazy_task_graph,
&self.glob_resolver,
)
.await?;
Ok((plan_result, is_cwd_only))
Expand All @@ -829,6 +845,7 @@ impl<'a> Session<'a> {
&self.envs,
&mut self.plan_request_parser,
&mut self.lazy_task_graph,
&self.glob_resolver,
)
.await?;
Ok(plan_result.graph)
Expand Down
2 changes: 1 addition & 1 deletion crates/vt_graph/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ impl ResolvedGlobConfig {
/// workspaces repeat the same patterns across packages and tasks, so loading a task graph would
/// otherwise compile the same expressions thousands of times.
///
/// Create one for each task graph load or plan and pass it by reference. It is neither `Send` nor
/// Pass one by reference to every resolve that should share the cache. It is neither `Send` nor
/// `Sync`, so its cache needs no lock.
#[derive(Debug, Default)]
pub struct GlobResolver {
Expand Down
6 changes: 3 additions & 3 deletions crates/vt_graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ impl IndexedTaskGraph {
pub async fn load(
workspace_root: &WorkspaceRoot,
config_loader: &dyn loader::UserConfigLoader,
glob_resolver: &GlobResolver,
) -> Result<Self, TaskGraphLoadError> {
let mut task_graph = DiGraph::<TaskNode, TaskDependencyType, TaskIx>::default();

Expand Down Expand Up @@ -319,7 +320,6 @@ impl IndexedTaskGraph {
let resolved_global_cache = ResolvedGlobalCacheConfig::resolve_from(root_cache.as_ref());

let mut top_level_cache_fields = TopLevelCacheFieldsCollector::default();
let glob_resolver = GlobResolver::default();

// Second pass: create task nodes (cache is NOT applied here; it's applied at plan time)
for (package_index, package_dir, user_config) in package_configs {
Expand Down Expand Up @@ -370,7 +370,7 @@ impl IndexedTaskGraph {
task_user_config,
&package_dir,
&workspace_root.path,
&glob_resolver,
glob_resolver,
)
.map_err(|err| TaskGraphLoadError::ResolveConfigError {
error: err,
Expand Down Expand Up @@ -404,7 +404,7 @@ impl IndexedTaskGraph {
&package_dir,
package_json_script,
&workspace_root.path,
&glob_resolver,
glob_resolver,
)
.map_err(|err| TaskGraphLoadError::ResolveConfigError {
error: err,
Expand Down
10 changes: 7 additions & 3 deletions crates/vt_plan/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ pub struct PlanContext<'a> {

indexed_task_graph: &'a IndexedTaskGraph,

/// Shared with every nested context, so each glob in synthetic commands' cache configs is
/// parsed once per plan.
/// The session's glob resolver, shared with task graph loading and every nested context.
glob_resolver: Rc<GlobResolver>,

/// Final resolved global cache config, combining the graph's config with any CLI override.
Expand All @@ -81,12 +80,17 @@ pub struct PlanContext<'a> {
}

impl<'a> PlanContext<'a> {
#[expect(
clippy::too_many_arguments,
reason = "each argument initializes its own context field"
)]
pub fn new(
workspace_path: &'a Arc<AbsolutePath>,
cwd: Arc<AbsolutePath>,
envs: Arc<FxHashMap<EnvName<Arc<OsStr>>, Arc<OsStr>>>,
callbacks: &'a mut (dyn PlanRequestParser + 'a),
indexed_task_graph: &'a IndexedTaskGraph,
glob_resolver: Rc<GlobResolver>,
resolved_global_cache: ResolvedGlobalCacheConfig,
parent_query: Arc<TaskQuery>,
) -> Self {
Expand All @@ -97,7 +101,7 @@ impl<'a> PlanContext<'a> {
callbacks,
task_call_stack: Vec::new(),
indexed_task_graph,
glob_resolver: Rc::default(),
glob_resolver,
extra_args: Arc::default(),
resolved_global_cache,
resolved_remote_cache: None,
Expand Down
7 changes: 5 additions & 2 deletions crates/vt_plan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub mod plan_request;
mod ps1_shim;
pub mod remote_cache;

use std::{collections::BTreeMap, ffi::OsStr, fmt::Debug, sync::Arc};
use std::{collections::BTreeMap, ffi::OsStr, fmt::Debug, rc::Rc, sync::Arc};

use context::PlanContext;
pub use envs::MARKER_ENV_NAME;
Expand Down Expand Up @@ -206,6 +206,7 @@ pub async fn plan_query(
envs: &Arc<FxHashMap<EnvName<Arc<OsStr>>, Arc<OsStr>>>,
plan_request_parser: &mut (dyn PlanRequestParser + '_),
task_graph_loader: &mut (dyn TaskGraphLoader + '_),
glob_resolver: &Rc<GlobResolver>,
) -> Result<PlanResult, Error> {
let indexed_task_graph = task_graph_loader.load_task_graph().await?;

Expand All @@ -219,6 +220,7 @@ pub async fn plan_query(
Arc::clone(envs),
plan_request_parser,
indexed_task_graph,
Rc::clone(glob_resolver),
resolved_global_cache,
Arc::clone(&query),
);
Expand Down Expand Up @@ -258,6 +260,7 @@ pub fn plan_synthetic(
cwd: &Arc<AbsolutePath>,
synthetic_plan_request: SyntheticPlanRequest,
cache_key: Arc<[Str]>,
glob_resolver: &GlobResolver,
) -> Result<SpawnExecution, Error> {
let execution_cache_key = cache_metadata::ExecutionCacheKey::ExecAPI(cache_key);
plan_synthetic_request(
Expand All @@ -269,6 +272,6 @@ pub fn plan_synthetic(
cwd,
ParentCacheConfig::None,
None,
&GlobResolver::default(),
glob_resolver,
)
}
Loading