Skip to content

Commit 8d37a7c

Browse files
Implement visualise_cyclic_deps
1 parent 8d540a9 commit 8d37a7c

File tree

11 files changed

+101
-9
lines changed

11 files changed

+101
-9
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ Here's a component include dependency visualisation generated for the `queue` co
7474

7575
![Queue include graph d3](examples/rethinkdb_queue_include_d3.svg)
7676

77+
### Cyclic dependencies only graph
78+
79+
This will highlight cyclic dependencies between components within a project. This is especially useful for targeted refactoring activities to reduce coupling between components.
80+
81+
`cpp_dependency_graph visualise_cyclic_deps -r spec\test\example_project\`
82+
83+
Here's the cyclic dependencies only visualisation generated for [rethinkdb](https://github.com/rethinkdb/rethinkdb) and [leveldb](https://github.com/google/leveldb)
84+
85+
![rethinkdb](examples/rethinkdb_cyclic_deps.svg)
86+
87+
![leveldb](examples/leveldb_cyclic_deps.svg)
88+
7789
## Development
7890

7991
`bundle exec cpp_dependency_graph visualise -r <dir>`

TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
- [ ] Documentation
44
- [ ] Screencast of how to use tool on projects
55
- [ ] Create a github.io homepage
6+
- [ ] Add interactive `html` or `svg` examples to homepage
67
- [ ] Add a [CONTRIBUTING.md](https://github.com/nayafia/contributing-template/blob/master/CONTRIBUTING-template.md)
7-
- [ ] Add interactive `html` or `svg` examples into README, is this possible?
88
- [ ] Progress messages
99
- [ ] Progress bar?
1010
- [x] Allow user to specify a single component and the tool should print only that component
@@ -13,6 +13,7 @@
1313
- [ ] Manual scanning does not work when #includes are #ifdefed out for example
1414
- [ ] <system> include vs "project" include (how will this work for third party sources that are not part of the codebase?)
1515
- [ ] Work with any type of include relative includes ('blah.h') vs absolute includes ('/path/blah.h') vs relative with path includes ('blah/blah.h')
16+
- [ ] Look at https://github.com/Sarcasm/compdb to see if it can generate dependencies
1617
- [ ] Parallelise dependency scanning as much as possible to get the best possible performance
1718
- [ ] Open up the graph automatically after generating an individual graph
1819
- [ ] Work with any sort of project structure

examples/leveldb_cyclic_deps.svg

Lines changed: 1 addition & 0 deletions
Loading

examples/rethinkdb_cyclic_deps.svg

Lines changed: 1 addition & 0 deletions
Loading

exe/cpp_dependency_graph

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ begin
5858
Kernel.exit(0)
5959
end
6060

61-
unless File.directory?(args['--root_dir'])
61+
project_dir = args['--root_dir'].gsub(/\\/,'/')
62+
63+
unless File.directory?(project_dir)
6264
puts('Not a valid project source directory')
6365
Kernel.exit(1)
6466
end
6567

66-
project_dir = args['--root_dir'].gsub(/\\/,'/')
67-
6868
if args['visualise']
6969
if args['--component']
7070
generate_component_graph(project_dir, args['--component'], args['--output_format'], args['--output_file'])
@@ -76,7 +76,7 @@ begin
7676
elsif args['visualise_components']
7777
# TODO: Output components
7878
elsif args['visualise_cyclic_deps']
79-
output_cyclic_dependencies(project_dir)
79+
generate_cyclic_dependencies(project_dir, args['--output_format'], args['--output_file'])
8080
end
8181
rescue Docopt::Exit => e
8282
puts e.message

lib/cpp_dependency_graph.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ def generate_component_include_graph(project_dir, component_name, format, output
2929
generate_visualisation(deps, format, output_file)
3030
end
3131

32-
def output_cyclic_dependencies(project_dir)
32+
def generate_cyclic_dependencies(project_dir, format, file)
3333
project = Project.new(project_dir)
3434
graph = DependencyGraph.new(project)
35-
deps = graph.all_component_links
36-
cyclic_deps = deps.values.flatten.select { |dep| dep.cyclic? }
37-
puts JSON.pretty_generate(cyclic_deps)
35+
deps = graph.all_cyclic_links
36+
generate_visualisation(deps, format, file)
3837
end
3938

4039
def generate_visualisation(deps, format, file)
@@ -43,6 +42,8 @@ def generate_visualisation(deps, format, file)
4342
GraphVisualiser.new.generate_dot_file(deps, file)
4443
when 'html'
4544
GraphVisualiser.new.generate_html_file(deps, file)
45+
when 'json'
46+
File.write(file, JSON.pretty_generate(deps))
4647
end
4748
end
4849
end

lib/cpp_dependency_graph/component_link.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ def cyclic?
2121
@cyclic
2222
end
2323

24+
def ==(other)
25+
(source == other.source && target == other.target && cyclic? == other.cyclic?) ||
26+
(source == other.target && target == other.source && cyclic? == other.cyclic?)
27+
end
28+
29+
def hash
30+
[source, target, cyclic?].to_set.hash
31+
end
32+
2433
def to_s
2534
if cyclic?
2635
"#{source} <-> #{target}"

lib/cpp_dependency_graph/dependency_graph.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ def all_component_links
1818
@all_component_links ||= build_hash_component_links
1919
end
2020

21+
def all_cyclic_links
22+
@cyclic_links ||= build_cyclic_links
23+
end
24+
2125
def component_links(name)
2226
return {} unless all_component_links.key?(name)
2327
incoming_links(name).merge(outgoing_links(name))
@@ -35,6 +39,11 @@ def build_hash_component_links
3539
component_links
3640
end
3741

42+
def build_cyclic_links
43+
cyclic_links = all_component_links.select { |_, links| links.any? { |link| link.cyclic? } }
44+
cyclic_links.each { |_, links| links.select! { |link| link.cyclic? } }
45+
end
46+
3847
def outgoing_links(name)
3948
all_component_links.slice(name)
4049
end

spec/test/component_link_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,16 @@
1616
it 'returns correct cyclic? attribute' do
1717
expect(ComponentLink.new('source', 'target', true).cyclic?).to eq(true)
1818
end
19+
20+
it 'returns equal if another instance has same attributes' do
21+
c1 = ComponentLink.new('source', 'target', true)
22+
c2 = ComponentLink.new('target', 'source', true)
23+
expect(c1). to eq(c2)
24+
end
25+
26+
it 'returns not equal if another instance has different attributes' do
27+
c1 = ComponentLink.new('source1', 'target1', true)
28+
c2 = ComponentLink.new('source2', 'target2', true)
29+
expect(c1).to_not eq(c2)
30+
end
1931
end

spec/test/cyclic_link_spec.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,13 @@
88
it 'returns the same hash for two equivalent objects with the same nodes' do
99
expect(CyclicLink.new('nodeA', 'nodeB').hash).to eq(CyclicLink.new('nodeB', 'nodeA').hash)
1010
end
11+
12+
it 'implements eql? operator correctly for usage as a hash key' do
13+
h = {}
14+
c1 = CyclicLink.new('nodeA', 'nodeB')
15+
c2 = CyclicLink.new('nodeB', 'nodeA')
16+
h[c1] = true
17+
h[c2] = false
18+
expect(h[c1]).to eq(false)
19+
end
1120
end

0 commit comments

Comments
 (0)