minor touchups
[deps.git] / graph-includes
blob8b732174bbaaeb58a76ac18c256c4d023a736ef7
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 Yann Dirson <ydirson@altern.org>
8 # Distributed under version 2 of the GNU GPL.
10 use warnings;
11 use strict;
13 use Getopt::Long qw(GetOptions);
14 use List::Util qw(sum);
15 use File::Find qw(find);
16 use graphincludes::params;
18 package foo;
19 our $a=0;
20 package main;
21 $a=1;
22 #FIXME: also set arrow head
23 our $paperparams='-Gnodesep=0.1 -Granksep=0.1 -Nfontsize=5 -Efontsize=5';
24 our %paper = (
25 a4 => '11.6,8.2',
26 a3 => '16.5,11.6',
27 letter => '11,8.5',
30 our $showalldeps=0;
31 our $class='default';
32 our $language='C';
33 our (@colors);
34 our ($outfile, $prefixstrip, $paper);
36 our $usage = <<EOF;
37 Usage: $0 [options] src/*.[ch]
38 Options:
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
59 --help This help text
60 EOF
62 our @colspecs;
64 GetOptions ('alldeps' => \$showalldeps,
65 'showdropped' => \$graphincludes::params::showdropped,
67 'focus=s' => \@graphincludes::params::focus,
68 'class=s' => \$class,
69 'language=s' => \$language,
71 'Include=s' => \@graphincludes::params::inclpath,
72 'sysInclude=s' => \@graphincludes::params::sysinclpath,
74 'group=s' => sub {
75 my (undef, $range) = @_;
76 ($graphincludes::params::minshow, $graphincludes::params::maxshow) = split /-/, $range;
78 'color=s@' => sub {
79 my (undef, $colspec) = @_;
80 my @temp = split /:/, $colspec;
81 push @colspecs, [$temp[1], $temp[2]];
83 'output=s' => \$outfile,
84 'paper=s' => \$paper,
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
96 our $dotflags = '';
97 our $outputformat;
99 if (defined $paper) {
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";
110 $outputformat = $1;
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'";
124 our @files;
125 foreach my $arg (@ARGV) {
126 if (-d $arg) {
127 find ( { no_chdir => 0,
128 wanted => sub {
129 push @files, $File::Find::name if m/\.[ch]pp$/;
130 } }, $arg);
131 } elsif (-r $arg) {
132 push @files, $arg;
133 } else {
134 die "file does not exist: $arg";
137 our $project = ($classmodule)->new(prefixstrip => $prefixstrip,
138 files => \@files);
139 $project->init();
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}}));
161 # print graph
163 print "strict digraph dependencies {\nrankdir=LR;\n";
165 sub sprintnode {
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) {
171 my $fill='';
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 . "];";
176 } else {
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;
194 print ";\n";
198 print "}\n";
201 # print report
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";
217 print REPORT "\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";
235 close REPORT;
237 # wait for dot if needed
238 if ($dotflags ne '') {
239 close STDOUT;
240 wait;