#!/usr/bin/perl -w
#
#
#       makerpm.pl - A Perl script for building binary distributions
#                    of Perl packages
#
#       This script is Copyright (C) 1999       Jochen Wiedmann
#                                               Am Eisteich 9
#                                               72555 Metzingen
#                                               Germany
#
#                                               E-Mail: [email protected]
#
#       You may distribute under the terms of either the GNU General
#       Public License or the Artistic License, as specified in the
#       Perl README.
#

use strict;

use Cwd ();
use File::Find ();
use File::Path ();
use File::Spec ();
use File::Basename ();
use Getopt::Long ();
use Config ();


use vars qw($VERSION);
$VERSION = "makerpm 0.1102 02-Jan-2000, (C) 1999 Jochen Wiedmann";

=pod

=head1 NAME

 makerpm - Build binary distributions of Perl packages


=head1 SYNOPSIS

Create a SPECS file:

 makerpm --specs --source=<package>-<version>.tar.gz

Apply the SPECS file (which in turn uses makerpm.pl):

 rpm -ba <package>-<version>.spec

Create a PPM and a PPD file:

 makerpm --ppm --source=<package>-<version>.tar.gz


=head1 DESCRIPTION

The I<makerpm> script is designed for creating binary distributions of
Perl modules, for example RPM packages (Linux) or PPM files (Windows,
running ActivePerl).


=head2 Creating RPM packages

To create a new binary and source RPM, you typically store the tar.gz
file in F</usr/src/redhat/SOURCES> (F</usr/src/packages/SOURCES> in
case of SuSE and F</usr/src/OpenLinux/SOURCES> in case of Caldera) and
do a

 makerpm --specs --source=<package>-<version>.tar.gz

This will create a SPECS file in F</usr/src/redhat/SPECS>
(F</usr/src/packages/SOURCES> in case of SuSE and
F</usr/src/OpenLinux/SOURCES> in case of Caldera) which you
can use with

 rpm -ba /usr/src/redhat/SPECS/<package>-<version>.spec

If the default behaviour is fine for you, that will do. Otherwise see
the list of options below.


=head2 Creating PPM packages

A PPM package consists of two files: The PPD (Perl Package description)
file which contains XML source describing the package details and the
PPM file which is nothing else than the archived blib directory.

You can create the package with

 makerpm --ppm --source=<package>-<version>.tar.gz


=head2 Command Line Options

Possible command line options are:

=over 8

=item --build

Compile the sources, typically by running

       perl Makefile.PL
       make

=item --build-root=<dir>

Installation of the Perl package occurs into a separate directory, the
build root directory. For example, a package DBI 1.07 could be installed
into F</var/tmp/DBI-1.07>. Binaries are installed into F<$build_root/usr/bin>
rather than F</usr/bin>, man pages in F<$build_root/usr/man> and so on.

The idea is making the build process really reproducible and building the
package without destructing an existing installation.

You don't need to supply a build root directory, a default name is
choosen.

=item --copyright=<msg>

Set the packages copyright message. The default is

 GNU General Public or Artistic License, as specified in the Perl README

=item --debug

Turns on debugging mode. Debugging mode prevents most things from really
being done and implies verbose mode.

=item --help

Print the usage message and exit.

=item --install

Install the sources, typically by running

       make install

Installation doesn't occur into the final destination. Instead a
so-called buildroot directory (for example F</var/tmp/build-root>)
is created and installation is adapted relative to that directory.
See the I<--build-root> option for details.

=item --make=<path>

Set path of the I<make> binary; defaults to the location read from Perl's
Config module. L<Config(3)>.

=item --makeopts=<opts>

Set options for running "make" and "make install"; defaults to none.

=item --makemakeropts=<opts>

If you need certain options for running "perl Makefile.PL", this is
your friend. By default no options are set.

=item --mode=<mode>

Set build mode, for example RPM or PPM. By default the build mode
is read from the script name: If you invoke it as I<makerpm>, then
RPM mode is choosen. When running as I<makeppm>, then PPM mode is
enabled.

=item --package-name=<name>

=item --package-version=<version>

Set the package name and version. These options are required for --build and
--install.

=item --prep

Extract the sources and prepare the source directory.

=item --rpm-top-dir=<dir>

=item --rpm-build-dir=<dir>

=item --rpm-source-dir=<dir>

=item --rpm-specs-dir=<dir>

Sets certain directory names related to RPM mode, defaults to
F</usr/src/redhat> (or F</usr/src/packages> on SuSE Linux or
F</usr/src/OpenLinux> on Caldera) F<$topdir/BUILD>, F<$topdir/SOURCES>
and F<$topdir/SPECS>.

=item --rpm-group=<group>

