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