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 Getopt
::Long
qw(GetOptions);
14 use List
::Util
qw(sum);
15 use File
::Find
qw(find);
16 use graphincludes
::params
;
22 #FIXME: also set arrow head
23 our $paperparams='-Gnodesep=0.1 -Granksep=0.1 -Nfontsize=5 -Efontsize=5';
34 our ($outfile, $prefixstrip, $paper);
37 Usage: $0 [options] src/*.[ch]
39 --class {default|uniqueincludes|<your-own-class>}
40 Select "class" of source code
41 --language <lang> Select language syntax for dependency extraction (default: C)
42 -I <directory> Adds a directory to the path where to look for include files
43 --prefixstrip <prefix> Strip <prefix> (eg. "src/") from filenames in the graph
44 --group <min>-<max> Draw file groups of levels <min> through <max> (default: 1 1)
45 --color <n>:<label>=<color>[,<label>=<color>...]
46 Use specified colors to show members of level-<n> group labelled <label>
47 --alldeps Do not apply transitive reduction to the graph
49 --showdropped Show in special color edges dropped during transitive reduction
50 --focus <node-label> Like -showdropped but only for edges starting from given node
52 --output <outfile>.<fmt>
53 Format to output file, using <fmt> as target format
55 --verbose Show progress
56 --verbose Show details of ignored include directives
57 --debug Loads of debuging output
64 GetOptions
('alldeps' => \
$showalldeps,
65 'showdropped' => \
$graphincludes::params
::showdropped
,
67 'focus=s' => \
@graphincludes::params
::focus
,
69 'language=s' => \
$language,
71 'Include=s' => \
@graphincludes::params
::inclpath
,
72 'sysInclude=s' => \
@graphincludes::params
::sysinclpath
,
75 my (undef, $range) = @_;
76 ($graphincludes::params
::minshow
, $graphincludes::params
::maxshow
) = split /-/, $range;
79 my (undef, $colspec) = @_;
80 my @temp = split /:/, $colspec;
81 push @colspecs, [$temp[1], $temp[2]];
83 'output=s' => \
$outfile,
86 'prefixstrip=s' => \
$prefixstrip,
88 'verbose+' => \
$graphincludes::params
::verbose
,
89 'debug' => \
$graphincludes::params
::debug
,
90 'help' => sub { print $usage; exit 0; },
92 ) or die "Could not parse command-line: $!";
94 # deal with non-default output formats
100 die "Unkown paper size \`$paper'" unless defined $paper{$paper};
101 # paper output format is postscript on stdout unless otherwise specified
102 $outputformat = 'ps';
103 $dotflags .= " $paperparams -Gpage=" . $paper{$paper};
106 if (defined $outfile) {
107 $dotflags .= " -o $outfile";
109 $outfile =~ m/.*\.([^.]+)$/ or die "cannot guess output format";
113 $dotflags .= " -T$outputformat" if defined $outputformat;
115 if ($dotflags ne '') {
116 print STDERR
"Running through \`dot $dotflags'\n" if $graphincludes::params
::verbose
;
117 open STDOUT
, "| dot $dotflags";
120 # create a project with specified files
121 our $classmodule = "graphincludes::project::" . $class;
122 eval "require $classmodule" or die "cannot load $classmodule from " . join ':', @INC;
123 $classmodule->set_language ($language) or die "cannot set language to '$language'";
125 foreach my $arg (@ARGV) {
127 find
( { no_chdir
=> 0,
129 push @files, $File::Find
::name
if m/\.[ch]pp$/;
134 die "file does not exist: $arg";
137 our $project = ($classmodule)->new(prefixstrip
=> $prefixstrip,
141 @colors = $project->defaultcolors();
142 foreach my $colspec (@colspecs) {
143 foreach my $coldef (split /,/, $colspec->[2]) {
144 my @coldef = split /=/, $coldef;
145 $colors[$colspec->[1]]->{$coldef[0]} = $coldef[1];
149 our $stat_nfiles = scalar @
{$project->{FILES
}};
150 # NOTE: $stat_nedges below is a cut'n'paste of $stat_ndeps
151 our $stat_ndeps = sum
(map { scalar keys %{$project->{DEPS
}{$_}} } (keys %{$project->{DEPS
}}));
153 # maybe get rid of shortcut deps (transitive reduction)
154 $project->reduce() unless ($showalldeps);
156 our $stat_nnodes = scalar keys %{$project->{NODES
}};
157 our $stat_nleaves = $stat_nnodes - scalar keys %{$project->{DEPS
}};
158 # NOTE: $stat_ndeps above is a cut'n'paste of $stat_nedges
159 our $stat_nedges = sum
(map { scalar keys %{$project->{DEPS
}{$_}} } (keys %{$project->{DEPS
}}));
163 print "strict digraph dependencies {\nrankdir=LR;\n";
166 my ($file, $min, $max) = @_;
167 my $node = $project->filelabel($file,$max);
168 if (!defined $node and $max > $min) {
169 return sprintnode
($file, $min, $max-1);
170 } elsif ($min == $max) {
172 $fill=",style=filled,fillcolor=" . $colors[2]->{$project->filelabel($file,2)}
173 if defined $colors[2] and defined $project->filelabel($file,2) and
174 defined $colors[2]->{$project->filelabel($file,2)};
175 return $project->{NODEIDS
}->{$node} . " [label=\"$node\"" . $fill . "];";
177 return "subgraph \"cluster $node\" {" . sprintnode
($file, $min, $max-1) . '}';
181 foreach my $file (@
{$project->{FILES
}}) {
182 print sprintnode
($file, $graphincludes::params
::minshow
, $graphincludes::params
::maxshow
), "\n";
185 foreach my $file ($project->get_dep_origins) {
186 foreach my $dest ($project->get_dependencies($file)) {
187 print "$file -> $dest";
188 my $special = $project->special_edge($file, $dest);
189 # special handling for label, as array
190 push @
{$special->{label
}}, '[' . $project->get_dependency_weight($file, $dest) . ']';
191 $special->{label
} = join '\n', @
{$special->{label
}};
193 print " [", join (',', map {$_ . '="' . $special->{$_} . '"'} keys %$special), "]" if defined $special;
203 our $report = 'graph-includes.report';
204 $report = $outfile . '.' . $report if defined $outfile;
205 open REPORT
, ">$report" or die "cannot open $report for writing: $!";
206 print REPORT
"\n Graph-includes report";
207 print REPORT
"\n =====================\n";
209 print REPORT
"\nGeneral statistics:";
210 print REPORT
"\n-------------------\n\n";
211 print REPORT
"$stat_nfiles files, $stat_nnodes nodes (",
212 int(100*($stat_nfiles-$stat_nnodes)/$stat_nfiles), "% dropped)\n";
213 print REPORT
"$stat_ndeps dependencies, $stat_nedges edges (",
214 int(100*($stat_ndeps-$stat_nedges)/$stat_ndeps), "% dropped)\n";
215 print REPORT
"$stat_nleaves leaf node(s)\n";
218 print REPORT
scalar keys %{$project->{REPORT
}->{HDR
}}, " dependencies not found\n";
219 print REPORT
scalar keys %{$project->{REPORT
}->{SYS
}}, " dependencies identified as system headers\n";
221 print REPORT
"\nDeclared dependencies not found:";
222 print REPORT
"\n--------------------------------\n\n";
223 for my $dep (sort keys %{$project->{REPORT
}->{HDR
}}) {
224 print REPORT
" $dep\n";
225 for my $src (@
{$project->{REPORT
}->{HDR
}->{$dep}}) {
226 print REPORT
" from $src\n";
230 print REPORT
"\nUsed system headers:";
231 print REPORT
"\n--------------------\n\n";
232 for my $dep (sort keys %{$project->{REPORT
}->{SYS
}}) {
233 print REPORT
" $dep\n";
237 # wait for dot if needed
238 if ($dotflags ne '') {