1package ExtUtils::Manifest; 2 3require Exporter; 4use Config; 5use File::Basename; 6use File::Copy 'copy'; 7use File::Find; 8use File::Spec; 9use Carp; 10use strict; 11 12use vars qw($VERSION @ISA @EXPORT_OK 13 $Is_MacOS $Is_VMS 14 $Debug $Verbose $Quiet $MANIFEST $DEFAULT_MSKIP); 15 16$VERSION = '1.46'; 17@ISA=('Exporter'); 18@EXPORT_OK = qw(mkmanifest 19 manicheck filecheck fullcheck skipcheck 20 manifind maniread manicopy maniadd 21 ); 22 23$Is_MacOS = $^O eq 'MacOS'; 24$Is_VMS = $^O eq 'VMS'; 25require VMS::Filespec if $Is_VMS; 26 27$Debug = $ENV{PERL_MM_MANIFEST_DEBUG} || 0; 28$Verbose = defined $ENV{PERL_MM_MANIFEST_VERBOSE} ? 29 $ENV{PERL_MM_MANIFEST_VERBOSE} : 1; 30$Quiet = 0; 31$MANIFEST = 'MANIFEST'; 32 33$DEFAULT_MSKIP = File::Spec->catfile( dirname(__FILE__), "$MANIFEST.SKIP" ); 34 35 36=head1 NAME 37 38ExtUtils::Manifest - utilities to write and check a MANIFEST file 39 40=head1 SYNOPSIS 41 42 use ExtUtils::Manifest qw(...funcs to import...); 43 44 mkmanifest(); 45 46 my @missing_files = manicheck; 47 my @skipped = skipcheck; 48 my @extra_files = filecheck; 49 my($missing, $extra) = fullcheck; 50 51 my $found = manifind(); 52 53 my $manifest = maniread(); 54 55 manicopy($read,$target); 56 57 maniadd({$file => $comment, ...}); 58 59 60=head1 DESCRIPTION 61 62=head2 Functions 63 64ExtUtils::Manifest exports no functions by default. The following are 65exported on request 66 67=over 4 68 69=item mkmanifest 70 71 mkmanifest(); 72 73Writes all files in and below the current directory to your F<MANIFEST>. 74It works similar to 75 76 find . > MANIFEST 77 78All files that match any regular expression in a file F<MANIFEST.SKIP> 79(if it exists) are ignored. 80 81Any existing F<MANIFEST> file will be saved as F<MANIFEST.bak>. Lines 82from the old F<MANIFEST> file is preserved, including any comments 83that are found in the existing F<MANIFEST> file in the new one. 84 85=cut 86 87sub _sort { 88 return sort { lc $a cmp lc $b } @_; 89} 90 91sub mkmanifest { 92 my $manimiss = 0; 93 my $read = (-r 'MANIFEST' && maniread()) or $manimiss++; 94 $read = {} if $manimiss; 95 local *M; 96 rename $MANIFEST, "$MANIFEST.bak" unless $manimiss; 97 open M, ">$MANIFEST" or die "Could not open $MANIFEST: $!"; 98 my $skip = _maniskip(); 99 my $found = manifind(); 100 my($key,$val,$file,%all); 101 %all = (%$found, %$read); 102 $all{$MANIFEST} = ($Is_VMS ? "$MANIFEST\t\t" : '') . 'This list of files' 103 if $manimiss; # add new MANIFEST to known file list 104 foreach $file (_sort keys %all) { 105 if ($skip->($file)) { 106 # Policy: only remove files if they're listed in MANIFEST.SKIP. 107 # Don't remove files just because they don't exist. 108 warn "Removed from $MANIFEST: $file\n" if $Verbose and exists $read->{$file}; 109 next; 110 } 111 if ($Verbose){ 112 warn "Added to $MANIFEST: $file\n" unless exists $read->{$file}; 113 } 114 my $text = $all{$file}; 115 ($file,$text) = split(/\s+/,$text,2) if $Is_VMS && $text; 116 $file = _unmacify($file); 117 my $tabs = (5 - (length($file)+1)/8); 118 $tabs = 1 if $tabs < 1; 119 $tabs = 0 unless $text; 120 print M $file, "\t" x $tabs, $text, "\n"; 121 } 122 close M; 123} 124 125# Geez, shouldn't this use File::Spec or File::Basename or something? 126# Why so careful about dependencies? 127sub clean_up_filename { 128 my $filename = shift; 129 $filename =~ s|^\./||; 130 $filename =~ s/^:([^:]+)$/$1/ if $Is_MacOS; 131 return $filename; 132} 133 134 135=item manifind 136 137 my $found = manifind(); 138 139returns a hash reference. The keys of the hash are the files found 140below the current directory. 141 142=cut 143 144sub manifind { 145 my $p = shift || {}; 146 my $found = {}; 147 148 my $wanted = sub { 149 my $name = clean_up_filename($File::Find::name); 150 warn "Debug: diskfile $name\n" if $Debug; 151 return if -d $_; 152 153 if( $Is_VMS ) { 154 $name =~ s#(.*)\.$#\L$1#; 155 $name = uc($name) if $name =~ /^MANIFEST(\.SKIP)?$/i; 156 } 157 $found->{$name} = ""; 158 }; 159 160 # We have to use "$File::Find::dir/$_" in preprocess, because 161 # $File::Find::name is unavailable. 162 # Also, it's okay to use / here, because MANIFEST files use Unix-style 163 # paths. 164 find({wanted => $wanted}, 165 $Is_MacOS ? ":" : "."); 166 167 return $found; 168} 169 170 171=item manicheck 172 173 my @missing_files = manicheck(); 174 175checks if all the files within a C<MANIFEST> in the current directory 176really do exist. If C<MANIFEST> and the tree below the current 177directory are in sync it silently returns an empty list. 178Otherwise it returns a list of files which are listed in the 179C<MANIFEST> but missing from the directory, and by default also 180outputs these names to STDERR. 181 182=cut 183 184sub manicheck { 185 return _check_files(); 186} 187 188 189=item filecheck 190 191 my @extra_files = filecheck(); 192 193finds files below the current directory that are not mentioned in the 194C<MANIFEST> file. An optional file C<MANIFEST.SKIP> will be 195consulted. Any file matching a regular expression in such a file will 196not be reported as missing in the C<MANIFEST> file. The list of any 197extraneous files found is returned, and by default also reported to 198STDERR. 199 200=cut 201 202sub filecheck { 203 return _check_manifest(); 204} 205 206 207=item fullcheck 208 209 my($missing, $extra) = fullcheck(); 210 211does both a manicheck() and a filecheck(), returning then as two array 212refs. 213 214=cut 215 216sub fullcheck { 217 return [_check_files()], [_check_manifest()]; 218} 219 220 221=item skipcheck 222 223 my @skipped = skipcheck(); 224 225lists all the files that are skipped due to your C<MANIFEST.SKIP> 226file. 227 228=cut 229 230sub skipcheck { 231 my($p) = @_; 232 my $found = manifind(); 233 my $matches = _maniskip(); 234 235 my @skipped = (); 236 foreach my $file (_sort keys %$found){ 237 if (&$matches($file)){ 238 warn "Skipping $file\n"; 239 push @skipped, $file; 240 next; 241 } 242 } 243 244 return @skipped; 245} 246 247 248sub _check_files { 249 my $p = shift; 250 my $dosnames=(defined(&Dos::UseLFN) && Dos::UseLFN()==0); 251 my $read = maniread() || {}; 252 my $found = manifind($p); 253 254 my(@missfile) = (); 255 foreach my $file (_sort keys %$read){ 256 warn "Debug: manicheck checking from $MANIFEST $file\n" if $Debug; 257 if ($dosnames){ 258 $file = lc $file; 259 $file =~ s=(\.(\w|-)+)=substr ($1,0,4)=ge; 260 $file =~ s=((\w|-)+)=substr ($1,0,8)=ge; 261 } 262 unless ( exists $found->{$file} ) { 263 warn "No such file: $file\n" unless $Quiet; 264 push @missfile, $file; 265 } 266 } 267 268 return @missfile; 269} 270 271 272sub _check_manifest { 273 my($p) = @_; 274 my $read = maniread() || {}; 275 my $found = manifind($p); 276 my $skip = _maniskip(); 277 278 my @missentry = (); 279 foreach my $file (_sort keys %$found){ 280 next if $skip->($file); 281 warn "Debug: manicheck checking from disk $file\n" if $Debug; 282 unless ( exists $read->{$file} ) { 283 my $canon = $Is_MacOS ? "\t" . _unmacify($file) : ''; 284 warn "Not in $MANIFEST: $file$canon\n" unless $Quiet; 285 push @missentry, $file; 286 } 287 } 288 289 return @missentry; 290} 291 292 293=item maniread 294 295 my $manifest = maniread(); 296 my $manifest = maniread($manifest_file); 297 298reads a named C<MANIFEST> file (defaults to C<MANIFEST> in the current 299directory) and returns a HASH reference with files being the keys and 300comments being the values of the HASH. Blank lines and lines which 301start with C<#> in the C<MANIFEST> file are discarded. 302 303=cut 304 305sub maniread { 306 my ($mfile) = @_; 307 $mfile ||= $MANIFEST; 308 my $read = {}; 309 local *M; 310 unless (open M, $mfile){ 311 warn "$mfile: $!"; 312 return $read; 313 } 314 local $_; 315 while (<M>){ 316 chomp; 317 next if /^\s*#/; 318 319 my($file, $comment) = /^(\S+)\s*(.*)/; 320 next unless $file; 321 322 if ($Is_MacOS) { 323 $file = _macify($file); 324 $file =~ s/\\([0-3][0-7][0-7])/sprintf("%c", oct($1))/ge; 325 } 326 elsif ($Is_VMS) { 327 require File::Basename; 328 my($base,$dir) = File::Basename::fileparse($file); 329 # Resolve illegal file specifications in the same way as tar 330 $dir =~ tr/./_/; 331 my(@pieces) = split(/\./,$base); 332 if (@pieces > 2) { $base = shift(@pieces) . '.' . join('_',@pieces); } 333 my $okfile = "$dir$base"; 334 warn "Debug: Illegal name $file changed to $okfile\n" if $Debug; 335 $file = $okfile; 336 $file = lc($file) unless $file =~ /^MANIFEST(\.SKIP)?$/; 337 } 338 339 $read->{$file} = $comment; 340 } 341 close M; 342 $read; 343} 344 345# returns an anonymous sub that decides if an argument matches 346sub _maniskip { 347 my @skip ; 348 my $mfile = "$MANIFEST.SKIP"; 349 local(*M,$_); 350 open M, $mfile or open M, $DEFAULT_MSKIP or return sub {0}; 351 while (<M>){ 352 chomp; 353 next if /^#/; 354 next if /^\s*$/; 355 push @skip, _macify($_); 356 } 357 close M; 358 my $opts = $Is_VMS ? '(?i)' : ''; 359 360 # Make sure each entry is isolated in its own parentheses, in case 361 # any of them contain alternations 362 my $regex = join '|', map "(?:$_)", @skip; 363 364 return sub { $_[0] =~ qr{$opts$regex} }; 365} 366 367=item manicopy 368 369 manicopy(\%src, $dest_dir); 370 manicopy(\%src, $dest_dir, $how); 371 372Copies the files that are the keys in %src to the $dest_dir. %src is 373typically returned by the maniread() function. 374 375 manicopy( maniread(), $dest_dir ); 376 377This function is useful for producing a directory tree identical to the 378intended distribution tree. 379 380$how can be used to specify a different methods of "copying". Valid 381values are C<cp>, which actually copies the files, C<ln> which creates 382hard links, and C<best> which mostly links the files but copies any 383symbolic link to make a tree without any symbolic link. C<cp> is the 384default. 385 386=cut 387 388sub manicopy { 389 my($read,$target,$how)=@_; 390 croak "manicopy() called without target argument" unless defined $target; 391 $how ||= 'cp'; 392 require File::Path; 393 require File::Basename; 394 395 $target = VMS::Filespec::unixify($target) if $Is_VMS; 396 File::Path::mkpath([ $target ],! $Quiet,$Is_VMS ? undef : 0755); 397 foreach my $file (keys %$read){ 398 if ($Is_MacOS) { 399 if ($file =~ m!:!) { 400 my $dir = _maccat($target, $file); 401 $dir =~ s/[^:]+$//; 402 File::Path::mkpath($dir,1,0755); 403 } 404 cp_if_diff($file, _maccat($target, $file), $how); 405 } else { 406 $file = VMS::Filespec::unixify($file) if $Is_VMS; 407 if ($file =~ m!/!) { # Ilya, that hurts, I fear, or maybe not? 408 my $dir = File::Basename::dirname($file); 409 $dir = VMS::Filespec::unixify($dir) if $Is_VMS; 410 File::Path::mkpath(["$target/$dir"],! $Quiet,$Is_VMS ? undef : 0755); 411 } 412 cp_if_diff($file, "$target/$file", $how); 413 } 414 } 415} 416 417sub cp_if_diff { 418 my($from, $to, $how)=@_; 419 -f $from or carp "$0: $from not found"; 420 my($diff) = 0; 421 local(*F,*T); 422 open(F,"< $from\0") or die "Can't read $from: $!\n"; 423 if (open(T,"< $to\0")) { 424 local $_; 425 while (<F>) { $diff++,last if $_ ne <T>; } 426 $diff++ unless eof(T); 427 close T; 428 } 429 else { $diff++; } 430 close F; 431 if ($diff) { 432 if (-e $to) { 433 unlink($to) or confess "unlink $to: $!"; 434 } 435 STRICT_SWITCH: { 436 best($from,$to), last STRICT_SWITCH if $how eq 'best'; 437 cp($from,$to), last STRICT_SWITCH if $how eq 'cp'; 438 ln($from,$to), last STRICT_SWITCH if $how eq 'ln'; 439 croak("ExtUtils::Manifest::cp_if_diff " . 440 "called with illegal how argument [$how]. " . 441 "Legal values are 'best', 'cp', and 'ln'."); 442 } 443 } 444} 445 446sub cp { 447 my ($srcFile, $dstFile) = @_; 448 my ($access,$mod) = (stat $srcFile)[8,9]; 449 450 copy($srcFile,$dstFile); 451 utime $access, $mod + ($Is_VMS ? 1 : 0), $dstFile; 452 _manicopy_chmod($dstFile); 453} 454 455 456sub ln { 457 my ($srcFile, $dstFile) = @_; 458 return &cp if $Is_VMS or ($^O eq 'MSWin32' and Win32::IsWin95()); 459 link($srcFile, $dstFile); 460 461 unless( _manicopy_chmod($dstFile) ) { 462 unlink $dstFile; 463 return; 464 } 465 1; 466} 467 468# 1) Strip off all group and world permissions. 469# 2) Let everyone read it. 470# 3) If the owner can execute it, everyone can. 471sub _manicopy_chmod { 472 my($file) = shift; 473 474 my $perm = 0444 | (stat $file)[2] & 0700; 475 chmod( $perm | ( $perm & 0100 ? 0111 : 0 ), $file ); 476} 477 478# Files that are often modified in the distdir. Don't hard link them. 479my @Exceptions = qw(MANIFEST META.yml SIGNATURE); 480sub best { 481 my ($srcFile, $dstFile) = @_; 482 483 my $is_exception = grep $srcFile =~ /$_/, @Exceptions; 484 if ($is_exception or !$Config{d_link} or -l $srcFile) { 485 cp($srcFile, $dstFile); 486 } else { 487 ln($srcFile, $dstFile) or cp($srcFile, $dstFile); 488 } 489} 490 491sub _macify { 492 my($file) = @_; 493 494 return $file unless $Is_MacOS; 495 496 $file =~ s|^\./||; 497 if ($file =~ m|/|) { 498 $file =~ s|/+|:|g; 499 $file = ":$file"; 500 } 501 502 $file; 503} 504 505sub _maccat { 506 my($f1, $f2) = @_; 507 508 return "$f1/$f2" unless $Is_MacOS; 509 510 $f1 .= ":$f2"; 511 $f1 =~ s/([^:]:):/$1/g; 512 return $f1; 513} 514 515sub _unmacify { 516 my($file) = @_; 517 518 return $file unless $Is_MacOS; 519 520 $file =~ s|^:||; 521 $file =~ s|([/ \n])|sprintf("\\%03o", unpack("c", $1))|ge; 522 $file =~ y|:|/|; 523 524 $file; 525} 526 527 528=item maniadd 529 530 maniadd({ $file => $comment, ...}); 531 532Adds an entry to an existing F<MANIFEST> unless its already there. 533 534$file will be normalized (ie. Unixified). B<UNIMPLEMENTED> 535 536=cut 537 538sub maniadd { 539 my($additions) = shift; 540 541 _normalize($additions); 542 _fix_manifest($MANIFEST); 543 544 my $manifest = maniread(); 545 my @needed = grep { !exists $manifest->{$_} } keys %$additions; 546 return 1 unless @needed; 547 548 open(MANIFEST, ">>$MANIFEST") or 549 die "maniadd() could not open $MANIFEST: $!"; 550 551 foreach my $file (_sort @needed) { 552 my $comment = $additions->{$file} || ''; 553 printf MANIFEST "%-40s %s\n", $file, $comment; 554 } 555 close MANIFEST or die "Error closing $MANIFEST: $!"; 556 557 return 1; 558} 559 560 561# Sometimes MANIFESTs are missing a trailing newline. Fix this. 562sub _fix_manifest { 563 my $manifest_file = shift; 564 565 open MANIFEST, $MANIFEST or die "Could not open $MANIFEST: $!"; 566 567 # Yes, we should be using seek(), but I'd like to avoid loading POSIX 568 # to get SEEK_* 569 my @manifest = <MANIFEST>; 570 close MANIFEST; 571 572 unless( $manifest[-1] =~ /\n\z/ ) { 573 open MANIFEST, ">>$MANIFEST" or die "Could not open $MANIFEST: $!"; 574 print MANIFEST "\n"; 575 close MANIFEST; 576 } 577} 578 579 580# UNIMPLEMENTED 581sub _normalize { 582 return; 583} 584 585 586=back 587 588=head2 MANIFEST 589 590A list of files in the distribution, one file per line. The MANIFEST 591always uses Unix filepath conventions even if you're not on Unix. This 592means F<foo/bar> style not F<foo\bar>. 593 594Anything between white space and an end of line within a C<MANIFEST> 595file is considered to be a comment. Any line beginning with # is also 596a comment. 597 598 # this a comment 599 some/file 600 some/other/file comment about some/file 601 602 603=head2 MANIFEST.SKIP 604 605The file MANIFEST.SKIP may contain regular expressions of files that 606should be ignored by mkmanifest() and filecheck(). The regular 607expressions should appear one on each line. Blank lines and lines 608which start with C<#> are skipped. Use C<\#> if you need a regular 609expression to start with a C<#>. 610 611For example: 612 613 # Version control files and dirs. 614 \bRCS\b 615 \bCVS\b 616 ,v$ 617 \B\.svn\b 618 619 # Makemaker generated files and dirs. 620 ^MANIFEST\. 621 ^Makefile$ 622 ^blib/ 623 ^MakeMaker-\d 624 625 # Temp, old and emacs backup files. 626 ~$ 627 \.old$ 628 ^#.*#$ 629 ^\.# 630 631If no MANIFEST.SKIP file is found, a default set of skips will be 632used, similar to the example above. If you want nothing skipped, 633simply make an empty MANIFEST.SKIP file. 634 635 636=head2 EXPORT_OK 637 638C<&mkmanifest>, C<&manicheck>, C<&filecheck>, C<&fullcheck>, 639C<&maniread>, and C<&manicopy> are exportable. 640 641=head2 GLOBAL VARIABLES 642 643C<$ExtUtils::Manifest::MANIFEST> defaults to C<MANIFEST>. Changing it 644results in both a different C<MANIFEST> and a different 645C<MANIFEST.SKIP> file. This is useful if you want to maintain 646different distributions for different audiences (say a user version 647and a developer version including RCS). 648 649C<$ExtUtils::Manifest::Quiet> defaults to 0. If set to a true value, 650all functions act silently. 651 652C<$ExtUtils::Manifest::Debug> defaults to 0. If set to a true value, 653or if PERL_MM_MANIFEST_DEBUG is true, debugging output will be 654produced. 655 656=head1 DIAGNOSTICS 657 658All diagnostic output is sent to C<STDERR>. 659 660=over 4 661 662=item C<Not in MANIFEST:> I<file> 663 664is reported if a file is found which is not in C<MANIFEST>. 665 666=item C<Skipping> I<file> 667 668is reported if a file is skipped due to an entry in C<MANIFEST.SKIP>. 669 670=item C<No such file:> I<file> 671 672is reported if a file mentioned in a C<MANIFEST> file does not 673exist. 674 675=item C<MANIFEST:> I<$!> 676 677is reported if C<MANIFEST> could not be opened. 678 679=item C<Added to MANIFEST:> I<file> 680 681is reported by mkmanifest() if $Verbose is set and a file is added 682to MANIFEST. $Verbose is set to 1 by default. 683 684=back 685 686=head1 ENVIRONMENT 687 688=over 4 689 690=item B<PERL_MM_MANIFEST_DEBUG> 691 692Turns on debugging 693 694=back 695 696=head1 SEE ALSO 697 698L<ExtUtils::MakeMaker> which has handy targets for most of the functionality. 699 700=head1 AUTHOR 701 702Andreas Koenig C<andreas.koenig@anima.de> 703 704Currently maintained by Michael G Schwern C<schwern@pobox.com> 705 706=cut 707 7081; 709