refactor(resolving-deps-resolver): split resolve_node into its sequential phases (#13564)
Walker::resolve_node was 549 lines — by a wide margin the largest function in the crate (next is resolve_node_seed at 321, the median is 13). Nesting was never the problem: real block depth peaks at 5, in the two-pass child loop that is legitimately nested. Length was, because eleven sequential phases shared one large local context. Lift the three phases that have clean seams into named helpers: - The graph-entry / NodeRecord capture moves to finalize.rs as Walker::record_walked_node, next to the post-walk passes that consume those records, taking a borrowed WalkedNode context. - Building the ParentRefs map descendants see — locked-peer scoping, provider children, locked pins — becomes Walker::build_child_parent_refs, returning the shared map, the node's own contribution, and whether anything changed. - The node's own peer resolution, its fold with the children's, and the depPath it renders to become Walker::resolve_node_peers. The early-return short-circuits (context-free depPath, cycle re-entry, fast cache hit, peersCache hit) stay inline: as Option<NodeOutput>-returning helpers they would make the control flow harder to follow, not easier. resolve_node is now 378 lines of linear short-circuits plus the child walk. No allocation, cloning, or traversal change. The extracted phases take borrowed context structs, following the CacheHitContext / DeferredChildContext precedent, so no borrow conflict is resolved with a clone. The one behavioral identity worth naming: resolve_node_peers folds the node's own resolved peers into auto_install_resolved_peers after its loop instead of during it — the two maps agree because resolve_one_peer only ever inserts under the peer name it was called for. Also fold in the adjacent micro-cleanup the issue called out: Walker::deferred_child_resolution did two pure_pkgs hash lookups where one if-let chain does. Closes pnpm/pnpm#13553.
This commit is contained in:
1 parent
a98ff3e466
commit
fea49c1fd2
3 files changed
+383
-231
No files matched your search
@@ -416,12 +416,10 @@ impl Walker<'_> {
|
||||
parent_refs: &ParentRefs,
|
||||
pkg_id: &str,
|
||||
) -> DeferredChildResolution {
|
||||
if self
|
||||
.pure_pkgs
|
||||
.get(pkg_id)
|
||||
.is_some_and(|_| self.tree.packages[pkg_id].peer_dependencies.is_empty())
|
||||
if let Some(dep_path) = self.pure_pkgs.get(pkg_id)
|
||||
&& self.tree.packages[pkg_id].peer_dependencies.is_empty()
|
||||
{
|
||||
return DeferredChildResolution::Pure(self.pure_pkgs[pkg_id].clone());
|
||||
return DeferredChildResolution::Pure(dep_path.clone());
|
||||
}
|
||||
if let Some(cached) = self
|
||||
.find_fast_hit_for_lazy(parent_ids, parent_refs, pkg_id)
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
//! The post-walk passes: pending-edge repair, SCC-based cycle
|
||||
//! detection, the final depPath recomputation that gives every resolved
|
||||
//! peer its full suffix, and the [`DependenciesGraph`] built from the
|
||||
//! per-node records keyed by those depPaths.
|
||||
//! The graph-entry capture each walked node performs, and the post-walk
|
||||
//! passes it feeds: pending-edge repair, SCC-based cycle detection, the
|
||||
//! final depPath recomputation that gives every resolved peer its full
|
||||
//! suffix, and the [`DependenciesGraph`] built from the per-node records
|
||||
//! keyed by those depPaths.
|
||||
|
||||
use crate::{
|
||||
dependencies_graph::{DependenciesGraph, DependenciesGraphNode},
|
||||
node_id::NodeId,
|
||||
resolve_peers::{
|
||||
context::{
|
||||
link_node_id_as_dep_path, node_id_sort_key, peer_segment_names, pkg_name_version,
|
||||
SharedChain, link_node_id_as_dep_path, node_id_sort_key, peer_segment_names,
|
||||
pkg_name_version,
|
||||
},
|
||||
walker::Walker,
|
||||
walker::{MissingPeerInfo, Walker},
|
||||
},
|
||||
resolved_tree::ResolvedPackage,
|
||||
};
|
||||
use pacquet_deps_path::{DepPath, PeerId, create_peer_dep_graph_hash, link_path_to_peer_version};
|
||||
use pacquet_resolving_resolver_base::ResolveResult;
|
||||
@@ -45,6 +48,30 @@ pub(super) struct NodeRecord {
|
||||
pub(super) order: u64,
|
||||
}
|
||||
|
||||
/// One walked node's contribution to the graph, as
|
||||
/// [`Walker::record_walked_node`] receives it: the node's identity, the
|
||||
/// depPath the walk gave it, and the edge and peer sets its graph entry
|
||||
/// and [`NodeRecord`] are built from.
|
||||
pub(super) struct WalkedNode<'a> {
|
||||
pub(super) node_id: &'a NodeId,
|
||||
pub(super) pkg: &'a ResolvedPackage,
|
||||
pub(super) dep_path: &'a DepPath,
|
||||
pub(super) parent_node_ids: &'a SharedChain<NodeId>,
|
||||
pub(super) parent_pkg_ids_chain: &'a SharedChain<String>,
|
||||
/// This node's realized `alias → NodeId` children.
|
||||
pub(super) children: &'a BTreeMap<String, NodeId>,
|
||||
/// The depPaths those same children resolved to.
|
||||
pub(super) child_dep_paths: BTreeMap<String, DepPath>,
|
||||
/// Every peer resolved anywhere in this node's subtree.
|
||||
pub(super) all_resolved_peers: &'a HashMap<String, NodeId>,
|
||||
pub(super) all_missing_peers: &'a HashMap<String, MissingPeerInfo>,
|
||||
/// The subset of the above this node declares itself.
|
||||
pub(super) own_resolved_peers: &'a HashMap<String, NodeId>,
|
||||
pub(super) depth: i32,
|
||||
pub(super) installable: bool,
|
||||
pub(super) is_pure: bool,
|
||||
}
|
||||
|
||||
/// One `parent → child` edge whose target wasn't walked yet at the
|
||||
/// time the parent's `graph_children` was built. Patched up by
|
||||
/// [`Walker::patch_pending_peer_edges`] after the main walk completes.
|
||||
@@ -61,6 +88,123 @@ struct FinalPeerContext<'a> {
|
||||
}
|
||||
|
||||
impl Walker<'_> {
|
||||
/// Record one walked node: its entry in the provisional
|
||||
/// depPath-keyed graph, and the [`NodeRecord`] the post-walk rebuild
|
||||
/// consumes. A discovery pass runs neither of the passes that read
|
||||
/// these, so it never calls this.
|
||||
pub(super) fn record_walked_node(&mut self, node: WalkedNode<'_>) {
|
||||
let WalkedNode {
|
||||
node_id,
|
||||
pkg,
|
||||
dep_path,
|
||||
parent_node_ids,
|
||||
parent_pkg_ids_chain,
|
||||
children,
|
||||
child_dep_paths,
|
||||
all_resolved_peers,
|
||||
all_missing_peers,
|
||||
own_resolved_peers,
|
||||
depth,
|
||||
installable,
|
||||
is_pure,
|
||||
} = node;
|
||||
|
||||
// The children's depPath edges become this node's graph children.
|
||||
// Resolved peers become extra edges, aliased by peer name. If a
|
||||
// peer's depPath isn't known yet — typically a later sibling
|
||||
// direct dep — defer the edge to the post-walk patch pass; the
|
||||
// install layer drives off `graph_children`, so skipping the
|
||||
// edge entirely would leave the peer un-symlinked in the
|
||||
// parent's slot.
|
||||
let mut graph_children = BTreeMap::new();
|
||||
for (alias, child_node_id) in
|
||||
self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id)
|
||||
{
|
||||
self.add_graph_child_or_pending(&mut graph_children, dep_path, alias, child_node_id);
|
||||
}
|
||||
for (alias, child_dep_path) in child_dep_paths {
|
||||
graph_children.insert(alias, child_dep_path);
|
||||
}
|
||||
for (peer_alias, peer_node_id) in all_resolved_peers {
|
||||
self.add_graph_child_or_pending(
|
||||
&mut graph_children,
|
||||
dep_path,
|
||||
peer_alias.clone(),
|
||||
peer_node_id.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
// Compute transitive peer set: peers visible in this subtree
|
||||
// that are NOT declared in this package's own peerDependencies.
|
||||
let mut transitive_peer_dependencies: HashSet<String> = HashSet::default();
|
||||
for peer_alias in all_resolved_peers.keys() {
|
||||
if !pkg.peer_dependencies.contains_key(peer_alias) {
|
||||
transitive_peer_dependencies.insert(peer_alias.clone());
|
||||
}
|
||||
}
|
||||
for peer_alias in all_missing_peers.keys() {
|
||||
if !pkg.peer_dependencies.contains_key(peer_alias) {
|
||||
transitive_peer_dependencies.insert(peer_alias.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Capture this node's NodeId-level edges + metadata for the
|
||||
// post-walk [`Walker::build_final_dep_paths`] rebuild. Edges are
|
||||
// the node's regular children overlaid with its *own* resolved
|
||||
// peers — this node's own peer resolution, not the descendants'
|
||||
// peers bubbled up for the suffix. A peer a descendant resolved
|
||||
// (e.g. `debug`'s optional `supports-color`) is symlinked at the
|
||||
// descendant that declares it, so it must not appear in this
|
||||
// node's dependencies. Carries NodeIds so the rebuild can
|
||||
// resolve each to its corrected final depPath.
|
||||
let mut record_edges =
|
||||
self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id);
|
||||
record_edges.extend(children.clone());
|
||||
for (peer_alias, peer_node_id) in own_resolved_peers {
|
||||
record_edges.insert(peer_alias.clone(), peer_node_id.clone());
|
||||
}
|
||||
let optional_child_aliases = self.optional_child_aliases(&pkg.id, &record_edges);
|
||||
let record_order = self.next_record_order;
|
||||
self.next_record_order += 1;
|
||||
self.node_records.insert(
|
||||
node_id.clone(),
|
||||
NodeRecord {
|
||||
edges: record_edges,
|
||||
optional_child_aliases: optional_child_aliases.clone(),
|
||||
transitive_peer_dependencies: transitive_peer_dependencies.clone(),
|
||||
depth,
|
||||
installable,
|
||||
is_pure,
|
||||
order: record_order,
|
||||
},
|
||||
);
|
||||
|
||||
// Multiple visits with the same depPath collapse onto the same
|
||||
// graph entry. On a conflict, keep the entry with the smallest
|
||||
// `depth` so install order matches.
|
||||
self.graph
|
||||
.entry(dep_path.clone())
|
||||
.and_modify(|node| {
|
||||
if node.depth > depth {
|
||||
node.depth = depth;
|
||||
}
|
||||
})
|
||||
.or_insert(DependenciesGraphNode {
|
||||
dep_path: dep_path.clone(),
|
||||
resolved_package_id: pkg.id.clone(),
|
||||
resolve_result: Arc::clone(&pkg.result),
|
||||
children: graph_children,
|
||||
optional_children: optional_child_aliases,
|
||||
peer_dependencies: pkg.peer_dependencies.clone(),
|
||||
transitive_peer_dependencies,
|
||||
resolved_peer_names: all_resolved_peers.keys().cloned().collect(),
|
||||
depth,
|
||||
installable,
|
||||
is_pure,
|
||||
optional: pkg.optional,
|
||||
});
|
||||
}
|
||||
|
||||
/// Fill in `graph_children` edges that were skipped during the main
|
||||
/// walk because the peer target's `DepPath` hadn't been computed
|
||||
/// yet. Each direct dep's subtree is fully walked by the time
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
use crate::{
|
||||
dependencies_graph::{
|
||||
DependenciesGraph, DependenciesGraphNode, MissingPeer, ParentPackageRef,
|
||||
PeerDependencyIssue, PeerDependencyIssues,
|
||||
DependenciesGraph, MissingPeer, ParentPackageRef, PeerDependencyIssue, PeerDependencyIssues,
|
||||
},
|
||||
node_id::NodeId,
|
||||
resolve_peers::{
|
||||
@@ -22,7 +21,7 @@ use crate::{
|
||||
scoped_hoisted_optional_parent_refs,
|
||||
},
|
||||
discovery::PeerDiscoveryCaches,
|
||||
finalize::{NodeRecord, PendingPeerEdge},
|
||||
finalize::{NodeRecord, PendingPeerEdge, WalkedNode},
|
||||
},
|
||||
resolved_tree::{ChildEdge, DirectDep, PeerDep, ResolvedPackage, ResolvedTree, TreeChildren},
|
||||
};
|
||||
@@ -107,7 +106,7 @@ pub(super) struct Walker<'tree> {
|
||||
/// the post-walk [`Walker::build_final_dep_paths`] /
|
||||
/// [`Walker::build_final_graph`] pass. See [`NodeRecord`].
|
||||
pub(super) node_records: HashMap<NodeId, NodeRecord>,
|
||||
next_record_order: u64,
|
||||
pub(super) next_record_order: u64,
|
||||
/// Reverse index over the tree nodes' `previous_dep_path`, built
|
||||
/// only when [`ResolvePeersOptions::resolved_peer_provider_paths`]
|
||||
/// is set. The upstream `nodeIdsByPreviousDepPath`.
|
||||
@@ -325,6 +324,46 @@ impl ChildOutputs {
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`ParentRefs`] view a node hands down to its descendants,
|
||||
/// as [`Walker::build_child_parent_refs`] computes it.
|
||||
struct ChildParentRefs {
|
||||
refs: Arc<ParentRefs>,
|
||||
/// Only what this node itself contributed: its peer-relevant
|
||||
/// children.
|
||||
own: ParentRefs,
|
||||
/// Whether `refs` says anything the caller's map didn't. `false`
|
||||
/// lets the caller pass its own parent-context snapshot down
|
||||
/// instead of rebuilding an identical one.
|
||||
changed: bool,
|
||||
}
|
||||
|
||||
/// The peers of one node, as [`Walker::resolve_node_peers`] resolves
|
||||
/// them, and the depPath the combined set renders to.
|
||||
struct NodePeers {
|
||||
/// The node's own `peerDependencies`, resolved.
|
||||
own_resolved: HashMap<String, NodeId>,
|
||||
/// The above folded with what the subtree resolved against
|
||||
/// ancestors, minus the node's own name — the set `dep_path`'s
|
||||
/// suffix renders.
|
||||
all_resolved: HashMap<String, NodeId>,
|
||||
all_missing: HashMap<String, MissingPeerInfo>,
|
||||
dep_path: DepPath,
|
||||
}
|
||||
|
||||
struct NodePeersContext<'a> {
|
||||
pkg: &'a ResolvedPackage,
|
||||
pkg_name: &'a str,
|
||||
/// The augmented refs visible at this node, including its own
|
||||
/// peer-relevant children.
|
||||
parent_refs: &'a ParentRefs,
|
||||
chain_names: &'a SharedChain<String>,
|
||||
ancestor_pkg_ids: &'a SharedChain<String>,
|
||||
/// Taken by value: it is the base the combined resolved-peer map is
|
||||
/// built on, so folding into it costs no extra map.
|
||||
external_from_children: HashMap<String, NodeId>,
|
||||
missing_from_children: &'a HashMap<String, MissingPeerInfo>,
|
||||
}
|
||||
|
||||
impl Walker<'_> {
|
||||
pub(super) fn walk(mut self) -> ResolvePeersResult {
|
||||
let importer_parents = Arc::new(self.build_importer_parents());
|
||||
@@ -577,78 +616,19 @@ impl Walker<'_> {
|
||||
};
|
||||
let pkg = self.owned_package(&pkg_id);
|
||||
let (provider_children, preview_undo) = self.preview_peer_provider_children(node_id);
|
||||
let mut refs_changed = locked_peer_names.is_some();
|
||||
let parent_parent_refs = if let Some(locked_peer_names) = &locked_peer_names {
|
||||
Arc::new(scoped_hoisted_optional_parent_refs(
|
||||
parent_parent_refs,
|
||||
locked_peer_names,
|
||||
&self.opts.hoisted_optional_peer_node_ids,
|
||||
))
|
||||
} else {
|
||||
Arc::clone(parent_parent_refs)
|
||||
};
|
||||
let (pkg_name, _pkg_version) = pkg_name_version(&pkg.result);
|
||||
|
||||
// Build the ParentRefs map that descendants of this node see:
|
||||
// parent's view + this node's own peer-relevant children. Kept
|
||||
// behind `Arc` copy-on-write: most nodes contribute nothing, so
|
||||
// they pass the parent's map down by refcount instead of cloning
|
||||
// it — the per-node map clones dominated the walker's CPU time on
|
||||
// peer-heavy workspaces.
|
||||
let mut child_parent_refs = parent_parent_refs;
|
||||
let mut new_parent_refs = ParentRefs::default();
|
||||
for (alias, child_node_id) in &provider_children {
|
||||
let Some(child_tree) = self.tree.dependencies_tree.get(child_node_id) else { continue };
|
||||
let Some(child_pkg) = self.tree.packages.get(&child_tree.resolved_package_id) else {
|
||||
continue;
|
||||
};
|
||||
insert_parent_ref(
|
||||
&mut new_parent_refs,
|
||||
alias,
|
||||
child_node_id.clone(),
|
||||
child_pkg,
|
||||
child_tree.depth,
|
||||
);
|
||||
}
|
||||
if !new_parent_refs.is_empty() {
|
||||
refs_changed = true;
|
||||
let refs = Arc::make_mut(&mut child_parent_refs);
|
||||
// Built only when a name collision actually consults it — the
|
||||
// common no-collision node never pays for the extra map clone.
|
||||
let mut refs_with_new: Option<ParentRefs> = None;
|
||||
for (name, mut new_parent_ref) in new_parent_refs.clone() {
|
||||
if let Some(existing) = refs.get(&name) {
|
||||
let with_new = refs_with_new.get_or_insert_with(|| {
|
||||
let mut with_new = refs.clone();
|
||||
with_new.extend(new_parent_refs.clone());
|
||||
with_new
|
||||
});
|
||||
if !self.parent_refs_match(existing, &new_parent_ref)
|
||||
|| self.inherited_parent_pkg_breaks_peer_diamond(
|
||||
with_new,
|
||||
existing,
|
||||
&new_parent_ref,
|
||||
node_id,
|
||||
)
|
||||
{
|
||||
new_parent_ref.occurrence = existing.occurrence + 1;
|
||||
refs.insert(name, new_parent_ref);
|
||||
}
|
||||
} else {
|
||||
refs.insert(name, new_parent_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let locked_pins =
|
||||
self.locked_peer_context_pins(node_id, &pkg, &child_parent_refs, parent_node_ids);
|
||||
if !locked_pins.is_empty() {
|
||||
refs_changed = true;
|
||||
let refs = Arc::make_mut(&mut child_parent_refs);
|
||||
for (name, parent_ref) in locked_pins {
|
||||
refs.insert(name, parent_ref);
|
||||
}
|
||||
}
|
||||
let ChildParentRefs {
|
||||
refs: child_parent_refs,
|
||||
own: new_parent_refs,
|
||||
changed: refs_changed,
|
||||
} = self.build_child_parent_refs(
|
||||
node_id,
|
||||
&pkg,
|
||||
parent_parent_refs,
|
||||
locked_peer_names.as_deref(),
|
||||
&provider_children,
|
||||
parent_node_ids,
|
||||
);
|
||||
|
||||
// Record this node's parent context for the descendants'
|
||||
// [`peers_cache`] lookups. We compute and store the snapshot
|
||||
@@ -788,53 +768,25 @@ impl Walker<'_> {
|
||||
mut missing_summaries,
|
||||
} = child_outputs;
|
||||
|
||||
// Resolve this node's own peer requirements against the augmented
|
||||
// ParentRefs visible at this node, including peer-relevant children.
|
||||
let mut own_resolved_peers: HashMap<String, NodeId> = HashMap::default();
|
||||
let mut own_missing_peers: HashMap<String, MissingPeerInfo> = HashMap::default();
|
||||
for (peer_name, peer_dep) in &pkg.peer_dependencies {
|
||||
self.resolve_one_peer(
|
||||
&pkg_name,
|
||||
peer_name,
|
||||
peer_dep,
|
||||
&child_parent_refs,
|
||||
&child_chain_names,
|
||||
parent_pkg_ids_chain,
|
||||
&mut own_resolved_peers,
|
||||
&mut own_missing_peers,
|
||||
);
|
||||
if let Some(peer_node_id) = own_resolved_peers.get(peer_name) {
|
||||
auto_install_resolved_peers.insert(peer_name.clone(), peer_node_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Combine all resolved peers (this node's own + descendants').
|
||||
// Filter out the node's own name (a package doesn't peer-depend
|
||||
// on itself).
|
||||
let mut all_resolved_peers = external_from_children;
|
||||
for (peer_alias, peer_node_id) in &own_resolved_peers {
|
||||
all_resolved_peers.insert(peer_alias.clone(), peer_node_id.clone());
|
||||
}
|
||||
all_resolved_peers.remove(&pkg_name);
|
||||
|
||||
// Same for missing peers (children + own).
|
||||
let mut all_missing_peers = missing_from_children.clone();
|
||||
for (peer_alias, info) in &own_missing_peers {
|
||||
all_missing_peers.insert(peer_alias.clone(), info.clone());
|
||||
}
|
||||
|
||||
// Construct the depPath. Empty resolved-peers ⇒ pure node:
|
||||
// depPath = pkgIdWithPatchHash.
|
||||
let dep_path = if all_resolved_peers.is_empty() {
|
||||
DepPath::from(pkg.id.clone())
|
||||
} else {
|
||||
let peer_ids: Vec<PeerId> = all_resolved_peers
|
||||
let NodePeers {
|
||||
own_resolved: own_resolved_peers,
|
||||
all_resolved: all_resolved_peers,
|
||||
all_missing: all_missing_peers,
|
||||
dep_path,
|
||||
} = self.resolve_node_peers(NodePeersContext {
|
||||
pkg: &pkg,
|
||||
pkg_name: &pkg_name,
|
||||
parent_refs: &child_parent_refs,
|
||||
chain_names: &child_chain_names,
|
||||
ancestor_pkg_ids: parent_pkg_ids_chain,
|
||||
external_from_children,
|
||||
missing_from_children: &missing_from_children,
|
||||
});
|
||||
auto_install_resolved_peers.extend(
|
||||
own_resolved_peers
|
||||
.iter()
|
||||
.map(|(peer_alias, peer_node_id)| self.build_peer_id(peer_alias, peer_node_id))
|
||||
.collect();
|
||||
let suffix = create_peer_dep_graph_hash(&peer_ids, self.opts.peers_suffix_max_length);
|
||||
DepPath::from(format!("{}{}", pkg.id, suffix))
|
||||
};
|
||||
.map(|(peer_name, peer_node_id)| (peer_name.clone(), peer_node_id.clone())),
|
||||
);
|
||||
|
||||
// Register the depPath ↔ NodeId mapping and per-node
|
||||
// propagated state before inserting into the graph (so any
|
||||
@@ -895,105 +847,21 @@ impl Walker<'_> {
|
||||
}
|
||||
|
||||
if !self.discovery {
|
||||
// The children's depPath edges become this node's graph children.
|
||||
// Resolved peers become extra edges, aliased by peer name. If a
|
||||
// peer's depPath isn't known yet — typically a later sibling
|
||||
// direct dep — defer the edge to the post-walk patch pass; the
|
||||
// install layer drives off `graph_children`, so skipping the
|
||||
// edge entirely would leave the peer un-symlinked in the
|
||||
// parent's slot.
|
||||
let mut graph_children = BTreeMap::new();
|
||||
for (alias, child_node_id) in
|
||||
self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id)
|
||||
{
|
||||
self.add_graph_child_or_pending(
|
||||
&mut graph_children,
|
||||
&dep_path,
|
||||
alias,
|
||||
child_node_id,
|
||||
);
|
||||
}
|
||||
for (alias, child_dep_path) in child_dep_paths {
|
||||
graph_children.insert(alias, child_dep_path);
|
||||
}
|
||||
for (peer_alias, peer_node_id) in all_resolved_peers.iter() {
|
||||
self.add_graph_child_or_pending(
|
||||
&mut graph_children,
|
||||
&dep_path,
|
||||
peer_alias.clone(),
|
||||
peer_node_id.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
// Compute transitive peer set: peers visible in this subtree
|
||||
// that are NOT declared in this package's own peerDependencies.
|
||||
let mut transitive_peer_dependencies: HashSet<String> = HashSet::default();
|
||||
for peer_alias in all_resolved_peers.keys() {
|
||||
if !pkg.peer_dependencies.contains_key(peer_alias) {
|
||||
transitive_peer_dependencies.insert(peer_alias.clone());
|
||||
}
|
||||
}
|
||||
for peer_alias in all_missing_peers.keys() {
|
||||
if !pkg.peer_dependencies.contains_key(peer_alias) {
|
||||
transitive_peer_dependencies.insert(peer_alias.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Capture this node's NodeId-level edges + metadata for the
|
||||
// post-walk [`Walker::build_final_dep_paths`] rebuild. Edges are
|
||||
// the node's regular children overlaid with its *own* resolved
|
||||
// peers — this node's own peer resolution, not the descendants'
|
||||
// peers bubbled up for the suffix. A peer a descendant resolved
|
||||
// (e.g. `debug`'s optional `supports-color`) is symlinked at the
|
||||
// descendant that declares it, so it must not appear in this
|
||||
// node's dependencies. Carries NodeIds so the rebuild can
|
||||
// resolve each to its corrected final depPath.
|
||||
let mut record_edges =
|
||||
self.previously_resolved_children(parent_node_ids, parent_pkg_ids_chain, &pkg.id);
|
||||
record_edges.extend(children_map.clone());
|
||||
for (peer_alias, peer_node_id) in &own_resolved_peers {
|
||||
record_edges.insert(peer_alias.clone(), peer_node_id.clone());
|
||||
}
|
||||
let optional_child_aliases = self.optional_child_aliases(&pkg.id, &record_edges);
|
||||
let record_order = self.next_record_order;
|
||||
self.next_record_order += 1;
|
||||
self.node_records.insert(
|
||||
node_id.clone(),
|
||||
NodeRecord {
|
||||
edges: record_edges,
|
||||
optional_child_aliases: optional_child_aliases.clone(),
|
||||
transitive_peer_dependencies: transitive_peer_dependencies.clone(),
|
||||
depth: tree_node_depth,
|
||||
installable: tree_node_installable,
|
||||
is_pure,
|
||||
order: record_order,
|
||||
},
|
||||
);
|
||||
|
||||
// Multiple visits with the same depPath collapse onto the same
|
||||
// graph entry. On a conflict, keep the entry with the smallest
|
||||
// `depth` so install order matches.
|
||||
self.graph
|
||||
.entry(dep_path.clone())
|
||||
.and_modify(|node| {
|
||||
if node.depth > tree_node_depth {
|
||||
node.depth = tree_node_depth;
|
||||
}
|
||||
})
|
||||
.or_insert(DependenciesGraphNode {
|
||||
dep_path: dep_path.clone(),
|
||||
resolved_package_id: pkg.id.clone(),
|
||||
resolve_result: Arc::clone(&pkg.result),
|
||||
children: graph_children,
|
||||
optional_children: optional_child_aliases,
|
||||
peer_dependencies: pkg.peer_dependencies.clone(),
|
||||
transitive_peer_dependencies,
|
||||
resolved_peer_names: all_resolved_peers.keys().cloned().collect(),
|
||||
depth: tree_node_depth,
|
||||
installable: tree_node_installable,
|
||||
is_pure,
|
||||
optional: pkg.optional,
|
||||
});
|
||||
self.record_walked_node(WalkedNode {
|
||||
node_id,
|
||||
pkg: &pkg,
|
||||
dep_path: &dep_path,
|
||||
parent_node_ids,
|
||||
parent_pkg_ids_chain,
|
||||
children: &children_map,
|
||||
child_dep_paths,
|
||||
all_resolved_peers: &all_resolved_peers,
|
||||
all_missing_peers: &all_missing_peers,
|
||||
own_resolved_peers: &own_resolved_peers,
|
||||
depth: tree_node_depth,
|
||||
installable: tree_node_installable,
|
||||
is_pure,
|
||||
});
|
||||
}
|
||||
|
||||
self.in_progress.remove(node_id);
|
||||
@@ -1022,6 +890,148 @@ impl Walker<'_> {
|
||||
output
|
||||
}
|
||||
|
||||
/// Build the [`ParentRefs`] map that descendants of this node see:
|
||||
/// the parent's view (scoped down when the node's locked peer names
|
||||
/// exclude hoisted optional providers), plus the node's own
|
||||
/// peer-relevant children, plus the pins the wanted lockfile locked
|
||||
/// in. Kept behind `Arc` copy-on-write: most nodes contribute
|
||||
/// nothing, so they pass the parent's map down by refcount instead
|
||||
/// of cloning it — the per-node map clones dominated the walker's
|
||||
/// CPU time on peer-heavy workspaces.
|
||||
fn build_child_parent_refs(
|
||||
&self,
|
||||
node_id: &NodeId,
|
||||
pkg: &ResolvedPackage,
|
||||
parent_parent_refs: &Arc<ParentRefs>,
|
||||
locked_peer_names: Option<&HashSet<String>>,
|
||||
provider_children: &BTreeMap<String, NodeId>,
|
||||
parent_node_ids: &SharedChain<NodeId>,
|
||||
) -> ChildParentRefs {
|
||||
let mut refs_changed = locked_peer_names.is_some();
|
||||
let mut child_parent_refs = if let Some(locked_peer_names) = locked_peer_names {
|
||||
Arc::new(scoped_hoisted_optional_parent_refs(
|
||||
parent_parent_refs,
|
||||
locked_peer_names,
|
||||
&self.opts.hoisted_optional_peer_node_ids,
|
||||
))
|
||||
} else {
|
||||
Arc::clone(parent_parent_refs)
|
||||
};
|
||||
|
||||
let mut new_parent_refs = ParentRefs::default();
|
||||
for (alias, child_node_id) in provider_children {
|
||||
let Some(child_tree) = self.tree.dependencies_tree.get(child_node_id) else { continue };
|
||||
let Some(child_pkg) = self.tree.packages.get(&child_tree.resolved_package_id) else {
|
||||
continue;
|
||||
};
|
||||
insert_parent_ref(
|
||||
&mut new_parent_refs,
|
||||
alias,
|
||||
child_node_id.clone(),
|
||||
child_pkg,
|
||||
child_tree.depth,
|
||||
);
|
||||
}
|
||||
if !new_parent_refs.is_empty() {
|
||||
refs_changed = true;
|
||||
let refs = Arc::make_mut(&mut child_parent_refs);
|
||||
// Built only when a name collision actually consults it — the
|
||||
// common no-collision node never pays for the extra map clone.
|
||||
let mut refs_with_new: Option<ParentRefs> = None;
|
||||
for (name, mut new_parent_ref) in new_parent_refs.clone() {
|
||||
if let Some(existing) = refs.get(&name) {
|
||||
let with_new = refs_with_new.get_or_insert_with(|| {
|
||||
let mut with_new = refs.clone();
|
||||
with_new.extend(new_parent_refs.clone());
|
||||
with_new
|
||||
});
|
||||
if !self.parent_refs_match(existing, &new_parent_ref)
|
||||
|| self.inherited_parent_pkg_breaks_peer_diamond(
|
||||
with_new,
|
||||
existing,
|
||||
&new_parent_ref,
|
||||
node_id,
|
||||
)
|
||||
{
|
||||
new_parent_ref.occurrence = existing.occurrence + 1;
|
||||
refs.insert(name, new_parent_ref);
|
||||
}
|
||||
} else {
|
||||
refs.insert(name, new_parent_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let locked_pins =
|
||||
self.locked_peer_context_pins(node_id, pkg, &child_parent_refs, parent_node_ids);
|
||||
if !locked_pins.is_empty() {
|
||||
refs_changed = true;
|
||||
let refs = Arc::make_mut(&mut child_parent_refs);
|
||||
for (name, parent_ref) in locked_pins {
|
||||
refs.insert(name, parent_ref);
|
||||
}
|
||||
}
|
||||
|
||||
ChildParentRefs { refs: child_parent_refs, own: new_parent_refs, changed: refs_changed }
|
||||
}
|
||||
|
||||
/// Resolve this node's own peer requirements against the augmented
|
||||
/// [`ParentRefs`] visible at it, fold the result with what its
|
||||
/// children reported, and render the depPath. Empty resolved-peers
|
||||
/// ⇒ pure node: depPath = `pkgIdWithPatchHash`.
|
||||
fn resolve_node_peers(&mut self, context: NodePeersContext<'_>) -> NodePeers {
|
||||
let NodePeersContext {
|
||||
pkg,
|
||||
pkg_name,
|
||||
parent_refs,
|
||||
chain_names,
|
||||
ancestor_pkg_ids,
|
||||
external_from_children,
|
||||
missing_from_children,
|
||||
} = context;
|
||||
|
||||
let mut own_resolved: HashMap<String, NodeId> = HashMap::default();
|
||||
let mut own_missing: HashMap<String, MissingPeerInfo> = HashMap::default();
|
||||
for (peer_name, peer_dep) in &pkg.peer_dependencies {
|
||||
self.resolve_one_peer(
|
||||
pkg_name,
|
||||
peer_name,
|
||||
peer_dep,
|
||||
parent_refs,
|
||||
chain_names,
|
||||
ancestor_pkg_ids,
|
||||
&mut own_resolved,
|
||||
&mut own_missing,
|
||||
);
|
||||
}
|
||||
|
||||
// A package doesn't peer-depend on itself, so its own name never
|
||||
// enters its suffix.
|
||||
let mut all_resolved = external_from_children;
|
||||
for (peer_alias, peer_node_id) in &own_resolved {
|
||||
all_resolved.insert(peer_alias.clone(), peer_node_id.clone());
|
||||
}
|
||||
all_resolved.remove(pkg_name);
|
||||
|
||||
let mut all_missing = missing_from_children.clone();
|
||||
for (peer_alias, info) in &own_missing {
|
||||
all_missing.insert(peer_alias.clone(), info.clone());
|
||||
}
|
||||
|
||||
let dep_path = if all_resolved.is_empty() {
|
||||
DepPath::from(pkg.id.clone())
|
||||
} else {
|
||||
let peer_ids: Vec<PeerId> = all_resolved
|
||||
.iter()
|
||||
.map(|(peer_alias, peer_node_id)| self.build_peer_id(peer_alias, peer_node_id))
|
||||
.collect();
|
||||
let suffix = create_peer_dep_graph_hash(&peer_ids, self.opts.peers_suffix_max_length);
|
||||
DepPath::from(format!("{}{}", pkg.id, suffix))
|
||||
};
|
||||
|
||||
NodePeers { own_resolved, all_resolved, all_missing, dep_path }
|
||||
}
|
||||
|
||||
/// The upstream locked-peer-provider reuse block
|
||||
/// (`resolvePeers.ts:594`): for each `peer name → provider DepPath`
|
||||
/// the wanted lockfile recorded on this node, re-pin the provider
|
||||
|
||||
Reference in new issue
Block a user