@@ -787,7 +787,11 @@ def _build_nodes_names_in_graph(graph_list: "list[Container]", nodes_dict: "dict
787787 names = {}
788788 for edge in graph_list :
789789 for ename in [edge ["source" ], edge ["sink" ]]:
790- node = nodes_dict [ename ]
790+ node = nodes_dict .get (ename )
791+ if node is None :
792+ # Some feature topologies reference graph endpoints not present in
793+ # DAPM widgets. Keep these endpoints as-is instead of failing.
794+ continue
791795 for name in [node ["widget" ]["name" ], node ["widget" ]["sname" ]]:
792796 if name != ename :
793797 names [name ] = ename
@@ -811,6 +815,17 @@ def _build_edges(graph_list: "list[Container]") -> "tuple[dict[str, list[str]],
811815 backward_edge [edge ["sink" ]].append (edge ["source" ])
812816 return forward_edge , backward_edge
813817
818+ @staticmethod
819+ def _build_unknown_nodes (graph_list : "list[Container]" , nodes_dict : "dict[str, Container]" ) -> "set[str]" :
820+ "Collect graph nodes that do not map to any widget name/sname."
821+ unknown = set ()
822+ for edge in graph_list :
823+ if edge ["source" ] not in nodes_dict :
824+ unknown .add (edge ["source" ])
825+ if edge ["sink" ] not in nodes_dict :
826+ unknown .add (edge ["sink" ])
827+ return unknown
828+
814829 @staticmethod
815830 def _build_leaves (node_names : "list[str]" , forward_edges : "dict[str, list[str]]" , backward_edges : "dict[str, list[str]]" ) -> "tuple[set[str], set[str], set[str]]" :
816831 r"""Build leaves.
@@ -845,6 +860,10 @@ def __init__(self, grouped_tplg: GroupedTplg):
845860 self ._nodes_dict = TplgGraph ._build_nodes_dict (grouped_tplg .widget_list )
846861 self ._nodes_names_in_graph = TplgGraph ._build_nodes_names_in_graph (grouped_tplg .graph_list , self ._nodes_dict )
847862 self ._forward_edges , self ._backward_edges = TplgGraph ._build_edges (grouped_tplg .graph_list )
863+ self ._unknown_nodes = TplgGraph ._build_unknown_nodes (grouped_tplg .graph_list , self ._nodes_dict )
864+ if self ._unknown_nodes :
865+ unknown_nodes = ", " .join (sorted (self ._unknown_nodes ))
866+ print (f"WARNING: graph-only node(s) without matching widget: { unknown_nodes } " , file = sys .stderr )
848867 self ._isolated , self ._heads , self ._tails = TplgGraph ._build_leaves (map (self .node_name_in_graph , grouped_tplg .widget_list ), self ._forward_edges , self ._backward_edges )
849868
850869 def _node_name_in_graph_from_name (self , name : str ) -> str :
@@ -949,12 +968,31 @@ def _display_node_attrs(self, name: str, widget: Container):
949968
950969 def _display_edge_attr (self , edge : Container ):
951970 attr = {}
952- if GroupedTplg .is_virtual_widget (self ._nodes_dict [edge ["source" ]])\
953- or GroupedTplg .is_virtual_widget (self ._nodes_dict [edge ["sink" ]]):
971+ source_widget = self ._nodes_dict .get (edge ["source" ])
972+ sink_widget = self ._nodes_dict .get (edge ["sink" ])
973+ if source_widget is None or sink_widget is None :
974+ return attr
975+ if GroupedTplg .is_virtual_widget (source_widget ) \
976+ or GroupedTplg .is_virtual_widget (sink_widget ):
954977 attr ['style' ] = "dotted"
955978 attr ['color' ] = 'blue'
956979 return attr
957980
981+ def _display_unknown_node_attrs (self , name : str ):
982+ "Style graph-only nodes so missing widgets are visible in rendered graph."
983+ attr = {
984+ 'shape' : 'box' ,
985+ 'style' : 'dashed' ,
986+ 'color' : 'orangered3' ,
987+ 'fontcolor' : 'orangered3' ,
988+ }
989+ if self ._forward_edges [name ] and self ._backward_edges [name ]:
990+ kind = 'graph-only junction'
991+ else :
992+ kind = 'graph-only endpoint'
993+ attr ['label' ] = f'<{ name } <BR ALIGN="CENTER"/><SUB>{ kind } </SUB>>'
994+ return attr
995+
958996 def draw (self , outfile : str , outdir : str = '.' , file_format : str = "png" , live_view : bool = False ):
959997 r"""Draw graph and write it to file.
960998
@@ -978,6 +1016,8 @@ def draw(self, outfile: str, outdir: str = '.', file_format: str = "png", live_v
9781016 from tempfile import gettempdir
9791017
9801018 graph = Digraph ("Topology Graph" , format = file_format )
1019+ for name in self ._unknown_nodes :
1020+ graph .node (name , ** self ._display_unknown_node_attrs (name ))
9811021 for node in self ._tplg .widget_list :
9821022 name = self .node_name_in_graph (node )
9831023 if name not in self ._isolated : # skip isolated nodes.
@@ -1088,7 +1128,7 @@ def _find_connected_comp(self, node_name: str, name_predicate) -> "list[Containe
10881128 acc = set ()
10891129 self ._find_connected_node_recursively (self ._forward_edges , node_name , name_predicate , acc )
10901130 self ._find_connected_node_recursively (self ._backward_edges , node_name , name_predicate , acc )
1091- return [self ._nodes_dict [name ] for name in acc ]
1131+ return [self ._nodes_dict [name ] for name in acc if name in self . _nodes_dict ]
10921132
10931133 def find_connected_comp (self , ref_node : Container , predicate ) -> "list[Container]" :
10941134 r"""Find specified components connected to `ref_node`.
@@ -1127,7 +1167,7 @@ def find_connected_comp(self, ref_node: Container, predicate) -> "list[Container
11271167 """
11281168 return self ._find_connected_comp (
11291169 self .node_name_in_graph (ref_node ),
1130- lambda name : predicate (self ._nodes_dict [name ]))
1170+ lambda name : name in self . _nodes_dict and predicate (self ._nodes_dict [name ]))
11311171
11321172 def find_comp_for_pcm (self , pcm : Container , prefix : str ) -> "list[list[Container]]" :
11331173 r"""Find specified components for PCM.
0 commit comments