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