Sets the RPM group; defaults to Development/Languages/Perl.

=item --setup-dir=<dir>

Name of setup directory; defaults to <package>-<version>. The setup
directory is the name of the directory that is created by extracting
the sources. Example: DBI-1.07.

=item --source=<file>

Source file name; used to determine defaults for --package-name and
--package-version. This option is required for --specs and --prep.

=item --summary=<msg>

Summary line; defaults to "The Perl package <name>".

=item --verbose

Turn on verbose mode. Lots of debugging messages are emitted.

=item --version

Print version string and exit.

=back

=cut


package Distribution;


$Distribution::TMP_DIR = '/tmp';
foreach my $dir (qw(/var/tmp /tmp C:/Windows/temp D:/Windows/temp)) {
   if (-d $dir) {
       $Distribution::TMP_DIR = $dir;
       last;
   }
}

$Distribution::COPYRIGHT = "Artistic or GNU General Public License,"
   . " as specified by the Perl README";


sub new {
   my $proto = shift;
   my $self = { @_ };
   bless($self, ref($proto) || $proto);

   if ($self->{'source'}  &&
       $self->{'source'} =~ /(.*(?:\/|\\))?(.*)-(.+)
                             (\.(tar\.gz|tgz|zip))$/x) {
       $self->{'package-name'} ||= $2;
       $self->{'package-version'} ||= $3;
   }

   $self->{'name'} = $self->{'package-name'}
       or die "Missing package name";
   $self->{'version'} = $self->{'package-version'}
       or die "Missing package version";

   $self->{'source_dirs'} ||= [ File::Spec->curdir() ];
   $self->{'default_setup_dir'} = "$self->{'name'}-$self->{'version'}";
   $self->{'setup-dir'} ||= $self->{'default_setup_dir'};
   $self->{'build_dir'} = File::Spec->curdir();
   $self->{'make'} ||= $Config::Config{'make'};
   $self->{'build-root'} ||= File::Spec->catdir($Distribution::TMP_DIR,
                                                $self->{'setup-dir'});
   $self->{'copyright'} ||= $Distribution::COPYRIGHT;
   $self->{'summary'} ||= "The Perl package $self->{'name'}";

   if (!defined($self->{'start_perl'} = $self->{'perl-path'})) {
       $self->{'start_perl'} = substr($Config::Config{'startperl'}, 2)
           if defined $Config::Config{'startperl'};
   }
   $self->{'start_perl'} = undef
       if defined($self->{'start_perl'}) && $self->{'start_perl'} eq 'undef';

   $self;
}


sub MakeDirFor {
   my($self, $file) = @_;
   my $dir = File::Basename::dirname($file);
   if (! -d $dir) {
       print "Making directory $dir\n" if $self->{'verbose'};
       File::Path::mkpath($dir, 0, 0755)  ||
           die "Failed to create directory $dir: $!";
   }
}


sub Extract {
   my $self = shift;  my $dir = shift || File::Spec->curdir();
   print "Changing directory to $dir\n" if $self->{'verbose'};
   chdir $dir || die "Failed to chdir to $dir: $!";

   # Look for the source file
   my $source = $self->{'source'} || die "Missing source definition";
   if (! -f $source) {
       foreach my $dir (@{$self->{'source_dirs'}}) {
           print "Looking for $source in $dir\n" if $self->{'debug'};
           my $s = File::Spec->catfile($dir, $source);
           if (-f $s) {
               print "Found $source in $dir\n" if $self->{'debug'};
               $source = $s;
               last;
           }
       }
   }

   $dir = $self->{'setup-dir'};
   if (-d $dir) {
       print "Removing directory $dir" if $self->{'verbose'};
       File::Path::rmtree($dir, 0, 0) unless $self->{'debug'};
   }

   print "Extracting $source\n" if $self->{'verbose'};
   eval { require Archive::Tar; require Compress::Zlib; };
   if ($@) {
       # Archive::Tar is not available; fallback to tar and gzip
       my $command = $^O eq "MSWin32" ?
           "tar xzf $source" :
           "gzip -cd $source | tar xf - 2>&1";
       my $output = `$command`;
       die "Archive::Tar and Compress::Zlib are not available\n"
           . " and using tar and gzip failed.\n"
           . " Command was: $command\n"
           . " Output was: $output\n"
               if $output;
   } elsif (Archive::Tar->can("extract_archive")) {
       die "Failed to extract archive $source: " . Archive::Tar->error()
           unless defined(Archive::Tar->extract_archive($source));
   } else {
       my $tar = Archive::Tar->new();
       my $compressed = $source =~ /\.(?:tgz|gz|z|zip)$/i;
       my $numFiles = $tar->read($source, $compressed);
       die("Failed to read archive $source")
           unless $numFiles;
       die("Failed to store contents of archive $source: ", $tar->error())
           if $tar->extract($tar->list_files());
   }
}

