fix(gvs): run dependency build scripts under the global virtual store#11987
Conversation
Review Summary by QodoFix dependency builds in global virtual store
WalkthroughsDescription• Fix dependency build scripts not executing under global virtual store • Resolve GVS projection directories using hash-based layout • Serialize concurrent builds to prevent directory race conditions • Add regression test for GVS rebuild functionality Diagramflowchart LR
A["Rebuild Request"] --> B{"enableGlobalVirtualStore?"}
B -->|Yes| C["Compute GVS Hash"]
C --> D["Resolve Projection Dir"]
D --> E["Check Build Lock"]
E -->|Locked| F["Wait for Result"]
E -->|Unlocked| G["Acquire Lock"]
G --> H["Execute Build"]
H --> I["Release Lock"]
B -->|No| J["Use Classic Path"]
F --> K["Complete"]
I --> K
J --> K
File Changes1. building/after-install/src/extendBuildOptions.ts
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
📝 WalkthroughWalkthroughUpdates build options for Global Virtual Store support, resolves rebuild targets to GVS projection directories with hashed-graph iteration, serializes concurrent rebuilds per projection via a process-wide lock map (released in finally), and adds end-to-end tests validating rebuilds inside the GVS layout. ChangesGlobal Virtual Store Build Script Fix
Sequence DiagramsequenceDiagram
participant WorkspaceProject as Workspace Project
participant RebuildTask as Rebuild Task
participant GVSLockMap as gvsBuildLocks
participant LifecycleScripts as Lifecycle Scripts
WorkspaceProject->>RebuildTask: Start rebuild for dep path
RebuildTask->>RebuildTask: Compute gvsDir from GVS projection
RebuildTask->>GVSLockMap: Check if gvsDir lock exists
alt Lock already held
GVSLockMap-->>RebuildTask: Promise to ongoing build
RebuildTask->>RebuildTask: Wait for promise
RebuildTask-->>WorkspaceProject: Return (marked rebuilt)
else No lock held
RebuildTask->>GVSLockMap: Register lock for gvsDir
RebuildTask->>LifecycleScripts: Run postinstall/build scripts
LifecycleScripts-->>RebuildTask: Complete
RebuildTask->>GVSLockMap: Release lock (finally)
RebuildTask-->>WorkspaceProject: Return
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
d0a1e86 to
13fcf0c
Compare
The post-lifecycle bin re-link pass used the classic virtualStoreDir path even under the global virtual store, so bins created by the build scripts this fix runs were never re-linked into the GVS projection. Use the same pkgModulesDir helper as the rest of the rebuild path. Also thread enableGlobalVirtualStore through the (recursive) rebuild command opts explicitly, and list pnpm in the changeset.
…ects Adds a multi-project recursive rebuild test under the global virtual store with per-project lockfiles (sharedWorkspaceLockfile: false), which routes through recursiveRebuild's per-project concurrent branch. Both projects depend on the same package, deduped into one shared GVS projection, so the concurrent passes select the same projection directory and exercise the per-projection build lock. Asserts the projection is deduped to one directory and built exactly once.
|
Congrats on merging your first pull request! 🎉🎉🎉 |
Summary
Under
enableGlobalVirtualStore: true, dependency build scripts (native addons vianode-gyp/prebuild-install, and anyinstall/postinstallhooks) are never executed during aworkspace install. The affected packages end up present but unbuilt, and crash at runtime — e.g.:
Error: Cannot find module '.../store/v11/links/@/node-expat/.../build/Release/node_expat.node'
This affects every native dependency in a workspace that uses the global virtual store (e.g.
node-expat,better-sqlite3, the imagemin bins). Packages that ship prebuilt binaries intheir tarball (e.g.
bcryptvianode-gyp-build) are unaffected because they need no build step.Root cause
In a workspace install, dependency builds are intentionally deferred to a single central pass at the end (
pnpm rebuild→buildProjectsin@pnpm/building.after-install), so ashared dependency is built once rather than once per project.
That pass resolved each package's location from the classic virtual-store layout:
//node_modules/
Under the global virtual store that directory does not exist — packages are projected into a hash-addressed directory in the store's
linksfolder:/links//node_modules/
So the rebuild looked in a non-existent path, found nothing to build, and silently did nothing. (The code even carried a note that "rebuild doesn't work for such packages at all, which
should be fixed.")
Fix
buildProjectsis now global-virtual-store aware:Correct projection directory — when
enableGlobalVirtualStoreis set, each package's root is resolved to<globalVirtualStoreDir>/<hash>/node_modules/<name>. The base resolvesto
<storeDir>/links(in the per-project rebuild contextctx.virtualStoreDiris the project-localnode_modules/.pnpm, not the shared store). The same projection directory is usedfor the post-lifecycle bin re-link pass too, so bins created by the build scripts themselves land in the projection instead of a non-existent classic path.
Matching hash — the projection hash is computed with the same helpers and inputs the installer uses (
iterateHashedGraphNodes+iteratePkgMeta, with the sameallowBuild/supportedArchitectures/ resolved-runtime-node-version), so it points at the exact directory the install created.Concurrency safety — because a single shared projection can be selected for rebuild by many workspace projects in parallel (unlimited
workspaceConcurrency, per-projectlockfiles), a process-wide lock keyed by the projection directory serializes builds of the same projection. The first build runs; concurrent ones wait for it and reuse the result.
Without this, parallel builds race on the same directory (
prebuild-installbus errors,node-gypEEXISTon the python symlink, etc.).Behavior is unchanged when
enableGlobalVirtualStoreis off (all new logic is gated on that flag).Files changed
building/after-install/src/index.ts— the fix (GVS projection resolution, per-projection build lock, and GVS-aware post-lifecycle bin re-linking).building/after-install/src/extendBuildOptions.ts— addsenableGlobalVirtualStore/globalVirtualStoreDirbuild options.building/commands/src/build/rebuild.ts,building/commands/src/build/recursive.ts— threadenableGlobalVirtualStorethrough the (recursive) rebuild command options.building/commands/test/build/index.ts— regression tests (single-project + multi-project shared projection)..changeset/gvs-rebuild-native-deps.md— changeset (@pnpm/building.after-install+pnpm, patch).cspell.json— allowlistprebuild.Test plan
Two tests in
building/commands/test/build/index.ts:rebuilds dependencies in the global virtual store— installs a package with a build script under GVS with scripts deferred (--ignore-scripts), so it is projected into<storeDir>/links/<hash>but unbuilt; runsrebuildand asserts the build artifact now exists inside the GVS projection.rebuilds a dependency shared by multiple workspace projects in the global virtual store— two-project workspace with per-project lockfiles (sharedWorkspaceLockfile: false), sorebuild -rruns a separate concurrent pass per project. Both projects share one GVS projection; asserts the projection is collapsed to a single directory and built once, exercisingthe per-projection build lock.
Results:
@pnpm/building.commandsbuild suite: 10/10 passing, no regressions (stable across repeated runs).@pnpm/building.after-installtype-checks.Related: #11385
Description updated by an agent (Claude Code, claude-opus-4-8) to reflect follow-up commits (bin re-link fix, rebuild-options threading, the multi-project test, changeset/cspell changes).
Summary by CodeRabbit
Bug Fixes
Tests
New Features
Chores