(leaf_nodes: Set[str])
| 293 | Returns: |
| 294 | A list of leaf nodes |
| 295 | """ |
| 296 | # First, resolve cycles to ensure we have a DAG |
| 297 | acyclic_graph = resolve_cycles(graph) |
| 298 | |
| 299 | # Find leaf nodes (nodes that no other nodes depend on) |
| 300 | leaf_nodes = set(acyclic_graph.keys()) |
| 301 | |
| 302 | valid_types = compute_valid_leaf_types(components) |
| 303 | |
| 304 | def concise_node(leaf_nodes: set[str]) -> set[str]: |
| 305 | concise_leaf_nodes = set() |
| 306 | for node in leaf_nodes: |
| 307 | if node.endswith("__init__"): |
| 308 | # replace by class name |
| 309 | concise_leaf_nodes.add(node.replace(".__init__", "")) |
| 310 | else: |
| 311 | concise_leaf_nodes.add(node) |
| 312 | |
| 313 | return filter_leaf_nodes(concise_leaf_nodes, components, valid_types) |
| 314 | |
| 315 | concise_leaf_nodes = concise_node(leaf_nodes) |
| 316 | if len(concise_leaf_nodes) >= LEAF_REDUCTION_THRESHOLD: |
| 317 | count_before = len(concise_leaf_nodes) |
| 318 | logger.info( |
| 319 | "Leaf nodes are too many (%d >= %d); reducing to components " |
| 320 | "that nothing else depends on.", |
| 321 | count_before, |
| 322 | LEAF_REDUCTION_THRESHOLD, |
| 323 | ) |
| 324 | # Remove nodes that are dependencies of other nodes. Edges that start |
| 325 | # at an artifact node (a Dockerfile COPY, a CI `run:` line, a manifest |
| 326 | # entry point) are references, not calls: they must not demote the |
| 327 | # code component they point at. |
| 328 | for node, deps in acyclic_graph.items(): |
| 329 | owner = components.get(node) |
| 330 | if owner is not None and owner.component_type == "artifact": |
| 331 | continue |
| 332 | for dep in deps: |
| 333 | leaf_nodes.discard(dep) |
| 334 | |
| 335 | concise_leaf_nodes = concise_node(leaf_nodes) |
| 336 | logger.info("Reduced from %d to %d leaf nodes.", count_before, len(concise_leaf_nodes)) |
| 337 |
no test coverage detected