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