|
18 | 18 | DependencyGraph, |
19 | 19 | DependencyNode, |
20 | 20 | ) |
| 21 | +from fromager.packagesettings import PatchMap |
21 | 22 | from fromager.requirements_file import RequirementType |
22 | 23 |
|
23 | 24 | logger = logging.getLogger(__name__) |
@@ -70,57 +71,143 @@ def to_constraints(wkctx: context.WorkContext, graph_file: str, output: pathlib. |
70 | 71 | "-o", |
71 | 72 | "--output", |
72 | 73 | type=clickext.ClickPath(), |
| 74 | + default=None, |
| 75 | +) |
| 76 | +@click.option( |
| 77 | + "--install-only", |
| 78 | + is_flag=True, |
| 79 | + help="Only show installation dependencies, excluding build dependencies", |
73 | 80 | ) |
74 | 81 | @click.argument( |
75 | 82 | "graph-file", |
76 | 83 | type=str, |
77 | 84 | ) |
78 | 85 | @click.pass_obj |
79 | | -def to_dot(wkctx: context.WorkContext, graph_file: str, output: pathlib.Path): |
| 86 | +def to_dot( |
| 87 | + wkctx: context.WorkContext, |
| 88 | + graph_file: str, |
| 89 | + output: pathlib.Path | None, |
| 90 | + install_only: bool, |
| 91 | +): |
80 | 92 | "Convert a graph file to a DOT file suitable to pass to graphviz." |
81 | 93 | graph = DependencyGraph.from_file(graph_file) |
82 | 94 | if output: |
83 | 95 | with open(output, "w") as f: |
84 | | - write_dot(graph, f) |
| 96 | + write_dot(wkctx, graph, f, install_only=install_only) |
85 | 97 | else: |
86 | | - write_dot(graph, sys.stdout) |
| 98 | + write_dot(wkctx, graph, sys.stdout, install_only=install_only) |
87 | 99 |
|
88 | 100 |
|
89 | | -def write_dot(graph: DependencyGraph, output: typing.TextIO) -> None: |
| 101 | +def write_dot( |
| 102 | + wkctx: context.WorkContext, |
| 103 | + graph: DependencyGraph, |
| 104 | + output: typing.TextIO, |
| 105 | + install_only: bool = False, |
| 106 | +) -> None: |
90 | 107 | install_constraints = set(node.key for node in graph.get_install_dependencies()) |
| 108 | + overridden_packages: set[str] = set(wkctx.settings.list_overrides()) |
91 | 109 |
|
92 | 110 | output.write("digraph {\n") |
93 | 111 | output.write("\n") |
94 | 112 |
|
95 | | - seen_nodes = {} |
| 113 | + seen_nodes: dict[str, str] = {} |
96 | 114 | id_generator = itertools.count(1) |
97 | 115 |
|
98 | | - def get_node_id(node): |
| 116 | + def get_node_id(node: str) -> str: |
99 | 117 | if node not in seen_nodes: |
100 | 118 | seen_nodes[node] = f"node{next(id_generator)}" |
101 | 119 | return seen_nodes[node] |
102 | 120 |
|
103 | | - for node in graph.get_all_nodes(): |
| 121 | + _node_shape_properties = { |
| 122 | + "build_settings": "shape=box", |
| 123 | + "build": "shape=oval", |
| 124 | + "default": "shape=oval", |
| 125 | + "patches": "shape=note", |
| 126 | + "plugin_and_patches": "shape=tripleoctagon", |
| 127 | + "plugin": "shape=trapezium", |
| 128 | + "pre_built": "shape=parallelogram", |
| 129 | + "toplevel": "shape=circle", |
| 130 | + } |
| 131 | + |
| 132 | + # Determine which nodes to include |
| 133 | + if install_only: |
| 134 | + nodes_to_include = [graph.nodes[ROOT]] |
| 135 | + nodes_to_include.extend(graph.get_install_dependencies()) |
| 136 | + else: |
| 137 | + nodes_to_include = list(graph.get_all_nodes()) |
| 138 | + |
| 139 | + for node in sorted(nodes_to_include, key=lambda x: x.key): |
104 | 140 | node_id = get_node_id(node.key) |
105 | | - properties = f'label="{node.key}"' |
| 141 | + |
106 | 142 | if not node: |
107 | | - properties = 'label="*"' |
108 | | - if node.key in install_constraints: |
109 | | - properties += " style=filled fillcolor=red color=red fontcolor=white" |
| 143 | + label = "*" |
110 | 144 | else: |
111 | | - properties += " style=filled fillcolor=lightgrey color=lightgrey" |
| 145 | + label = node.key |
| 146 | + |
| 147 | + node_type: list[str] = [] |
| 148 | + name = node.canonicalized_name |
| 149 | + if not name: |
| 150 | + node_type.append("toplevel") |
| 151 | + else: |
| 152 | + pbi = wkctx.settings.package_build_info(name) |
| 153 | + all_patches: PatchMap = pbi.get_all_patches() |
| 154 | + |
| 155 | + if node.pre_built: |
| 156 | + node_type.append("pre_built") |
| 157 | + elif pbi.plugin and all_patches: |
| 158 | + node_type.append("plugin_and_patches") |
| 159 | + elif pbi.plugin: |
| 160 | + node_type.append("plugin") |
| 161 | + elif all_patches: |
| 162 | + node_type.append("patches") |
| 163 | + elif name in overridden_packages: |
| 164 | + node_type.append("build_settings") |
| 165 | + else: |
| 166 | + node_type.append("default") |
| 167 | + |
| 168 | + style = "filled" |
| 169 | + if not install_only: |
| 170 | + if node.key in install_constraints or node.key == ROOT: |
| 171 | + style += ",bold" |
| 172 | + else: |
| 173 | + style += ",dashed" |
| 174 | + |
| 175 | + properties = f'label="{label}" style="{style}" color=black fillcolor=white fontcolor=black ' |
| 176 | + properties += " ".join(_node_shape_properties[t] for t in node_type) |
| 177 | + |
112 | 178 | output.write(f" {node_id} [{properties}]\n") |
113 | 179 |
|
114 | 180 | output.write("\n") |
115 | 181 |
|
116 | | - for node in graph.get_all_nodes(): |
| 182 | + # Create a set of included node keys for efficient lookup |
| 183 | + included_node_keys = {node.key for node in nodes_to_include} |
| 184 | + |
| 185 | + known_edges: set[tuple[str, str]] = set() |
| 186 | + for node in nodes_to_include: |
117 | 187 | node_id = get_node_id(node.key) |
118 | 188 | for edge in node.children: |
| 189 | + # Skip edges if we're in install-only mode and the edge is a build dependency |
| 190 | + if install_only and edge.req_type not in [ |
| 191 | + RequirementType.INSTALL, |
| 192 | + RequirementType.TOP_LEVEL, |
| 193 | + ]: |
| 194 | + continue |
| 195 | + |
| 196 | + # Skip duplicate edges |
| 197 | + if (node.key, edge.destination_node.key) in known_edges: |
| 198 | + continue |
| 199 | + known_edges.add((node.key, edge.destination_node.key)) |
| 200 | + |
| 201 | + # Skip edges to nodes that aren't included |
| 202 | + if edge.destination_node.key not in included_node_keys: |
| 203 | + continue |
| 204 | + |
119 | 205 | child_id = get_node_id(edge.destination_node.key) |
120 | 206 | sreq = str(edge.req).replace('"', "'") |
121 | 207 | properties = f'labeltooltip="{sreq}"' |
122 | | - if edge.req_type != "install": |
| 208 | + if edge.req_type != RequirementType.INSTALL: |
123 | 209 | properties += " style=dotted" |
| 210 | + |
124 | 211 | output.write(f" {node_id} -> {child_id} [{properties}]\n") |
125 | 212 | output.write("}\n") |
126 | 213 |
|
|
0 commit comments