1*53ee8cc1Swenshuai.xi#! /usr/bin/perl 2*53ee8cc1Swenshuai.xi# 3*53ee8cc1Swenshuai.xi# Detect cycles in the header file dependency graph 4*53ee8cc1Swenshuai.xi# Vegard Nossum <vegardno@ifi.uio.no> 5*53ee8cc1Swenshuai.xi# 6*53ee8cc1Swenshuai.xi 7*53ee8cc1Swenshuai.xiuse strict; 8*53ee8cc1Swenshuai.xiuse warnings; 9*53ee8cc1Swenshuai.xi 10*53ee8cc1Swenshuai.xiuse Getopt::Long; 11*53ee8cc1Swenshuai.xi 12*53ee8cc1Swenshuai.ximy $opt_all; 13*53ee8cc1Swenshuai.ximy @opt_include; 14*53ee8cc1Swenshuai.ximy $opt_graph; 15*53ee8cc1Swenshuai.xi 16*53ee8cc1Swenshuai.xi&Getopt::Long::Configure(qw(bundling pass_through)); 17*53ee8cc1Swenshuai.xi&GetOptions( 18*53ee8cc1Swenshuai.xi help => \&help, 19*53ee8cc1Swenshuai.xi version => \&version, 20*53ee8cc1Swenshuai.xi 21*53ee8cc1Swenshuai.xi all => \$opt_all, 22*53ee8cc1Swenshuai.xi "I=s" => \@opt_include, 23*53ee8cc1Swenshuai.xi graph => \$opt_graph, 24*53ee8cc1Swenshuai.xi); 25*53ee8cc1Swenshuai.xi 26*53ee8cc1Swenshuai.xipush @opt_include, 'include'; 27*53ee8cc1Swenshuai.ximy %deps = (); 28*53ee8cc1Swenshuai.ximy %linenos = (); 29*53ee8cc1Swenshuai.xi 30*53ee8cc1Swenshuai.ximy @headers = grep { strip($_) } @ARGV; 31*53ee8cc1Swenshuai.xi 32*53ee8cc1Swenshuai.xiparse_all(@headers); 33*53ee8cc1Swenshuai.xi 34*53ee8cc1Swenshuai.xiif($opt_graph) { 35*53ee8cc1Swenshuai.xi graph(); 36*53ee8cc1Swenshuai.xi} else { 37*53ee8cc1Swenshuai.xi detect_cycles(@headers); 38*53ee8cc1Swenshuai.xi} 39*53ee8cc1Swenshuai.xi 40*53ee8cc1Swenshuai.xi 41*53ee8cc1Swenshuai.xisub help { 42*53ee8cc1Swenshuai.xi print "Usage: $0 [options] file...\n"; 43*53ee8cc1Swenshuai.xi print "\n"; 44*53ee8cc1Swenshuai.xi print "Options:\n"; 45*53ee8cc1Swenshuai.xi print " --all\n"; 46*53ee8cc1Swenshuai.xi print " --graph\n"; 47*53ee8cc1Swenshuai.xi print "\n"; 48*53ee8cc1Swenshuai.xi print " -I includedir\n"; 49*53ee8cc1Swenshuai.xi print "\n"; 50*53ee8cc1Swenshuai.xi print "To make nice graphs, try:\n"; 51*53ee8cc1Swenshuai.xi print " $0 --graph include/linux/kernel.h | dot -Tpng -o graph.png\n"; 52*53ee8cc1Swenshuai.xi exit; 53*53ee8cc1Swenshuai.xi} 54*53ee8cc1Swenshuai.xi 55*53ee8cc1Swenshuai.xisub version { 56*53ee8cc1Swenshuai.xi print "headerdep version 2\n"; 57*53ee8cc1Swenshuai.xi exit; 58*53ee8cc1Swenshuai.xi} 59*53ee8cc1Swenshuai.xi 60*53ee8cc1Swenshuai.xi# Get a file name that is relative to our include paths 61*53ee8cc1Swenshuai.xisub strip { 62*53ee8cc1Swenshuai.xi my $filename = shift; 63*53ee8cc1Swenshuai.xi 64*53ee8cc1Swenshuai.xi for my $i (@opt_include) { 65*53ee8cc1Swenshuai.xi my $stripped = $filename; 66*53ee8cc1Swenshuai.xi $stripped =~ s/^$i\///; 67*53ee8cc1Swenshuai.xi 68*53ee8cc1Swenshuai.xi return $stripped if $stripped ne $filename; 69*53ee8cc1Swenshuai.xi } 70*53ee8cc1Swenshuai.xi 71*53ee8cc1Swenshuai.xi return $filename; 72*53ee8cc1Swenshuai.xi} 73*53ee8cc1Swenshuai.xi 74*53ee8cc1Swenshuai.xi# Search for the file name in the list of include paths 75*53ee8cc1Swenshuai.xisub search { 76*53ee8cc1Swenshuai.xi my $filename = shift; 77*53ee8cc1Swenshuai.xi return $filename if -f $filename; 78*53ee8cc1Swenshuai.xi 79*53ee8cc1Swenshuai.xi for my $i (@opt_include) { 80*53ee8cc1Swenshuai.xi my $path = "$i/$filename"; 81*53ee8cc1Swenshuai.xi return $path if -f $path; 82*53ee8cc1Swenshuai.xi } 83*53ee8cc1Swenshuai.xi return; 84*53ee8cc1Swenshuai.xi} 85*53ee8cc1Swenshuai.xi 86*53ee8cc1Swenshuai.xisub parse_all { 87*53ee8cc1Swenshuai.xi # Parse all the headers. 88*53ee8cc1Swenshuai.xi my @queue = @_; 89*53ee8cc1Swenshuai.xi while(@queue) { 90*53ee8cc1Swenshuai.xi my $header = pop @queue; 91*53ee8cc1Swenshuai.xi next if exists $deps{$header}; 92*53ee8cc1Swenshuai.xi 93*53ee8cc1Swenshuai.xi $deps{$header} = [] unless exists $deps{$header}; 94*53ee8cc1Swenshuai.xi 95*53ee8cc1Swenshuai.xi my $path = search($header); 96*53ee8cc1Swenshuai.xi next unless $path; 97*53ee8cc1Swenshuai.xi 98*53ee8cc1Swenshuai.xi open(my $file, '<', $path) or die($!); 99*53ee8cc1Swenshuai.xi chomp(my @lines = <$file>); 100*53ee8cc1Swenshuai.xi close($file); 101*53ee8cc1Swenshuai.xi 102*53ee8cc1Swenshuai.xi for my $i (0 .. $#lines) { 103*53ee8cc1Swenshuai.xi my $line = $lines[$i]; 104*53ee8cc1Swenshuai.xi if(my($dep) = ($line =~ m/^#\s*include\s*<(.*?)>/)) { 105*53ee8cc1Swenshuai.xi push @queue, $dep; 106*53ee8cc1Swenshuai.xi push @{$deps{$header}}, [$i + 1, $dep]; 107*53ee8cc1Swenshuai.xi } 108*53ee8cc1Swenshuai.xi } 109*53ee8cc1Swenshuai.xi } 110*53ee8cc1Swenshuai.xi} 111*53ee8cc1Swenshuai.xi 112*53ee8cc1Swenshuai.xisub print_cycle { 113*53ee8cc1Swenshuai.xi # $cycle[n] includes $cycle[n + 1]; 114*53ee8cc1Swenshuai.xi # $cycle[-1] will be the culprit 115*53ee8cc1Swenshuai.xi my $cycle = shift; 116*53ee8cc1Swenshuai.xi 117*53ee8cc1Swenshuai.xi # Adjust the line numbers 118*53ee8cc1Swenshuai.xi for my $i (0 .. $#$cycle - 1) { 119*53ee8cc1Swenshuai.xi $cycle->[$i]->[0] = $cycle->[$i + 1]->[0]; 120*53ee8cc1Swenshuai.xi } 121*53ee8cc1Swenshuai.xi $cycle->[-1]->[0] = 0; 122*53ee8cc1Swenshuai.xi 123*53ee8cc1Swenshuai.xi my $first = shift @$cycle; 124*53ee8cc1Swenshuai.xi my $last = pop @$cycle; 125*53ee8cc1Swenshuai.xi 126*53ee8cc1Swenshuai.xi my $msg = "In file included"; 127*53ee8cc1Swenshuai.xi printf "%s from %s,\n", $msg, $last->[1] if defined $last; 128*53ee8cc1Swenshuai.xi 129*53ee8cc1Swenshuai.xi for my $header (reverse @$cycle) { 130*53ee8cc1Swenshuai.xi printf "%s from %s:%d%s\n", 131*53ee8cc1Swenshuai.xi " " x length $msg, 132*53ee8cc1Swenshuai.xi $header->[1], $header->[0], 133*53ee8cc1Swenshuai.xi $header->[1] eq $last->[1] ? ' <-- here' : ''; 134*53ee8cc1Swenshuai.xi } 135*53ee8cc1Swenshuai.xi 136*53ee8cc1Swenshuai.xi printf "%s:%d: warning: recursive header inclusion\n", 137*53ee8cc1Swenshuai.xi $first->[1], $first->[0]; 138*53ee8cc1Swenshuai.xi} 139*53ee8cc1Swenshuai.xi 140*53ee8cc1Swenshuai.xi# Find and print the smallest cycle starting in the specified node. 141*53ee8cc1Swenshuai.xisub detect_cycles { 142*53ee8cc1Swenshuai.xi my @queue = map { [[0, $_]] } @_; 143*53ee8cc1Swenshuai.xi while(@queue) { 144*53ee8cc1Swenshuai.xi my $top = pop @queue; 145*53ee8cc1Swenshuai.xi my $name = $top->[-1]->[1]; 146*53ee8cc1Swenshuai.xi 147*53ee8cc1Swenshuai.xi for my $dep (@{$deps{$name}}) { 148*53ee8cc1Swenshuai.xi my $chain = [@$top, [$dep->[0], $dep->[1]]]; 149*53ee8cc1Swenshuai.xi 150*53ee8cc1Swenshuai.xi # If the dep already exists in the chain, we have a 151*53ee8cc1Swenshuai.xi # cycle... 152*53ee8cc1Swenshuai.xi if(grep { $_->[1] eq $dep->[1] } @$top) { 153*53ee8cc1Swenshuai.xi print_cycle($chain); 154*53ee8cc1Swenshuai.xi next if $opt_all; 155*53ee8cc1Swenshuai.xi return; 156*53ee8cc1Swenshuai.xi } 157*53ee8cc1Swenshuai.xi 158*53ee8cc1Swenshuai.xi push @queue, $chain; 159*53ee8cc1Swenshuai.xi } 160*53ee8cc1Swenshuai.xi } 161*53ee8cc1Swenshuai.xi} 162*53ee8cc1Swenshuai.xi 163*53ee8cc1Swenshuai.xisub mangle { 164*53ee8cc1Swenshuai.xi $_ = shift; 165*53ee8cc1Swenshuai.xi s/\//__/g; 166*53ee8cc1Swenshuai.xi s/\./_/g; 167*53ee8cc1Swenshuai.xi s/-/_/g; 168*53ee8cc1Swenshuai.xi $_; 169*53ee8cc1Swenshuai.xi} 170*53ee8cc1Swenshuai.xi 171*53ee8cc1Swenshuai.xi# Output dependency graph in GraphViz language. 172*53ee8cc1Swenshuai.xisub graph { 173*53ee8cc1Swenshuai.xi print "digraph {\n"; 174*53ee8cc1Swenshuai.xi 175*53ee8cc1Swenshuai.xi print "\t/* vertices */\n"; 176*53ee8cc1Swenshuai.xi for my $header (keys %deps) { 177*53ee8cc1Swenshuai.xi printf "\t%s [label=\"%s\"];\n", 178*53ee8cc1Swenshuai.xi mangle($header), $header; 179*53ee8cc1Swenshuai.xi } 180*53ee8cc1Swenshuai.xi 181*53ee8cc1Swenshuai.xi print "\n"; 182*53ee8cc1Swenshuai.xi 183*53ee8cc1Swenshuai.xi print "\t/* edges */\n"; 184*53ee8cc1Swenshuai.xi for my $header (keys %deps) { 185*53ee8cc1Swenshuai.xi for my $dep (@{$deps{$header}}) { 186*53ee8cc1Swenshuai.xi printf "\t%s -> %s;\n", 187*53ee8cc1Swenshuai.xi mangle($header), mangle($dep->[1]); 188*53ee8cc1Swenshuai.xi } 189*53ee8cc1Swenshuai.xi } 190*53ee8cc1Swenshuai.xi 191*53ee8cc1Swenshuai.xi print "}\n"; 192*53ee8cc1Swenshuai.xi} 193