(find_included_lib_files): Hard-code another
[gnulib.git] / check-module
1 #!/usr/bin/perl -w
2 # Read a module description file and derive the set of files
3 # included directly by any .c or .h file listed in the `Files:' section.
4 # Take the union of all such sets for any dependent modules.
5 # Then, compare that set with the set derived from the names
6 # listed in the various Files: sections.
7
8 # This script makes no attempt to diagnose invalid or empty
9 # module-description files.
10
11 # Written by Jim Meyering
12
13 use strict;
14 use Getopt::Long;
15 #use Coda;
16
17 (my $VERSION = '$Revision: 1.2 $ ') =~ tr/[0-9].//cd;
18 (my $ME = $0) =~ s|.*/||;
19
20 use constant ST_INIT => 1;
21 use constant ST_FILES => 2;
22 use constant ST_DEPENDENTS => 3;
23
24 # Parse a module file (returning list of Files: names and
25 # list of dependent-modules.
26 # my ($file, $dep) = parse_module_file $module_file;
27 sub parse_module_file ($)
28 {
29   my ($module_file) = @_;
30
31   open FH, '<', $module_file
32     or die "$ME: can't open `$module_file' for reading: $!\n";
33
34   my %file_set;
35   my %dep_set;
36
37   my $state = ST_INIT;
38   while (defined (my $line = <FH>))
39     {
40       if ($state eq ST_INIT)
41         {
42           if ($line =~ /^Files:$/)
43             {
44               $state = ST_FILES;
45             }
46           elsif ($line =~ /^Depends-on:$/)
47             {
48               $state = ST_DEPENDENTS;
49             }
50         }
51       else
52         {
53           chomp $line;
54           $line =~ s/^\s+//;
55           $line =~ s/\s+$//;
56           if ( ! $line)
57             {
58               $state = ST_INIT;
59               next;
60             }
61
62           if ($state eq ST_FILES)
63             {
64               $file_set{$line} = 1;
65             }
66           elsif ($state eq ST_DEPENDENTS)
67             {
68               $dep_set{$line} = 1;
69             }
70         }
71     }
72   close FH;
73
74   # my @t = sort keys %file_set;
75   # print "files: @t\n";
76   # my @u = sort keys %dep_set;
77   # print "dependents: @u\n";
78
79   return (\%file_set, \%dep_set);
80 }
81
82 # Extract the set of files required for this module, including
83 # those required via dependent modules.
84
85 # Files:
86 # lib/stat.c
87 # m4/stat.m4
88 # lib/foo.h
89 #
90 # Depends-on:
91 # some-other-module
92
93 sub usage ($)
94 {
95   my ($exit_code) = @_;
96   my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
97   if ($exit_code != 0)
98     {
99       print $STREAM "Try `$ME --help' for more information.\n";
100     }
101   else
102     {
103       print $STREAM <<EOF;
104 Usage: $ME [OPTIONS] FILE...
105
106 Read a module description file and derive the set of files
107 included directly by any .c or .h file listed in the `Files:' section.
108 Take the union of all such sets for any dependent modules.
109 Then, compare that set with the set derived from the names
110 listed in the various Files: sections.
111
112 OPTIONS:
113
114    --help             display this help and exit
115    --version          output version information and exit
116
117 EOF
118     }
119   exit $exit_code;
120 }
121
122 sub find_included_lib_files ($)
123 {
124   my ($file) = @_;
125
126   # Special cases...
127   my %special_non_dup = ( 'fnmatch_loop.c' => 1, 'regex.c' => 1 );
128
129   my %inc;
130   open FH, '<', $file
131     or die "$ME: can't open `$file' for reading: $!\n";
132
133   while (defined (my $line = <FH>))
134     {
135       # Ignore test-driver code at end of file.
136       $line =~ m!^\#if(def)? TEST_!
137         and last;
138
139       $line =~ m!^\s*\#\s*include\s+"!
140         or next;
141       $line =~ s///;
142       chomp $line;
143       $line =~ s/".*//;
144       exists $inc{$line} && ! exists $special_non_dup{$line}
145         and warn "$ME: $file: duplicate inclusion of $line\n";
146
147       # Some known exceptions.
148       $file =~ /\bfull-write\.c$/ && $line eq 'full-read.h'
149         and next;
150       $file =~ /\bsafe-read.c$/ && $line eq 'safe-write.h'
151         and next;
152       $file =~ /\bhash\.c$/ && $line eq 'obstack.h'
153         and next;
154       $file =~ /\bfts\.c$/ &&
155         ($line eq 'fts-cycle.c' || $line eq 'unistd-safer.h')
156           and next;
157
158       $inc{$line} = 1;
159     }
160   close FH;
161
162   return \%inc;
163 }
164
165 {
166   GetOptions
167     (
168      help => sub { usage 0 },
169      version => sub { print "$ME version $VERSION\n"; exit },
170     ) or usage 1;
171
172   @ARGV < 1
173     and (warn "$ME: missing FILE argument\n"), usage 1;
174
175   my %file;
176   my %module_all_files;
177   my %dep;
178   my %seen_module;
179
180   my @m = $ARGV[0];
181
182   while (@m)
183     {
184       my $m = pop @m;
185       # warn "M: $m\n";
186       exists $seen_module{$m}
187         and next;
188       $seen_module{$m} = 1;
189       my ($file, $dep) = parse_module_file $m;
190       push @m, keys %$dep;
191       foreach my $f (keys %$file)
192         {
193           $module_all_files{$f} = 1;
194         }
195     }
196
197   my %exempt_header =
198     (
199      # Exempt headers like unlocked-io.h that are `#include'd
200      # but not necessarily used.
201      'unlocked-io.h' => 1,
202
203      # Give gettext.h a free pass only when included from lib/error.c,
204      # since we've made that exception solely to make the error
205      # module easier to use -- at RMS's request.
206      'lib/error.c:gettext.h' => 1,
207     );
208
209   my @t = sort keys %module_all_files;
210   # warn "ALL files: @t\n";
211
212   # Derive from %module_all_files (by parsing the .c and .h files therein),
213   # the list of all #include'd files that reside in lib/.
214   foreach my $f (keys %module_all_files)
215     {
216       $f =~ /\.[ch]$/
217         or next;
218       # FIXME: this is too naive
219       my $inc = find_included_lib_files "../$f";
220       foreach my $i (sort keys %$inc)
221         {
222           my $lib_file = "lib/$i";
223           exists $exempt_header{"$f:$i"}
224             || exists $exempt_header{$i}
225               and next;
226           !exists $module_all_files{$lib_file} && -f "../lib/$i"
227             and warn "$f: $i is `#include'd, but not "
228               . "listed in module's Files: section\n";
229         }
230       #my @t = sort keys %$inc;
231       #print "** $f: @t\n";
232     }
233
234   exit 0;
235 }