diff options
16 files changed, 824 insertions, 226 deletions
diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/flask/flask_backend.py b/buildscripts/libdeps/graph_visualizer_web_stack/flask/flask_backend.py index f9f1b2b25aa..85d7e993d1f 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/flask/flask_backend.py +++ b/buildscripts/libdeps/graph_visualizer_web_stack/flask/flask_backend.py @@ -32,6 +32,7 @@ from collections import namedtuple, OrderedDict import flask import networkx +import cxxfilt from flask_cors import CORS from flask_session import Session @@ -157,16 +158,12 @@ class BackendServer: 'name': key, 'value': value } for key, value in dependents_graph.nodes(data=True)[str(node)].items()], 'dependers': [{ - 'node': - depender, 'symbols': - dependents_graph[str(node)][depender].get('symbols', - '').split(' ') + 'node': depender, 'symbols': dependents_graph[str(node)] + [depender].get('symbols') } for depender in dependents_graph[str(node)]], 'dependencies': [{ - 'node': - dependency, 'symbols': - dependents_graph[dependency][str(node)].get('symbols', - '').split(' ') + 'node': dependency, 'symbols': dependents_graph[dependency] + [str(node)].get('symbols') } for dependency in dependency_graph[str(node)]], }) @@ -189,16 +186,17 @@ class BackendServer: nodes = {} links = {} + links_trans = {} def add_node_to_graph_data(node): nodes[str(node)] = { - 'id': str(node), 'name': Path(node).name, 'type': dependents_graph.nodes() - [str(node)]['bin_type'] + 'id': str(node), 'name': Path(node).name, + 'type': dependents_graph.nodes()[str(node)].get('bin_type', '') } - def add_link_to_graph_data(source, target): + def add_link_to_graph_data(source, target, data): links[str(source) + str(target)] = { - 'source': str(source), 'target': str(target) + 'source': str(source), 'target': str(target), 'data': data } for node in selected_nodes: @@ -207,7 +205,15 @@ class BackendServer: for libdep in dependency_graph[str(node)]: if dependents_graph[libdep][str(node)].get('direct'): add_node_to_graph_data(libdep) - add_link_to_graph_data(node, libdep) + add_link_to_graph_data(node, libdep, + dependents_graph[libdep][str(node)]) + + if "transitive_edges" in req_body.keys() and req_body["transitive_edges"] is True: + for node in selected_nodes: + for libdep in dependency_graph[str(node)]: + if str(libdep) in nodes.keys(): + add_link_to_graph_data(node, libdep, + dependents_graph[libdep][str(node)]) if "extra_nodes" in req_body.keys(): extra_nodes = req_body["extra_nodes"] @@ -216,12 +222,14 @@ class BackendServer: for libdep in dependency_graph.get_direct_nonprivate_graph()[str(node)]: add_node_to_graph_data(libdep) - add_link_to_graph_data(node, libdep) + add_link_to_graph_data(node, libdep, + dependents_graph[libdep][str(node)]) node_data = { 'graphData': { 'nodes': [data for node, data in nodes.items()], 'links': [data for link, data in links.items()], + 'links_trans': [data for link, data in links_trans.items()], } } return node_data, 200 @@ -306,7 +314,27 @@ class BackendServer: else: if git_hash in self.graph_files: file_path = self.graph_files[git_hash].graph_file - graph = libdeps.graph.LibdepsGraph(networkx.read_graphml(file_path)) + nx_graph = networkx.read_graphml(file_path) + if int(self.get_graph_build_data(file_path).version) > 3: + for source, target in nx_graph.edges: + try: + nx_graph[source][target]['symbols'] = list( + nx_graph[source][target].get('symbols').split('\n')) + except AttributeError: + nx_graph[source][target]['symbols'] = [] + else: + for source, target in nx_graph.edges: + try: + nx_graph[source][target]['symbols'] = list( + map(cxxfilt.demangle, + nx_graph[source][target].get('symbols').split())) + except AttributeError: + try: + nx_graph[source][target]['symbols'] = list( + nx_graph[source][target].get('symbols').split()) + except AttributeError: + nx_graph[source][target]['symbols'] = [] + graph = libdeps.graph.LibdepsGraph(nx_graph) self.loaded_graphs[git_hash] = graph return graph return None diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/package.json b/buildscripts/libdeps/graph_visualizer_web_stack/package.json index f1da6698942..ace34ed2d8d 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/package.json +++ b/buildscripts/libdeps/graph_visualizer_web_stack/package.json @@ -38,7 +38,7 @@ "react-redux": "^7.2.2", "react-resize-aware": "^3.1.0", "react-resize-detector": "^6.6.5", - "react-scripts": "latest", + "react-scripts": "^4.0.3", "react-split-pane": "^0.1.92", "react-virtualized": "^9.22.2", "react-window": "^1.8.6", diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/AlgorithmExpander.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/AlgorithmExpander.js index c75a6513d7d..7473e6be830 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/AlgorithmExpander.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/AlgorithmExpander.js @@ -65,7 +65,7 @@ const AccordionDetails = withStyles((theme) => ({ }, }))(MuiAccordionDetails); -const AlgorithmExpander = ({ loading, width }) => { +const AlgorithmExpander = ({ loading, width, transPathFrom, transPathTo }) => { const classes = useStyles(); return ( @@ -93,7 +93,7 @@ const AlgorithmExpander = ({ loading, width }) => { <Typography className={classes.heading}>Graph Paths</Typography> </AccordionSummary> <AccordionDetails> - <GraphPaths datawidth={width} /> + <GraphPaths datawidth={width} transPathFrom={transPathFrom} transPathTo={transPathTo}/> </AccordionDetails> </Accordion> </Paper> diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/DataGrid.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/DataGrid.js index a1d43ca7de2..00220d4078a 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/DataGrid.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/DataGrid.js @@ -12,6 +12,8 @@ import { getRows } from "./redux/store"; import { updateSelected } from "./redux/nodes"; import { setGraphData } from "./redux/graphData"; import { setNodeInfos } from "./redux/nodeInfo"; +import { setLinks } from "./redux/links"; +import { setLinksTrans } from "./redux/linksTrans"; function componentToHex(c) { var hex = c.toString(16); @@ -90,10 +92,13 @@ const DataGrid = ({ updateSelected, classes, setGraphData, + setLinks, + setLinksTrans, selectedGraph, setNodeInfos, selectedNodes, searchedNodes, + showTransitive }) => { const [checkBoxes, setCheckBoxes] = React.useState([]); @@ -103,31 +108,36 @@ const DataGrid = ({ function newGraphData() { let gitHash = selectedGraph; - let postData = { - "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node) - }; - fetch('/api/graphs/' + gitHash + '/d3', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - setGraphData(data.graphData); - }); - fetch('/api/graphs/' + gitHash + '/nodes/details', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - setNodeInfos(data.nodeInfos); - }); + if (gitHash) { + let postData = { + "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node), + "transitive_edges": showTransitive + }; + fetch('/api/graphs/' + gitHash + '/d3', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + setGraphData(data.graphData); + setLinks(data.graphData.links); + setLinksTrans(data.graphData.links_trans); + }); + fetch('/api/graphs/' + gitHash + '/nodes/details', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + setNodeInfos(data.nodeInfos); + }); + } } const getRowClassName = ({ index }) => { @@ -249,6 +259,6 @@ const DataGrid = ({ ); }; -export default connect(getRows, { updateSelected, setGraphData, setNodeInfos })( +export default connect(getRows, { updateSelected, setGraphData, setNodeInfos, setLinks, setLinksTrans })( withStyles(styles)(DataGrid) ); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/DrawGraph.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/DrawGraph.js index 2e1b429928a..b0724498b71 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/DrawGraph.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/DrawGraph.js @@ -6,6 +6,8 @@ import ForceGraph3D from "react-force-graph-3d"; import SwitchComponents from "./SwitchComponent"; import Button from "@material-ui/core/Button"; import TextField from "@material-ui/core/TextField"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Checkbox from "@material-ui/core/Checkbox"; import theme from "./theme"; import { getGraphData } from "./redux/store"; @@ -13,6 +15,9 @@ import { updateCheckbox } from "./redux/nodes"; import { setFindNode } from "./redux/findNode"; import { setGraphData } from "./redux/graphData"; import { setNodeInfos } from "./redux/nodeInfo"; +import { setLinks } from "./redux/links"; +import { setLinksTrans } from "./redux/linksTrans"; +import { setShowTransitive } from "./redux/showTransitive"; import LoadingBar from "./LoadingBar"; const handleFindNode = (node_value, graphData, activeComponent, forceRef) => { @@ -55,6 +60,7 @@ const DrawGraph = ({ size, graphData, nodes, + links, loading, graphPaths, updateCheckbox, @@ -62,18 +68,29 @@ const DrawGraph = ({ setFindNode, setGraphData, setNodeInfos, - selectedGraph + selectedGraph, + setLinks, + setLinksTrans, + setShowTransitive, + showTransitive }) => { const [activeComponent, setActiveComponent] = React.useState("2D"); const [pathNodes, setPathNodes] = React.useState({}); const [pathEdges, setPathEdges] = React.useState([]); const forceRef = useRef(null); + const PARTICLE_SIZE = 5; + React.useEffect(() => { handleFindNode(findNode, graphData, activeComponent, forceRef); setFindNode(""); }, [findNode, graphData, activeComponent, forceRef]); + React.useEffect(() => { + newGraphData(); + }, [showTransitive]); + + const selectedEdge = links.filter(link => link.selected == true)[0]; const selectedNodes = nodes.filter(node => node.selected == true).map(node => node.node); React.useEffect(() => { @@ -109,31 +126,36 @@ const DrawGraph = ({ function newGraphData() { let gitHash = selectedGraph; - let postData = { - "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node) - }; - fetch('/api/graphs/' + gitHash + '/d3', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - setGraphData(data.graphData); - }); - fetch('/api/graphs/' + gitHash + '/nodes/details', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - setNodeInfos(data.nodeInfos); - }); + if (gitHash) { + let postData = { + "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node), + "transitive_edges": showTransitive + }; + fetch('/api/graphs/' + gitHash + '/d3', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + setGraphData(data.graphData); + setLinks(data.graphData.links); + setLinksTrans(data.graphData.links_trans); + }); + fetch('/api/graphs/' + gitHash + '/nodes/details', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + setNodeInfos(data.nodeInfos); + }); + } } const paintRing = React.useCallback( @@ -166,6 +188,20 @@ const DrawGraph = ({ } } + function isSameEdge(edgeA, edgeB) { + if (edgeA.source.id && edgeA.target.id) { + if (edgeB.source.id && edgeB.target.id) { + return (edgeA.source.id == edgeB.source.id && + edgeA.target.id == edgeB.target.id); + } + } + if (edgeA.source == edgeB.source && + edgeA.target == edgeB.target) { + return true; + } + return false; + } + return ( <LoadingBar loading={loading} height={"100%"}> <Button @@ -191,6 +227,15 @@ const DrawGraph = ({ ); }} /> + <FormControlLabel + style={{ marginInline: 5 }} + control={<Checkbox + style={{ marginInline: 10 }} + checked={ showTransitive } + onClick={ () => setShowTransitive(!showTransitive) } + />} + label="Show Viewable Transitive Edges" + /> <SwitchComponents active={activeComponent}> <ForceGraph2D name="3D" @@ -215,10 +260,15 @@ const DrawGraph = ({ pathEdges[graphPaths.selectedPath][i].source == d.source.id && pathEdges[graphPaths.selectedPath][i].target == d.target.id ) { - return 5; + return PARTICLE_SIZE; } } } + if (selectedEdge) { + if (isSameEdge(selectedEdge, d)) { + return PARTICLE_SIZE; + } + } return 0; }} linkDirectionalParticleSpeed={(d) => { @@ -229,7 +279,18 @@ const DrawGraph = ({ return "before"; } }} + linkLineDash={(d) => { + if (d.data.direct) { + return []; + } + return [5, 3]; + }} linkColor={(d) => { + if (selectedEdge) { + if (isSameEdge(selectedEdge, d)) { + return "#ED7811"; + } + } if (graphPaths.selectedPath >= 0) { for ( var i = 0; @@ -248,6 +309,11 @@ const DrawGraph = ({ }} linkDirectionalParticleWidth={6} linkWidth={(d) => { + if (selectedEdge) { + if (isSameEdge(selectedEdge, d)) { + return 2; + } + } if (graphPaths.selectedPath >= 0) { for ( var i = 0; @@ -264,6 +330,29 @@ const DrawGraph = ({ } return 1; }} + onLinkClick={(link, event) => { + if (selectedEdge) { + if (isSameEdge(selectedEdge, link)) { + setLinks( + links.map((temp_link) => { + temp_link.selected = false; + return temp_link; + }) + ); + return; + } + } + setLinks( + links.map((temp_link, index) => { + if (index == link.index) { + temp_link.selected = true; + } else { + temp_link.selected = false; + } + return temp_link; + }) + ); + }} nodeRelSize={7} nodeCanvasObject={paintRing} onNodeClick={(node, event) => { @@ -309,7 +398,15 @@ const DrawGraph = ({ } } } - return "#FAFAFA"; + if (selectedEdge) { + if (isSameEdge(selectedEdge, d)) { + return "#ED7811"; + } + } + if (d.data.direct == false) { + return "#303030"; + } + return "#FFFFFF"; }} linkDirectionalParticleWidth={7} linkWidth={(d) => { @@ -323,10 +420,15 @@ const DrawGraph = ({ pathEdges[graphPaths.selectedPath][i].source == d.source.id && pathEdges[graphPaths.selectedPath][i].target == d.target.id ) { - return 5; + return 3; } } } + if (selectedEdge) { + if (isSameEdge(selectedEdge, d)) { + return 3; + } + } return 1; }} linkDirectionalParticles={(d) => { @@ -340,10 +442,15 @@ const DrawGraph = ({ pathEdges[graphPaths.selectedPath][i].source == d.source.id && pathEdges[graphPaths.selectedPath][i].target == d.target.id ) { - return 5; + return PARTICLE_SIZE; } } } + if (selectedEdge) { + if (isSameEdge(selectedEdge, d)) { + return PARTICLE_SIZE; + } + } return 0; }} linkDirectionalParticleSpeed={(d) => { @@ -351,6 +458,29 @@ const DrawGraph = ({ }} linkDirectionalParticleResolution={10} linkOpacity={0.6} + onLinkClick={(link, event) => { + if (selectedEdge) { + if (isSameEdge(selectedEdge, link)) { + setLinks( + links.map((temp_link) => { + temp_link.selected = false; + return temp_link; + }) + ); + return; + } + } + setLinks( + links.map((temp_link, index) => { + if (index == link.index) { + temp_link.selected = true; + } else { + temp_link.selected = false; + } + return temp_link; + }) + ); + }} nodeRelSize={7} backgroundColor={theme.palette.secondary.dark} linkDirectionalArrowLength={3.5} @@ -362,6 +492,6 @@ const DrawGraph = ({ ); }; -export default connect(getGraphData, { setFindNode, updateCheckbox, setGraphData, setNodeInfos })( +export default connect(getGraphData, { setFindNode, updateCheckbox, setGraphData, setNodeInfos, setLinks, setLinksTrans, setShowTransitive })( DrawGraph ); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/EdgeList.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/EdgeList.js new file mode 100644 index 00000000000..28226bc6206 --- /dev/null +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/EdgeList.js @@ -0,0 +1,261 @@ +import React, { useState } from "react"; +import clsx from "clsx"; +import { connect } from "react-redux"; +import { getEdges } from "./redux/store"; +import { setFindNode } from "./redux/findNode"; +import { setLinks } from "./redux/links"; +import { setGraphData } from "./redux/graphData"; +import { setSelectedPath } from "./redux/graphPaths"; +import { AutoSizer, Column, Table } from "react-virtualized"; +import TableCell from "@material-ui/core/TableCell"; +import Typography from "@material-ui/core/Typography"; +import Tooltip from '@material-ui/core/Tooltip'; +import GraphPaths from "./GraphPaths"; + +import { makeStyles, withStyles } from "@material-ui/core/styles"; + +import LoadingBar from "./LoadingBar"; +import TextField from "@material-ui/core/TextField"; +import { List, ListItemText, Paper, Button } from "@material-ui/core"; + +const columns = [ + { dataKey: "type", label: "Type", width: 30 }, + { dataKey: "source", label: "From", width: 180 }, + { dataKey: "to", label: "➔", width: 40 }, + { dataKey: "target", label: "To", width: 180 }, +]; + +const visibilityTypes = ['Global', 'Public', 'Private', 'Interface']; + +function componentToHex(c) { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; + } + + function rgbToHex(r, g, b) { + return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); + } + + function hexToRgb(hex) { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } + : null; + } + + function incrementPallete(palleteColor, increment) { + var rgb = hexToRgb(palleteColor); + rgb.r += increment; + rgb.g += increment; + rgb.b += increment; + return rgbToHex(rgb.r, rgb.g, rgb.b); + } + + const styles = (theme) => ({ + flexContainer: { + display: "flex", + alignItems: "center", + }, + table: { + // temporary right-to-left patch, waiting for + // https://github.com/bvaughn/react-virtualized/issues/454 + "& .ReactVirtualized__Table__headerRow": { + flip: false, + paddingRight: theme.direction === "rtl" ? "0 !important" : undefined, + }, + }, + tableRowOdd: { + backgroundColor: incrementPallete(theme.palette.grey[800], 10), + }, + tableRowEven: { + backgroundColor: theme.palette.grey[800], + }, + tableRowHover: { + "&:hover": { + backgroundColor: theme.palette.grey[600], + }, + }, + tableCell: { + flex: 1, + }, + noClick: { + cursor: "initial", + }, + }); + +const EdgeList = ({ selectedGraph, links, setLinks, linksTrans, loading, setFindNode, classes, setTransPath }) => { + const [searchTerm, setSearchTerm] = useState(''); + + const selectedLinks = links.filter(link => link.selected); + + function searchedLinks() { + if (searchTerm == '') { + return links; + } + return links.filter(link => { + if (link.source.name && link.target.name) { + return link.source.name.indexOf(searchTerm) > -1 || link.target.name.indexOf(searchTerm) > -1; + }}); + } + + function handleRowClick(event) { + setLinks( + links.map((temp_link, index) => { + if (index == searchedLinks()[event.index].index) { + temp_link.selected = !temp_link.selected; + } else { + temp_link.selected = false; + } + return temp_link; + }) + ); + setTransPath(event, '', ''); + } + + function handleSearchTermChange(event) { + setSearchTerm(event.target.value); + } + + function reduceNodeName(node) { + if (node.name) { + return node.name; + } + return node.substring(node.lastIndexOf('/') + 1); + } + + const getRowClassName = ({ index }) => { + return clsx( + index % 2 == 0 ? styles.tableRowEven : classes.tableRowOdd, + classes.flexContainer, + { + [classes.tableRowHover]: index !== -1, + } + ); + }; + + const cellRenderer = ({ cellData, columnIndex, rowIndex }) => { + + return ( + <TableCell + component="div" + > + { columnIndex == 0 ? + ( searchedLinks()[rowIndex].data?.direct ? + <Tooltip title="DIRECT" placement="right" arrow><p>D</p></Tooltip> + : + <Tooltip title="TRANSITIVE" placement="right" arrow><p>T</p></Tooltip> + ) + : + "" + } + { columnIndex == 1 ? reduceNodeName(searchedLinks()[rowIndex].source) : "" } + { columnIndex == 2 ? (searchedLinks()[rowIndex].selected ? <span style={{ color: "#ED7811" }}>➔</span> : "➔") : "" } + { columnIndex == 3 ? reduceNodeName(searchedLinks()[rowIndex].target) : "" } + </TableCell> + ); + }; + + const headerRenderer = ({ label, columnIndex }) => { + return ( + <TableCell + component="div" + > + <Typography + style={{ width: "100%" }} + align="left" + variant="caption" + component="h2" + > + {label} + </Typography> + </TableCell> + ); + }; + + return ( + <LoadingBar loading={loading} height={"95%"}> + <TextField + fullWidth + onChange={handleSearchTermChange} + onClick={(event)=> event.target.select()} + label="Search for Edge" + /> + <div style={{ height: "30%" }}> + <AutoSizer> + {({ height, width }) => ( + <Table + height={height} + width={width} + rowCount={searchedLinks().length} + rowGetter={({ index }) => searchedLinks()[index].target} + rowHeight={25} + onRowClick={handleRowClick} + gridStyle={{ + direction: "inherit", + }} + size={"small"} + rowClassName={getRowClassName} + headerHeight={35} + > + {columns.map(({ dataKey, ...other }, index) => { + return ( + <Column + key={dataKey} + headerRenderer={(headerProps) => + headerRenderer({ + ...headerProps, + columnIndex: index, + }) + } + cellRenderer={cellRenderer} + dataKey={dataKey} + {...other} + /> + ); + })} + </Table> + )} + </AutoSizer> + </div> + <Paper style={{ border: "2px solid", height: "55%", padding: 5, overflow: 'auto' }} hidden={(selectedLinks.length <= 0)}> + <List dense={true} style={{ padding: 5 }}> + <Paper elevation={3} style={{ backgroundColor: "rgba(33, 33, 33)", padding: 15 }}> + <h4 style={{ margin: 0 }}>{ selectedLinks[0]?.source.name } ➔ { selectedLinks[0]?.target.name }</h4> + <ListItemText primary={ <span><strong>Type:</strong> { selectedLinks[0]?.data.direct ? "Direct" : "Transitive" }</span> }/> + <ListItemText primary={ <span><strong>Visibility:</strong> { visibilityTypes[selectedLinks[0]?.data.visibility] }</span> }/> + <ListItemText primary={ <span><strong>Source:</strong> { selectedLinks[0]?.source.id }</span> } secondary= { selectedLinks[0]?.source.type }/> + <ListItemText primary={ <span><strong>Target:</strong> { selectedLinks[0]?.target.id }</span> } secondary= { selectedLinks[0]?.target.type }/> + <div> + <ListItemText primary={ <strong>Symbol Dependencies: { selectedLinks[0]?.data.symbols.length }</strong> }/> + { selectedLinks[0]?.data.symbols.map((symbol, index) => { + return ( + <span key={index}> + <ListItemText secondary={ symbol } style={{textIndent: "-1em", marginLeft: "1em", overflowWrap: "break-word"}}></ListItemText> + <hr style={{ border: "0px", borderTop: "0.5px solid rgba(255, 255, 255, .2)", marginTop: "2px", marginBottom: "2px"}}></hr> + </span> + ); + }) + } + </div> + <div hidden={(selectedLinks[0]?.data.direct ? "Direct" : "Transitive") == "Direct" }> + <br></br> + <Button variant="contained" onClick={ (event) => setTransPath(event, selectedLinks[0]?.source.id, selectedLinks[0]?.target.id) }>View Paths</Button> + </div> + </Paper> + </List> + </Paper> + </LoadingBar> + ); +}; + +export default connect(getEdges, { setGraphData, setFindNode, setLinks, setSelectedPath })(withStyles(styles)(EdgeList));
\ No newline at end of file diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfo.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfo.js index b5f3cec93eb..5863d2bea9d 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfo.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfo.js @@ -25,11 +25,13 @@ const useStyles = makeStyles({ const GraphInfo = ({ selectedGraph, counts, datawidth, setCounts }) => { React.useEffect(() => { let gitHash = selectedGraph; - fetch('/api/graphs/' + gitHash + '/analysis') - .then(response => response.json()) - .then(data => { - setCounts(data.results); - }); + if (gitHash) { + fetch('/api/graphs/' + gitHash + '/analysis') + .then(response => response.json()) + .then(data => { + setCounts(data.results); + }); + } }, [selectedGraph]); const classes = useStyles(); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfoTabs.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfoTabs.js index 3636581f375..671a8d775af 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfoTabs.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphInfoTabs.js @@ -5,6 +5,7 @@ import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; import NodeList from "./NodeList"; +import EdgeList from "./EdgeList"; import InfoExpander from "./InfoExpander"; import AlgorithmExpander from "./AlgorithmExpander"; @@ -26,17 +27,27 @@ const useStyles = makeStyles((theme) => ({ export default function GraphInfoTabs({ nodes, width }) { const classes = useStyles(); - const [value, setValue] = React.useState(0); + const [tab, setTab] = React.useState(1); + const [transPathFrom, setTransPathFrom] = React.useState(''); + const [transPathTo, setTransPathTo] = React.useState(''); const handleChange = (event, newValue) => { - setValue(newValue); + setTab(newValue); + }; + + const handleTransPath = (event, fromNode, toNode) => { + setTransPathFrom(fromNode); + setTransPathTo(toNode); + if (fromNode != '' && toNode != '') { + setTab(3); + } }; return ( <div className={classes.root}> <AppBar position="static" color="default"> <Tabs - value={value} + value={tab} onChange={handleChange} indicatorColor="primary" textColor="primary" @@ -50,15 +61,17 @@ export default function GraphInfoTabs({ nodes, width }) { <Tab label="Algorithms" {...a11yProps(3)} /> </Tabs> </AppBar> - <div style={{ height: "100%" }} hidden={value != 0}> + <div style={{ height: "100%" }} hidden={tab != 0}> <InfoExpander width={width}></InfoExpander> </div> - <div style={{ height: "100%" }} hidden={value != 1}> + <div style={{ height: "100%" }} hidden={tab != 1}> <NodeList nodes={nodes}></NodeList> </div> - <div style={{ height: "100%" }} hidden={value != 2}></div> - <div style={{ height: "100%" }} hidden={value != 3}> - <AlgorithmExpander width={width}></AlgorithmExpander> + <div style={{ height: "100%" }} hidden={tab != 2}> + <EdgeList nodes={nodes} setTransPath={handleTransPath}></EdgeList> + </div> + <div style={{ height: "100%" }} hidden={tab != 3}> + <AlgorithmExpander width={width} transPathFrom={transPathFrom} transPathTo={transPathTo}></AlgorithmExpander> </div> </div> ); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphPaths.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphPaths.js index 39d35aeb50d..ead5fd4245d 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphPaths.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/GraphPaths.js @@ -17,6 +17,8 @@ import useResizeAware from "react-resize-aware"; import { getSelected } from "./redux/store"; import { selectedGraphPaths, setSelectedPath } from "./redux/graphPaths"; import { setGraphData } from "./redux/graphData"; +import { setLinks } from "./redux/links"; +import { setLinksTrans } from "./redux/linksTrans"; import OverflowTooltip from "./OverflowTooltip"; @@ -63,7 +65,21 @@ const AccordionDetails = withStyles((theme) => ({ }, }))(MuiAccordionDetails); -const GraphPaths = ({ nodes, selectedGraph, selectedNodes, graphPaths, setSelectedPath, width, selectedGraphPaths, setGraphData }) => { +const GraphPaths = ({ + nodes, + selectedGraph, + selectedNodes, + graphPaths, + setSelectedPath, + width, + selectedGraphPaths, + setGraphData, + setLinks, + setLinksTrans, + showTransitive, + transPathFrom, + transPathTo +}) => { const [fromNode, setFromNode] = React.useState(""); const [toNode, setToNode] = React.useState(""); const [fromNodeId, setFromNodeId] = React.useState(0); @@ -89,39 +105,71 @@ const GraphPaths = ({ nodes, selectedGraph, selectedNodes, graphPaths, setSelect }, })); const classes = useStyles(); + + React.useEffect(() => { + setFromNode(transPathFrom); + setFromNodeExpanded(false); + setToNode(transPathFrom); + setToNodeExpanded(false); + setPaneSize("50%"); + if (transPathFrom != '' && transPathTo != '') { + getGraphPaths(transPathFrom, transPathTo); + } else { + selectedGraphPaths({ + fromNode: '', + toNode: '', + paths: [], + selectedPath: -1 + }); + } + }, [transPathFrom, transPathTo]); function getGraphPaths(fromNode, toNode) { let gitHash = selectedGraph; - let postData = { - "fromNode": fromNode, - "toNode": toNode - }; - fetch('/api/graphs/' + gitHash + '/paths', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - selectedGraphPaths(data); - let postData = { - "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node), - "extra_nodes": data.extraNodes - }; - fetch('/api/graphs/' + gitHash + '/d3', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - setGraphData(data.graphData); - }); - }); + if (gitHash) { + let postData = { + "fromNode": fromNode, + "toNode": toNode + }; + fetch('/api/graphs/' + gitHash + '/paths', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + selectedGraphPaths(data); + let postData = { + "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node), + "extra_nodes": data.extraNodes, + "transitive_edges": showTransitive + }; + fetch('/api/graphs/' + gitHash + '/d3', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + setGraphData(data.graphData); + setLinks( + data.graphData.links.map((link) => { + if (link.source == fromNode && link.target == toNode) { + link.selected = true; + } else { + link.selected = false; + } + return link; + }) + ); + setLinksTrans(data.graphData.links_trans); + }); + }); + } } function toNodeRow({ index, style, data }) { @@ -315,6 +363,6 @@ const GraphPaths = ({ nodes, selectedGraph, selectedNodes, graphPaths, setSelect ); }; -export default connect(getSelected, { selectedGraphPaths, setSelectedPath, setGraphData })( +export default connect(getSelected, { selectedGraphPaths, setSelectedPath, setGraphData, setLinks, setLinksTrans })( GraphPaths ); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/NodeList.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/NodeList.js index 7a385853ed6..bdcc5755f43 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/NodeList.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/NodeList.js @@ -9,8 +9,10 @@ import LoadingBar from "./LoadingBar"; import TextField from "@material-ui/core/TextField"; import { setNodes, updateCheckbox, updateSelected } from "./redux/nodes"; +import { setNodeInfos } from "./redux/nodeInfo"; import { setGraphData } from "./redux/graphData"; -import { addLinks } from "./redux/links"; +import { setLinks } from "./redux/links"; +import { setLinksTrans } from "./redux/linksTrans"; import { setLoading } from "./redux/loading"; import { setListSearchTerm } from "./redux/listSearchTerm"; import { Button, Autocomplete, Grid } from "@material-ui/core"; @@ -21,57 +23,63 @@ const columns = [ { id: "ID", dataKey: "node", label: "Node", width: 200 }, ]; -const NodeList = ({ selectedGraph, nodes, searchedNodes, loading, setFindNode, setNodes, addLinks, setLoading, setListSearchTerm, updateCheckbox, updateSelected, setGraphData}) => { +const NodeList = ({ selectedGraph, nodes, searchedNodes, loading, setFindNode, setNodeInfos, setNodes, setLinks, setLinksTrans, setLoading, setListSearchTerm, updateCheckbox, updateSelected, setGraphData, showTransitive}) => { const [searchPath, setSearchPath] = React.useState(''); React.useEffect(() => { let gitHash = selectedGraph; - fetch('/api/graphs/' + gitHash + '/nodes') - .then(response => response.json()) - .then(data => { - setNodes(data.nodes.map((node, index) => { - return { - id: index, - node: node, - name: node.substring(node.lastIndexOf('/') + 1), - check: "checkbox", - selected: false, - }; - })); - addLinks(data.links); - setLoading(false); - }); - setSearchPath(null); - setListSearchTerm(''); + if (gitHash) { + fetch('/api/graphs/' + gitHash + '/nodes') + .then(response => response.json()) + .then(data => { + setNodes(data.nodes.map((node, index) => { + return { + id: index, + node: node, + name: node.substring(node.lastIndexOf('/') + 1), + check: "checkbox", + selected: false, + }; + })); + setLoading(false); + }); + setSearchPath(null); + setListSearchTerm(''); + } }, [selectedGraph]); function newGraphData() { let gitHash = selectedGraph; - let postData = { - "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node) - }; - fetch('/api/graphs/' + gitHash + '/d3', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - setGraphData(data.graphData); - }); - fetch('/api/graphs/' + gitHash + '/nodes/details', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - setNodeInfos(data.nodeInfos); - }); + if (gitHash) { + let postData = { + "selected_nodes": nodes.filter(node => node.selected == true).map(node => node.node), + "transitive_edges": showTransitive + }; + fetch('/api/graphs/' + gitHash + '/d3', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + setGraphData(data.graphData); + setLinks(data.graphData.links); + setLinksTrans(data.graphData.links_trans); + }); + fetch('/api/graphs/' + gitHash + '/nodes/details', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + setNodeInfos(data.nodeInfos); + }); + } } function nodePaths() { @@ -102,7 +110,7 @@ const NodeList = ({ selectedGraph, nodes, searchedNodes, loading, setFindNode, s function handleSearchTermChange(event, newTerm) { if (newTerm == null) { setSearchPath(''); - setListSearchTerm(newTerm); + setListSearchTerm(''); } else { setSearchPath(newTerm); setListSearchTerm(newTerm); @@ -125,7 +133,6 @@ const NodeList = ({ selectedGraph, nodes, searchedNodes, loading, setFindNode, s renderInput={(params) => <TextField {...params} label="Search by Path or Name" variant="outlined" - onClick={(event) => event.target.select()} />} /> </Grid> @@ -167,4 +174,4 @@ const NodeList = ({ selectedGraph, nodes, searchedNodes, loading, setFindNode, s ); }; -export default connect(getNodes, { setFindNode, setNodes, addLinks, setLoading, setListSearchTerm, updateCheckbox, updateSelected, setGraphData })(NodeList);
\ No newline at end of file +export default connect(getNodes, { setFindNode, setNodes, setNodeInfos, setLinks, setLinksTrans, setLoading, setListSearchTerm, updateCheckbox, updateSelected, setGraphData })(NodeList);
\ No newline at end of file diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/OverflowTooltip.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/OverflowTooltip.js index d41891364a1..c09ec44f319 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/OverflowTooltip.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/OverflowTooltip.js @@ -11,6 +11,8 @@ import { updateCheckbox } from "./redux/nodes"; import { setGraphData } from "./redux/graphData"; import { setNodeInfos } from "./redux/nodeInfo"; import { getGraphData } from "./redux/store"; +import { setLinks } from "./redux/links"; +import { setLinksTrans } from "./redux/linksTrans"; const OverflowTip = (props) => { const textElementRef = useRef(null); @@ -26,31 +28,36 @@ const OverflowTip = (props) => { function newGraphData() { let gitHash = props.selectedGraph; - let postData = { - "selected_nodes": props.nodes.filter(node => node.selected == true).map(node => node.node) - }; - fetch('/api/graphs/' + gitHash + '/d3', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - props.setGraphData(data.graphData); - }); - fetch('/api/graphs/' + gitHash + '/nodes/details', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - }) - .then(response => response.json()) - .then(data => { - props.setNodeInfos(data.nodeInfos); - }); + if (gitHash) { + let postData = { + "selected_nodes": props.nodes.filter(node => node.selected == true).map(node => node.node), + "transitive_edges": props.showTransitive + }; + fetch('/api/graphs/' + gitHash + '/d3', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + props.setGraphData(data.graphData); + props.setLinks(data.graphData.links); + props.setLinksTrans(data.graphData.links_trans); + }); + fetch('/api/graphs/' + gitHash + '/nodes/details', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(postData) + }) + .then(response => response.json()) + .then(data => { + props.setNodeInfos(data.nodeInfos); + }); + } } useEffect(() => { @@ -98,4 +105,4 @@ const OverflowTip = (props) => { ); }; -export default connect(getGraphData, { updateCheckbox, setGraphData, setNodeInfos })(OverflowTip); +export default connect(getGraphData, { updateCheckbox, setGraphData, setNodeInfos, setLinks, setLinksTrans })(OverflowTip); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/links.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/links.js index 04d0d3d2de8..9d47584b7f5 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/links.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/links.js @@ -2,15 +2,31 @@ import { initialState } from "./store"; export const links = (state = initialState, action) => { switch (action.type) { - case "addLinks": + case "addLink": + var arr = Object.assign(state); + return [...arr, action.payload]; + case "setLinks": return action.payload; - + case "updateSelectedLinks": + var newState = Object.assign(state); + newState[action.payload.index].selected = action.payload.value; + return newState; default: return state; } }; -export const addLinks = (links) => ({ - type: "addLinks", +export const addLink = (link) => ({ + type: "addLink", + payload: link, +}); + +export const setLinks = (links) => ({ + type: "setLinks", payload: links, }); + +export const updateSelectedLinks = (newValue) => ({ + type: "updateSelectedLinks", + payload: newValue, +}); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/linksTrans.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/linksTrans.js new file mode 100644 index 00000000000..f0cff77a892 --- /dev/null +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/linksTrans.js @@ -0,0 +1,32 @@ +import { initialState } from "./store"; + +export const linksTrans = (state = initialState, action) => { + switch (action.type) { + case "addLinkTrans": + var arr = Object.assign(state); + return [...arr, action.payload]; + case "setLinksTrans": + return action.payload; + case "updateSelectedLinksTrans": + var newState = Object.assign(state); + newState[action.payload.index].selected = action.payload.value; + return newState; + default: + return state; + } +}; + +export const addLinkTrans = (link) => ({ + type: "addLinkTrans", + payload: link, +}); + +export const setLinksTrans = (links) => ({ + type: "setLinksTrans", + payload: links, +}); + +export const updateSelectedLinksTrans = (newValue) => ({ + type: "updateSelectedLinksTrans", + payload: newValue, +}); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/showTransitive.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/showTransitive.js new file mode 100644 index 00000000000..05e1f727275 --- /dev/null +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/showTransitive.js @@ -0,0 +1,16 @@ +import { initialState } from "./store"; + +export const showTransitive = (state = initialState, action) => { + switch (action.type) { + case "setShowTransitive": + return action.payload; + + default: + return state; + } +}; + +export const setShowTransitive = (showTransitive) => ({ + type: "setShowTransitive", + payload: showTransitive, +}); diff --git a/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/store.js b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/store.js index 4ef6b2e74d5..f12517980b8 100644 --- a/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/store.js +++ b/buildscripts/libdeps/graph_visualizer_web_stack/src/redux/store.js @@ -5,6 +5,8 @@ import { counts } from "./counts"; import { nodeInfo } from "./nodeInfo"; import { loading } from "./loading"; import { links } from "./links"; +import { linksTrans } from "./linksTrans"; +import { showTransitive } from "./showTransitive"; import { graphData } from "./graphData"; import { findNode } from "./findNode"; import { graphPaths } from "./graphPaths"; @@ -13,25 +15,18 @@ import { listSearchTerm } from "./listSearchTerm"; export const initialState = { loading: false, graphFiles: [ - // {id: 0, value: 'graphfile.graphml', version: 1, git: '1234567', selected: false} + // { id: 0, value: 'graphfile.graphml', version: 1, git: '1234567', selected: false } ], nodes: [ - { - id: 0, - node: "test/test1.so", - name: "test1", - check: "checkbox", - selected: false, - }, - { - id: 1, - node: "test/test2.so", - name: "test2", - check: "checkbox", - selected: false, - }, + // { id: 0, node: "test/test1.so", name: "test1", check: "checkbox", selected: false } + ], + links: [ + // { source: "test/test1.so", target: "test/test2.so" } + ], + linksTrans: [ + // { source: "test/test1.so", target: "test/test2.so" } ], - links: [{ source: "test/test1.so", target: "test/test2.so" }], + showTransitive: false, graphData: { nodes: [ // {id: 'test/test1.so', name: 'test1.so'}, @@ -67,7 +62,7 @@ export const initialState = { export const getCurrentGraphHash = (state) => { let selectedGraphFiles = state.graphFiles.filter(x => x.selected == true); - let selectedGraph = '0000000'; + let selectedGraph = undefined; if (selectedGraphFiles.length > 0) { selectedGraph = selectedGraphFiles[0].git; } @@ -107,7 +102,8 @@ export const getRows = (state) => { rowGetter: ({ index }) => searchedNodes[index], checkBox: ({ index }) => searchedNodes[index].selected, nodes: state.nodes, - searchedNodes: searchedNodes + searchedNodes: searchedNodes, + showTransitive: state.showTransitive, }; }; @@ -116,9 +112,11 @@ export const getSelected = (state) => { selectedGraph: getCurrentGraphHash(state), selectedNodes: state.nodes.filter((node) => node.selected), nodes: state.nodes, + links: state.links, selectedEdges: [], loading: state.loading, graphPaths: state.graphPaths, + showTransitive: state.showTransitive, }; }; @@ -129,6 +127,19 @@ export const getNodes = (state) => { loading: state.loading, listSearchTerm: state.listSearchTerm, searchedNodes: state.nodes.filter(node => node.node.indexOf(state.listSearchTerm) > -1), + showTransitive: state.showTransitive + }; +}; + +export const getEdges = (state) => { + return { + selectedGraph: getCurrentGraphHash(state), + nodes: state.nodes, + links: state.links, + linksTrans: state.linksTrans, + selectedLinks: state.links.filter(link => link.selected == true), + searchedNodes: state.nodes.filter(node => node.node.indexOf(state.listSearchTerm) > -1), + showTransitive: state.showTransitive, }; }; @@ -136,10 +147,12 @@ export const getGraphData = (state) => { return { selectedGraph: getCurrentGraphHash(state), nodes: state.nodes, + links: state.links, graphData: state.graphData, loading: state.loading, findNode: state.findNode, graphPaths: state.graphPaths, + showTransitive: state.showTransitive, }; }; @@ -155,10 +168,12 @@ const store = createStore( graphFiles, loading, links, + linksTrans, graphData, findNode, graphPaths, listSearchTerm, + showTransitive }), initialState ); diff --git a/site_scons/libdeps.py b/site_scons/libdeps.py index 767f2a7baf4..9cb445bd257 100644 --- a/site_scons/libdeps.py +++ b/site_scons/libdeps.py @@ -63,6 +63,7 @@ import textwrap import hashlib import json import fileinput +import subprocess try: import networkx @@ -1265,7 +1266,9 @@ def generate_graph(env, target, source): libdeps_graph = env.GetLibdepsGraph() + demangled_symbols = {} for symbol_deps_file in source: + with open(str(symbol_deps_file)) as f: symbols = {} try: @@ -1279,16 +1282,26 @@ def generate_graph(env, target, source): except json.JSONDecodeError: env.FatalError(f"Failed processing json file: {str(symbol_deps_file)}") - for libdep in symbols: - from_node = os.path.abspath(str(symbol_deps_file)[:-len(env['SYMBOLDEPSSUFFIX'])]) - to_node = os.path.abspath(libdep).strip() - libdeps_graph.add_edges_from([( - from_node, - to_node, - {EdgeProps.symbols.name: " ".join(symbols[libdep])}, - )]) - node = env.File(str(symbol_deps_file)[:-len(env['SYMBOLDEPSSUFFIX'])]) - add_node_from(env, node) + demangled_symbols[str(symbol_deps_file)] = symbols + + p1 = subprocess.Popen(['c++filt', '-n'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdout, stderr = p1.communicate(json.dumps(demangled_symbols).encode('utf-8')) + demangled_symbols = json.loads(stdout.decode("utf-8")) + + for deps_file in demangled_symbols: + + for libdep in demangled_symbols[deps_file]: + + from_node = os.path.abspath(str(deps_file)[:-len(env['SYMBOLDEPSSUFFIX'])]) + to_node = os.path.abspath(libdep).strip() + libdeps_graph.add_edges_from([( + from_node, + to_node, + {EdgeProps.symbols.name: "\n".join(demangled_symbols[deps_file][libdep])}, + )]) + node = env.File(str(deps_file)[:-len(env['SYMBOLDEPSSUFFIX'])]) + add_node_from(env, node) libdeps_graph_file = f"{env.Dir('$BUILD_DIR').path}/libdeps/libdeps.graphml" networkx.write_graphml(libdeps_graph, libdeps_graph_file, named_key_ids=True) @@ -1364,7 +1377,7 @@ def setup_environment(env, emitting_shared=False, debug='off', linting='on'): env['LIBDEPS_SYMBOL_DEP_FILES'] = symbol_deps env['LIBDEPS_GRAPH_FILE'] = env.File("${BUILD_DIR}/libdeps/libdeps.graphml") - env['LIBDEPS_GRAPH_SCHEMA_VERSION'] = 3 + env['LIBDEPS_GRAPH_SCHEMA_VERSION'] = 4 env["SYMBOLDEPSSUFFIX"] = '.symbol_deps' libdeps_graph = LibdepsGraph() |