def _char_graph(self, vertices, edges): """Convenience method for constructing a graph where each vertex is a single character. :param string vertices: Vertices, given as a string of characters. :param list edges: Edges, given as an iterable of strings, where each string is expected to be of the form 'uv'. Eg, edges might be ('ab', 'bc'). """ return Graph(vertices=vertices, edges=[Graph.Edge(u, v) for u, v in edges])
def _simplify_module_dependency_graph(cls, modules, modules_by_name=None, prune_libraries=False): """Removes forward dependency edges and redundant library dependencies. This simplifies the dependency graph as much as possible, to make IntelliJ less like to get confused, to make the graph more space efficient, and to make it cleaner and more readable by humans. :param list modules: List of modules to simplify the dependencies of. :param dict modules_by_name: Map of names to their corresponding modules. If not specified, it will simply be computed from the list of modules. :param bool prune_libraries: If true, omits library dependencies that are already referenced transitively. This won't affect the set of libraries that a module depends on, but it may affect the order they appear on the classpath. """ if not modules_by_name: modules_by_name = {module.name: module for module in modules} edges = set() for module in modules: edges.update(Graph.Edge(module.name, dependency) for dependency in module.dependencies) graph = Graph(vertices={module.name for module in modules}, edges=edges) dependency_closure = {} # Process dependencies before dependees. for module_name in reversed(graph.topological_ordering(stable=True)): module = modules_by_name[module_name] dependency_closure[module_name] = set() direct_dependencies = sorted(module.dependencies) # Sort for stability. # Remove any direct dependencies which are already pulled in by our transitive dependencies. for dependency in direct_dependencies: if any(dependency in dependency_closure[other] for other in module.dependencies): logger.debug('Removing redundant dependency {} -> {}.'.format(module_name, dependency)) module.dependencies.remove(dependency) else: dependency_closure[module_name].update(dependency_closure[dependency]) dependency_closure[module_name].add(dependency) if prune_libraries: # Remove any libraries which are already pulled in by our transitive dependencies. transitive_libraries = defaultdict(set) for dep_name in dependency_closure[module_name]: dependency = modules_by_name[dep_name] for conf, jars in dependency.libraries.items(): transitive_libraries[conf].update(jars) for conf in transitive_libraries: module.libraries[conf] -= transitive_libraries[conf]
def _create_target_graph(self, targets, predicate=None): predicate = predicate or (lambda x: True) graph = Graph() for target in self._collect_dependencies(targets, predicate): graph.add_vertex(target.id) for dep in self._target_dependencies(target): if predicate(dep): graph.add_edge(Graph.Edge(target.id, dep.id)) return graph
def test_topological_ordering(self): graph = self._char_graph('abc', ('ab', 'bc')) self.assertEquals(tuple('abc'), tuple(graph.topological_ordering())) self.assertEquals(tuple('abc'), tuple(graph.topological_ordering(stable=True))) graph = graph.transposed self.assertEquals(tuple('cba'), tuple(graph.topological_ordering())) self.assertEquals(tuple('cba'), tuple(graph.topological_ordering(stable=True))) graph = Graph(vertices='cabzyx') self.assertEquals(tuple('abcxyz'), tuple(graph.topological_ordering(stable=True))) graph.add_edge(Graph.Edge('x', 'a')) self.assertEquals(tuple('bcxayz'), tuple(graph.topological_ordering(stable=True))) with self.assertRaises(Graph.CycleError): self._char_graph('abc', ('ab', 'bc', 'ca')).topological_ordering() with self.assertRaises(Graph.CycleError): self._char_graph('abc', ('ab', 'bc', 'ba')).topological_ordering() with self.assertRaises(Graph.CycleError): self._char_graph('ab', ('ab', 'ba')).topological_ordering()
def test_graph_search(self): graph = Graph() graph.add_vertex('a') graph.add_vertex('b') graph.add_vertex('c') graph.add_vertex('d') graph.add_vertex('george') graph.add_vertex('dave') graph.add_edge(Graph.Edge('a', 'b')) graph.add_edge(Graph.Edge('b', 'a')) graph.add_edge(Graph.Edge('b', 'c')) graph.add_edge(Graph.Edge('a', 'd')) graph.add_edge(Graph.Edge('a', 'dave')) graph.add_edge(Graph.Edge('george', 'dave')) expected = ''' Vertices: a b c d dave george Edges: a -> b a -> d a -> dave b -> a b -> c george -> dave ''' self.assertEquals(expected.strip(), str(graph)) self.assertEquals(set('ac'), graph.outgoing_vertices('b'), 'outgoing(b)') self.assertEquals(set('a'), graph.incoming_vertices('b'), 'incoming(b)') self.assertEquals(set('b'), graph.incoming_vertices('c'), 'incoming(c)') self.assertEquals(set('abcd').union(['dave']), graph.search_set('a'), 'search a -> ') self.assertEquals(set('c'), graph.search_set('c'), 'search c ->') self.assertEquals(set('abc'), graph.search_set('c', adjacent=graph.incoming_vertices), 'search <- c') self.assertEquals({Graph.Edge('a', 'b'), Graph.Edge('a', 'c')}, {Graph.Edge('a', 'b'), Graph.Edge('a', 'b'), Graph.Edge('a', 'c')})
def generate_gv(self, targets): all_targets = set() skipped_targets = set() def target_filter(target): if not self.get_options().include_tests: test_names = ('test', 'tests', 'testing') if target.has_label('tests') or any(part in test_names for part in target.id.split('.')): skipped_targets.add(target) return False all_targets.add(target) return True self._work_log('Generating target graph.') graph = self._create_target_graph(targets, predicate=target_filter) if skipped_targets: print(''.join('\n skipped {}'.format(target.id) for target in skipped_targets)) compress_projects = self.get_options().compress_projects if compress_projects: with self._work_block('Compressing projects.'): target_to_project = self._target_to_project_map(all_targets) # Make an exception for 3rdparty. for target in all_targets: if '3rdparty' in target.id: target_to_project[target.id] = target.id new_graph = Graph() for vertex in graph.vertices: new_graph.add_vertex(target_to_project[vertex]) for edge in graph.edges: new_graph.add_edge(Graph.Edge(target_to_project[edge.src], target_to_project[edge.dst])) graph = new_graph all_targets = { target_to_project.get(target.id) for target in all_targets } target_to_project = { target: target for target in all_targets } project_to_targets = { target: {target} for target in all_targets } else: with self._work_block('Calculating target project groups.'): target_to_project, project_to_targets = self._get_target_project_maps(targets) with self._work_block('Finding subgraphs.'): groups = sorted(map(sorted, project_to_targets.values())) target_to_color = {} if self.get_options().rainbow: with self._work_block('Assigning colors.'): for index, group in enumerate(groups): color = '"{hue}, {saturation}, {value}"'.format( hue = ((index * 32) % 360) / 360.0, saturation = 0.5 if index%2==0 else 0.2, value = 0.9 if index%2==0 else 0.7, ) for vertex in group: if '3rdparty' in vertex: continue target_to_color[vertex] = color with self._work_block('Assigning vertex names.'): alphabet = [chr(c) for c in range(ord('a'), ord('z')+1)] vertex_names = {} counter = [0] for group in groups: for vertex in group: if vertex in vertex_names: raise ValueError('Vertex is duplicated between disjoint sets! {}'.format(vertex)) vertex_names[vertex] = ''.join(alphabet[c] for c in counter) i = len(counter)-1 while True: if i < 0: counter.insert(0, 0) break counter[i] += 1 if counter[i] >= len(alphabet): counter[i] = 0 else: break i -= 1 with self._work_block('Generating .gv file.'): name = vertex_names.get external_color = 'maroon' yield 'digraph "{}" {{'.format(' '.join(sys.argv[1:])) yield ' rankdir=LR;' yield ' compound=true;' if self.get_options().concentrate_edges: yield ' concentrate=true;' self._work_log('Generating target vertices.') for index, group in enumerate(groups): label = None yield ' subgraph project_{} {{'.format(index) yield ' color=black;' for vertex in group: if label is None: label = vertex[:vertex.find('.')] yield ' label = "{}";'.format(label) args = { 'label': '"{}"'.format(vertex), 'shape': 'box', 'color': 'black', 'style': 'filled' } if '3rdparty' in vertex: args['shape'] = 'ellipse' args['color'] = external_color args['fillcolor'] = 'pink' else: args['fillcolor'] = target_to_color.get(vertex, 'lightgrey') yield ' {name} {args};'.format( name=name(vertex), args=self._dot_args(args), ) yield ' }' self._work_log('Generating edges.') for edge in graph.edges: same_project = target_to_project[edge.src] == target_to_project[edge.dst] args = { 'color': target_to_color.get(edge.dst, 'black'), } if '3rdparty' in edge.dst: args['color'] = external_color args['arrowsize'] = 0.5 args['style'] = 'dashed' elif same_project: args['weight'] = self.get_options().dot_project_weight yield ' {src} -> {dst}{args};'.format( src=name(edge.src), dst=name(edge.dst), args=self._dot_args(args) ) yield '}'