def _resolve_dependencies_to_remove(self, packages: list[str]) -> list[str]: """Perform a BFS to find all unneeded dependencies""" result: set[str] = set() to_resolve = list(packages) ws = WorkingSet() graph = build_dependency_graph(ws) while to_resolve: temp: list[Package] = [] for name in to_resolve: key = normalize_name(name) if key in ws: result.add(key) package = Package(key, "0.0.0", {}) if package not in graph: continue for dep in graph.iter_children(package): temp.append(dep) graph.remove(package) to_resolve.clear() for dep in temp: if not any(graph.iter_parents(dep)) and dep.name != "pdm": to_resolve.append(dep.name) return sorted(result)
def build_dependency_graph(working_set: WorkingSet) -> DirectedGraph: """Build a dependency graph from locked result.""" graph: DirectedGraph[Package | None] = DirectedGraph() graph.add(None) # sentinel parent of top nodes. node_with_extras = set() def add_package(key: str, dist: Distribution) -> Package: name, extras = strip_extras(key) extras = extras or () reqs: dict[str, Requirement] = {} if dist: requirements = ( Requirement.from_pkg_requirement(r) for r in dist.requires(extras) # type: ignore ) for req in requirements: reqs[req.identify()] = req version = dist.version else: version = None node = Package(key, version, reqs) if node not in graph: if extras: node_with_extras.add(name) graph.add(node) for k in reqs: child = add_package( k, cast(Distribution, working_set.get(strip_extras(k)[0]))) graph.connect(node, child) return node for k, dist in working_set.items(): add_package(k, dist) for node in list(graph): if node is not None and not list(graph.iter_parents(node)): # Top requirements if node.name in node_with_extras: # Already included in package[extra], no need to keep the top level # non-extra package. graph.remove(node) else: graph.connect(None, node) return graph