This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Bump version to 5.33.5
[perl5.git] / Porting / pod_lib.pl
CommitLineData
d7816c47
NC
1#!/usr/bin/perl -w
2
3use strict;
9bbb230a 4use File::Find;
d7816c47 5
1e62a62d
JK
6=head1 NAME
7
8Porting/pod_lib.pl - functions for building and installing POD
9
10=head1 SYNOPSIS
11
12 require './Porting/pod_lib.pl';
13
14=cut
15
16=head1 DESCRIPTION
17
18This program, when C<require>d into other programs in the Perl 5 core
19distribution, provides functions useful during building and, secondarily,
20testing.
21
22As of this writing, the functions in this program are used in these other
23programs:
24
25 installman
26 installperl
27 pod/buildtoc
28 pod/perl.pod
29 Porting/new-perldelta.pl
30 Porting/pod_rules.pl
31
32Note: Since these functions are used during the Perl build process, they must
33work with F<miniperl>. That necessarily implies that these functions must not
34rely on XS modules, either directly or indirectly (e.g., C<autodie>).
35
36=head1 SUBROUTINES
37
38=head2 C<my_die()>
39
40=over 4
41
42=item * Purpose
43
44Exit from a process with an error code and a message.
45
46=item * Arguments
47
48List of arguments to be passed with the error message. Example:
49
50 close $fh or my_die("close 'utils.lst': $!");
51
52=item * Return Value
53
54Exit code C<255>.
55
56=item * Comment
57
58Prints C<ABORTED> to STDERR.
59
60=back
61
62=cut
d7816c47 63
252696ec
JR
64# In some situations, eg cross-compiling, we get run with miniperl, so we can't use Digest::MD5
65my $has_md5;
66BEGIN {
67 use Carp;
68 $has_md5 = eval { require Digest::MD5; Digest::MD5->import('md5'); 1; };
69}
70
71
72# make it clearer when we haven't run to completion, as we can be quite
73# noisy when things are working ok
74
d7816c47
NC
75sub my_die {
76 print STDERR "$0: ", @_;
77 print STDERR "\n" unless $_[-1] =~ /\n\z/;
78 print STDERR "ABORTED\n";
79 exit 255;
80}
81
1e62a62d
JK
82=head2 C<open_or_die()>
83
84=over 4
85
86=item * Purpose
87
88Opens a file or fails if it cannot.
89
90=item * Arguments
91
92String holding filename to be opened. Example:
93
94 $fh = open_or_die('utils.lst');
95
96=item * Return Value
97
98Handle to opened file.
99
100=back
101
102=cut
103
d7816c47
NC
104sub open_or_die {
105 my $filename = shift;
106 open my $fh, '<', $filename or my_die "Can't open $filename: $!";
107 return $fh;
108}
109
1e62a62d
JK
110=head2 C<slurp_or_die()>
111
112=over 4
113
114=item * Purpose
115
116Read the contents of a file into memory as a single string.
117
118=item * Arguments
119
120String holding name of file to be read into memory.
121
122 $olddelta = slurp_or_die('pod/perldelta.pod');
123
124=item * Return Value
125
126String holding contents of file.
127
128=back
129
130=cut
131
bcfe7366
NC
132sub slurp_or_die {
133 my $filename = shift;
134 my $fh = open_or_die($filename);
135 binmode $fh;
136 local $/;
137 my $contents = <$fh>;
138 die "Can't read $filename: $!" unless defined $contents and close $fh;
139 return $contents;
140}
141
1e62a62d
JK
142=head2 C<write_or_die()>
143
144=over 4
145
146=item * Purpose
147
148Write out a string to a file.
149
150=item * Arguments
151
152List of two arguments: (i) String holding name of file to be written to; (ii)
153String holding contents to be written.
154
155 write_or_die($olddeltaname, $olddelta);
156
157=item * Return Value
158
159Implicitly returns true value upon success.
160
161=back
162
163=cut
164
bcfe7366
NC
165sub write_or_die {
166 my ($filename, $contents) = @_;
167 open my $fh, '>', $filename or die "Can't open $filename for writing: $!";
168 binmode $fh;
169 print $fh $contents or die "Can't write to $filename: $!";
170 close $fh or die "Can't close $filename: $!";
171}
172
93b1bf68
NC
173=head2 C<verify_contiguous()>
174
175=over 4
176
177=item * Purpose
178
179Verify that a file contains exactly one contiguous run of lines which matches
180the passed in pattern. C<croak()>s if the pattern is not found, or found in
181more than one place.
182
183=item * Arguments
184
185=over 4
186
187=item * Name of file
188
189=item * Contents of file
190
191=item * Pattern of interest
192
193=item * Name to report on error
194
195=back
196
197=item * Return Value
198
199The contents of the file, with C<qr/\0+/> substituted for the pattern.
200
201=back
202
203=cut
204
205sub verify_contiguous {
206 my ($name, $content, $re, $what) = @_;
207 require Carp;
208 $content =~ s/$re/\0/g;
209 my $sections = () = $content =~ m/\0+/g;
210 Carp::croak("$0: $name contains no $what") if $sections < 1;
211 Carp::croak("$0: $name contains discontiguous $what") if $sections > 1;
212 return $content;
213}
214
244a7751
NC
215=head2 C<process()>
216
217=over 4
218
219=item * Purpose
220
221Read a file from disk, pass the contents to the callback, and either update
222the file on disk (if changed) or generate TAP output to confirm that the
223version on disk is up to date. C<die>s if the file contains any C<NUL> bytes.
224This permits the callback routine to use C<NUL> bytes as placeholders while
225manipulating the file's contents.
226
227=item * Arguments
228
229=over 4
230
231=item * Description for use in error messages
232
233=item * Name of file
234
235=item * Callback
236
237Passed description and file contents, should return updated file contents.
238
239=item * Test number
240
241If defined, generate TAP output to C<STDOUT>. If defined and false, generate
242an unnumbered test. Otherwise this is the test number in the I<ok> line.
243
244=item * Verbose flag
245
246If true, generate verbose output.
247
248=back
249
250=item * Return Value
251
252Does not return anything.
253
254=back
255
256=cut
257
258sub process {
259 my ($desc, $filename, $callback, $test, $verbose) = @_;
260
261 print "Now processing $filename\n" if $verbose;
262 my $orig = slurp_or_die($filename);
263 my_die "$filename contains NUL bytes" if $orig =~ /\0/;
264
265 my $new = $callback->($desc, $orig);
266
267 if (defined $test) {
268 printf "%s%s # $filename is up to date\n",
269 ($new eq $orig ? 'ok' : 'not ok'), ($test ? " $test" : '');
270 return;
271 } elsif ($new eq $orig) {
272 print "Was not modified\n"
273 if $verbose;
274 return;
275 }
276
277 my $mode = (stat $filename)[2];
278 my_die "Can't stat $filename: $!"
279 unless defined $mode;
280 rename $filename, "$filename.old"
281 or my_die "Can't rename $filename to $filename.old: $!";
282
283 write_or_die($filename, $new);
284 chmod $mode & 0777, $filename or my_die "can't chmod $mode $filename: $!";
285}
286
1e62a62d
JK
287=head2 C<pods_to_install()>
288
289=over 4
290
291=item * Purpose
292
293Create a lookup table holding information about PODs to be installed.
294
295=item * Arguments
296
297None.
298
299=item * Return Value
300
301Reference to a hash with a structure like this:
302
303 $found = {
304 'MODULE' => {
305 'CPAN::Bundle' => 'lib/CPAN/Bundle.pm',
306 'Locale::Codes::Script_Retired' =>
307 'lib/Locale/Codes/Script_Retired.pm',
308 'Pod::Simple::DumpAsText' =>
309 'lib/Pod/Simple/DumpAsText.pm',
310 # ...
311 'Locale::Codes::LangVar' =>
312 'lib/Locale/Codes/LangVar.pod'
313 },
314 'PRAGMA' => {
315 'fields' => 'lib/fields.pm',
316 'subs' => 'lib/subs.pm',
317 # ...
318 },
319
320=item * Comment
321
322Broadly speaking, the function assembles a list of all F<.pm> and F<.pod>
323files in the distribution and then excludes certain files from installation.
324
325=back
326
327=cut
328
9bbb230a
NC
329sub pods_to_install {
330 # manpages not to be installed
331 my %do_not_install = map { ($_ => 1) }
332 qw(Pod::Functions XS::APItest XS::Typemap);
a04fd069
TC
333 $do_not_install{"ExtUtils::XSSymSet"} = 1
334 unless $^O eq "VMS";
9bbb230a
NC
335
336 my (%done, %found);
337
338 File::Find::find({no_chdir=>1,
339 wanted => sub {
65e5b016
NC
340 if (m!/t\z!) {
341 ++$File::Find::prune;
342 return;
343 }
344
9bbb230a
NC
345 # $_ is $File::Find::name when using no_chdir
346 return unless m!\.p(?:m|od)\z! && -f $_;
9bbb230a
NC
347 return if m!lib/Net/FTP/.+\.pm\z!; # Hi, Graham! :-)
348 # Skip .pm files that have corresponding .pod files
349 return if s!\.pm\z!.pod! && -e $_;
350 s!\.pod\z!!;
351 s!\Alib/!!;
352 s!/!::!g;
353
354 my_die("Duplicate files for $_, '$done{$_}' and '$File::Find::name'")
355 if exists $done{$_};
356 $done{$_} = $File::Find::name;
357
358 return if $do_not_install{$_};
359 return if is_duplicate_pod($File::Find::name);
360 $found{/\A[a-z]/ ? 'PRAGMA' : 'MODULE'}{$_}
361 = $File::Find::name;
362 }}, 'lib');
363 return \%found;
364}
9887f448
NC
365
366my %state = (
367 # Don't copy these top level READMEs
368 ignore => {
369 micro => 1,
370 # vms => 1,
371 },
372 );
373
4027e27b
NC
374{
375 my (%Lengths, %MD5s);
376
377 sub is_duplicate_pod {
378 my $file = shift;
75d90f49 379 local $_;
4027e27b 380
252696ec
JR
381 return if !$has_md5;
382
4027e27b
NC
383 # Initialise the list of possible source files on the first call.
384 unless (%Lengths) {
385 __prime_state() unless $state{master};
386 foreach (@{$state{master}}) {
2ce3647c 387 next unless $_->[2]{dual};
4027e27b
NC
388 # This is a dual-life perl*.pod file, which will have be copied
389 # to lib/ by the build process, and hence also found there.
390 # These are the only pod files that might become duplicated.
2ce3647c
NC
391 ++$Lengths{-s $_->[1]};
392 ++$MD5s{md5(slurp_or_die($_->[1]))};
4027e27b
NC
393 }
394 }
395
396 # We are a file in lib. Are we a duplicate?
397 # Don't bother calculating the MD5 if there's no interesting file of
398 # this length.
399 return $Lengths{-s $file} && $MD5s{md5(slurp_or_die($file))};
400 }
449e2f79
NC
401}
402
9887f448 403sub __prime_state {
d7816c47
NC
404 my $source = 'perldelta.pod';
405 my $filename = "pod/$source";
64587559 406 my $contents = slurp_or_die($filename);
d7816c47 407 my @want =
cbc2e172 408 $contents =~ /perldelta - what is new for perl v(\d+)\.(\d+)\.(\d+)\r?\n/;
d7816c47 409 die "Can't extract version from $filename" unless @want;
0aef0fe5
NC
410 my $delta_leaf = join '', 'perl', @want, 'delta';
411 $state{delta_target} = "$delta_leaf.pod";
bcfe7366 412 $state{delta_version} = \@want;
d7816c47
NC
413
414 # This way round so that keys can act as a MANIFEST skip list
415 # Targets will always be in the pod directory. Currently we can only cope
416 # with sources being in the same directory.
417 $state{copies}{$state{delta_target}} = $source;
418
0aef0fe5
NC
419 # The default flags if none explicitly set for the current file.
420 my $current_flags = '';
421 my (%flag_set, @paths);
d7816c47 422
0aef0fe5
NC
423 my $master = open_or_die('pod/perl.pod');
424
425 while (<$master>) {
426 last if /^=begin buildtoc$/;
427 }
428 die "Can't find '=begin buildtoc':" if eof $master;
429
430 while (<$master>) {
431 next if /^$/ or /^#/;
432 last if /^=end buildtoc/;
433 my ($command, @args) = split ' ';
434 if ($command eq 'flag') {
435 # For the named pods, use these flags, instead of $current_flags
436 my $flags = shift @args;
437 my_die("Malformed flag $flags")
438 unless $flags =~ /\A=([a-z]*)\z/;
439 $flag_set{$_} = $1 foreach @args;
440 } elsif ($command eq 'path') {
441 # If the pod's name matches the regex, prepend the given path.
442 my_die("Malformed path for /$args[0]/")
443 unless @args == 2;
444 push @paths, [qr/\A$args[0]\z/, $args[1]];
445 } elsif ($command eq 'aux') {
446 # The contents of perltoc.pod's "AUXILIARY DOCUMENTATION" section
4e604983 447 $state{aux} = [sort @args];
0aef0fe5
NC
448 } else {
449 my_die("Unknown buildtoc command '$command'");
450 }
451 }
d7816c47
NC
452
453 foreach (<$master>) {
0aef0fe5
NC
454 next if /^$/ or /^#/;
455 next if /^=head2/;
456 last if /^=for buildtoc __END__$/;
457
458 if (my ($action, $flags) = /^=for buildtoc flag ([-+])([a-z]+)/) {
459 if ($action eq '+') {
460 $current_flags .= $flags;
461 } else {
462 my_die("Attempt to unset [$flags] failed - flags are '$current_flags")
463 unless $current_flags =~ s/[\Q$flags\E]//g;
464 }
465 } elsif (my ($leafname, $desc) = /^\s+(\S+)\s+(.*)/) {
466 my $podname = $leafname;
467 my $filename = "pod/$podname.pod";
468 foreach (@paths) {
469 my ($re, $path) = @$_;
470 if ($leafname =~ $re) {
471 $podname = $path . $leafname;
472 $filename = "$podname.pod";
473 last;
474 }
475 }
476
477 # Keep this compatible with pre-5.10
478 my $flags = delete $flag_set{$leafname};
479 $flags = $current_flags unless defined $flags;
480
481 my %flags;
d7816c47 482 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
dc437300 483 $flags{dual} = $podname ne $leafname;
d7816c47
NC
484
485 $state{generated}{"$podname.pod"}++ if $flags =~ tr/g//d;
486
487 if ($flags =~ tr/r//d) {
488 my $readme = $podname;
489 $readme =~ s/^perl//;
64587559 490 $state{readmes}{$readme} = $desc;
d7816c47 491 $flags{readme} = 1;
d7816c47
NC
492 } else {
493 $state{pods}{$podname} = $desc;
494 }
495 my_die "Unknown flag found in section line: $_" if length $flags;
449e2f79 496
d7816c47 497 push @{$state{master}},
2ce3647c 498 [$leafname, $filename, \%flags];
0aef0fe5
NC
499
500 if ($podname eq 'perldelta') {
501 local $" = '.';
0aef0fe5 502 push @{$state{master}},
2ce3647c 503 [$delta_leaf, "pod/$state{delta_target}"];
dc437300 504 $state{pods}{$delta_leaf} = "Perl changes in version @want";
0aef0fe5
NC
505 }
506
d7816c47 507 } else {
0aef0fe5 508 my_die("Malformed line: $_");
d7816c47
NC
509 }
510 }
0aef0fe5
NC
511 close $master or my_die("close pod/perl.pod: $!");
512
513 my_die("perl.pod sets flags for unknown pods: "
514 . join ' ', sort keys %flag_set)
515 if keys %flag_set;
9887f448
NC
516}
517
1e62a62d
JK
518=head2 C<get_pod_metadata()>
519
520=over 4
521
522=item * Purpose
523
524=item * Arguments
525
526List of one or more arguments.
527
528=over 4
529
530=item * Boolean true or false
531
730ad6b9 532=item * Reference to a subroutine.
1e62a62d
JK
533
534=item * Various other arguments.
535
536=back
537
538Example:
539
540 $state = get_pod_metadata(
541 0, sub { warn @_ if @_ }, 'pod/perltoc.pod');
542
543 get_pod_metadata(
544 1, sub { warn @_ if @_ }, values %Build);
545
546=item * Return Value
547
548Hash reference; each element provides either a list or a lookup table for
549information about various types of POD files.
550
551 'aux' => [ # utility programs like
8166b4e0 552 'h2xs' and 'perldoc' ]
1e62a62d
JK
553 'generated' => { # lookup table for generated POD files
554 like 'perlapi.pod' }
555 'ignore' => { # lookup table for files to be ignored }
556 'pods' => { # lookup table in "name" =>
557 "short description" format }
558 'readmes' => { # lookup table for OS-specific
559 and other READMEs }
560 'delta_version' => [ # major version number, minor no.,
561 patch no. ]
562 'delta_target' => 'perl<Mmmpp>delta.pod',
563 'master' => [ # list holding entries for files callable
564 by 'perldoc' ]
565 'copies' => { # patch version perldelta =>
566 minor version perldelta }
567
568=back
569
570=cut
571
9887f448
NC
572sub get_pod_metadata {
573 # Do we expect to find generated pods on disk?
574 my $permit_missing_generated = shift;
575 # Do they want a consistency report?
576 my $callback = shift;
75d90f49 577 local $_;
d7816c47 578
9887f448 579 __prime_state() unless $state{master};
d4c6b7ae
NC
580 return \%state unless $callback;
581
9887f448
NC
582 my %BuildFiles;
583
584 foreach my $path (@_) {
585 $path =~ m!([^/]+)$!;
586 ++$BuildFiles{$1};
587 }
588
d7816c47
NC
589 # Sanity cross check
590
0aef0fe5 591 my (%disk_pods, %manipods, %manireadmes);
852355fc 592 my (%cpanpods, %cpanpods_leaf);
d7816c47
NC
593 my (%our_pods);
594
6ca94f41
RGS
595 # There are files that we don't want to list in perl.pod.
596 # Maybe the various stub manpages should be listed there.
597 my %ignoredpods = map { ( "$_.pod" => 1 ) } qw( );
d7816c47
NC
598
599 # Convert these to a list of filenames.
64587559
NC
600 ++$our_pods{"$_.pod"} foreach keys %{$state{pods}};
601 foreach (@{$state{master}}) {
2ce3647c
NC
602 ++$our_pods{"$_->[0].pod"}
603 if $_->[2]{readme};
d7816c47
NC
604 }
605
606 opendir my $dh, 'pod';
607 while (defined ($_ = readdir $dh)) {
608 next unless /\.pod\z/;
d7816c47
NC
609 ++$disk_pods{$_};
610 }
611
612 # Things we copy from won't be in perl.pod
613 # Things we copy to won't be in MANIFEST
614
615 my $mani = open_or_die('MANIFEST');
616 while (<$mani>) {
617 chomp;
618 s/\s+.*$//;
619 if (m!^pod/([^.]+\.pod)!i) {
6fdb59a5 620 ++$manipods{$1};
d7816c47
NC
621 } elsif (m!^README\.(\S+)!i) {
622 next if $state{ignore}{$1};
6fdb59a5 623 ++$manireadmes{"perl$1.pod"};
d7816c47 624 } elsif (exists $our_pods{$_}) {
852355fc
NC
625 ++$cpanpods{$_};
626 m!([^/]+)$!;
627 ++$cpanpods_leaf{$1};
d7816c47
NC
628 $disk_pods{$_}++
629 if -e $_;
630 }
631 }
632 close $mani or my_die "close MANIFEST: $!\n";
633
c26a697b
NC
634 # Are we running before known generated files have been generated?
635 # (eg in a clean checkout)
636 my %not_yet_there;
637 if ($permit_missing_generated) {
638 # If so, don't complain if these files aren't yet in place
639 %not_yet_there = (%manireadmes, %{$state{generated}}, %{$state{copies}})
640 }
641
d7816c47
NC
642 my @inconsistent;
643 foreach my $i (sort keys %disk_pods) {
644 push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
6ca94f41 645 unless $our_pods{$i} || $ignoredpods{$i};
d7816c47 646 push @inconsistent, "$0: $i exists but is unknown by MANIFEST\n"
9e241592
NC
647 if !$BuildFiles{'MANIFEST'} # Ignore if we're rebuilding MANIFEST
648 && !$manipods{$i} && !$manireadmes{$i} && !$state{copies}{$i}
649 && !$state{generated}{$i} && !$cpanpods{$i};
d7816c47
NC
650 }
651 foreach my $i (sort keys %our_pods) {
652 push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
c26a697b 653 unless $disk_pods{$i} or $BuildFiles{$i} or $not_yet_there{$i};
d7816c47 654 }
9e241592
NC
655 unless ($BuildFiles{'MANIFEST'}) {
656 # Again, ignore these if we're about to rebuild MANIFEST
657 foreach my $i (sort keys %manipods) {
658 push @inconsistent, "$0: $i is known by MANIFEST but does not exist\n"
659 unless $disk_pods{$i};
660 push @inconsistent, "$0: $i is known by MANIFEST but is marked as generated\n"
661 if $state{generated}{$i};
662 }
d7816c47 663 }
d4c6b7ae 664 &$callback(@inconsistent);
d7816c47
NC
665 return \%state;
666}
667
6681;
669
d7816c47 670# ex: set ts=8 sts=4 sw=4 et: