Add transform::consolidate
[deps.git] / graph-includes
blob2d9d13467f2faa8c4758167f075e8441ff6b5344
1 #!/usr/bin/env perl
3 # graph-includes - create a graphviz graph of source-files
4 # dependencies, with an emphasis on getting usable graphs even for
5 # large projects
7 # (c) 2005,2006 Yann Dirson <ydirson@altern.org>
8 # Distributed under version 2 of the GNU GPL.
10 use warnings;
11 use strict;
13 use File::Basename qw(dirname);
14 use File::Spec::Functions qw(catdir);
15 use lib catdir(dirname($0), 'lib');
17 #BEGIN { print STDERR '@INC=', join (':', @INC)}
19 use Getopt::Long qw(GetOptions);
20 use List::Util qw(sum);
21 use File::Find qw(find);
22 use graphincludes::params;
24 our $showalldeps=0;
25 our $class='default';
26 our $language='C';
27 our (@colors, %colorstyles);
28 our ($outfile, $prefixstrip, $paper);
29 our $rendererclass = 'graphincludes::renderer::dot';
31 our $usage = <<EOF;
32 Usage: $0 [options] src/*.[ch]
33 Options:
34 -class {default|uniqueincludes|<your-own-class>}
35 Select "class" of source code
36 -language <lang> Select language syntax for dependency extraction (default: C)
37 -fileregexp <perl-regexp>
38 Use this regexp to identify interesting files inside directories
39 (overrides per-language default regexp)
40 -Include <directory> Adds a directory to the path where to look for project's include files
41 -sysInclude <directory> Adds a directory to the path where to look for system include files
42 -prefixstrip <prefix> Strip <prefix> (eg. "src/") from filenames in the graph
43 -group <min>-<max> Draw file groups of levels <min> through <max> (default: 1 1)
44 -color <n>:<label>=<color>[,<label>=<color>...]
45 Use specified colors to show members of level-<n> group labelled <label>
46 -alldeps Do not apply transitive reduction to the graph
48 -showdropped Show in special color edges dropped during transitive reduction
49 -focus <node-label> Like -showdropped but only for edges starting from given node
51 -renderer <engine> Select the rendering program to produce a graph for (default: dot)
52 -output <outfile>.<fmt>
53 Format to output file, using <fmt> as target format
54 -paper a4|a3|letter Select paper size of multi-paged postscript output
56 -verbose Show progress
57 -debug Loads of debuging output
59 -version Display this program's version
60 -help This help text
61 EOF
63 our @colspecs;
65 # memorize command-line for the report
66 our @commandline = @ARGV;
68 GetOptions ('alldeps' => \$showalldeps,
69 'showdropped' => \$graphincludes::params::showdropped,
71 'focus=s' => \@graphincludes::params::focus,
72 'class=s' => \$class,
73 'language=s' => \$language,
74 'fileregexp=s' => \$graphincludes::params::filename_regexp,
76 'renderer=s' => sub {
77 my (undef, $renderer) = @_;
78 $rendererclass = 'graphincludes::renderer::' . $renderer;
81 'Include=s' => \@graphincludes::params::inclpath,
82 'sysInclude=s' => \@graphincludes::params::sysinclpath,
84 'group=s' => sub {
85 my (undef, $range) = @_;
86 ($graphincludes::params::minshow, $graphincludes::params::maxshow) = split /-/, $range;
88 'color=s@' => sub {
89 my (undef, $colspec) = @_;
90 my @temp = split /:/, $colspec;
91 push @colspecs, [$temp[0], $temp[1]];
93 'output=s' => \$outfile,
94 'paper=s' => \$paper,
96 'prefixstrip=s' => \$prefixstrip,
98 'verbose+' => \$graphincludes::params::verbose,
99 'debug' => \$graphincludes::params::debug,
100 'help' => sub { print $usage; exit 0; },
101 'version' => sub { print "$0 version $graphincludes::params::VERSION\n"; exit 0; },
103 ) or print STDERR $usage and exit 1;
105 if (@ARGV == 0) {
106 print STDERR $usage;
107 exit 1;
110 eval "require $rendererclass" or die "cannot load $rendererclass from " . join ':', @INC;
111 my $renderer = new $rendererclass;
113 # deal with non-default output formats
115 $renderer->set_multipage($paper) if defined $paper;
116 $renderer->set_outputfile($outfile) if defined $outfile;
118 # create a project with specified files
119 our $classmodule = "graphincludes::project::" . $class;
120 eval "require $classmodule" or die "cannot load $classmodule from " . join ':', @INC;
121 $classmodule->set_language ($language) or die "cannot set language to '$language'";
122 our @files;
123 foreach my $arg (@ARGV) {
124 if (-d $arg) {
125 find ( { no_chdir => 0,
126 wanted => sub {
127 if ($classmodule->accepts_file ($_)) {
128 push @files, $File::Find::name;
129 print STDERR "Adding $File::Find::name\n" if $graphincludes::params::debug;
131 } }, $arg);
132 } elsif (-r $arg) {
133 push @files, $arg;
134 } else {
135 die "file does not exist: $arg";
139 # generate "level 0" graph
140 our $project = ($classmodule)->new(prefixstrip => $prefixstrip,
141 files => \@files);
142 push @graphincludes::params::sysinclpath, $project->get_default_sysincludes();
143 $project->init();
145 # generate group graphs according to fileabel()
146 use graphincludes::transform::compatgroup;
147 for (my $i = 1; $i <= $graphincludes::params::maxshow; $i++) {
148 $project->{GRAPHS}[$i] = graphincludes::transform::compatgroup::apply
149 ( graph => $project->{GRAPHS}[0],
150 level => $i,
151 labeller => $project,
152 previous => $project->{GRAPHS});
155 # consolidate graphs as requested by --group flag
156 if ($graphincludes::params::maxshow != $graphincludes::params::minshow) {
157 use graphincludes::transform::consolidate;
158 my @graphs = @{$project->{GRAPHS}}[$graphincludes::params::minshow .. $graphincludes::params::maxshow];
159 push @{$project->{GRAPHS}}, graphincludes::transform::consolidate::apply
160 ( graphs => \@graphs );
163 # maybe get rid of shortcut deps (transitive reduction)
164 unless ($showalldeps) {
165 use graphincludes::transform::transitivereduction;
166 push @{$project->{GRAPHS}},
167 graphincludes::transform::transitivereduction::apply
168 ( graph => $project->{GRAPHS}[$#{$project->{GRAPHS}}] );
171 @colors = $project->defaultcolors();
172 foreach my $colspec (@colspecs) {
173 foreach my $coldef (split /,/, $colspec->[1]) {
174 my @coldef = split /=/, $coldef;
175 $colors[$colspec->[0]]->{$coldef[0]} = $coldef[1];
179 # assign a role to each color: background, outline
181 my @roles = qw(bg border); my $role=0;
182 for (my $i=$#colors; $i >= $graphincludes::params::minshow; $i--) {
183 if (defined($colors[$i])) {
184 die "not enough supported color roles to color level $i"
185 if $role >= 2;
186 $colorstyles{$roles[$role]} = $i;
187 $role++;
192 our $stat_nfiles = scalar $project->{GRAPHS}[0]->get_nodes;
193 # NOTE: $stat_nedges below is a cut'n'paste of $stat_ndeps
194 our $stat_ndeps = sum (map { scalar ($project->{GRAPHS}[0]->get_edges_from($_)) }
195 ($project->{GRAPHS}[0]->get_edge_origins) );
197 if (!defined $stat_ndeps) {
198 print STDERR "$0: found no dependency\n";
199 exit 0;
202 #FIXME: ...
203 our $stat_nnodes = scalar $project->{GRAPHS}[0]->get_nodes;
204 our $stat_nleaves = $stat_nnodes - scalar ($project->{GRAPHS}[0]->get_edge_origins);
205 # NOTE: $stat_ndeps above is a cut'n'paste of $stat_nedges
206 our $stat_nedges = sum (map { scalar ($project->{GRAPHS}[0]->get_edges_from($_)) }
207 ($project->{GRAPHS}[0]->get_edge_origins));
209 # print graph
211 $renderer->printgraph($project->{GRAPHS}[$#{$project->{GRAPHS}}],
212 \@colors, \%colorstyles);
214 # print report
216 our $report = 'graph-includes.report';
217 $report = $outfile . '.' . $report if defined $outfile;
218 open REPORT, ">$report" or die "cannot open $report for writing: $!";
219 print REPORT "\n Graph-includes report";
220 print REPORT "\n =====================\n";
222 print REPORT "\nGeneral statistics:";
223 print REPORT "\n-------------------\n\n";
224 print REPORT "$stat_nfiles files, $stat_nnodes nodes (",
225 int(100*($stat_nfiles-$stat_nnodes)/$stat_nfiles), "% dropped)\n";
226 print REPORT "$stat_ndeps dependencies, $stat_nedges edges (",
227 int(100*($stat_ndeps-$stat_nedges)/$stat_ndeps), "% dropped)\n";
228 print REPORT "$stat_nleaves leaf node(s)\n";
230 print REPORT "\n";
231 print REPORT scalar keys %{$project->{REPORT}->{HDR}}, " dependencies not found\n";
232 print REPORT scalar keys %{$project->{REPORT}->{SYS}}, " dependencies identified as system headers\n";
234 print REPORT "\nDeclared dependencies not found:";
235 print REPORT "\n--------------------------------\n\n";
236 for my $dep (sort keys %{$project->{REPORT}->{HDR}}) {
237 print REPORT " $dep\n";
238 for my $src (@{$project->{REPORT}->{HDR}->{$dep}}) {
239 print REPORT " from $src\n";
243 print REPORT "\nUsed system headers:";
244 print REPORT "\n--------------------\n\n";
245 for my $dep (sort keys %{$project->{REPORT}->{SYS}}) {
246 print REPORT " $dep\n";
249 print REPORT "\nCommand-line used:";
250 print REPORT "\n------------------\n\n";
251 # display arguments separated by space, quoting any argument with embedded whitespace
252 print REPORT "$0 ", join ' ', map { m/\s/ ? "\"$_\"" : $_ } @commandline;
254 print REPORT "\n\nThis was $0 version $graphincludes::params::VERSION\n";
255 print REPORT "\n=== End of report ===\n";
256 close REPORT;
258 # wait for renderer to finish if needed
259 $renderer->wait();