162306a36Sopenharmony_ci#! /usr/bin/env perl 262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 362306a36Sopenharmony_ci# 462306a36Sopenharmony_ci# Detect cycles in the header file dependency graph 562306a36Sopenharmony_ci# Vegard Nossum <vegardno@ifi.uio.no> 662306a36Sopenharmony_ci# 762306a36Sopenharmony_ci 862306a36Sopenharmony_ciuse strict; 962306a36Sopenharmony_ciuse warnings; 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ciuse Getopt::Long; 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cimy $opt_all; 1462306a36Sopenharmony_cimy @opt_include; 1562306a36Sopenharmony_cimy $opt_graph; 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci&Getopt::Long::Configure(qw(bundling pass_through)); 1862306a36Sopenharmony_ci&GetOptions( 1962306a36Sopenharmony_ci help => \&help, 2062306a36Sopenharmony_ci version => \&version, 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci all => \$opt_all, 2362306a36Sopenharmony_ci "I=s" => \@opt_include, 2462306a36Sopenharmony_ci graph => \$opt_graph, 2562306a36Sopenharmony_ci); 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cipush @opt_include, 'include'; 2862306a36Sopenharmony_cimy %deps = (); 2962306a36Sopenharmony_cimy %linenos = (); 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cimy @headers = grep { strip($_) } @ARGV; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ciparse_all(@headers); 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ciif($opt_graph) { 3662306a36Sopenharmony_ci graph(); 3762306a36Sopenharmony_ci} else { 3862306a36Sopenharmony_ci detect_cycles(@headers); 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cisub help { 4362306a36Sopenharmony_ci print "Usage: $0 [options] file...\n"; 4462306a36Sopenharmony_ci print "\n"; 4562306a36Sopenharmony_ci print "Options:\n"; 4662306a36Sopenharmony_ci print " --all\n"; 4762306a36Sopenharmony_ci print " --graph\n"; 4862306a36Sopenharmony_ci print "\n"; 4962306a36Sopenharmony_ci print " -I includedir\n"; 5062306a36Sopenharmony_ci print "\n"; 5162306a36Sopenharmony_ci print "To make nice graphs, try:\n"; 5262306a36Sopenharmony_ci print " $0 --graph include/linux/kernel.h | dot -Tpng -o graph.png\n"; 5362306a36Sopenharmony_ci exit; 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cisub version { 5762306a36Sopenharmony_ci print "headerdep version 2\n"; 5862306a36Sopenharmony_ci exit; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci# Get a file name that is relative to our include paths 6262306a36Sopenharmony_cisub strip { 6362306a36Sopenharmony_ci my $filename = shift; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci for my $i (@opt_include) { 6662306a36Sopenharmony_ci my $stripped = $filename; 6762306a36Sopenharmony_ci $stripped =~ s/^$i\///; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return $stripped if $stripped ne $filename; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci return $filename; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci# Search for the file name in the list of include paths 7662306a36Sopenharmony_cisub search { 7762306a36Sopenharmony_ci my $filename = shift; 7862306a36Sopenharmony_ci return $filename if -f $filename; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci for my $i (@opt_include) { 8162306a36Sopenharmony_ci my $path = "$i/$filename"; 8262306a36Sopenharmony_ci return $path if -f $path; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci return; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cisub parse_all { 8862306a36Sopenharmony_ci # Parse all the headers. 8962306a36Sopenharmony_ci my @queue = @_; 9062306a36Sopenharmony_ci while(@queue) { 9162306a36Sopenharmony_ci my $header = pop @queue; 9262306a36Sopenharmony_ci next if exists $deps{$header}; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci $deps{$header} = [] unless exists $deps{$header}; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci my $path = search($header); 9762306a36Sopenharmony_ci next unless $path; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci open(my $file, '<', $path) or die($!); 10062306a36Sopenharmony_ci chomp(my @lines = <$file>); 10162306a36Sopenharmony_ci close($file); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci for my $i (0 .. $#lines) { 10462306a36Sopenharmony_ci my $line = $lines[$i]; 10562306a36Sopenharmony_ci if(my($dep) = ($line =~ m/^#\s*include\s*<(.*?)>/)) { 10662306a36Sopenharmony_ci push @queue, $dep; 10762306a36Sopenharmony_ci push @{$deps{$header}}, [$i + 1, $dep]; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cisub print_cycle { 11462306a36Sopenharmony_ci # $cycle[n] includes $cycle[n + 1]; 11562306a36Sopenharmony_ci # $cycle[-1] will be the culprit 11662306a36Sopenharmony_ci my $cycle = shift; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci # Adjust the line numbers 11962306a36Sopenharmony_ci for my $i (0 .. $#$cycle - 1) { 12062306a36Sopenharmony_ci $cycle->[$i]->[0] = $cycle->[$i + 1]->[0]; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci $cycle->[-1]->[0] = 0; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci my $first = shift @$cycle; 12562306a36Sopenharmony_ci my $last = pop @$cycle; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci my $msg = "In file included"; 12862306a36Sopenharmony_ci printf "%s from %s,\n", $msg, $last->[1] if defined $last; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci for my $header (reverse @$cycle) { 13162306a36Sopenharmony_ci printf "%s from %s:%d%s\n", 13262306a36Sopenharmony_ci " " x length $msg, 13362306a36Sopenharmony_ci $header->[1], $header->[0], 13462306a36Sopenharmony_ci $header->[1] eq $last->[1] ? ' <-- here' : ''; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci printf "%s:%d: warning: recursive header inclusion\n", 13862306a36Sopenharmony_ci $first->[1], $first->[0]; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci# Find and print the smallest cycle starting in the specified node. 14262306a36Sopenharmony_cisub detect_cycles { 14362306a36Sopenharmony_ci my @queue = map { [[0, $_]] } @_; 14462306a36Sopenharmony_ci while(@queue) { 14562306a36Sopenharmony_ci my $top = pop @queue; 14662306a36Sopenharmony_ci my $name = $top->[-1]->[1]; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci for my $dep (@{$deps{$name}}) { 14962306a36Sopenharmony_ci my $chain = [@$top, [$dep->[0], $dep->[1]]]; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci # If the dep already exists in the chain, we have a 15262306a36Sopenharmony_ci # cycle... 15362306a36Sopenharmony_ci if(grep { $_->[1] eq $dep->[1] } @$top) { 15462306a36Sopenharmony_ci print_cycle($chain); 15562306a36Sopenharmony_ci next if $opt_all; 15662306a36Sopenharmony_ci return; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci push @queue, $chain; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cisub mangle { 16562306a36Sopenharmony_ci $_ = shift; 16662306a36Sopenharmony_ci s/\//__/g; 16762306a36Sopenharmony_ci s/\./_/g; 16862306a36Sopenharmony_ci s/-/_/g; 16962306a36Sopenharmony_ci $_; 17062306a36Sopenharmony_ci} 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci# Output dependency graph in GraphViz language. 17362306a36Sopenharmony_cisub graph { 17462306a36Sopenharmony_ci print "digraph {\n"; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci print "\t/* vertices */\n"; 17762306a36Sopenharmony_ci for my $header (keys %deps) { 17862306a36Sopenharmony_ci printf "\t%s [label=\"%s\"];\n", 17962306a36Sopenharmony_ci mangle($header), $header; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci print "\n"; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci print "\t/* edges */\n"; 18562306a36Sopenharmony_ci for my $header (keys %deps) { 18662306a36Sopenharmony_ci for my $dep (@{$deps{$header}}) { 18762306a36Sopenharmony_ci printf "\t%s -> %s;\n", 18862306a36Sopenharmony_ci mangle($header), mangle($dep->[1]); 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci print "}\n"; 19362306a36Sopenharmony_ci} 194