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