This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
f3459ae5b707d965e85f7bec5d29a98884e6036a
[perl5.git] / lib / ExtUtils / Manifest.pm
1 package ExtUtils::Manifest;
2
3 require Exporter;
4 use Config;
5 use File::Basename;
6 use File::Copy 'copy';
7 use File::Find;
8 use File::Spec;
9 use Carp;
10 use strict;
11
12 use vars qw($VERSION @ISA @EXPORT_OK 
13           $Is_MacOS $Is_VMS 
14           $Debug $Verbose $Quiet $MANIFEST $DEFAULT_MSKIP);
15
16 $VERSION = '1.51';
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';
25 require 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
38 ExtUtils::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
64 ExtUtils::Manifest exports no functions by default.  The following are
65 exported on request
66
67 =over 4
68
69 =item mkmanifest
70
71     mkmanifest();
72
73 Writes all files in and below the current directory to your F<MANIFEST>.
74 It works similar to
75
76     find . > MANIFEST
77
78 All files that match any regular expression in a file F<MANIFEST.SKIP>
79 (if it exists) are ignored.
80
81 Any existing F<MANIFEST> file will be saved as F<MANIFEST.bak>.  Lines
82 from the old F<MANIFEST> file is preserved, including any comments
83 that are found in the existing F<MANIFEST> file in the new one.
84
85 =cut
86
87 sub _sort {
88     return sort { lc $a cmp lc $b } @_;
89 }
90
91 sub 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?
127 sub 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
139 returns a hash reference. The keys of the hash are the files found
140 below the current directory.
141
142 =cut
143
144 sub 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
175 checks if all the files within a C<MANIFEST> in the current directory
176 really do exist. If C<MANIFEST> and the tree below the current
177 directory are in sync it silently returns an empty list.
178 Otherwise it returns a list of files which are listed in the
179 C<MANIFEST> but missing from the directory, and by default also
180 outputs these names to STDERR.
181
182 =cut
183
184 sub manicheck {
185     return _check_files();
186 }
187
188
189 =item filecheck
190
191     my @extra_files = filecheck();
192
193 finds files below the current directory that are not mentioned in the
194 C<MANIFEST> file. An optional file C<MANIFEST.SKIP> will be
195 consulted. Any file matching a regular expression in such a file will
196 not be reported as missing in the C<MANIFEST> file. The list of any
197 extraneous files found is returned, and by default also reported to
198 STDERR.
199
200 =cut
201
202 sub filecheck {
203     return _check_manifest();
204 }
205
206
207 =item fullcheck
208
209     my($missing, $extra) = fullcheck();
210
211 does both a manicheck() and a filecheck(), returning then as two array
212 refs.
213
214 =cut
215
216 sub fullcheck {
217     return [_check_files()], [_check_manifest()];
218 }
219
220
221 =item skipcheck
222
223     my @skipped = skipcheck();
224
225 lists all the files that are skipped due to your C<MANIFEST.SKIP>
226 file.
227
228 =cut
229
230 sub 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
248 sub _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
272 sub _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
298 reads a named C<MANIFEST> file (defaults to C<MANIFEST> in the current
299 directory) and returns a HASH reference with files being the keys and
300 comments being the values of the HASH.  Blank lines and lines which
301 start with C<#> in the C<MANIFEST> file are discarded.
302
303 =cut
304
305 sub maniread {
306     my ($mfile) = @_;
307     $mfile ||= $MANIFEST;
308     my $read = {};
309     local *M;
310     unless (open M, $mfile){
311         warn "Problem opening $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
346 sub _maniskip {
347     my @skip ;
348     my $mfile = "$MANIFEST.SKIP";
349     _check_mskip_directives($mfile) if -f $mfile;
350     local(*M, $_);
351     open M, $mfile or open M, $DEFAULT_MSKIP or return sub {0};
352     while (<M>){
353         chomp;
354         s/\r//;
355         next if /^#/;
356         next if /^\s*$/;
357         push @skip, _macify($_);
358     }
359     close M;
360     return sub {0} unless (scalar @skip > 0);
361
362     my $opts = $Is_VMS ? '(?i)' : '';
363
364     # Make sure each entry is isolated in its own parentheses, in case
365     # any of them contain alternations
366     my $regex = join '|', map "(?:$_)", @skip;
367
368     return sub { $_[0] =~ qr{$opts$regex} };
369 }
370
371 # checks for the special directives
372 #   #!include_default
373 #   #!include /path/to/some/manifest.skip
374 # in a custom MANIFEST.SKIP for, for including
375 # the content of, respectively, the default MANIFEST.SKIP
376 # and an external manifest.skip file
377 sub _check_mskip_directives {
378     my $mfile = shift;
379     local (*M, $_);
380     my @lines = ();
381     my $flag = 0;
382     unless (open M, $mfile) {
383         warn "Problem opening $mfile: $!";
384         return;
385     }
386     while (<M>) {
387         if (/^#!include_default\s*$/) {
388             if (my @default = _include_mskip_file()) {
389                 push @lines, @default;
390                 warn "Debug: Including default MANIFEST.SKIP\n" if $Debug;
391                 $flag++;
392             }
393             next;
394         }
395         if (/^#!include\s+(.*)\s*$/) {
396             my $external_file = $1;
397             if (my @external = _include_mskip_file($external_file)) {
398                 push @lines, @external;
399                 warn "Debug: Including external $external_file\n" if $Debug;
400                 $flag++;
401             }
402             next;
403         }
404         push @lines, $_;
405     }
406     close M;
407     return unless $flag;
408     rename $mfile, "$mfile.bak";
409     warn "Debug: Saving original $mfile as $mfile.bak\n" if $Debug;
410     unless (open M, ">$mfile") {
411         warn "Problem opening $mfile: $!";
412         return;
413     }
414     print M $_ for (@lines);
415     close M;
416     return;
417 }
418
419 # returns an array containing the lines of an external
420 # manifest.skip file, if given, or $DEFAULT_MSKIP
421 sub _include_mskip_file {
422     my $mskip = shift || $DEFAULT_MSKIP;
423     unless (-f $mskip) {
424         warn qq{Included file "$mskip" not found - skipping};
425         return;
426     }
427     local (*M, $_);
428     unless (open M, $mskip) {
429         warn "Problem opening $mskip: $!";
430         return;
431     }
432     my @lines = ();
433     push @lines, "\n#!start included $mskip\n";
434     push @lines, $_ while <M>;
435     close M;
436     push @lines, "#!end included $mskip\n\n";
437     return @lines;
438 }
439
440 =item manicopy
441
442     manicopy(\%src, $dest_dir);
443     manicopy(\%src, $dest_dir, $how);
444
445 Copies the files that are the keys in %src to the $dest_dir.  %src is
446 typically returned by the maniread() function.
447
448     manicopy( maniread(), $dest_dir );
449
450 This function is useful for producing a directory tree identical to the 
451 intended distribution tree. 
452
453 $how can be used to specify a different methods of "copying".  Valid
454 values are C<cp>, which actually copies the files, C<ln> which creates
455 hard links, and C<best> which mostly links the files but copies any
456 symbolic link to make a tree without any symbolic link.  C<cp> is the 
457 default.
458
459 =cut
460
461 sub manicopy {
462     my($read,$target,$how)=@_;
463     croak "manicopy() called without target argument" unless defined $target;
464     $how ||= 'cp';
465     require File::Path;
466     require File::Basename;
467
468     $target = VMS::Filespec::unixify($target) if $Is_VMS;
469     File::Path::mkpath([ $target ],! $Quiet,$Is_VMS ? undef : 0755);
470     foreach my $file (keys %$read){
471         if ($Is_MacOS) {
472             if ($file =~ m!:!) { 
473                 my $dir = _maccat($target, $file);
474                 $dir =~ s/[^:]+$//;
475                 File::Path::mkpath($dir,1,0755);
476             }
477             cp_if_diff($file, _maccat($target, $file), $how);
478         } else {
479             $file = VMS::Filespec::unixify($file) if $Is_VMS;
480             if ($file =~ m!/!) { # Ilya, that hurts, I fear, or maybe not?
481                 my $dir = File::Basename::dirname($file);
482                 $dir = VMS::Filespec::unixify($dir) if $Is_VMS;
483                 File::Path::mkpath(["$target/$dir"],! $Quiet,$Is_VMS ? undef : 0755);
484             }
485             cp_if_diff($file, "$target/$file", $how);
486         }
487     }
488 }
489
490 sub cp_if_diff {
491     my($from, $to, $how)=@_;
492     -f $from or carp "$0: $from not found";
493     my($diff) = 0;
494     local(*F,*T);
495     open(F,"< $from\0") or die "Can't read $from: $!\n";
496     if (open(T,"< $to\0")) {
497         local $_;
498         while (<F>) { $diff++,last if $_ ne <T>; }
499         $diff++ unless eof(T);
500         close T;
501     }
502     else { $diff++; }
503     close F;
504     if ($diff) {
505         if (-e $to) {
506             unlink($to) or confess "unlink $to: $!";
507         }
508         STRICT_SWITCH: {
509             best($from,$to), last STRICT_SWITCH if $how eq 'best';
510             cp($from,$to), last STRICT_SWITCH if $how eq 'cp';
511             ln($from,$to), last STRICT_SWITCH if $how eq 'ln';
512             croak("ExtUtils::Manifest::cp_if_diff " .
513                   "called with illegal how argument [$how]. " .
514                   "Legal values are 'best', 'cp', and 'ln'.");
515         }
516     }
517 }
518
519 sub cp {
520     my ($srcFile, $dstFile) = @_;
521     my ($access,$mod) = (stat $srcFile)[8,9];
522
523     copy($srcFile,$dstFile);
524     utime $access, $mod + ($Is_VMS ? 1 : 0), $dstFile;
525     _manicopy_chmod($srcFile, $dstFile);
526 }
527
528
529 sub ln {
530     my ($srcFile, $dstFile) = @_;
531     return &cp if $Is_VMS or ($^O eq 'MSWin32' and Win32::IsWin95());
532     link($srcFile, $dstFile);
533
534     unless( _manicopy_chmod($srcFile, $dstFile) ) {
535         unlink $dstFile;
536         return;
537     }
538     1;
539 }
540
541 # 1) Strip off all group and world permissions.
542 # 2) Let everyone read it.
543 # 3) If the owner can execute it, everyone can.
544 sub _manicopy_chmod {
545     my($srcFile, $dstFile) = @_;
546
547     my $perm = 0444 | (stat $srcFile)[2] & 0700;
548     chmod( $perm | ( $perm & 0100 ? 0111 : 0 ), $dstFile );
549 }
550
551 # Files that are often modified in the distdir.  Don't hard link them.
552 my @Exceptions = qw(MANIFEST META.yml SIGNATURE);
553 sub best {
554     my ($srcFile, $dstFile) = @_;
555
556     my $is_exception = grep $srcFile =~ /$_/, @Exceptions;
557     if ($is_exception or !$Config{d_link} or -l $srcFile) {
558         cp($srcFile, $dstFile);
559     } else {
560         ln($srcFile, $dstFile) or cp($srcFile, $dstFile);
561     }
562 }
563
564 sub _macify {
565     my($file) = @_;
566
567     return $file unless $Is_MacOS;
568
569     $file =~ s|^\./||;
570     if ($file =~ m|/|) {
571         $file =~ s|/+|:|g;
572         $file = ":$file";
573     }
574
575     $file;
576 }
577
578 sub _maccat {
579     my($f1, $f2) = @_;
580
581     return "$f1/$f2" unless $Is_MacOS;
582
583     $f1 .= ":$f2";
584     $f1 =~ s/([^:]:):/$1/g;
585     return $f1;
586 }
587
588 sub _unmacify {
589     my($file) = @_;
590
591     return $file unless $Is_MacOS;
592
593     $file =~ s|^:||;
594     $file =~ s|([/ \n])|sprintf("\\%03o", unpack("c", $1))|ge;
595     $file =~ y|:|/|;
596
597     $file;
598 }
599
600
601 =item maniadd
602
603   maniadd({ $file => $comment, ...});
604
605 Adds an entry to an existing F<MANIFEST> unless its already there.
606
607 $file will be normalized (ie. Unixified).  B<UNIMPLEMENTED>
608
609 =cut
610
611 sub maniadd {
612     my($additions) = shift;
613
614     _normalize($additions);
615     _fix_manifest($MANIFEST);
616
617     my $manifest = maniread();
618     my @needed = grep { !exists $manifest->{$_} } keys %$additions;
619     return 1 unless @needed;
620
621     open(MANIFEST, ">>$MANIFEST") or 
622       die "maniadd() could not open $MANIFEST: $!";
623
624     foreach my $file (_sort @needed) {
625         my $comment = $additions->{$file} || '';
626         printf MANIFEST "%-40s %s\n", $file, $comment;
627     }
628     close MANIFEST or die "Error closing $MANIFEST: $!";
629
630     return 1;
631 }
632
633
634 # Sometimes MANIFESTs are missing a trailing newline.  Fix this.
635 sub _fix_manifest {
636     my $manifest_file = shift;
637
638     open MANIFEST, $MANIFEST or die "Could not open $MANIFEST: $!";
639
640     # Yes, we should be using seek(), but I'd like to avoid loading POSIX
641     # to get SEEK_*
642     my @manifest = <MANIFEST>;
643     close MANIFEST;
644
645     unless( $manifest[-1] =~ /\n\z/ ) {
646         open MANIFEST, ">>$MANIFEST" or die "Could not open $MANIFEST: $!";
647         print MANIFEST "\n";
648         close MANIFEST;
649     }
650 }
651
652
653 # UNIMPLEMENTED
654 sub _normalize {
655     return;
656 }
657
658
659 =back
660
661 =head2 MANIFEST
662
663 A list of files in the distribution, one file per line.  The MANIFEST
664 always uses Unix filepath conventions even if you're not on Unix.  This
665 means F<foo/bar> style not F<foo\bar>.
666
667 Anything between white space and an end of line within a C<MANIFEST>
668 file is considered to be a comment.  Any line beginning with # is also
669 a comment.
670
671     # this a comment
672     some/file
673     some/other/file            comment about some/file
674
675
676 =head2 MANIFEST.SKIP
677
678 The file MANIFEST.SKIP may contain regular expressions of files that
679 should be ignored by mkmanifest() and filecheck(). The regular
680 expressions should appear one on each line. Blank lines and lines
681 which start with C<#> are skipped.  Use C<\#> if you need a regular
682 expression to start with a C<#>.
683
684 For example:
685
686     # Version control files and dirs.
687     \bRCS\b
688     \bCVS\b
689     ,v$
690     \B\.svn\b
691
692     # Makemaker generated files and dirs.
693     ^MANIFEST\.
694     ^Makefile$
695     ^blib/
696     ^MakeMaker-\d
697
698     # Temp, old and emacs backup files.
699     ~$
700     \.old$
701     ^#.*#$
702     ^\.#
703
704 If no MANIFEST.SKIP file is found, a default set of skips will be
705 used, similar to the example above.  If you want nothing skipped,
706 simply make an empty MANIFEST.SKIP file.
707
708 In one's own MANIFEST.SKIP file, certain directives
709 can be used to include the contents of other MANIFEST.SKIP
710 files. At present two such directives are recognized.
711
712 =over 4
713
714 =item #!include_default
715
716 This inserts the contents of the default MANIFEST.SKIP file
717
718 =item #!include /Path/to/another/manifest.skip
719
720 This inserts the contents of the specified external file
721
722 =back
723
724 The included contents will be inserted into the MANIFEST.SKIP
725 file in between I<#!start included /path/to/manifest.skip>
726 and I<#!end included /path/to/manifest.skip> markers.
727 The original MANIFEST.SKIP is saved as MANIFEST.SKIP.bak.
728
729 =head2 EXPORT_OK
730
731 C<&mkmanifest>, C<&manicheck>, C<&filecheck>, C<&fullcheck>,
732 C<&maniread>, and C<&manicopy> are exportable.
733
734 =head2 GLOBAL VARIABLES
735
736 C<$ExtUtils::Manifest::MANIFEST> defaults to C<MANIFEST>. Changing it
737 results in both a different C<MANIFEST> and a different
738 C<MANIFEST.SKIP> file. This is useful if you want to maintain
739 different distributions for different audiences (say a user version
740 and a developer version including RCS).
741
742 C<$ExtUtils::Manifest::Quiet> defaults to 0. If set to a true value,
743 all functions act silently.
744
745 C<$ExtUtils::Manifest::Debug> defaults to 0.  If set to a true value,
746 or if PERL_MM_MANIFEST_DEBUG is true, debugging output will be
747 produced.
748
749 =head1 DIAGNOSTICS
750
751 All diagnostic output is sent to C<STDERR>.
752
753 =over 4
754
755 =item C<Not in MANIFEST:> I<file>
756
757 is reported if a file is found which is not in C<MANIFEST>.
758
759 =item C<Skipping> I<file>
760
761 is reported if a file is skipped due to an entry in C<MANIFEST.SKIP>.
762
763 =item C<No such file:> I<file>
764
765 is reported if a file mentioned in a C<MANIFEST> file does not
766 exist.
767
768 =item C<MANIFEST:> I<$!>
769
770 is reported if C<MANIFEST> could not be opened.
771
772 =item C<Added to MANIFEST:> I<file>
773
774 is reported by mkmanifest() if $Verbose is set and a file is added
775 to MANIFEST. $Verbose is set to 1 by default.
776
777 =back
778
779 =head1 ENVIRONMENT
780
781 =over 4
782
783 =item B<PERL_MM_MANIFEST_DEBUG>
784
785 Turns on debugging
786
787 =back
788
789 =head1 SEE ALSO
790
791 L<ExtUtils::MakeMaker> which has handy targets for most of the functionality.
792
793 =head1 AUTHOR
794
795 Andreas Koenig C<andreas.koenig@anima.de>
796
797 Maintained by Michael G Schwern C<schwern@pobox.com> within the
798 ExtUtils-MakeMaker package and, as a separate CPAN package, by
799 Randy Kobes C<r.kobes@uwinnipeg.ca>.
800
801 =cut
802
803 1;