This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
a24266845c0869ed888fa6e413e397cca0c53d2c
[perl5.git] / dist / Devel-PPPort / devel / scanprov
1 #!/usr/bin/perl -w
2 ################################################################################
3 #
4 #  scanprov -- scan Perl headers for provided macros, and add known exceptions
5 #
6 #  The lines added have a code to signify they are added by us:
7 #   M means it is a macro
8 #   X means it is a known exceptional item
9 #
10 ################################################################################
11 #
12 #  Version 3.x, Copyright (C) 2004-2013, Marcus Holland-Moritz.
13 #  Version 2.x, Copyright (C) 2001, Paul Marquess.
14 #  Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
15 #
16 #  This program is free software; you can redistribute it and/or
17 #  modify it under the same terms as Perl itself.
18 #
19 ################################################################################
20
21 use strict;
22 use Getopt::Long;
23
24 require './parts/ppptools.pl';
25 require './parts/inc/inctools';
26 require './devel/devtools.pl';
27
28 our %opt = (
29   mode    => 'check',
30   install => '/tmp/perl/install/default',
31   blead   => 'bleadperl',
32   debug   => 0,
33  'debug-start' => "",
34 );
35
36 GetOptions(\%opt, qw( install=s mode=s blead=s debug=i debug-start=s)) or die;
37
38 my $write = $opt{mode} eq 'write';
39
40 my %embed = map { ( $_->{name} => 1 ) }
41             parse_embed(qw(parts/embed.fnc parts/apidoc.fnc parts/ppport.fnc ));
42
43 # @provided is set to the elements that are provided, but not functions in the
44 # .fnc files
45 my @provided = grep { !exists $embed{$_} }
46                map { /^(\w+)/ ? $1 : () }
47                `$^X ppport.h --list-provided`;
48
49 # There are a few exceptions that have to be dealt with specially.  Add these
50 # to the list of things to scan for.
51 my $hard_to_test_ref = known_but_hard_to_test_for();
52 push @provided, keys %$hard_to_test_ref;
53
54 my $base_dir = 'parts/base';
55 my $todo_dir = 'parts/todo';
56
57 if ($write) {
58
59     # Get the list of files, which are returned sorted, and so the min version
60     # is in the 0th element
61     my @files = all_files_in_dir($base_dir);
62     my $file =  $files[0];
63     my $min_perl = $file;
64     $min_perl =~ s,.*/,,;    # The name is the integer of __MIN_PERL__
65
66     # There are a very few special cases that we may not find in scanning, but
67     # exist all the way back.  Add them now to avoid throwing later things
68     # off.
69     print "-- $file --\n";
70     open F, ">>$file" or die "$file: $!\n";
71     for (qw(RETVAL CALL THIS)) { # These are also in hard_to_test_for(),
72                                  # so can't be in blead, as they are skipped
73                                  # in testing, so no real need to check that
74                                  # they aren't dups.
75         print "Adding $_ to $file\n";
76         print F format_output_line($_, 'X');
77     }
78     close F;
79
80     # Now we're going to add the hard to test symbols.  The hash has been
81     # manually populated and commited, with the version number ppport supports
82     # them to.
83     #
84     # This is a hash ref with the keys being all symbols found in all the
85     # files in the directory, and the values being the perl versions of each
86     # symbol.
87     my $todo = parse_todo($todo_dir);
88
89     # The keys of $hard_to_test_ref are the symbols, and the values are
90     # subhashes, with each 'version' key being its proper perl version.
91     # Below, we invert %hard_to_test, so that the keys are the version, and
92     # the values are the symbols that go in that version
93     my %add_by_version;
94     for my $hard (keys %$hard_to_test_ref) {
95
96         # But if someone ups the min version we support, we don't want to add
97         # something less than that.
98         my $version = int_parse_version($hard_to_test_ref->{$hard});
99         $version = $min_perl if $version < $min_perl;
100         $version = format_version_line($version);
101
102         push @{$add_by_version{$version}}, $hard
103                 unless grep { $todo->{$_}->{version} eq $hard } keys %$todo;
104     }
105
106     # Only a few files will have exceptions that apply to them.  Rewrite each
107     foreach my $version (keys %add_by_version) {
108         my $file = "$todo_dir/" . int_parse_version($version);
109         print "-- Adding known exceptions to $file --\n";
110         my $need_version_line = ! -e $file;
111         open F, ">>$file" or die "$file: $!\n";
112         print F format_version_line($version) . "\n" if $need_version_line;
113         foreach my $symbol (sort dictionary_order @{$add_by_version{$version}})
114         {
115             print "adding $symbol\n";
116             print F format_output_line($symbol, 'X');
117         }
118         close F;
119     }
120 }
121
122 # Now that we've added the exceptions to a few files, we can parse
123 # and deal with all of them.
124 my $perls_ref = get_and_sort_perls(\%opt);
125
126 die "Couldn't find any perls" unless @$perls_ref > 1;
127
128 find_first_mentions($perls_ref,   # perls to look in
129                     \@provided,   # List of symbol names to look for
130                     '*.h',        # Look in all hdrs.
131                     1,            # Strip comments
132                    'M'
133                    );
134
135 sub format_output_line
136 {
137     my $sym = shift;
138     my $code = shift;
139
140     return sprintf "%-30s # $code added by $0\n", $sym;
141 }
142
143 sub find_first_mentions
144 {
145     my $perls_ref =    shift;   # List of perls to look in
146     my $look_for_ref = shift;   # List of symbol names to look for
147     my $hdrs =         shift;   # Glob of hdrs to look in
148     my $strip_comments = shift;
149     my $code           = shift; # Mark entries as having this type
150
151     $hdrs = [ $hdrs ] unless ref $hdrs;
152
153     my @remaining = @$look_for_ref;
154
155     my %v;
156
157     # We look in descending order of perl versions.  Each time through the
158     # loop @remaining is narrowed.
159     for my $p (@$perls_ref) {
160         print "checking perl $p->{version}...\n";
161
162         # Get the hdr files associated with this version
163         my $archlib = `$p->{path} -MConfig -l -e 'print \$Config{archlib}'`;
164         chomp $archlib;
165         local @ARGV;
166         push @ARGV, glob "$archlib/CORE/$_" for @$hdrs;
167
168         my %sym;
169
170         # %sym's keys are every single thing that looks like an identifier
171         # (beginning with a non-digit \w, followed by \w*) that occurs in all
172         # the headers, regardless of where (outside of comments).
173         local $/ = undef;
174         while (<>) {  # Read in the next file
175
176             # Strip comments, from perl faq
177             if ($strip_comments) {
178                 s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
179             }
180
181             $sym{$_}++ for /(\b[^\W\d]\w*)/g;
182         }
183
184         # @remaining is narrowed to include only those identifier-like things
185         # that are mentioned in some hdr in this release.  (If it isn't even
186         # mentioned, it won't exist in the release.)  For those not mentioned,
187         # a key is added of the identifier-like thing in %v.  It is a subkey
188         # of this release's "todo" release, which is the next higher one.  If
189         # we are at version n, we have already done version n+1 and the
190         # provided element was mentioned there, and now it no longer is.  We
191         # take that to mean that to mean that the element became provided for
192         # in n+1.
193         @remaining = map { $sym{$_} or $v{$p->{todo}}{$_}++;
194                             $sym{$_} ? $_ : ()
195                         } @remaining;
196
197     }
198
199     $v{$perls_ref->[-1]{file}}{$_}++ for @remaining;
200
201     # Read in the parts/base files.  The hash ref has keys being all symbols
202     # found in all the files in base/, which are all we are concerned with
203     # became defined in.
204     my $base_ref = parse_todo($base_dir);
205
206
207     # Now add the results from above.  At this point, The keys of %v are the 7
208     # digit BCD version numbers, and their subkeys are the symbols provided by
209     # D:P that are first mentioned in this version, like this:
210     #   '5009002' => {
211     #                  'MY_CXT_CLONE' => 1,
212     #                  'SV_NOSTEAL' => 1,
213     #                  'UTF8_MAXBYTES' => 1
214     #                },
215
216     for my $v (keys %v) {
217
218         # Things listed in blead (the most recent file) are special.  They are
219         # there by default because we haven't found them anywhere, so they
220         # don't really exist as far as we can determine, so shouldn't be
221         # listed as existing.
222         next if $v > $perls_ref->[0]->{file};
223
224         # @new becomes the symbols for version $v not already in the file for
225         # $v
226         my @new = sort dictionary_order grep { !exists $base_ref->{$_} }
227                                                                 keys %{$v{$v}};
228         @new or next; # Nothing new, skip writing
229
230         my $file = $v;
231         $file =~ s/\.//g;
232         $file = "$base_dir/$file";
233         -e $file or die "non-existent: $file\n";
234         print "-- $file --\n";
235         $write and (open F, ">>$file" or die "$file: $!\n");
236         for (@new) {
237             print "adding $_\n";
238             $write and print F format_output_line($_, $code);
239         }
240         $write and close F;
241     }
242 }