blob: b257c66ed78ad45e9c035df930fb75479e767c72 [file] [log] [blame]
#!/usr/bin/perl -w
use strict;
use Digest::MD5 'md5';
use File::Find;
# make it clearer when we haven't run to completion, as we can be quite
# noisy when things are working ok
sub my_die {
print STDERR "$0: ", @_;
print STDERR "\n" unless $_[-1] =~ /\n\z/;
print STDERR "ABORTED\n";
exit 255;
}
sub open_or_die {
my $filename = shift;
open my $fh, '<', $filename or my_die "Can't open $filename: $!";
return $fh;
}
sub slurp_or_die {
my $filename = shift;
my $fh = open_or_die($filename);
binmode $fh;
local $/;
my $contents = <$fh>;
die "Can't read $filename: $!" unless defined $contents and close $fh;
return $contents;
}
sub write_or_die {
my ($filename, $contents) = @_;
open my $fh, '>', $filename or die "Can't open $filename for writing: $!";
binmode $fh;
print $fh $contents or die "Can't write to $filename: $!";
close $fh or die "Can't close $filename: $!";
}
sub pods_to_install {
# manpages not to be installed
my %do_not_install = map { ($_ => 1) }
qw(Pod::Functions XS::APItest XS::Typemap);
my (%done, %found);
File::Find::find({no_chdir=>1,
wanted => sub {
if (m!/t\z!) {
++$File::Find::prune;
return;
}
# $_ is $File::Find::name when using no_chdir
return unless m!\.p(?:m|od)\z! && -f $_;
return if m!lib/Net/FTP/.+\.pm\z!; # Hi, Graham! :-)
# Skip .pm files that have corresponding .pod files
return if s!\.pm\z!.pod! && -e $_;
s!\.pod\z!!;
s!\Alib/!!;
s!/!::!g;
my_die("Duplicate files for $_, '$done{$_}' and '$File::Find::name'")
if exists $done{$_};
$done{$_} = $File::Find::name;
return if $do_not_install{$_};
return if is_duplicate_pod($File::Find::name);
$found{/\A[a-z]/ ? 'PRAGMA' : 'MODULE'}{$_}
= $File::Find::name;
}}, 'lib');
return \%found;
}
my %state = (
# Don't copy these top level READMEs
ignore => {
micro => 1,
# vms => 1,
},
);
{
my (%Lengths, %MD5s);
sub is_duplicate_pod {
my $file = shift;
local $_;
# Initialise the list of possible source files on the first call.
unless (%Lengths) {
__prime_state() unless $state{master};
foreach (@{$state{master}}) {
next unless $_->[2]{dual};
# This is a dual-life perl*.pod file, which will have be copied
# to lib/ by the build process, and hence also found there.
# These are the only pod files that might become duplicated.
++$Lengths{-s $_->[1]};
++$MD5s{md5(slurp_or_die($_->[1]))};
}
}
# We are a file in lib. Are we a duplicate?
# Don't bother calculating the MD5 if there's no interesting file of
# this length.
return $Lengths{-s $file} && $MD5s{md5(slurp_or_die($file))};
}
}
sub __prime_state {
my $source = 'perldelta.pod';
my $filename = "pod/$source";
my $contents = slurp_or_die($filename);
my @want =
$contents =~ /perldelta - what is new for perl v(5)\.(\d+)\.(\d+)\n/;
die "Can't extract version from $filename" unless @want;
my $delta_leaf = join '', 'perl', @want, 'delta';
$state{delta_target} = "$delta_leaf.pod";
$state{delta_version} = \@want;
# This way round so that keys can act as a MANIFEST skip list
# Targets will always be in the pod directory. Currently we can only cope
# with sources being in the same directory.
$state{copies}{$state{delta_target}} = $source;
# The default flags if none explicitly set for the current file.
my $current_flags = '';
my (%flag_set, @paths);
my $master = open_or_die('pod/perl.pod');
while (<$master>) {
last if /^=begin buildtoc$/;
}
die "Can't find '=begin buildtoc':" if eof $master;
while (<$master>) {
next if /^$/ or /^#/;
last if /^=end buildtoc/;
my ($command, @args) = split ' ';
if ($command eq 'flag') {
# For the named pods, use these flags, instead of $current_flags
my $flags = shift @args;
my_die("Malformed flag $flags")
unless $flags =~ /\A=([a-z]*)\z/;
$flag_set{$_} = $1 foreach @args;
} elsif ($command eq 'path') {
# If the pod's name matches the regex, prepend the given path.
my_die("Malformed path for /$args[0]/")
unless @args == 2;
push @paths, [qr/\A$args[0]\z/, $args[1]];
} elsif ($command eq 'aux') {
# The contents of perltoc.pod's "AUXILIARY DOCUMENTATION" section
$state{aux} = [sort @args];
} else {
my_die("Unknown buildtoc command '$command'");
}
}
foreach (<$master>) {
next if /^$/ or /^#/;
next if /^=head2/;
last if /^=for buildtoc __END__$/;
if (my ($action, $flags) = /^=for buildtoc flag ([-+])([a-z]+)/) {
if ($action eq '+') {
$current_flags .= $flags;
} else {
my_die("Attempt to unset [$flags] failed - flags are '$current_flags")
unless $current_flags =~ s/[\Q$flags\E]//g;
}
} elsif (my ($leafname, $desc) = /^\s+(\S+)\s+(.*)/) {
my $podname = $leafname;
my $filename = "pod/$podname.pod";
foreach (@paths) {
my ($re, $path) = @$_;
if ($leafname =~ $re) {
$podname = $path . $leafname;
$filename = "$podname.pod";
last;
}
}
# Keep this compatible with pre-5.10
my $flags = delete $flag_set{$leafname};
$flags = $current_flags unless defined $flags;
my %flags;
$flags{toc_omit} = 1 if $flags =~ tr/o//d;
$flags{dual} = $podname ne $leafname;
$state{generated}{"$podname.pod"}++ if $flags =~ tr/g//d;
if ($flags =~ tr/r//d) {
my $readme = $podname;
$readme =~ s/^perl//;
$state{readmes}{$readme} = $desc;
$flags{readme} = 1;
} else {
$state{pods}{$podname} = $desc;
}
my_die "Unknown flag found in section line: $_" if length $flags;
push @{$state{master}},
[$leafname, $filename, \%flags];
if ($podname eq 'perldelta') {
local $" = '.';
push @{$state{master}},
[$delta_leaf, "pod/$state{delta_target}"];
$state{pods}{$delta_leaf} = "Perl changes in version @want";
}
} else {
my_die("Malformed line: $_");
}
}
close $master or my_die("close pod/perl.pod: $!");
# This has to be special-cased somewhere. Turns out this is cleanest:
push @{$state{master}}, ['a2p', 'x2p/a2p.pod', {toc_omit => 1}];
my_die("perl.pod sets flags for unknown pods: "
. join ' ', sort keys %flag_set)
if keys %flag_set;
}
sub get_pod_metadata {
# Do we expect to find generated pods on disk?
my $permit_missing_generated = shift;
# Do they want a consistency report?
my $callback = shift;
local $_;
__prime_state() unless $state{master};
return \%state unless $callback;
my %BuildFiles;
foreach my $path (@_) {
$path =~ m!([^/]+)$!;
++$BuildFiles{$1};
}
# Sanity cross check
my (%disk_pods, %manipods, %manireadmes);
my (%cpanpods, %cpanpods_leaf);
my (%our_pods);
# There are files that we don't want to list in perl.pod.
# Maybe the various stub manpages should be listed there.
my %ignoredpods = map { ( "$_.pod" => 1 ) } qw( );
# Convert these to a list of filenames.
++$our_pods{"$_.pod"} foreach keys %{$state{pods}};
foreach (@{$state{master}}) {
++$our_pods{"$_->[0].pod"}
if $_->[2]{readme};
}
opendir my $dh, 'pod';
while (defined ($_ = readdir $dh)) {
next unless /\.pod\z/;
++$disk_pods{$_};
}
# Things we copy from won't be in perl.pod
# Things we copy to won't be in MANIFEST
my $mani = open_or_die('MANIFEST');
while (<$mani>) {
chomp;
s/\s+.*$//;
if (m!^pod/([^.]+\.pod)!i) {
++$manipods{$1};
} elsif (m!^README\.(\S+)!i) {
next if $state{ignore}{$1};
++$manireadmes{"perl$1.pod"};
} elsif (exists $our_pods{$_}) {
++$cpanpods{$_};
m!([^/]+)$!;
++$cpanpods_leaf{$1};
$disk_pods{$_}++
if -e $_;
}
}
close $mani or my_die "close MANIFEST: $!\n";
# Are we running before known generated files have been generated?
# (eg in a clean checkout)
my %not_yet_there;
if ($permit_missing_generated) {
# If so, don't complain if these files aren't yet in place
%not_yet_there = (%manireadmes, %{$state{generated}}, %{$state{copies}})
}
my @inconsistent;
foreach my $i (sort keys %disk_pods) {
push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
unless $our_pods{$i} || $ignoredpods{$i};
push @inconsistent, "$0: $i exists but is unknown by MANIFEST\n"
if !$BuildFiles{'MANIFEST'} # Ignore if we're rebuilding MANIFEST
&& !$manipods{$i} && !$manireadmes{$i} && !$state{copies}{$i}
&& !$state{generated}{$i} && !$cpanpods{$i};
}
foreach my $i (sort keys %our_pods) {
push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
unless $disk_pods{$i} or $BuildFiles{$i} or $not_yet_there{$i};
}
unless ($BuildFiles{'MANIFEST'}) {
# Again, ignore these if we're about to rebuild MANIFEST
foreach my $i (sort keys %manipods) {
push @inconsistent, "$0: $i is known by MANIFEST but does not exist\n"
unless $disk_pods{$i};
push @inconsistent, "$0: $i is known by MANIFEST but is marked as generated\n"
if $state{generated}{$i};
}
}
&$callback(@inconsistent);
return \%state;
}
1;
# Local variables:
# cperl-indent-level: 4
# indent-tabs-mode: nil
# End:
#
# ex: set ts=8 sts=4 sw=4 et: