3 # graph-includes - create a graphviz graph of source-files
4 # dependencies, with an emphasis on getting usable graphs even for
7 # (c) 2005,2006 Yann Dirson <ydirson@altern.org>
8 # Distributed under version 2 of the GNU GPL.
13 use File
::Basename
qw(dirname);
14 use File
::Spec
::Functions
qw(catdir canonpath);
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
;
27 our (@colors, @nodestylers, @edgestylers);
28 our ($outfile, $prefixstrip, $paper);
29 our $rendererclass = 'graphincludes::renderer::dot';
32 Usage: $0 [options] src/*.[ch]
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 -consolidate <min>-<max>
44 Consolidate 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 -renderer <engine> Select the rendering program to produce a graph for (default: dot)
53 -output <outfile>.<fmt>
54 Format to output file, using <fmt> as target format
55 -paper a4|a3|letter Select paper size of multi-paged postscript output
57 -verbose Show progress
58 -debug Loads of debuging output
60 -version Display this program's version
66 # memorize command-line for the report
67 our @commandline = @ARGV;
69 GetOptions
('alldeps' => \
$showalldeps,
70 'showdropped' => \
$graphincludes::params
::showdropped
,
72 'focus=s' => \
@graphincludes::params
::focus
,
74 'language=s' => \
$language,
75 'fileregexp=s' => \
$graphincludes::params
::filename_regexp
,
78 my (undef, $renderer) = @_;
79 $rendererclass = 'graphincludes::renderer::' . $renderer;
82 'Include=s' => \
@graphincludes::params
::inclpath
,
83 'sysInclude=s' => \
@graphincludes::params
::sysinclpath
,
85 'consolidate=s' => sub {
86 my (undef, $range) = @_;
87 ($graphincludes::params
::minshow
, $graphincludes::params
::maxshow
) = split /-/, $range;
90 my (undef, $colspec) = @_;
91 my @temp = split /:/, $colspec;
92 push @colspecs, [$temp[0], $temp[1]];
94 'output=s' => \
$outfile,
97 'prefixstrip=s' => \
$prefixstrip,
99 'verbose+' => \
$graphincludes::params
::verbose
,
100 'debug' => \
$graphincludes::params
::debug
,
101 'help' => sub { print $usage; exit 0; },
102 'version' => sub { print "$0 version $graphincludes::params::VERSION\n"; exit 0; },
104 ) or print STDERR
$usage and exit 1;
111 eval "require $rendererclass" or die "cannot load '$rendererclass': $@";
112 my $renderer = new
$rendererclass;
114 # deal with non-default output formats
116 $renderer->set_multipage($paper) if defined $paper;
117 $renderer->set_outputfile($outfile) if defined $outfile;
119 # create a project with specified files
120 our $classmodule = "graphincludes::project::" . $class;
121 eval "require $classmodule" or die "cannot load '$classmodule': $@";
122 $classmodule->set_language ($language) or die "cannot set language to '$language'";
124 foreach my $arg (@ARGV) {
126 find
( { no_chdir
=> 0,
128 if ($classmodule->accepts_file ($_)) {
129 push @files, canonpath
($File::Find
::name
);
130 print STDERR
"Adding $File::Find::name\n" if $graphincludes::params
::debug
;
136 die "file does not exist: $arg";
140 # generate "level 0" graph
141 our $project = ($classmodule)->new(prefixstrip
=> $prefixstrip,
143 push @graphincludes::params
::sysinclpath
, $project->get_default_sysincludes();
146 # Generate group graphs according to filelabel()
147 # Since we may use coloring according to groups, regardless of which groups the
148 # nodes we draw are from, we must compute graphs for all group levels
149 my @previous = ('files');
150 for (my $i = 1; $i <= $project->nlevels; $i++) {
151 $project->apply_transform('DEPS::Transform::CompatGroup',
153 labeller
=> $project,
154 previous
=> \
@previous},
157 DEPS
::Transform
::CompatGroup
::fixup_dep
($project->{TRANSGRAPH
},
158 $previous[$#previous], "level$i-groups", 'files');
159 push @previous, "level$i-groups";
164 # consolidate graphs as requested by --group flag
165 if ($graphincludes::params
::maxshow
!= $graphincludes::params
::minshow
) {
166 my @graphnames = map { ($_==0) ?
'files':"level$_-groups" } ($graphincludes::params
::minshow
.. $graphincludes::params
::maxshow
);
167 $graphtodraw = "consolidation $graphincludes::params::minshow-$graphincludes::params::maxshow";
168 $project->apply_transform('DEPS::Transform::Consolidate',
172 } elsif ($graphincludes::params
::maxshow
== 0) {
173 $graphtodraw = 'files';
175 $graphtodraw = 'level'.$graphincludes::params
::maxshow
.'-groups';
178 # maybe get rid of shortcut deps (transitive reduction)
179 unless ($showalldeps) {
180 $project->apply_transform('DEPS::Transform::TransitiveReduction',
184 $graphtodraw = 'reduction';
187 @colors = $project->defaultcolors();
188 foreach my $colspec (@colspecs) {
189 foreach my $coldef (split /,/, $colspec->[1]) {
190 my @coldef = split /=/, $coldef;
191 $colors[$colspec->[0]]->{$coldef[0]} = $coldef[1];
195 # assign a role to each color: background, outline
197 use DEPS
::Style
::Node
::PerGroup
;
198 my @roles = qw(bgcolor bordercolor); my $role=0;
199 for (my $i=$#colors; $i >= $graphincludes::params
::minshow
; $i--) {
200 if (defined($colors[$i])) {
201 die "not enough supported color roles to color level $i"
203 push @nodestylers, new DEPS
::Style
::Node
::PerGroup
(attribute
=> $roles[$role],
204 valuemap
=> $colors[$i],
205 transgraph
=> $project->{TRANSGRAPH
},
207 refgraph
=> "level$i-groups");
213 # number of ingredients and intra edges in nodes
214 use DEPS
::Style
::Node
::GroupStats
;
215 push @nodestylers, new DEPS
::Style
::Node
::GroupStats
();
216 # number of ingredients in edges
217 use DEPS
::Style
::Edge
::WeightLabel
;
218 push @edgestylers, new DEPS
::Style
::Edge
::WeightLabel
();
220 our $stat_nfiles = scalar $project->{ROOTGRAPH
}->get_nodes;
221 # NOTE: $stat_nedges below is a cut'n'paste of $stat_ndeps
222 our $stat_ndeps = sum
(map { scalar ($project->{ROOTGRAPH
}->get_edges_from($_)) }
223 ($project->{ROOTGRAPH
}->get_edge_origins) );
225 if (!defined $stat_ndeps or $stat_ndeps == 0) {
226 print STDERR
"$0: found no dependency\n";
230 # the graph to be drawn
231 my $thegraph = $project->{TRANSGRAPH
}->get_node_from_name($graphtodraw)->{DATA
};
234 our $stat_nnodes = scalar $thegraph->get_nodes;
235 our $stat_nroots = $stat_nnodes - scalar ($thegraph->get_edge_origins);
236 # NOTE: $stat_ndeps above is a cut'n'paste of $stat_nedges
237 our $stat_nedges = sum
(map { scalar ($thegraph->get_edges_from($_)) }
238 ($thegraph->get_edge_origins) );
241 $renderer->printgraph($project->{TRANSGRAPH
}->get_node_from_name($graphtodraw),
242 \
@nodestylers, \
@edgestylers);
246 our $report = 'graph-includes.report';
247 $report = $outfile . '.' . $report if defined $outfile;
248 open REPORT
, ">$report" or die "cannot open $report for writing: $!";
249 print REPORT
"\n Graph-includes report";
250 print REPORT
"\n =====================\n";
252 print REPORT
"\nGeneral statistics:";
253 print REPORT
"\n-------------------\n\n";
254 print REPORT
"$stat_nfiles files, $stat_nnodes nodes (",
255 int(100*($stat_nfiles-$stat_nnodes)/$stat_nfiles), "% dropped)\n";
256 print REPORT
"$stat_ndeps dependencies, $stat_nedges edges (",
257 int(100*($stat_ndeps-$stat_nedges)/$stat_ndeps), "% dropped)\n";
258 print REPORT
"$stat_nroots root node(s)\n";
261 print REPORT
scalar keys %{$project->{REPORT
}->{HDR
}}, " dependencies not found\n";
262 print REPORT
scalar keys %{$project->{REPORT
}->{SYS
}}, " dependencies identified as system headers\n";
264 print REPORT
"\nDeclared dependencies not found:";
265 print REPORT
"\n--------------------------------\n\n";
266 for my $dep (sort keys %{$project->{REPORT
}->{HDR
}}) {
267 print REPORT
" $dep\n";
268 for my $src (@
{$project->{REPORT
}->{HDR
}->{$dep}}) {
269 print REPORT
" from $src\n";
273 print REPORT
"\nUsed system headers:";
274 print REPORT
"\n--------------------\n\n";
275 for my $dep (sort keys %{$project->{REPORT
}->{SYS
}}) {
276 print REPORT
" $dep\n";
279 print REPORT
"\nCommand-line used:";
280 print REPORT
"\n------------------\n\n";
281 # display arguments separated by space, quoting any argument with embedded whitespace
282 print REPORT
"$0 ", join ' ', map { m/\s/ ?
"\"$_\"" : $_ } @commandline;
284 print REPORT
"\n\nThis was $0 version $graphincludes::params::VERSION\n";
285 print REPORT
"\n=== End of report ===\n";
288 # wait for renderer to finish if needed