#!/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: