예제 #1
0
  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])
예제 #2
0
  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]
예제 #3
0
 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
예제 #4
0
  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()
예제 #5
0
  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')})
예제 #6
0
  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 '}'