3 # graph-includes - create a graphviz graph of source-files
4 # dependencies, with an emphasis on getting usable graphs even for
7 # (c) 2005 Yann Dirson <ydirson@altern.org>
8 # Distributed under version 2 of the GNU GPL.
13 use File
::Basename
qw(dirname);
14 use lib dirname
($0).'/lib';
16 use Getopt
::Long
qw(GetOptions);
17 use List
::Util
qw(sum);
18 use File
::Find
qw(find);
19 use graphincludes
::params
;
21 #FIXME: also set arrow head
22 our $paperparams='-Gnodesep=0.1 -Granksep=0.1 -Nfontsize=5 -Efontsize=5';
33 our ($outfile, $prefixstrip, $paper);
36 Usage: $0 [options] src/*.[ch]
38 -class {default|uniqueincludes|<your-own-class>}
39 Select "class" of source code
40 -language <lang> Select language syntax for dependency extraction (default: C)
41 -fileregexp <perl-regexp>
42 Use this regexp to identify interesting files inside directories
43 (overrides per-language default regexp)
44 -Include <directory> Adds a directory to the path where to look for project's include files
45 -sysInclude <directory> Adds a directory to the path where to look for system include files
46 -prefixstrip <prefix> Strip <prefix> (eg. "src/") from filenames in the graph
47 -group <min>-<max> Draw file groups of levels <min> through <max> (default: 1 1)
48 -color <n>:<label>=<color>[,<label>=<color>...]
49 Use specified colors to show members of level-<n> group labelled <label>
50 -alldeps Do not apply transitive reduction to the graph
52 -showdropped Show in special color edges dropped during transitive reduction
53 -focus <node-label> Like -showdropped but only for edges starting from given node
55 -output <outfile>.<fmt>
56 Format to output file, using <fmt> as target format
57 -paper a4|a3|letter Select paper size of multi-paged postscript output
59 -verbose Show progress
60 -debug Loads of debuging output
62 -version Display this program's version
68 # memorize command-line for the report
69 our @commandline = @ARGV;
71 GetOptions
('alldeps' => \
$showalldeps,
72 'showdropped' => \
$graphincludes::params
::showdropped
,
74 'focus=s' => \
@graphincludes::params
::focus
,
76 'language=s' => \
$language,
77 'fileregexp=s' => \
$graphincludes::params
::filename_regexp
,
79 'Include=s' => \
@graphincludes::params
::inclpath
,
80 'sysInclude=s' => \
@graphincludes::params
::sysinclpath
,
83 my (undef, $range) = @_;
84 ($graphincludes::params
::minshow
, $graphincludes::params
::maxshow
) = split /-/, $range;
87 my (undef, $colspec) = @_;
88 my @temp = split /:/, $colspec;
89 push @colspecs, [$temp[1], $temp[2]];
91 'output=s' => \
$outfile,
94 'prefixstrip=s' => \
$prefixstrip,
96 'verbose+' => \
$graphincludes::params
::verbose
,
97 'debug' => \
$graphincludes::params
::debug
,
98 'help' => sub { print $usage; exit 0; },
99 'version' => sub { print "$0 version $graphincludes::params::VERSION\n"; exit 0; },
101 ) or print STDERR
$usage and exit 1;
108 # deal with non-default output formats
113 if (defined $paper) {
114 die "Unkown paper size \`$paper'" unless defined $paper{$paper};
115 # paper output format is postscript on stdout unless otherwise specified
116 $outputformat = 'ps';
117 $dotflags .= " $paperparams -Gpage=" . $paper{$paper};
120 if (defined $outfile) {
121 $dotflags .= " -o $outfile";
123 $outfile =~ m/.*\.([^.]+)$/ or die "cannot guess output format";
127 $dotflags .= " -T$outputformat" if defined $outputformat;
129 if ($dotflags ne '') {
130 print STDERR
"Running through \`dot $dotflags'\n" if $graphincludes::params
::verbose
;
131 open STDOUT
, "| dot $dotflags";
134 # create a project with specified files
135 our $classmodule = "graphincludes::project::" . $class;
136 eval "require $classmodule" or die "cannot load $classmodule from " . join ':', @INC;
137 $classmodule->set_language ($language) or die "cannot set language to '$language'";
139 foreach my $arg (@ARGV) {
141 find
( { no_chdir
=> 0,
143 if ($classmodule->accepts_file ($_)) {
144 push @files, $File::Find
::name
;
145 print STDERR
"Adding $File::Find::name\n" if $graphincludes::params
::debug
;
151 die "file does not exist: $arg";
154 our $project = ($classmodule)->new(prefixstrip
=> $prefixstrip,
158 @colors = $project->defaultcolors();
159 foreach my $colspec (@colspecs) {
160 foreach my $coldef (split /,/, $colspec->[2]) {
161 my @coldef = split /=/, $coldef;
162 $colors[$colspec->[1]]->{$coldef[0]} = $coldef[1];
166 our $stat_nfiles = scalar @
{$project->{FILES
}};
167 # NOTE: $stat_nedges below is a cut'n'paste of $stat_ndeps
168 our $stat_ndeps = sum
(map { scalar keys %{$project->{DEPS
}{$_}} } (keys %{$project->{DEPS
}}));
170 if (!defined $stat_ndeps) {
171 print STDERR
"$0: found no dependency\n";
175 # maybe get rid of shortcut deps (transitive reduction)
176 $project->reduce() unless ($showalldeps);
178 our $stat_nnodes = scalar keys %{$project->{NODES
}};
179 our $stat_nleaves = $stat_nnodes - scalar keys %{$project->{DEPS
}};
180 # NOTE: $stat_ndeps above is a cut'n'paste of $stat_nedges
181 our $stat_nedges = sum
(map { scalar keys %{$project->{DEPS
}{$_}} } (keys %{$project->{DEPS
}}));
185 print "strict digraph dependencies {\nrankdir=LR;\n";
188 my ($file, $min, $max) = @_;
189 my $node = $project->filelabel($file,$max);
190 if (!defined $node and $max > $min) {
191 return sprintnode
($file, $min, $max-1);
192 } elsif ($min == $max) {
194 $fill=",style=filled,fillcolor=" . $colors[2]->{$project->filelabel($file,2)}
195 if defined $colors[2] and defined $project->filelabel($file,2) and
196 defined $colors[2]->{$project->filelabel($file,2)};
197 return $project->{NODEIDS
}->{$node} . " [label=\"$node\"" . $fill . "];";
199 return "subgraph \"cluster $node\" {" . sprintnode
($file, $min, $max-1) . '}';
203 foreach my $file (@
{$project->{FILES
}}) {
204 print sprintnode
($file, $graphincludes::params
::minshow
, $graphincludes::params
::maxshow
), "\n";
207 foreach my $file ($project->get_dep_origins) {
208 foreach my $dest ($project->get_dependencies($file)) {
209 print "$file -> $dest";
210 my $special = $project->special_edge($file, $dest);
211 # special handling for label, as array
212 push @
{$special->{label
}}, '[' . $project->get_dependency_weight($file, $dest) . ']';
213 $special->{label
} = join '\n', @
{$special->{label
}};
215 print " [", join (',', map {$_ . '="' . $special->{$_} . '"'} keys %$special), "]" if defined $special;
225 our $report = 'graph-includes.report';
226 $report = $outfile . '.' . $report if defined $outfile;
227 open REPORT
, ">$report" or die "cannot open $report for writing: $!";
228 print REPORT
"\n Graph-includes report";
229 print REPORT
"\n =====================\n";
231 print REPORT
"\nGeneral statistics:";
232 print REPORT
"\n-------------------\n\n";
233 print REPORT
"$stat_nfiles files, $stat_nnodes nodes (",
234 int(100*($stat_nfiles-$stat_nnodes)/$stat_nfiles), "% dropped)\n";
235 print REPORT
"$stat_ndeps dependencies, $stat_nedges edges (",
236 int(100*($stat_ndeps-$stat_nedges)/$stat_ndeps), "% dropped)\n";
237 print REPORT
"$stat_nleaves leaf node(s)\n";
240 print REPORT
scalar keys %{$project->{REPORT
}->{HDR
}}, " dependencies not found\n";
241 print REPORT
scalar keys %{$project->{REPORT
}->{SYS
}}, " dependencies identified as system headers\n";
243 print REPORT
"\nDeclared dependencies not found:";
244 print REPORT
"\n--------------------------------\n\n";
245 for my $dep (sort keys %{$project->{REPORT
}->{HDR
}}) {
246 print REPORT
" $dep\n";
247 for my $src (@
{$project->{REPORT
}->{HDR
}->{$dep}}) {
248 print REPORT
" from $src\n";
252 print REPORT
"\nUsed system headers:";
253 print REPORT
"\n--------------------\n\n";
254 for my $dep (sort keys %{$project->{REPORT
}->{SYS
}}) {
255 print REPORT
" $dep\n";
258 print REPORT
"\nCommand-line used:";
259 print REPORT
"\n------------------\n\n";
260 # display arguments separated by space, quoting any argument with embedded whitespace
261 print REPORT
"$0 ", join ' ', map { m/\s/ ?
"\"$_\"" : $_ } @commandline;
263 print REPORT
"\n\nThis was $0 version $graphincludes::params::VERSION\n";
264 print REPORT
"\n=== End of report ===\n";
267 # wait for dot if needed
268 if ($dotflags ne '') {