18c2ecf20Sopenharmony_ci#! /usr/bin/env perl 28c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 38c2ecf20Sopenharmony_ci# 48c2ecf20Sopenharmony_ci# Detect cycles in the header file dependency graph 58c2ecf20Sopenharmony_ci# Vegard Nossum <vegardno@ifi.uio.no> 68c2ecf20Sopenharmony_ci# 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ciuse strict; 98c2ecf20Sopenharmony_ciuse warnings; 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ciuse Getopt::Long; 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_cimy $opt_all; 148c2ecf20Sopenharmony_cimy @opt_include; 158c2ecf20Sopenharmony_cimy $opt_graph; 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci&Getopt::Long::Configure(qw(bundling pass_through)); 188c2ecf20Sopenharmony_ci&GetOptions( 198c2ecf20Sopenharmony_ci help => \&help, 208c2ecf20Sopenharmony_ci version => \&version, 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci all => \$opt_all, 238c2ecf20Sopenharmony_ci "I=s" => \@opt_include, 248c2ecf20Sopenharmony_ci graph => \$opt_graph, 258c2ecf20Sopenharmony_ci); 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cipush @opt_include, 'include'; 288c2ecf20Sopenharmony_cimy %deps = (); 298c2ecf20Sopenharmony_cimy %linenos = (); 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cimy @headers = grep { strip($_) } @ARGV; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ciparse_all(@headers); 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ciif($opt_graph) { 368c2ecf20Sopenharmony_ci graph(); 378c2ecf20Sopenharmony_ci} else { 388c2ecf20Sopenharmony_ci detect_cycles(@headers); 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cisub help { 438c2ecf20Sopenharmony_ci print "Usage: $0 [options] file...\n"; 448c2ecf20Sopenharmony_ci print "\n"; 458c2ecf20Sopenharmony_ci print "Options:\n"; 468c2ecf20Sopenharmony_ci print " --all\n"; 478c2ecf20Sopenharmony_ci print " --graph\n"; 488c2ecf20Sopenharmony_ci print "\n"; 498c2ecf20Sopenharmony_ci print " -I includedir\n"; 508c2ecf20Sopenharmony_ci print "\n"; 518c2ecf20Sopenharmony_ci print "To make nice graphs, try:\n"; 528c2ecf20Sopenharmony_ci print " $0 --graph include/linux/kernel.h | dot -Tpng -o graph.png\n"; 538c2ecf20Sopenharmony_ci exit; 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cisub version { 578c2ecf20Sopenharmony_ci print "headerdep version 2\n"; 588c2ecf20Sopenharmony_ci exit; 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci# Get a file name that is relative to our include paths 628c2ecf20Sopenharmony_cisub strip { 638c2ecf20Sopenharmony_ci my $filename = shift; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci for my $i (@opt_include) { 668c2ecf20Sopenharmony_ci my $stripped = $filename; 678c2ecf20Sopenharmony_ci $stripped =~ s/^$i\///; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci return $stripped if $stripped ne $filename; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci return $filename; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci# Search for the file name in the list of include paths 768c2ecf20Sopenharmony_cisub search { 778c2ecf20Sopenharmony_ci my $filename = shift; 788c2ecf20Sopenharmony_ci return $filename if -f $filename; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci for my $i (@opt_include) { 818c2ecf20Sopenharmony_ci my $path = "$i/$filename"; 828c2ecf20Sopenharmony_ci return $path if -f $path; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci return; 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cisub parse_all { 888c2ecf20Sopenharmony_ci # Parse all the headers. 898c2ecf20Sopenharmony_ci my @queue = @_; 908c2ecf20Sopenharmony_ci while(@queue) { 918c2ecf20Sopenharmony_ci my $header = pop @queue; 928c2ecf20Sopenharmony_ci next if exists $deps{$header}; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci $deps{$header} = [] unless exists $deps{$header}; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci my $path = search($header); 978c2ecf20Sopenharmony_ci next unless $path; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci open(my $file, '<', $path) or die($!); 1008c2ecf20Sopenharmony_ci chomp(my @lines = <$file>); 1018c2ecf20Sopenharmony_ci close($file); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci for my $i (0 .. $#lines) { 1048c2ecf20Sopenharmony_ci my $line = $lines[$i]; 1058c2ecf20Sopenharmony_ci if(my($dep) = ($line =~ m/^#\s*include\s*<(.*?)>/)) { 1068c2ecf20Sopenharmony_ci push @queue, $dep; 1078c2ecf20Sopenharmony_ci push @{$deps{$header}}, [$i + 1, $dep]; 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cisub print_cycle { 1148c2ecf20Sopenharmony_ci # $cycle[n] includes $cycle[n + 1]; 1158c2ecf20Sopenharmony_ci # $cycle[-1] will be the culprit 1168c2ecf20Sopenharmony_ci my $cycle = shift; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci # Adjust the line numbers 1198c2ecf20Sopenharmony_ci for my $i (0 .. $#$cycle - 1) { 1208c2ecf20Sopenharmony_ci $cycle->[$i]->[0] = $cycle->[$i + 1]->[0]; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci $cycle->[-1]->[0] = 0; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci my $first = shift @$cycle; 1258c2ecf20Sopenharmony_ci my $last = pop @$cycle; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci my $msg = "In file included"; 1288c2ecf20Sopenharmony_ci printf "%s from %s,\n", $msg, $last->[1] if defined $last; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci for my $header (reverse @$cycle) { 1318c2ecf20Sopenharmony_ci printf "%s from %s:%d%s\n", 1328c2ecf20Sopenharmony_ci " " x length $msg, 1338c2ecf20Sopenharmony_ci $header->[1], $header->[0], 1348c2ecf20Sopenharmony_ci $header->[1] eq $last->[1] ? ' <-- here' : ''; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci printf "%s:%d: warning: recursive header inclusion\n", 1388c2ecf20Sopenharmony_ci $first->[1], $first->[0]; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci# Find and print the smallest cycle starting in the specified node. 1428c2ecf20Sopenharmony_cisub detect_cycles { 1438c2ecf20Sopenharmony_ci my @queue = map { [[0, $_]] } @_; 1448c2ecf20Sopenharmony_ci while(@queue) { 1458c2ecf20Sopenharmony_ci my $top = pop @queue; 1468c2ecf20Sopenharmony_ci my $name = $top->[-1]->[1]; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci for my $dep (@{$deps{$name}}) { 1498c2ecf20Sopenharmony_ci my $chain = [@$top, [$dep->[0], $dep->[1]]]; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci # If the dep already exists in the chain, we have a 1528c2ecf20Sopenharmony_ci # cycle... 1538c2ecf20Sopenharmony_ci if(grep { $_->[1] eq $dep->[1] } @$top) { 1548c2ecf20Sopenharmony_ci print_cycle($chain); 1558c2ecf20Sopenharmony_ci next if $opt_all; 1568c2ecf20Sopenharmony_ci return; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci push @queue, $chain; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cisub mangle { 1658c2ecf20Sopenharmony_ci $_ = shift; 1668c2ecf20Sopenharmony_ci s/\//__/g; 1678c2ecf20Sopenharmony_ci s/\./_/g; 1688c2ecf20Sopenharmony_ci s/-/_/g; 1698c2ecf20Sopenharmony_ci $_; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci# Output dependency graph in GraphViz language. 1738c2ecf20Sopenharmony_cisub graph { 1748c2ecf20Sopenharmony_ci print "digraph {\n"; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci print "\t/* vertices */\n"; 1778c2ecf20Sopenharmony_ci for my $header (keys %deps) { 1788c2ecf20Sopenharmony_ci printf "\t%s [label=\"%s\"];\n", 1798c2ecf20Sopenharmony_ci mangle($header), $header; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci print "\n"; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci print "\t/* edges */\n"; 1858c2ecf20Sopenharmony_ci for my $header (keys %deps) { 1868c2ecf20Sopenharmony_ci for my $dep (@{$deps{$header}}) { 1878c2ecf20Sopenharmony_ci printf "\t%s -> %s;\n", 1888c2ecf20Sopenharmony_ci mangle($header), mangle($dep->[1]); 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci print "}\n"; 1938c2ecf20Sopenharmony_ci} 194