sub Modes {
   my $self = shift; my $dir = shift || File::Spec->curdir();

   return if $^O eq "MSWin32";

   print "Changing directory to $dir\n" if $self->{'verbose'};
   chdir $dir || die "Failed to chdir to $dir: $!";
   my $handler = sub {
       my($dev, $ino, $mode, $nlink, $uid, $gid) = stat;
       my $new_mode = 0444;
       $new_mode |= 0200 if $mode & 0200;
       $new_mode |= 0111 if $mode & 0100;
       chmod $new_mode, $_
           or die "Failed to change mode of $File::Find::name: $!";
       if ($self->{chown}) {
           chown 0, 0, $_
               or die "Failed to change ownership of $File::Find::name: $!";
       }
   };

   $dir = File::Spec->curdir();
   print "Changing modes in $dir\n" if $self->{'verbose'};
   File::Find::find($handler, $dir);
}

sub Prep {
   my $self = shift;
   my $old_dir = Cwd::cwd();
   eval {
       my $dir = $self->{'build_dir'};
       print "Changing directory to $dir\n" if $self->{'verbose'};
       chdir $dir || die "Failed to chdir to $dir: $!";
       if (-d $self->{'setup-dir'}) {
           print "Removing directory: $self->{'setup-dir'}\n"
               if $self->{'verbose'};
           File::Path::rmtree($self->{'setup-dir'}, 0, 0);
       }
       $self->Extract();
       $self->Modes($self->{'setup-dir'});
   };
   my $status = $@;
   print "Changing directory to $old_dir\n" if $self->{'verbose'};
   chdir $old_dir;
   die $@ if $status;
}

sub PerlMakefilePL {
   my $self = shift; my $dir = shift || File::Spec->curdir();
   print "Changing directory to $dir\n" if $self->{'verbose'};
   chdir $dir || die "Failed to chdir to $dir: $!";
   my $command = "$^X Makefile.PL " . ($self->{'makemakeropts'} || '');
   print "Creating Makefile: $command\n" if $self->{'verbose'};
   exit 1 if system $command;
}

sub Make {
   my $self = shift;
   if (my $dir = shift) {
       print "Changing directory to $dir\n" if $self->{'verbose'};
       chdir $dir || die "Failed to chdir to $dir: $!";
   }
   my $command = "$self->{'make'} " . ($self->{'makeopts'} || '');
   print "Running Make: $command\n";
   exit 1 if system $command;

   if ($self->{'runtests'}) {
       $command .= " test";
       print "Running Make Test: $command\n";
       exit 1 if system $command;
   }
}

sub ReadLocations {
   my %vars;
   my $fh = Symbol::gensym();
   open($fh, "<Makefile") || die "Failed to open Makefile: $!";
   while (my $line = <$fh>) {
       # Skip comments and/or empty lines
       next if $line =~ /^\s*\#/ or $line =~ /^\s*$/;
       if ($line =~ /^\s*(\w+)\s*\=\s*(.*)\s*$/) {
           # Variable definition
           my $var = $1;
           my $val = $2;
           $val =~ s/\$(\w)/defined($vars{$1})?$vars{$1}:''/gse;
           $val =~ s/\$\((\w+)\)/defined($vars{$1})?$vars{$1}:''/gse;
           $val =~ s/\$\{(\w+)\}/defined($vars{$1})?$vars{$1}:''/gse;
           $vars{$var} = $val;
       }
   }
   \%vars;
}

sub AdjustPaths {
   my $self = shift; my $build_root = shift;
   my $adjustPathsSub = sub {
       my $f = $_;
       return unless -f $f && ! -z _;
       my $fh = Symbol::gensym();
       open($fh, "+<$f") or die "Failed to open $File::Find::name: $!";
       local $/ = undef;
       my $contents;
       die "Failed to read $File::Find::name: $!"
           unless defined($contents = <$fh>);
       my $modified;
       if ($self->{'start_perl'}) {
           $contents =~ s/^\#\!(\S+)/\#\!$self->{'start_perl'}/s;
           $modified = 1;
       }
       if ($contents =~ s/\Q$build_root\E//gs) {
           $modified = 1;
       }
       if ($modified) {
           seek($fh, 0, 0) or die "Failed to seek in $File::Find::name: $!";
           (print $fh $contents)
               or die "Failed to write $File::Find::name: $!";
           truncate $fh, length($contents)
               or die "Failed to truncate $File::Find::name: $!";
       }
       close($fh) or die "Failed to close $File::Find::name: $!";
   };
   File::Find::find($adjustPathsSub, $self->{'build-root'});
}


sub MakeInstall {
   my $self = shift;
   if (my $dir = shift) {
       print "Changing directory to $dir\n" if $self->{'verbose'};
       chdir $dir || die "Failed to chdir to $dir: $!";
   }

   my $locations = ReadLocations();

   my $command = "$self->{'make'} " . ($self->{'makeopts'} || '')
       . " install";
   foreach my $key (qw(INSTALLPRIVLIB INSTALLARCHLIB INSTALLSITELIB
                       INSTALLSITEARCH INSTALLBIN INSTALLSCRIPT
                       INSTALLMAN1DIR INSTALLMAN3DIR)) {
       my $d = File::Spec->canonpath(File::Spec->catdir($self->{'build-root'},
                                                        $locations->{$key}));
       $command .= " $key=$d";
   }
   print "Running Make Install: $command\n" if $self->{'verbose'};
   exit 1 if !$self->{'debug'} and system $command;

   print "Adjusting Paths in $self->{'build-root'}\n";
   $self->AdjustPaths($self->{'build-root'});

   my($files, $dirs) = $self->Files($self->{'build-root'});
   my $fileList = '';
   foreach my $dir (sort keys %$dirs) {
       next if $dirs->{$dir};
       $fileList .= "%dir $dir\n";
   }
   foreach my $file (sort keys %$files) {
       $fileList .= "$file\n";
   }

   my($filelist_path, $specs_path) = $self->FileListPath();
   if ($filelist_path) {
       my $fh = Symbol::gensym();
       (open($fh, ">$filelist_path")  and  (print $fh $fileList)
        and  close($fh))
           or  die "Failed to create list of files in $filelist_path: $!";
   }
   $specs_path;
}


sub Build {
   my $self = shift;
   my $old_dir = Cwd::cwd();
   eval {
       my $dir = $self->{'build_dir'};
       print "Changing directory to $dir\n" if $self->{'verbose'};
       chdir $dir || die "Failed to chdir to $dir: $!";
       $self->PerlMakefilePL($self->{'setup-dir'});
       $self->Make();
   };
   my $status = $@;
   chdir $old_dir;
   die $@ if $status;
}

sub CleanBuildRoot {
   my $self = shift; my $dir = shift || die "Missing directory name";
   print "Cleaning build root $dir\n" if $self->{'verbose'};
   File::Path::rmtree($dir, 0, 0) unless $self->{'debug'};
}

sub Install {
   my $self = shift;
   my $old_dir = Cwd::cwd();
   my $filelist;
   eval {
       my $dir = $self->{'build_dir'};
       print "Changing directory to $dir\n" if $self->{'verbose'};
       chdir $dir || die "Failed to chdir to $dir: $!";
       $self->CleanBuildRoot($self->{'build-root'});
       $filelist = $self->MakeInstall($self->{'setup-dir'});
   };
   my $status = $@;
   chdir $old_dir;
   die $@ if $status;
   $filelist;
}


package Distribution::RPM;

@Distribution::RPM::ISA = qw(Distribution);

{
   my($source_dir, $build_dir, $specs_dir);
   sub Init {
       my $proto = shift; my $fatal = shift;
       my $topdir;
       if (!$source_dir) {
           $source_dir = $ENV{'RPM_SOURCE_DIR'} if $ENV{'RPM_SOURCE_DIR'};
           $build_dir = $ENV{'RPM_BUILD_DIR'} if $ENV{'RPM_BUILD_DIR'};

           my $rpm_output = `rpm --showrc`;
           my $rpm_version = `rpm --version`;
           if ($rpm_version =~ /rpm\s+version\s+2\.+/i) {
               foreach my $ref (['topdir', \$topdir],
                                ['specdir', \$specs_dir],
                                ['sourcedir', \$source_dir],
                                ['builddir', \$build_dir]) {
                   my $var = $ref->[0];
                   if ($rpm_output =~ /^$var\s+\S+\s+(.*)/m) {
                       ${$ref->[1]} ||= $1;
                   }
               }
           } elsif ($rpm_version =~ /rpm\s+version\s+3\.+/i) {
               my $varfunc;
               $varfunc = sub {
                   my $var = shift;
                   my $val;
                   if ($rpm_output =~ /^\S+\s+$var\s+(.*)/m) {
                       $val = $1;
                       while ($val =~ /\%\{(\S+)\}/) {
                           my $vr = $1;
                           my $vl = &$varfunc($vr);
                           if (defined($vl)) {
                               $val =~ s/^\%\{\Q$vr\E\}/$vl/gs;
                           } else {
                               return undef;
                           }
                       }
                       return $val;
                   }
                   return undef;
               };
               foreach my $ref (['_topdir', \$topdir],
                                ['_specdir', \$specs_dir],
                                ['_sourcedir', \$source_dir],
                                ['_builddir', \$build_dir]) {
                   ${$ref->[1]} ||= &$varfunc($ref->[0]);
               }
           }
           die "Cannot handle your RPM version: " . ($rpm_version || "")
               if (!$source_dir or !$specs_dir or !$build_dir) and $fatal;
       }
       if (!$topdir) {
           foreach my $dir ("redhat", "packages", "OpenLinux") {
               if (-d "/usr/src/$dir") {
                   $topdir = "/usr/src/$dir";
                   last;
               }
           }
           die "Unable to determine RPM topdir";
       }
       $source_dir ||= "$topdir/SOURCES";
       $specs_dir ||= "$topdir/SPECS";
       $build_dir ||= "$topdir/BUILD";
       return ($source_dir, $build_dir, $specs_dir);
   }
}

sub new {
   my $proto = shift;
   my $self = $proto->SUPER::new(@_);
   ($self->{'rpm-source-dir'}, $self->{'rpm-build-dir'},
    $self->{'rpm-specs-dir'}) = $proto->Init(1);
   $self->{'rpm-group'} ||= 'Development/Languages/Perl';
   push(@{$self->{'source_dirs'}}, $self->{'rpm-source-dir'});
   $self->{'build_dir'} = $self->{'rpm-build-dir'};
   $self;
}


sub Files {
   my $self = shift;  my $buildRoot = shift;
   my(%files, %dirs);
   my $findSub = sub {
       if (-d $_) {
           $dirs{$File::Find::name} ||= 0;
           $dirs{$File::Find::dir} = 1;
       } elsif (-f _) {
           $files{$File::Find::name} = 1;
           $dirs{$File::Find::dir} = 1;
       } else {
           die "Unknown file type: $File::Find::name";
       }
   };
   File::Find::find($findSub, $buildRoot);

   # Remove the trailing buildRoot
   my(%f, %d);
   while (my($key, $val) = each %files) {
       $key =~ s/^\Q$buildRoot\E//;
       $f{$key} = $val
   }
   while (my($key, $val) = each %dirs) {
       $key =~ s/^\Q$buildRoot\E//;
       $d{$key} = $val
   }
   (\%f, \%d, \%files, \%dirs);
}

sub FileListPath {
   my $self = shift;
   my $fl = $self->{'setup-dir'} . ".rpmfilelist";
   ($fl, File::Spec->catdir($self->{'setup-dir'}, $fl));
}

sub Specs {
   my $self = shift;
   my $old_dir = Cwd::cwd();
   eval {
       $self->Prep();
       $self->Build();
       my $filelist = $self->Install();

       my($files, $dirs) = $self->Files($self->{'build-root'});

       my $specs = <<"EOF";
%define packagename $self->{'name'}
%define packageversion $self->{'version'}
%define release 1
EOF
       my $mo = $self->{'makeopts'} || '';
       $mo =~ s/\n\t/ /sg;
       $specs .= sprintf("%%define makeopts \"%s\"\n",
                         ($mo ? sprintf("--makeopts=%s",
                                        quotemeta($mo)) : ""));
       my $mmo = $self->{'makemakeropts'} || '';
       $mmo =~ s/\n\t/ /sg;
       $specs .= sprintf("%%define makemakeropts \"%s\"\n",
                         ($mmo ? sprintf("--makemakeropts=%s",
                                         quotemeta($mmo)) : ""));
       my $setup_dir = $self->{'setup-dir'} eq $self->{'default_setup_dir'} ?
           "" : " --setup-dir=$self->{'setup-dir'}";

       my $makerpm_path = File::Spec->catdir('$RPM_SOURCE_DIR', 'makerpm.pl');
       $makerpm_path = File::Spec->canonpath($makerpm_path) . $setup_dir .
           " --source=$self->{'source'}";
       $specs .= <<"EOF";

Name:      perl-%{packagename}
Version:   %{packageversion}
Release:   %{release}
Group:     $self->{'rpm-group'}
Source:    $self->{'source'}
Source1:   makerpm.pl
Copyright: $self->{'copyright'}
BuildRoot: $self->{'build-root'}
Provides:  perl-%{packagename}
Summary:   $self->{'summary'}
EOF

       if (my $req = $self->{'require'}) {
           $specs .= "Requires: " . join(" ", @$req) . "\n";
       }

       my $runtests = $self->{'runtests'} ? " --runtests" : "";


       $specs .= <<"EOF";

%description
$self->{'summary'}

%prep
$makerpm_path --prep

%build
$makerpm_path --build$runtests %{makeopts} %{makemakeropts}

%install
rm -rf \$RPM_BUILD_ROOT
$makerpm_path --install %{makeopts}

%clean
rm -rf \$RPM_BUILD_ROOT

%files -f $filelist
EOF

       my $specs_name = "$self->{'name'}-$self->{'version'}.spec";
       my $specs_file = File::Spec->catfile($self->{'rpm-specs-dir'},
                                            $specs_name);
       $specs_file = File::Spec->canonpath($specs_file);
       print "Creating SPECS file $specs_file\n";
       print $specs if $self->{'verbose'};
       unless ($self->{'debug'}) {
           my $fh = Symbol::gensym();
           open(FILE, ">$specs_file") or die "Failed to open $specs_file: $!";
           (print FILE $specs) or die "Failed to write to $specs_file: $!";
           close(FILE) or die "Failed to close $specs_file: $!";
       }
   };
   my $status = $@;
   chdir $old_dir;
   die $status if $status;
}

