xref: /OK3568_Linux_fs/kernel/scripts/documentation-file-ref-check (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env perl
2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# Treewide grep for references to files under Documentation, and report
5*4882a593Smuzhiyun# non-existing files in stderr.
6*4882a593Smuzhiyun
7*4882a593Smuzhiyunuse warnings;
8*4882a593Smuzhiyunuse strict;
9*4882a593Smuzhiyunuse Getopt::Long qw(:config no_auto_abbrev);
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun# NOTE: only add things here when the file was gone, but the text wants
12*4882a593Smuzhiyun# to mention a past documentation file, for example, to give credits for
13*4882a593Smuzhiyun# the original work.
14*4882a593Smuzhiyunmy %false_positives = (
15*4882a593Smuzhiyun	"Documentation/scsi/scsi_mid_low_api.rst" => "Documentation/Configure.help",
16*4882a593Smuzhiyun	"drivers/vhost/vhost.c" => "Documentation/virtual/lguest/lguest.c",
17*4882a593Smuzhiyun);
18*4882a593Smuzhiyun
19*4882a593Smuzhiyunmy $scriptname = $0;
20*4882a593Smuzhiyun$scriptname =~ s,.*/([^/]+/),$1,;
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun# Parse arguments
23*4882a593Smuzhiyunmy $help = 0;
24*4882a593Smuzhiyunmy $fix = 0;
25*4882a593Smuzhiyunmy $warn = 0;
26*4882a593Smuzhiyun
27*4882a593Smuzhiyunif (! -d ".git") {
28*4882a593Smuzhiyun	printf "Warning: can't check if file exists, as this is not a git tree\n";
29*4882a593Smuzhiyun	exit 0;
30*4882a593Smuzhiyun}
31*4882a593Smuzhiyun
32*4882a593SmuzhiyunGetOptions(
33*4882a593Smuzhiyun	'fix' => \$fix,
34*4882a593Smuzhiyun	'warn' => \$warn,
35*4882a593Smuzhiyun	'h|help|usage' => \$help,
36*4882a593Smuzhiyun);
37*4882a593Smuzhiyun
38*4882a593Smuzhiyunif ($help != 0) {
39*4882a593Smuzhiyun    print "$scriptname [--help] [--fix]\n";
40*4882a593Smuzhiyun    exit -1;
41*4882a593Smuzhiyun}
42*4882a593Smuzhiyun
43*4882a593Smuzhiyun# Step 1: find broken references
44*4882a593Smuzhiyunprint "Finding broken references. This may take a while...  " if ($fix);
45*4882a593Smuzhiyun
46*4882a593Smuzhiyunmy %broken_ref;
47*4882a593Smuzhiyun
48*4882a593Smuzhiyunmy $doc_fix = 0;
49*4882a593Smuzhiyun
50*4882a593Smuzhiyunopen IN, "git grep ':doc:\`' Documentation/|"
51*4882a593Smuzhiyun     or die "Failed to run git grep";
52*4882a593Smuzhiyunwhile (<IN>) {
53*4882a593Smuzhiyun	next if (!m,^([^:]+):.*\:doc\:\`([^\`]+)\`,);
54*4882a593Smuzhiyun	next if (m,sphinx/,);
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun	my $file = $1;
57*4882a593Smuzhiyun	my $d = $1;
58*4882a593Smuzhiyun	my $doc_ref = $2;
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun	my $f = $doc_ref;
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun	$d =~ s,(.*/).*,$1,;
63*4882a593Smuzhiyun	$f =~ s,.*\<([^\>]+)\>,$1,;
64*4882a593Smuzhiyun
65*4882a593Smuzhiyun	if ($f =~ m,^/,) {
66*4882a593Smuzhiyun		$f = "$f.rst";
67*4882a593Smuzhiyun		$f =~ s,^/,Documentation/,;
68*4882a593Smuzhiyun	} else {
69*4882a593Smuzhiyun		$f = "$d$f.rst";
70*4882a593Smuzhiyun	}
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun	next if (grep -e, glob("$f"));
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun	if ($fix && !$doc_fix) {
75*4882a593Smuzhiyun		print STDERR "\nWARNING: Currently, can't fix broken :doc:`` fields\n";
76*4882a593Smuzhiyun	}
77*4882a593Smuzhiyun	$doc_fix++;
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun	print STDERR "$file: :doc:`$doc_ref`\n";
80*4882a593Smuzhiyun}
81*4882a593Smuzhiyunclose IN;
82*4882a593Smuzhiyun
83*4882a593Smuzhiyunopen IN, "git grep 'Documentation/'|"
84*4882a593Smuzhiyun     or die "Failed to run git grep";
85*4882a593Smuzhiyunwhile (<IN>) {
86*4882a593Smuzhiyun	next if (!m/^([^:]+):(.*)/);
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun	my $f = $1;
89*4882a593Smuzhiyun	my $ln = $2;
90*4882a593Smuzhiyun
91*4882a593Smuzhiyun	# On linux-next, discard the Next/ directory
92*4882a593Smuzhiyun	next if ($f =~ m,^Next/,);
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun	# Makefiles and scripts contain nasty expressions to parse docs
95*4882a593Smuzhiyun	next if ($f =~ m/Makefile/ || $f =~ m/\.sh$/);
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun	# Skip this script
98*4882a593Smuzhiyun	next if ($f eq $scriptname);
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun	# Ignore the dir where documentation will be built
101*4882a593Smuzhiyun	next if ($ln =~ m,\b(\S*)Documentation/output,);
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun	if ($ln =~ m,\b(\S*)(Documentation/[A-Za-z0-9\_\.\,\~/\*\[\]\?+-]*)(.*),) {
104*4882a593Smuzhiyun		my $prefix = $1;
105*4882a593Smuzhiyun		my $ref = $2;
106*4882a593Smuzhiyun		my $base = $2;
107*4882a593Smuzhiyun		my $extra = $3;
108*4882a593Smuzhiyun
109*4882a593Smuzhiyun		# some file references are like:
110*4882a593Smuzhiyun		# /usr/src/linux/Documentation/DMA-{API,mapping}.txt
111*4882a593Smuzhiyun		# For now, ignore them
112*4882a593Smuzhiyun		next if ($extra =~ m/^{/);
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun		# Remove footnotes at the end like:
115*4882a593Smuzhiyun		# Documentation/devicetree/dt-object-internal.txt[1]
116*4882a593Smuzhiyun		$ref =~ s/(txt|rst)\[\d+]$/$1/;
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun		# Remove ending ']' without any '['
119*4882a593Smuzhiyun		$ref =~ s/\].*// if (!($ref =~ m/\[/));
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun		# Remove puntuation marks at the end
122*4882a593Smuzhiyun		$ref =~ s/[\,\.]+$//;
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun		my $fulref = "$prefix$ref";
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun		$fulref =~ s/^(\<file|ref)://;
127*4882a593Smuzhiyun		$fulref =~ s/^[\'\`]+//;
128*4882a593Smuzhiyun		$fulref =~ s,^\$\(.*\)/,,;
129*4882a593Smuzhiyun		$base =~ s,.*/,,;
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun		# Remove URL false-positives
132*4882a593Smuzhiyun		next if ($fulref =~ m/^http/);
133*4882a593Smuzhiyun
134*4882a593Smuzhiyun		# Remove sched-pelt false-positive
135*4882a593Smuzhiyun		next if ($fulref =~ m,^Documentation/scheduler/sched-pelt$,);
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun		# Discard some build examples from Documentation/target/tcm_mod_builder.rst
138*4882a593Smuzhiyun		next if ($fulref =~ m,mnt/sdb/lio-core-2.6.git/Documentation/target,);
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun		# Check if exists, evaluating wildcards
141*4882a593Smuzhiyun		next if (grep -e, glob("$ref $fulref"));
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun		# Accept relative Documentation patches for tools/
144*4882a593Smuzhiyun		if ($f =~ m/tools/) {
145*4882a593Smuzhiyun			my $path = $f;
146*4882a593Smuzhiyun			$path =~ s,(.*)/.*,$1,;
147*4882a593Smuzhiyun			next if (grep -e, glob("$path/$ref $path/../$ref $path/$fulref"));
148*4882a593Smuzhiyun		}
149*4882a593Smuzhiyun
150*4882a593Smuzhiyun		# Discard known false-positives
151*4882a593Smuzhiyun		if (defined($false_positives{$f})) {
152*4882a593Smuzhiyun			next if ($false_positives{$f} eq $fulref);
153*4882a593Smuzhiyun		}
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun		if ($fix) {
156*4882a593Smuzhiyun			if (!($ref =~ m/(scripts|Kconfig|Kbuild)/)) {
157*4882a593Smuzhiyun				$broken_ref{$ref}++;
158*4882a593Smuzhiyun			}
159*4882a593Smuzhiyun		} elsif ($warn) {
160*4882a593Smuzhiyun			print STDERR "Warning: $f references a file that doesn't exist: $fulref\n";
161*4882a593Smuzhiyun		} else {
162*4882a593Smuzhiyun			print STDERR "$f: $fulref\n";
163*4882a593Smuzhiyun		}
164*4882a593Smuzhiyun	}
165*4882a593Smuzhiyun}
166*4882a593Smuzhiyunclose IN;
167*4882a593Smuzhiyun
168*4882a593Smuzhiyunexit 0 if (!$fix);
169*4882a593Smuzhiyun
170*4882a593Smuzhiyun# Step 2: Seek for file name alternatives
171*4882a593Smuzhiyunprint "Auto-fixing broken references. Please double-check the results\n";
172*4882a593Smuzhiyun
173*4882a593Smuzhiyunforeach my $ref (keys %broken_ref) {
174*4882a593Smuzhiyun	my $new =$ref;
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun	my $basedir = ".";
177*4882a593Smuzhiyun	# On translations, only seek inside the translations directory
178*4882a593Smuzhiyun	$basedir  = $1 if ($ref =~ m,(Documentation/translations/[^/]+),);
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun	# get just the basename
181*4882a593Smuzhiyun	$new =~ s,.*/,,;
182*4882a593Smuzhiyun
183*4882a593Smuzhiyun	my $f="";
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun	# usual reason for breakage: DT file moved around
186*4882a593Smuzhiyun	if ($ref =~ /devicetree/) {
187*4882a593Smuzhiyun		# usual reason for breakage: DT file renamed to .yaml
188*4882a593Smuzhiyun		if (!$f) {
189*4882a593Smuzhiyun			my $new_ref = $ref;
190*4882a593Smuzhiyun			$new_ref =~ s/\.txt$/.yaml/;
191*4882a593Smuzhiyun			$f=$new_ref if (-f $new_ref);
192*4882a593Smuzhiyun		}
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun		if (!$f) {
195*4882a593Smuzhiyun			my $search = $new;
196*4882a593Smuzhiyun			$search =~ s,^.*/,,;
197*4882a593Smuzhiyun			$f = qx(find Documentation/devicetree/ -iname "*$search*") if ($search);
198*4882a593Smuzhiyun			if (!$f) {
199*4882a593Smuzhiyun				# Manufacturer name may have changed
200*4882a593Smuzhiyun				$search =~ s/^.*,//;
201*4882a593Smuzhiyun				$f = qx(find Documentation/devicetree/ -iname "*$search*") if ($search);
202*4882a593Smuzhiyun			}
203*4882a593Smuzhiyun		}
204*4882a593Smuzhiyun	}
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun	# usual reason for breakage: file renamed to .rst
207*4882a593Smuzhiyun	if (!$f) {
208*4882a593Smuzhiyun		$new =~ s/\.txt$/.rst/;
209*4882a593Smuzhiyun		$f=qx(find $basedir -iname $new) if ($new);
210*4882a593Smuzhiyun	}
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun	# usual reason for breakage: use dash or underline
213*4882a593Smuzhiyun	if (!$f) {
214*4882a593Smuzhiyun		$new =~ s/[-_]/[-_]/g;
215*4882a593Smuzhiyun		$f=qx(find $basedir -iname $new) if ($new);
216*4882a593Smuzhiyun	}
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun	# Wild guess: seek for the same name on another place
219*4882a593Smuzhiyun	if (!$f) {
220*4882a593Smuzhiyun		$f = qx(find $basedir -iname $new) if ($new);
221*4882a593Smuzhiyun	}
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun	my @find = split /\s+/, $f;
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun	if (!$f) {
226*4882a593Smuzhiyun		print STDERR "ERROR: Didn't find a replacement for $ref\n";
227*4882a593Smuzhiyun	} elsif (scalar(@find) > 1) {
228*4882a593Smuzhiyun		print STDERR "WARNING: Won't auto-replace, as found multiple files close to $ref:\n";
229*4882a593Smuzhiyun		foreach my $j (@find) {
230*4882a593Smuzhiyun			$j =~ s,^./,,;
231*4882a593Smuzhiyun			print STDERR "    $j\n";
232*4882a593Smuzhiyun		}
233*4882a593Smuzhiyun	} else {
234*4882a593Smuzhiyun		$f = $find[0];
235*4882a593Smuzhiyun		$f =~ s,^./,,;
236*4882a593Smuzhiyun		print "INFO: Replacing $ref to $f\n";
237*4882a593Smuzhiyun		foreach my $j (qx(git grep -l $ref)) {
238*4882a593Smuzhiyun			qx(sed "s\@$ref\@$f\@g" -i $j);
239*4882a593Smuzhiyun		}
240*4882a593Smuzhiyun	}
241*4882a593Smuzhiyun}
242