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_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 '}'