fix(graph): tolerate workspace package-ordering cycles#414
Conversation
`vp run --filter`/`-r` errored with "Cycle dependency detected" on workspaces where two siblings reference each other through a non-runtime link (e.g. `app` depends on `builder` while `builder` lists `app` under peerDependencies/devDependencies). pnpm builds the same workspace fine: it keeps every dependency kind in its graph and tolerates cycles by collapsing the cyclic packages into a single chunk that runs together, failing only under `--disallow-workspace-cycles`. vite-task instead planned a strictly acyclic execution graph and hard-errored on any cycle. Mirror pnpm's tolerance: after building the task execution graph, drop the package-ordering edges inside each cyclic strongly-connected component so the affected tasks run together with no ordering between them. Edges from an explicit `dependsOn` are kept, so genuine `dependsOn` cycles still error. Refs voidzero-dev/vite-plus#1610, voidzero-dev#411
|
Thanks! This is a clever solution! Please let me take my time to think about it. |
|
Hi @semimikoh. Thanks for the PR again, but I think we need more considerations on the fix. I pushed a plan snapshot test The fixture is: graph TD
consumer --> b
a --> b
b --> a
a --> base
pnpm would treat The generated snapshot shows a limitation of this PR's fix: after the internal Concretely:
So I honestly don't have a clean solution in mind yet, but suggestions are very welcome. |
Fixes the false "Cycle dependency detected" reported in voidzero-dev/vite-plus#1610 and discussed in #411.
Problem
vp run --filter/-rhard-errors on workspaces where two siblings reference each other through a non-runtime link — e.g.appdepends onbuilderwhilebuilderlistsappunderpeerDependencies(the nuxt / @nuxt/vite-builder shape).pnpm builds the same workspace fine: it keeps every dependency kind in its graph and tolerates cycles by collapsing the cyclic packages into a single chunk that runs together, failing only under
--disallow-workspace-cycles.Approach
Per @branchseer's analysis in #411, the gap is structural (hard-error vs cycle tolerance), not edge filtering. After the task execution graph is built, a new
IndexedTaskGraph::break_package_ordering_cyclesfinds each cyclic strongly-connected component and drops the package-ordering edges inside it — edges that have no correspondingdependsOnedge in the task graph. The cyclic tasks then run together with no ordering between them, mirroring pnpm's chunk behavior.The executor and the acyclic-execution-graph invariant are unchanged: package-ordering cycles are already broken before
ExecutionGraph::try_from_graphruns, so a remaining cycle there can only be an explicitdependsOncycle.Test plan
workspace_cycle_tolerated(covers both apeerDependenciesand adevDependenciesback-edge) plans cleanly instead of erroring.cycle_dependencyfixture (explicitdependsOncycle) still errors — snapshot unchanged.cargo test,cargo clippy --workspace --all-targets --all-features -- -D warnings,cargo fmt --check, andcargo doc -D warningsall pass locally.Open questions (marked draft)
dependsOncycles still hard-error (preserves existing behavior). Is that the desired split, or should those be tolerant too?--disallow-workspace-cyclesopt-in parity yet — happy to add as a follow-up if wanted.