sub PPM {
   die "Cannot build PPM files in RPM mode.\n";
}


package Distribution::PPM;

@Distribution::PPM::ISA = qw(Distribution);

sub new {
   my $proto = shift;
   my $self = $proto->SUPER::new(@_);
   $self->{'ppm-dir'} ||= Cwd::cwd();
   $self->{'ppm-ppdfile'} ||=
       $self->{'ppm-noversion'} ?
           "$self->{'package-name'}.ppd" :
           "$self->{'package-name'}-$self->{'package-version'}.ppd";
   if (!$self->{'ppm-ppmfile'}) {
       my($base, $dir, $suffix) =
           File::Basename::fileparse($self->{'ppm-ppdfile'}, "\.ppd");
       die("Failed to create name PPM file name from PPD file name ",
           $self->{'ppm-ppdfile'}) unless $suffix;
       $self->{'ppm-ppmfile'} =
           $self->{'ppm-noversion'} ?
               "$base.tar.gz" :
               File::Spec->catfile($dir, "x86",
                                   "$base.tar.gz");
   }
   $self;
}

sub Specs {
   die "Cannot build a SPECS file in PPM mode.\n";
}

sub MakePPD {
   my $self = shift;
   my $dir = File::Spec->catdir($self->{'build_dir'},
                                $self->{'setup-dir'});
   print "Changing directory to $dir\n" if $self->{'verbose'};
   chdir $dir || die "Failed to chdir to $dir: $!";
   my $command = "$self->{'make'} ppd " . ($self->{'makeopts'} || '');
   print "Running Make PPD: $command\n";
   exit 1 if system $command;
   my $fh = Symbol::gensym();
   my $ppd_name = "$self->{'package-name'}.ppd";
   open($fh, "<$ppd_name") ||
       die "Failed to open generated PPD file $ppd_name: $!";
   local $/ = undef;
   my $ppd_contents = <$fh>;
   die "Failed to read generated PPD file $ppd_name: $!"
       unless defined $ppd_contents;

   $ppd_contents =~ s/(\<codebase href=\").*(\")/$1$self->{'ppm-ppmfile'}$2/i;

   $ppd_name = $self->{'ppm-ppdfile'};
   $ppd_name = File::Spec->catdir($self->{'ppm-dir'}, $ppd_name)
       unless File::Spec->file_name_is_absolute($ppd_name);
   print "Creating PPD file $ppd_name.\n";
   $self->MakeDirFor($ppd_name);
   $fh = Symbol::gensym();
   (open($fh, ">$ppd_name") &&  (print $fh $ppd_contents)  &&  close($fh))  ||
       die "Failed to create PPD file $ppd_name: $!";
}

sub MakePPM {
   my $self = shift;
   my $ppm_file = $self->{'ppm-ppmfile'};
   $ppm_file = File::Spec->catdir($self->{'ppm-dir'}, $ppm_file)
       unless File::Spec->file_name_is_absolute($ppm_file);
   print "Creating PPM file $ppm_file.\n";
   $self->MakeDirFor($ppm_file);
   eval { require Archive::Tar; require Compress::Zlib; };
   if ($@) {
       # Archive::Tar is not available; fallback to tar and gzip
       my $command = $^O eq "MSWin32" ?
           "tar czf $ppm_file blib" :
           "tar czf - blib | gzip -c >$ppm_file 2>&1";
       print "Creating PPM file: $command\n" if $self->{'verbose'};
       $command .= " 2>&1" unless $^O eq "MSWin32";
       my $output = `$command 2>&1`;
       die "Archive::Tar and Compress::Zlib are not available\n"
           . " and using tar failed.\n"
           . " Command was: $command\n"
           . " Output was: $output\n"
               if $output;
   } else {
       my @files;
       File::Find::find(sub { push(@files, $File::Find::name) if -f $_},
                        "blib");
       my $tar = Archive::Tar->new();
       my $result = $tar->add_files(@files);
       die "Failed to add files to archive: $!" unless $result;
       $result = $tar->write($ppm_file, 1);
       die "Failed to store archive $ppm_file: $!" if $result;
   }
}

sub PPM {
   my $self = shift;
   my $old_dir = Cwd::cwd();
   eval {
       $self->Prep();
       $self->Build();
       $self->MakePPD();
       $self->MakePPM();
   };
   my $status = $@;
   chdir $old_dir;
   die $status if $status;
}


package main;

sub Mode {
   return "RPM" if $0 =~ /rpm/i;
   return "PPM" if $0 =~ /ppm/i;
   undef;
}

sub Usage {
   my $mode = Mode() || "undef";
   my $build_root = File::Spec->catdir($Distribution::TMP_DIR,
                                       "<name>-<version>");
   my $start_perl = substr($Config::Config{'startperl'}, 2);

   my ($rpm_source_dir, $rpm_build_dir, $rpm_specs_dir) =
       Distribution::RPM->Init(1);

   print <<EOF;
Usage: $0 <action> [options]

Possible actions are:

 --prep        Prepare the source directory
 --build       Compile the sources
 --install     Install the compiled sources into the buildroot directory
 --specs       Create a SPECS file by performing the above steps in order
               to determine the list of installed files.
 --ppm         Create an ActivePerl package

Possible options are:

 --build-root=<dir>            Set build-root directory for installation;
                               defaults to $build_root.
 --copyright=<msg>             Set copyright message, defaults to
                               "GNU General Public License or Artistic
                               License, as specified in the Perl README".
 --debug                       Turn on debugging mode
 --help                        Print this message
 --make=<path>                 Set "make" path; defaults to $Config::Config{'make'}
 --makemakeropts=<opts>        Set options for running "perl Makefile.PL";
                               defaults to none.
 --makeopts=<opts>             Set options for running "make" and "make
                               install"; defaults to none.
 --mode=<mode>                 Set build mode, defaults to $mode.
                               Possible modes are "RPM" or "PPM".
 --package-name=<name>         Set package name.
 --package-version=<name>      Set package version.
 --perl-path=<path>            Perl path to verify in generated scripts;
                               defaults to $start_perl
 --require=<package>           Set prerequisite packages. May be used
                               multiple times.
 --runtests                    By default no "make test" is done. You
                               can override this with --runtests.
 --setup-dir=<dir>             Name of setup directory; defaults to
                               <name>-<version>
 --source=<file>               Source file name; used to determine defaults
                               for <name> and <version>.
 --summary=<msg>               One line desription of the package; defaults
                               to "The Perl package <name>".
 --verbose                     Turn on verbose mode.
 --version                     Print version string and exit.

Options for RPM mode are:

 --rpm-build-dir=<dir>         RPM build directory; defaults to
                               $rpm_build_dir.
 --rpm-group=<group>           RPM group, default Development/Languages/Perl.
 --rpm-source-dir=<dir>        RPM source directory; defaults to
                               $rpm_source_dir.
 --rpm-specs-dir=<dir>         RPM specs directory; defaults to
                               $rpm_specs_dir.

Options for PPM mode are:

 --ppm-ppdfile=<file>          Set the name of the PPD file; defaults to
                               <package>-<version>.ppd in the directory
                               given by ppd-dir.
 --ppm-ppmfile=<file>          Set the name of the PPM file; defaults to
                               x86/<package>-<version>.tar.gz in the
                               directory given by ppd-dir.
 --ppm-dir=<dir>               Indicates the directory where to create
                               PPM and PPD file; defaults to the current
                               directory.
 --ppm-noversion               Changes the default values of
                               ppm-ppdfile and ppm-ppmfile to
                               <package>.ppd and <package>.tar.gz,
                               respectively.

$VERSION
EOF
   exit 1;
}

{
   my %o = ( 'chown' => 1 );
   Getopt::Long::GetOptions(\%o, 'build', 'build-root=s', 'copyright=s',
                            'chown!',
                            'debug', 'help', 'install', 'make=s',
                            'makemakeropts=s', 'makeopts=s', 'mode=s',
                            'package-name=s', 'package-version=s',
                            'ppm', 'ppm-ppdfile=s', 'ppm-ppmfile=s',
                            'ppm-dir=s', 'ppm-noversion', 'prep',
                            'require=s@', 'rpm-base-dir=s',
                            'rpm-build-dir=s', 'rpm-source-dir=s',
                            'rpm-specs-dir=s', 'rpm-group=s',
                            'runtests', 'setup-dir=s', 'source=s', 'specs',
                            'summary=s',
                            'verbose', 'version=s');
   Usage() if $o{'help'};
   if ($o{'version'}) { print "$VERSION\n"; exit 1}
   $o{'verbose'} = 1 if $o{'debug'};

   my $class;
   $o{'mode'} ||= Mode();
   if ($o{'mode'} =~ /^rpm$/i) {
       $class = 'Distribution::RPM';
   } elsif ($o{'mode'} =~ /^ppm$/i) {
       $class = 'Distribution::PPM';
   } else {
       die "Unknown mode: $o{'mode'}, use either of 'RPM' or 'PPM'";
   }
   my $self = $class->new(%o);

   if ($o{'ppm'}) {
       $self->PPM();
   } elsif ($o{'prep'}) {
       $self->Prep();
   } elsif ($o{'build'}) {
       $self->Build();
   } elsif ($o{'install'}) {
       $self->Install();
   } elsif ($o{'specs'}) {
       $self->Specs();
   } else {
       die "Missing action";
   }
}


__END__

=pod

=head1 INSTALLATION

Before using this script, you need to install the required packages:

 C<File::Spec>

If you are using Perl 5.00502 or later, then this package is already
part of your Perl installation. It is recommended to use the

 C<Archive::Tar>
 C<Compress::Zlib>

packages, if possible.

All of these packages are available on any CPAN mirror, for example

 ftp://ftp.funet.fi/pub/languages/perl/CPAN/modules/by-module

To install a package, fetch the corresponding distribution file, for
example

 Archive/Archive-Tar-0.21.tar.gz

extract it with

 gzip -cd Archive-Tar-0.21.tar.gz

and install it with

 cd Archive-Tar-0.21
 perl Makefile.PL
 make
 make test
 make install

Alternatively you might try automatic installation via the CPAN module:

 cpan          (until Perl 5.00503 you need: perl -MCPAN -e shell)
 install Archive::Tar
 install Compress::Zlib
 install File::Spec  (only with Perl 5.004 or lower)


=head1 AUTHOR AND COPYRIGHT

This script is Copyright (C) 1999

       Jochen Wiedmann
       Am Eisteich 9
       72555 Metzingen
       Germany

       E-Mail: [email protected]

You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the Perl README.


=head1 CPAN

This file is available as a CPAN script. The following subsections are
for CPAN's automatic link generation and not for humans. You can safely
ignore them.


=head2 SCRIPT CATEGORIES

UNIX/System_administration


=head2 README

This script can be used to build RPM or PPM packages automatically.


=head2 PREREQUISITES

This script requires the C<File::Spec> package.


=head1 TODO

=over 8

=item -

Handling of prerequisites by reading PREREQ_PM from the Makefile

=item -

Support for installation and deinstallation scripts

=item -

Make package relocatable

=item -

Support for %description.

=back


=head1 CHANGES

2000-01-02  Peter J. Braam <[email protected]>
      * Added support for $ENV{RPM_SOURCE_DIR} and $ENV{RPM_BUILD_DIR}.
      * Added --nochown.

1999-12-16  Jochen Wiedmann <[email protected]>
      * Added --ppm-noversion

1999-12-14  Jochen Wiedmann <[email protected]>
      * Added PPM support

1999-12-10  Peter J. Braam <[email protected]>
      * Fixed the $base_dir: correct naming is topdir and compute it
        from the rpm --showrc like the rest

1999-09-13  Jochen Wiedmann <[email protected]>

     * Modes: Fixed the use of ||= instead of |=; thanks to Tim Potter,
       Tim Potter <[email protected]>
     * Now using %files -f <listfile>

1999-07-22  Jochen Wiedmann <[email protected]>

     * Now falling back to use of "tar" and "gzip", if Archive::Tar and
       Compress::Zlib are not available.
     * Added --runtests, suggested by Seth Chaiklin <[email protected]>.

1999-07-09  Jochen Wiedmann <[email protected]>

     * Now using 'rpm --showrc' to determine RPM's base dirs.

1999-07-01  Jochen Wiedmann  <[email protected]>

     * /usr/src/redhat was used rather than $Distribution::RPM::BASE_DIR.
     * The AdjustPaths function is now handling files zero size files
       properly.
     * An INSTALLATION section was added to the docs that describes
       the installation of prerequisites.
     * A warning for <HANDLE> being possibly "0" is now suppressed with
       Perl 5.004.

1999-05-24  Jochen Wiedmann <[email protected]>

       * Added --perl-path and support for fixing startperl in scripts.
         Some authors don't know how to fix it. :-(

=head1 SEE ALSO

L<ExtUtils::MakeMaker(3)>, L<rpm(1)>, L<ppm(1)>


=cut