0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-30 09:06:21 +01:00
mongodb/site_scons/site_tools/dagger/graph.py

591 lines
18 KiB
Python

import sys
import logging
import abc
import json
import copy
from . import graph_consts
class Graph(object):
"""Graph class for storing the build dependency graph. The graph stores the
directed edges as a nested dict of { RelationshipType: {From_Node: Set of
connected nodes}} and nodes as a dict of {nodeid : nodeobject}. Can be
imported from a pickle or JSON file.
"""
def __init__(self, input=None):
"""
A graph can be initialized with a .json file, graph object, or with no args
"""
if isinstance(input, str):
if input.endswith('.json'):
with open(input, 'r') as f:
data = json.load(f, encoding="ascii")
nodes = {}
should_fail = False
for node in data["nodes"]:
id = str(node["id"])
try:
nodes[id] = node_factory(id, int(node["node"]["type"]),
dict_source=node["node"])
except Exception as e:
logging.warning("Malformed Data: " + id)
should_fail = True
if should_fail is True:
raise ValueError("json nodes are malformed")
edges = {}
for edge in data["edges"]:
if edge["type"] not in edges:
edges[edge["type"]] = {}
to_edges = set([str(e["id"]) for e in edge["to_node"]])
edges[edge["type"]][edge["from_node"]["id"]] = to_edges
self._nodes = nodes
self._edges = edges
elif isinstance(input, Graph):
self._nodes = input.nodes
self._edges = input.edges
else:
self._nodes = {}
self._edges = {}
for rel in graph_consts.RELATIONSHIP_TYPES:
self._edges[rel] = {}
@property
def nodes(self):
"""We want to ensure that we are not able to mutate
the nodes or edges properties outside of the specified adder methods
"""
return copy.deepcopy(self._nodes)
@property
def edges(self):
return copy.deepcopy(self._edges)
@nodes.setter
def nodes(self, value):
if isinstance(value, dict):
self._nodes = value
else:
raise TypeError("Nodes must be a dict")
@edges.setter
def edges(self, value):
if isinstance(value, dict):
self._edges = value
else:
raise TypeError("Edges must be a dict")
def get_node(self, id):
return self._nodes.get(id)
def find_node(self, id, type):
"""returns the node if it exists, otherwise, generates
it"""
if self.get_node(id) is not None:
return self.get_node(id)
else:
node = node_factory(id, type)
self.add_node(node)
return node
def get_edge_type(self, edge_type):
return self._edges[edge_type]
def add_node(self, node):
if not isinstance(node, NodeInterface):
raise TypeError
if node.id in self._nodes:
raise ValueError
self._nodes[node.id] = node
def add_edge(self, relationship, from_node, to_node):
if relationship not in graph_consts.RELATIONSHIP_TYPES:
raise TypeError
from_node_obj = self.get_node(from_node)
to_node_obj = self.get_node(to_node)
if from_node not in self._edges[relationship]:
self._edges[relationship][from_node] = set()
if any(item is None for item in (from_node, to_node, from_node_obj, to_node_obj)):
raise ValueError
self._edges[relationship][from_node].add(to_node)
to_node_obj.add_incoming_edges(from_node_obj, self)
# JSON does not support python sets, so we need to convert each
# set of edges to lists
def export_to_json(self, filename="graph.json"):
node_index = {}
data = {"edges": [], "nodes": []}
for idx, id in enumerate(self._nodes.keys()):
node = self.get_node(id)
node_index[id] = idx
node_dict = {}
node_dict["index"] = idx
node_dict["id"] = id
node_dict["node"] = {}
for property, value in vars(node).items():
if isinstance(value, set):
node_dict["node"][property] = list(value)
else:
node_dict["node"][property] = value
data["nodes"].append(node_dict)
for edge_type in graph_consts.RELATIONSHIP_TYPES:
edges_dict = self._edges[edge_type]
for node in list(edges_dict.keys()):
to_nodes = list(self._edges[edge_type][node])
to_nodes_dicts = [{"index": node_index[to_node], "id": to_node}
for to_node in to_nodes]
data["edges"].append({"type": edge_type,
"from_node": {"id": node,
"index": node_index[node]},
"to_node": to_nodes_dicts})
with open(filename, 'w', encoding="ascii") as outfile:
json.dump(data, outfile, indent=4)
def __str__(self):
return ("<Number of Nodes : {0}, Number of Edges : {1}, "
"Hash: {2}>").format(len(list(self._nodes.keys())),
sum(len(x) for x in list(self._edges.values())), hash(self))
class NodeInterface(object, metaclass=abc.ABCMeta):
"""Abstract base class for all Node Objects - All nodes must have an id and name
"""
@abc.abstractproperty
def id(self):
raise NotImplementedError()
@abc.abstractproperty
def name(self):
raise NotImplementedError()
class NodeLib(NodeInterface):
"""NodeLib class which represents a library within the graph
"""
def __init__(self, id, name, input=None):
if isinstance(input, dict):
should_fail = False
for k, v in input.items():
try:
if isinstance(v, list):
setattr(self, k, set(v))
else:
setattr(self, k, v)
except AttributeError as e:
logging.error("found something bad, {0}, {1}", e, type(e))
should_fail = True
if should_fail:
raise Exception("Problem setting attribute for NodeLib")
else:
self._id = id
self.type = graph_consts.NODE_LIB
self._name = name
self._defined_symbols = set()
self._defined_files = set()
self._dependent_files = set()
self._dependent_libs = set()
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@property
def defined_symbols(self):
return self._defined_symbols
@defined_symbols.setter
def defined_symbols(self, value):
if isinstance(value, set):
self._defined_symbols = value
else:
raise TypeError("NodeLib.defined_symbols must be a set")
@property
def defined_files(self):
return self._defined_files
@defined_files.setter
def defined_files(self, value):
if isinstance(value, set):
self._defined_files = value
else:
raise TypeError("NodeLib.defined_files must be a set")
@property
def dependent_files(self):
return self._dependent_files
@dependent_files.setter
def dependent_files(self, value):
if isinstance(value, set):
self._dependent_files = value
else:
raise TypeError("NodeLib.dependent_files must be a set")
@property
def dependent_libs(self):
return self._dependent_libs
@dependent_libs.setter
def dependent_libs(self, value):
if isinstance(value, set):
self._defined_libs = value
else:
raise TypeError("NodeLib.defined_libs must be a set")
def add_defined_symbol(self, symbol):
if symbol is not None:
self._defined_symbols.add(symbol)
def add_defined_file(self, file):
if file is not None:
self._defined_files.add(file)
def add_dependent_file(self, file):
if file is not None:
self._dependent_files.add(file)
def add_dependent_lib(self, lib):
if lib is not None:
self._dependent_libs.add(lib)
def add_incoming_edges(self, from_node, g):
"""Whenever you generate a LIB_LIB edge, you must add
the source lib to the dependent_lib field in the target lib
"""
if from_node.type == graph_consts.NODE_LIB:
self.add_dependent_lib(from_node.id)
def __eq__(self, other):
if isinstance(other, NodeLib):
return (self._id == other._id and self._defined_symbols == other._defined_symbols
and self._defined_files == other._defined_files
and self._dependent_libs == other._dependent_libs
and self._dependent_files == other._dependent_files)
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return self.id
class NodeSymbol(NodeInterface):
"""NodeSymbol class which represents a symbol within the dependency graph
"""
def __init__(self, id, name, input=None):
if isinstance(input, dict):
should_fail = False
for k, v in input.items():
try:
if isinstance(v, list):
setattr(self, k, set(v))
else:
setattr(self, k, v)
except AttributeError as e:
logging.error("found something bad, {0}, {1}", e, type(e))
should_fail = True
if should_fail:
raise Exception("Problem setting attribute for NodeLib")
else:
self._id = id
self.type = graph_consts.NODE_SYM
self._name = name
self._dependent_libs = set()
self._dependent_files = set()
self._libs = set()
self._files = set()
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@property
def libs(self):
return self._libs
@libs.setter
def libs(self, value):
if isinstance(value, set):
self._libs = value
else:
raise TypeError("NodeSymbol.libs must be a set")
@property
def files(self):
return self._files
@files.setter
def files(self, value):
if isinstance(value, set):
self._files = value
else:
raise TypeError("NodeSymbol.files must be a set")
@property
def dependent_libs(self):
return self._dependent_libs
@dependent_libs.setter
def dependent_libs(self, value):
if isinstance(value, set):
self._dependent_libs = value
else:
raise TypeError("NodeSymbol.dependent_libs must be a set")
@property
def dependent_files(self):
return self._dependent_files
@dependent_files.setter
def dependent_files(self, value):
if isinstance(value, set):
self._dependent_files = value
else:
raise TypeError("NodeSymbol.dependent_files must be a set")
def add_library(self, library):
if library is not None:
self._libs.add(library)
def add_file(self, file):
if file is not None:
self._files.add(file)
def add_dependent_file(self, file):
if file is not None:
self._dependent_files.add(file)
def add_dependent_lib(self, library):
if library is not None:
self._dependent_libs.add(library)
def add_incoming_edges(self, from_node, g):
if from_node.type == graph_consts.NODE_FILE:
if from_node.library not in self.libs:
self.add_dependent_lib(from_node.library)
self.add_dependent_file(from_node.id)
lib_node = g.get_node(from_node.library)
if lib_node is not None and from_node.library not in self.libs:
g.add_edge(graph_consts.LIB_SYM, lib_node.id, self.id)
def __eq__(self, other):
if isinstance(other, NodeSymbol):
return (self.id == other.id and self._libs == other._libs
and self._files == other._files
and self._dependent_libs == other._dependent_libs
and self._dependent_files == other._dependent_files)
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return self.id
class NodeFile(NodeInterface):
"""NodeFile class which represents an object file within the build dependency graph
"""
def __init__(self, id, name, input=None):
if isinstance(input, dict):
should_fail = False
for k, v in input.items():
try:
if isinstance(v, list):
setattr(self, k, set(v))
else:
setattr(self, k, v)
except AttributeError as e:
logging.error("found something bad, {0}, {1}", e, type(e))
should_fail = True
if should_fail:
raise Exception("Problem setting attribute for NodeLib")
else:
self._id = id
self.type = graph_consts.NODE_FILE
self._name = name
self._defined_symbols = set()
self._dependent_libs = set()
self._dependent_files = set()
self._lib = None
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@property
def defined_symbols(self):
return self._defined_symbols
@defined_symbols.setter
def defined_symbols(self, value):
if isinstance(value, set):
self._defined_symbols = value
else:
raise TypeError("NodeFile.defined_symbols must be a set")
@property
def dependent_libs(self):
return self._dependent_libs
@dependent_libs.setter
def dependent_libs(self, value):
if isinstance(value, set):
self._dependent_libs = value
else:
raise TypeError("NodeFile.dependent_libs must be a set")
@property
def dependent_files(self):
return self._dependent_files
@dependent_files.setter
def dependent_files(self, value):
if isinstance(value, set):
self._dependent_files = value
else:
raise TypeError("NodeFile.dependent_files must be a set")
@property
def library(self):
return self._lib
@library.setter
def library(self, library):
if library is not None:
self._lib = library
def add_defined_symbol(self, symbol):
if symbol is not None:
self._defined_symbols.add(symbol)
def add_dependent_file(self, file):
if file is not None:
self._dependent_files.add(file)
def add_dependent_lib(self, library):
if library is not None:
self._dependent_libs.add(library)
def add_incoming_edges(self, from_node, g):
if from_node.type == graph_consts.NODE_FILE:
self.add_dependent_file(from_node.id)
lib_node = g.get_node(self.library)
if from_node.library is not None and from_node.library != self.library:
self.add_dependent_lib(from_node.library)
g.add_edge(graph_consts.LIB_FIL, from_node.library, self.id)
if lib_node is not None:
lib_node.add_dependent_file(from_node.id)
lib_node.add_dependent_lib(from_node.library)
g.add_edge(graph_consts.FIL_LIB, from_node.id, lib_node.id)
def __eq__(self, other):
if isinstance(other, NodeSymbol):
return (self.id == other.id and self._lib == other._lib
and self._dependent_libs == other._dependent_libs
and self._dependent_files == other._dependent_files
and self._defined_symbols == other._defined_symbols)
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return self.id
class NodeExe(NodeInterface):
def __init__(self, id, name, input=None):
if isinstance(input, dict):
should_fail = False
for k, v in input.items():
try:
if isinstance(v, list):
setattr(self, k, set(v))
else:
setattr(self, k, v)
except AttributeError as e:
logging.error("found something bad, {0}, {1}", e, type(e))
should_fail = True
if should_fail:
raise Exception("Problem setting attribute for NodeExe")
else:
self._id = id
self.type = graph_consts.NODE_EXE
self._name = name
self.contained_files = set()
@property
def id(self):
return self._id
@property
def name(self):
return self._name
def __repr__(self):
return self.id
types = {
graph_consts.NODE_LIB: NodeLib,
graph_consts.NODE_SYM: NodeSymbol,
graph_consts.NODE_FILE: NodeFile,
graph_consts.NODE_EXE: NodeExe,
}
def node_factory(id, nodetype, dict_source=None):
if isinstance(dict_source, dict):
return types[nodetype](id, id, input=dict_source)
else:
return types[nodetype](id, id)