1179efcb4SVegard Nossum#! /usr/bin/perl 2179efcb4SVegard Nossum# 3179efcb4SVegard Nossum# Detect cycles in the header file dependency graph 4179efcb4SVegard Nossum# Vegard Nossum <vegardno@ifi.uio.no> 5179efcb4SVegard Nossum# 6179efcb4SVegard Nossum 7179efcb4SVegard Nossumuse strict; 8179efcb4SVegard Nossumuse warnings; 9179efcb4SVegard Nossum 10179efcb4SVegard Nossumuse Getopt::Long; 11179efcb4SVegard Nossum 12179efcb4SVegard Nossummy $opt_all; 13179efcb4SVegard Nossummy @opt_include; 14179efcb4SVegard Nossummy $opt_graph; 15179efcb4SVegard Nossum 16179efcb4SVegard Nossum&Getopt::Long::Configure(qw(bundling pass_through)); 17179efcb4SVegard Nossum&GetOptions( 18179efcb4SVegard Nossum help => \&help, 19179efcb4SVegard Nossum version => \&version, 20179efcb4SVegard Nossum 21179efcb4SVegard Nossum all => \$opt_all, 2279ff807cSUwe Kleine-König "I=s" => \@opt_include, 23179efcb4SVegard Nossum graph => \$opt_graph, 24179efcb4SVegard Nossum); 25179efcb4SVegard Nossum 26179efcb4SVegard Nossumpush @opt_include, 'include'; 27179efcb4SVegard Nossummy %deps = (); 28179efcb4SVegard Nossummy %linenos = (); 29179efcb4SVegard Nossum 30179efcb4SVegard Nossummy @headers = grep { strip($_) } @ARGV; 31179efcb4SVegard Nossum 32179efcb4SVegard Nossumparse_all(@headers); 33179efcb4SVegard Nossum 34179efcb4SVegard Nossumif($opt_graph) { 35179efcb4SVegard Nossum graph(); 36179efcb4SVegard Nossum} else { 37179efcb4SVegard Nossum detect_cycles(@headers); 38179efcb4SVegard Nossum} 39179efcb4SVegard Nossum 40179efcb4SVegard Nossum 41179efcb4SVegard Nossumsub help { 42179efcb4SVegard Nossum print "Usage: $0 [options] file...\n"; 43179efcb4SVegard Nossum print "\n"; 44179efcb4SVegard Nossum print "Options:\n"; 45179efcb4SVegard Nossum print " --all\n"; 46179efcb4SVegard Nossum print " --graph\n"; 47179efcb4SVegard Nossum print "\n"; 48179efcb4SVegard Nossum print " -I includedir\n"; 49179efcb4SVegard Nossum print "\n"; 50179efcb4SVegard Nossum print "To make nice graphs, try:\n"; 51179efcb4SVegard Nossum print " $0 --graph include/linux/kernel.h | dot -Tpng -o graph.png\n"; 52179efcb4SVegard Nossum exit; 53179efcb4SVegard Nossum} 54179efcb4SVegard Nossum 55179efcb4SVegard Nossumsub version { 56179efcb4SVegard Nossum print "headerdep version 2\n"; 57179efcb4SVegard Nossum exit; 58179efcb4SVegard Nossum} 59179efcb4SVegard Nossum 60179efcb4SVegard Nossum# Get a file name that is relative to our include paths 61179efcb4SVegard Nossumsub strip { 62179efcb4SVegard Nossum my $filename = shift; 63179efcb4SVegard Nossum 64179efcb4SVegard Nossum for my $i (@opt_include) { 65179efcb4SVegard Nossum my $stripped = $filename; 66179efcb4SVegard Nossum $stripped =~ s/^$i\///; 67179efcb4SVegard Nossum 68179efcb4SVegard Nossum return $stripped if $stripped ne $filename; 69179efcb4SVegard Nossum } 70179efcb4SVegard Nossum 71179efcb4SVegard Nossum return $filename; 72179efcb4SVegard Nossum} 73179efcb4SVegard Nossum 74179efcb4SVegard Nossum# Search for the file name in the list of include paths 75179efcb4SVegard Nossumsub search { 76179efcb4SVegard Nossum my $filename = shift; 77179efcb4SVegard Nossum return $filename if -f $filename; 78179efcb4SVegard Nossum 79179efcb4SVegard Nossum for my $i (@opt_include) { 80179efcb4SVegard Nossum my $path = "$i/$filename"; 81179efcb4SVegard Nossum return $path if -f $path; 82179efcb4SVegard Nossum } 83*1dcd8100SStephen Hemminger return; 84179efcb4SVegard Nossum} 85179efcb4SVegard Nossum 86179efcb4SVegard Nossumsub parse_all { 87179efcb4SVegard Nossum # Parse all the headers. 88179efcb4SVegard Nossum my @queue = @_; 89179efcb4SVegard Nossum while(@queue) { 90179efcb4SVegard Nossum my $header = pop @queue; 91179efcb4SVegard Nossum next if exists $deps{$header}; 92179efcb4SVegard Nossum 93179efcb4SVegard Nossum $deps{$header} = [] unless exists $deps{$header}; 94179efcb4SVegard Nossum 95179efcb4SVegard Nossum my $path = search($header); 96179efcb4SVegard Nossum next unless $path; 97179efcb4SVegard Nossum 98179efcb4SVegard Nossum open(my $file, '<', $path) or die($!); 99179efcb4SVegard Nossum chomp(my @lines = <$file>); 100179efcb4SVegard Nossum close($file); 101179efcb4SVegard Nossum 102179efcb4SVegard Nossum for my $i (0 .. $#lines) { 103179efcb4SVegard Nossum my $line = $lines[$i]; 104179efcb4SVegard Nossum if(my($dep) = ($line =~ m/^#\s*include\s*<(.*?)>/)) { 105179efcb4SVegard Nossum push @queue, $dep; 106179efcb4SVegard Nossum push @{$deps{$header}}, [$i + 1, $dep]; 107179efcb4SVegard Nossum } 108179efcb4SVegard Nossum } 109179efcb4SVegard Nossum } 110179efcb4SVegard Nossum} 111179efcb4SVegard Nossum 112179efcb4SVegard Nossumsub print_cycle { 113179efcb4SVegard Nossum # $cycle[n] includes $cycle[n + 1]; 114179efcb4SVegard Nossum # $cycle[-1] will be the culprit 115179efcb4SVegard Nossum my $cycle = shift; 116179efcb4SVegard Nossum 117179efcb4SVegard Nossum # Adjust the line numbers 118179efcb4SVegard Nossum for my $i (0 .. $#$cycle - 1) { 119179efcb4SVegard Nossum $cycle->[$i]->[0] = $cycle->[$i + 1]->[0]; 120179efcb4SVegard Nossum } 121179efcb4SVegard Nossum $cycle->[-1]->[0] = 0; 122179efcb4SVegard Nossum 123179efcb4SVegard Nossum my $first = shift @$cycle; 124179efcb4SVegard Nossum my $last = pop @$cycle; 125179efcb4SVegard Nossum 126179efcb4SVegard Nossum my $msg = "In file included"; 127179efcb4SVegard Nossum printf "%s from %s,\n", $msg, $last->[1] if defined $last; 128179efcb4SVegard Nossum 129179efcb4SVegard Nossum for my $header (reverse @$cycle) { 130179efcb4SVegard Nossum printf "%s from %s:%d%s\n", 131179efcb4SVegard Nossum " " x length $msg, 132179efcb4SVegard Nossum $header->[1], $header->[0], 133179efcb4SVegard Nossum $header->[1] eq $last->[1] ? ' <-- here' : ''; 134179efcb4SVegard Nossum } 135179efcb4SVegard Nossum 136179efcb4SVegard Nossum printf "%s:%d: warning: recursive header inclusion\n", 137179efcb4SVegard Nossum $first->[1], $first->[0]; 138179efcb4SVegard Nossum} 139179efcb4SVegard Nossum 140179efcb4SVegard Nossum# Find and print the smallest cycle starting in the specified node. 141179efcb4SVegard Nossumsub detect_cycles { 142179efcb4SVegard Nossum my @queue = map { [[0, $_]] } @_; 143179efcb4SVegard Nossum while(@queue) { 144179efcb4SVegard Nossum my $top = pop @queue; 145179efcb4SVegard Nossum my $name = $top->[-1]->[1]; 146179efcb4SVegard Nossum 147179efcb4SVegard Nossum for my $dep (@{$deps{$name}}) { 148179efcb4SVegard Nossum my $chain = [@$top, [$dep->[0], $dep->[1]]]; 149179efcb4SVegard Nossum 150179efcb4SVegard Nossum # If the dep already exists in the chain, we have a 151179efcb4SVegard Nossum # cycle... 152179efcb4SVegard Nossum if(grep { $_->[1] eq $dep->[1] } @$top) { 153179efcb4SVegard Nossum print_cycle($chain); 154179efcb4SVegard Nossum next if $opt_all; 155179efcb4SVegard Nossum return; 156179efcb4SVegard Nossum } 157179efcb4SVegard Nossum 158179efcb4SVegard Nossum push @queue, $chain; 159179efcb4SVegard Nossum } 160179efcb4SVegard Nossum } 161179efcb4SVegard Nossum} 162179efcb4SVegard Nossum 163179efcb4SVegard Nossumsub mangle { 164179efcb4SVegard Nossum $_ = shift; 165179efcb4SVegard Nossum s/\//__/g; 166179efcb4SVegard Nossum s/\./_/g; 167179efcb4SVegard Nossum s/-/_/g; 168179efcb4SVegard Nossum $_; 169179efcb4SVegard Nossum} 170179efcb4SVegard Nossum 171179efcb4SVegard Nossum# Output dependency graph in GraphViz language. 172179efcb4SVegard Nossumsub graph { 173179efcb4SVegard Nossum print "digraph {\n"; 174179efcb4SVegard Nossum 175179efcb4SVegard Nossum print "\t/* vertices */\n"; 176179efcb4SVegard Nossum for my $header (keys %deps) { 177179efcb4SVegard Nossum printf "\t%s [label=\"%s\"];\n", 178179efcb4SVegard Nossum mangle($header), $header; 179179efcb4SVegard Nossum } 180179efcb4SVegard Nossum 181179efcb4SVegard Nossum print "\n"; 182179efcb4SVegard Nossum 183179efcb4SVegard Nossum print "\t/* edges */\n"; 184179efcb4SVegard Nossum for my $header (keys %deps) { 185179efcb4SVegard Nossum for my $dep (@{$deps{$header}}) { 186179efcb4SVegard Nossum printf "\t%s -> %s;\n", 187179efcb4SVegard Nossum mangle($header), mangle($dep->[1]); 188179efcb4SVegard Nossum } 189179efcb4SVegard Nossum } 190179efcb4SVegard Nossum 191179efcb4SVegard Nossum print "}\n"; 192179efcb4SVegard Nossum} 193