@@ -106,6 +106,91 @@ def to_dot(
106106 )
107107
108108
109+ def _get_nodes_for_reduction (
110+ graph : DependencyGraph ,
111+ install_only : bool ,
112+ ) -> list [DependencyNode ]:
113+ """Determine starting node set based on install_only flag."""
114+ if install_only :
115+ nodes : list [DependencyNode ] = [graph .nodes [ROOT ]]
116+ nodes .extend (graph .get_install_dependencies ())
117+ return nodes
118+ return list (graph .get_all_nodes ())
119+
120+
121+ def _find_customized_nodes (
122+ wkctx : context .WorkContext ,
123+ nodes : list [DependencyNode ],
124+ ) -> list [DependencyNode ]:
125+ """Filter nodes to find only those with customizations."""
126+ customized_nodes : list [DependencyNode ] = []
127+ for node in nodes :
128+ pbi = wkctx .settings .package_build_info (node .canonicalized_name )
129+ if node .canonicalized_name != ROOT and pbi .has_customizations :
130+ customized_nodes .append (node )
131+ return customized_nodes
132+
133+
134+ def _find_customized_dependencies_for_node (
135+ wkctx : context .WorkContext ,
136+ node : DependencyNode ,
137+ install_only : bool ,
138+ ) -> dict [str , str ]:
139+ """
140+ Find all reachable customized nodes from a given node using depth-first search.
141+
142+ Returns:
143+ Dictionary mapping child keys to their requirement strings.
144+ Format: {child_key: requirement_string}
145+ """
146+ dependencies : dict [str , str ] = {}
147+ visited : set [str ] = set ()
148+ # Stack contains: (current_node, path_from_start, original_requirement)
149+ stack : list [tuple [DependencyNode , list [str ], str | None ]] = [(node , [], None )]
150+
151+ while stack :
152+ current_node , path , original_req = stack .pop ()
153+
154+ if current_node .key in visited :
155+ continue
156+ visited .add (current_node .key )
157+
158+ for edge in current_node .children :
159+ # Skip build dependencies if install_only is True
160+ if install_only and edge .req_type .is_build_requirement :
161+ continue
162+
163+ child = edge .destination_node
164+ child_pbi = wkctx .settings .package_build_info (child .canonicalized_name )
165+ new_path = path + [current_node .key ]
166+
167+ # Use the first requirement we encounter in the path
168+ current_req = original_req if original_req else str (edge .req )
169+
170+ # If the child has customizations, add it as a direct dependency
171+ if child_pbi .has_customizations :
172+ dependencies [child .key ] = current_req
173+ else :
174+ # If the child doesn't have customizations, continue traversing
175+ stack .append ((child , new_path , current_req ))
176+
177+ return dependencies
178+
179+
180+ def _build_reduced_dependency_map (
181+ wkctx : context .WorkContext ,
182+ customized_nodes : list [DependencyNode ],
183+ install_only : bool ,
184+ ) -> dict [str , dict [str , str ]]:
185+ """Build dependency map for all customized nodes."""
186+ reduced_dependencies : dict [str , dict [str , str ]] = {}
187+ for node in customized_nodes :
188+ reduced_dependencies [node .key ] = _find_customized_dependencies_for_node (
189+ wkctx , node , install_only
190+ )
191+ return reduced_dependencies
192+
193+
109194def reduce_graph (
110195 wkctx : context .WorkContext ,
111196 graph : DependencyGraph ,
@@ -120,57 +205,16 @@ def reduce_graph(
120205 - Dictionary mapping each included node to its direct dependencies with requirement info
121206 Format: {parent_key: {child_key: requirement_string}}
122207 """
123- # Start with all nodes or just install dependencies
124- if install_only :
125- all_nodes : list [DependencyNode ] = [graph .nodes [ROOT ]]
126- all_nodes .extend (graph .get_install_dependencies ())
127- else :
128- all_nodes = list (graph .get_all_nodes ())
208+ # Get starting node set based on install_only flag
209+ all_nodes = _get_nodes_for_reduction (graph , install_only )
129210
130211 # Find nodes with customizations
131- customized_nodes : list [DependencyNode ] = []
132- for node in all_nodes :
133- pbi = wkctx .settings .package_build_info (node .canonicalized_name )
134- if node .canonicalized_name != ROOT and pbi .has_customizations :
135- customized_nodes .append (node )
212+ customized_nodes = _find_customized_nodes (wkctx , all_nodes )
136213
137214 # Build reduced dependency relationships with requirement tracking
138- reduced_dependencies : dict [str , dict [str , str ]] = {}
139-
140- for node in customized_nodes :
141- reduced_dependencies [node .key ] = {}
142-
143- # Find all reachable customized nodes from this node
144- visited : set [str ] = set ()
145- # Stack now includes: (current_node, path_from_start, original_requirement)
146- stack : list [tuple [DependencyNode , list [str ], str | None ]] = [(node , [], None )]
147-
148- while stack :
149- current_node , path , original_req = stack .pop ()
150-
151- if current_node .key in visited :
152- continue
153- visited .add (current_node .key )
154-
155- for edge in current_node .children :
156- # Skip build dependencies if install_only is True
157- if install_only and edge .req_type .is_build_requirement :
158- continue
159-
160- child = edge .destination_node
161- child_pbi = wkctx .settings .package_build_info (child .canonicalized_name )
162- new_path = path + [current_node .key ]
163-
164- # Use the first requirement we encounter in the path
165- current_req = original_req if original_req else str (edge .req )
166-
167- # If the child has customizations, add it as a direct dependency
168- if child_pbi .has_customizations :
169- # Store the requirement string for this dependency
170- reduced_dependencies [node .key ][child .key ] = current_req
171- else :
172- # If the child doesn't have customizations, continue traversing
173- stack .append ((child , new_path , current_req ))
215+ reduced_dependencies = _build_reduced_dependency_map (
216+ wkctx , customized_nodes , install_only
217+ )
174218
175219 return customized_nodes , reduced_dependencies
176220
0 commit comments