#!/usr/bin/perl
# Copyright (c) 2012-2012 Sullivan Beck.  All rights reserved.
# This program is free software; you can redistribute it and/or modify it
# under the same terms as Perl itself.

###############################################################################

use strict;
use warnings;
use IO::File;
use POSIX;

$| = 1;

our ($COM,$DIR);
BEGIN {
   $COM = $0;
   $COM =~ s/^.*\///;
   $DIR = $0;
   $DIR =~ s/\/?$COM$//;
   $DIR = "."  if (! $DIR);
   chdir $DIR;
   $DIR = `pwd`;
   chomp($DIR);
}

use vars qw($ARCH $VERS);
use Config;
$ARCH  = $Config{'archname'};
$VERS  = $Config{'version'};

###############################################################################
# GLOBAL VARIABLES
###############################################################################

our $VERSION    = "1.00";

our $TMPDIR     = "/tmp/cpantorpm";

our %Macros     = (0 => {
                         '_optimize'  => '$RPM_OPT_FLAGS',
                         '_buildroot' => '$RPM_BUILD_ROOT',
                        },

                   1 => {
                         '_optimize'  => '%{optimize}',
                         '_buildroot' => '%{buildroot}',
                        }
                  );

our ($OUTPUT,@OUTPUT);

###############################################################################
# HELP
###############################################################################

our($usage);

$usage=
  "usage: $COM OPTIONS PACKAGE

   General options:
      -h/--help        : Print help.
      -v/--version     : Print out the version of this program
      -D/--debug       : Verbose output
      -t/--tmpdir DIR  : A scratch directory used by this script.
                         Default: $TMPDIR
      -f/--optfile FILE: Specifies an options file

   Download options:
      -c/--cpan        : By default, downloading modules from CPAN
                         will be done using CPANPLUS and, if that
                         fails, using CPAN.  If --cpan is passed in,
                         it will use CPAN only.
      --extracted DIR  : If the archive on CPAN does not contain a
                         properly named directory, use this to
                         specify the name that it DOES have.

   Package metadata options.  A description of the package is
   read from POD files, or other files in the package, or they
   can be set with these options (in which case, the package files
   will NOT be examined for this information).

      --name NAME      : Explicitly set the name of the package overriding
                         the one obtained from the archive name or the
                         metadata.
      --summary TEXT   : The summary (1-line) description of the package.
      --description FILE
                       : The long (multiline) description of the
                         package.  Normally it comes from a POD file.
      --mainpod FILE   : The description and summary can be obtained from
                         the main POD file, if it can be determined.  To
                         select the main POD file, use this option.  FILE
                         is the path to the file relative to the top
                         level of the build directory (i.e. the directory
                         with a Build.PL or Makefile.PL file in it).  It
                         would be specified as:
                            lib/Foo/Bar.pm
      --author AUTHOR  : An author for this module.  If the author(s) cannot
                         be determined from the META files, they need to
                         be specified with this option.  It can be included
                         any number of times.
      --vers VERS      : Specify the version explictly rather than using
                         the one that came from the metadata or archive.

   RPM creation options.

      -n/--no-tests    : By default, as part of building an RPM, the
      --NO-TESTS         module tests (if any) will be run.  These options
                         can be used to modify that behavior.  In the
                         first case, the SPEC file will contain lines to
                         run the tests, but they will not be run this
                         time.  With the second option, no lines will be
                         included in the SPEC file to run the tests.
      -d/--no-deps     : By default, dependencies are checked AND added
      --NO-DEPS          to the SPEC file.  If either of these options are
                         given, they will not be checked.  If the second
                         option is given, they will not be added to the
                         SPEC file.
      --prefix PREFIX  : By default, a prefix of 'perl-' will be applied
      --no-prefix        to the RPM.  To specify an alternate prefix, use
                         the --prefix option.  To specify that no prefix
                         be used, use the --no-prefix option.
      -p/--packager PACKAGER
                       : The name of the packager.  It will use the
                         default one (looking in a ~/.rpmmacros file) if
                         possible.
      --group GROUP    : Specify the RPM group.
      --rpmbuild DIR   : Specify the RPM build directory to use.
      --clean-macros   : Use a clean copy of ~/.rpmmacros to build with.
      --release STRING : These are used to set the release string that is
      --disttag STRING   added to the RPM name after the version number.
                         For example:  foo-bar-1.00-1a.rpm has a release
                         of '1' and a disttag of 'a'.  Release defaults to
                         '1' and disttag defaults to '%{?dist}'.
      --epoch EPOCH    : This is used to set an optional Epoch number in
                         the RPM.
      --add-require FEATURE
      --rem-require FEATURE
      --add-provide FEATURE
      --rem-provide FEATURE
                       : These add or remove a feature from the requires
                         or provides list of the RPM.
      -m/--macros      : Use the macro form of common SPEC constructs
                         instead of the environment variable form.
      --build-rec
      --test-rec
      --runtime-rec    : By default, modules that are recommended for
                         configure/build, test, and runtime are optional.
                         These arguments make them required.

   To actually create the module, we may need to pass special options
   to the 'perl Build.PL' or 'perl Makefile.PL' commands.  The
   following options are used to do this:

      --build-type TYPE: TYPE may be 'build' or 'make' and force the use of
                         the Build.PL and Makefile.PL files respectively.
                         If the file does not exist, an error is triggered.
                         an error will be triggered.
      --config STRING  : Pass STRING to the 'perl Build.PL' or 'perl Makefile.PL'
                         command.  This can be passed in any number of times.
      --build STRING   : Pass STRING to the './Build' or 'make' command.
                         This can be passed in any number of times.
      -i/--install-base DIR:
                         The base directory to install the module.
      -T/--install-type TYPE:
                         The type of installation.  TYPE must be one of:
                            perl (aka core), site, or vendor
                         It defaults to the version specified in the
                         module.
      --mandir STRING  : Used to specify the man directory (relative to a
                         prefix).  e.g. share/man
      --patch FILE
      --patch-dir DIR
      --script FILE
      --script-dir DIR : In some cases, a distribution cannot be packaged
                         without some modifications.  Modifications can
                         be supplied in the form of a patch or a script.

   Options to control what steps are done:

      --spec-only      : Stop after building the SPEC file.
      --no-clean       : Do not remove the build tree after the RPM
                         is built.
      -s/--sign        : Add a GPG signature.
      -I/--install     : Install the RPM on this system (by default,
                         it will install a new RPM, or upgrade an
                         existing one if the version changed).
      --install-new    : This will install the RPM if it is new, but
                         will not upgrade an existing version.
      --install-force  : This will install the RPM even if it already
                         is installed.
      -y/--yum DIR     : Copy the RPM to a local yum repository

   Misc. options:

      --gpg-path PATH  : The path to the GPG directory containing
                         the keyring.
      --gpg-name NAME  : The name of the user who's key should be
                         used to sign the package.
      --gpg-passwd PASSWORD
                       : The passphrase for the GPG key.
      --gpg-passfile FILE
                       : A file containing the passphrase for the GPG key.

This takes one or more perl modules and creates RPMs from them.

";

###############################################################################
# PARSE ARGUMENTS
###############################################################################

sub args {
   my @args = @ARGV;
   map {
      s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\ \	])/\\$1/g;
   } @args;
   return join(' ',@args);
}

our %package;
$package{'VERSION'}   = $VERSION;
$package{'CMD'}       = $COM;
$package{'command'}   = $0;
$package{'args'}      = args();
$package{'date'}      = POSIX::strftime("%a %B %d %Y",localtime());

our $OP_package       = '';
our $OP_cpan          = 'cpanplus';
our $OP_debug         = 0;
our $OP_description   = '';
our $OP_summary       = '';
our $OP_mainpod       = '';
our @OP_author        = ();
our $OP_name          = '';
our $OP_version       = '';
our $OP_prefix        = 'perl-';
our $OP_packager      = '';
our $OP_lib           = 'core';
our $OP_clean_macros  = 0;
our $OP_rpmbuild      = '';
our $OP_build_type    = '';
our @OP_config        = ();
our @OP_build         = ();
our $OP_release       = '1';
our $OP_disttag       = '%{?dist}';
our $OP_epoch         = '';
our $OP_group         = 'Development/Libraries';
our $OP_macros        = 0;
our @OP_add_require   = ();
our @OP_add_provide   = ();
our @OP_rem_require   = ();
our @OP_rem_provide   = ();
our $OP_spec_only     = 0;
our $OP_inst_type     = '';
our $OP_inst_base     = '';
our $OP_mandir        = '';
our $OP_no_tests      = 0;
our $OP_no_deps       = 0;
our $OP_no_clean      = 0;
our $OP_sign          = 0;
our $OP_gpg_path      = '';
our $OP_gpg_name      = '';
our $OP_gpg_passwd    = '';
our $OP_gpg_passfile  = '';
our $OP_install       = '';
our $OP_yum           = '';
our $OP_script        = '';
our $OP_script_dir    = '';
our $OP_patch         = '';
our $OP_patch_dir     = '';
our $OP_runtime_rec   = 0;
our $OP_build_rec     = 0;
our $OP_test_rec      = 0;
our $OP_extracted     = '';

if (! @ARGV) {
   die "ERROR: no package given.\n";
}

my $tmp               = pop;
if ($tmp eq '-h'  or  $tmp eq '--help') {
   print $usage;
   exit;
}
if ($tmp =~ /^-/) {
   die "ERROR: no package given.\n";
}
$OP_package           = $tmp;

while ($_ = shift) {

   (print $usage),             exit  if ($_ eq '-h'  ||  $_ eq '--help');
   print "$VERSION\n",         exit  if ($_ eq '-v'  ||  $_ eq '--version');
   $TMPDIR = shift,            next  if ($_ eq '-t'  ||  $_ eq '--tmpdir');
   $OP_debug = 1,              next  if ($_ eq '-d'  ||  $_ eq '--debug');
   unshift(@ARGV,opt_file(shift)),
                               next  if ($_ eq '-f'  ||  $_ eq '--optfile');
   $OP_no_tests = 1,           next  if ($_ eq '-n'  ||  $_ eq '--no-tests');
   $OP_no_tests = 2,           next  if (                $_ eq '--NO-TESTS');
   $OP_no_deps = 1,            next  if ($_ eq '-d'  ||  $_ eq '--no-deps');
   $OP_no_deps = 2,            next  if (                $_ eq '--NO-DEPS');

   $OP_cpan = 'cpan',          next  if ($_ eq '-c'  ||  $_ eq '--cpan');
   $OP_description = shift,    next  if (                $_ eq '--description');
   $OP_summary = shift,        next  if (                $_ eq '--summary');
   $OP_mainpod = shift,        next  if (                $_ eq '--mainpod');
   push(@OP_author, shift),    next  if (                $_ eq '--author');
   $OP_name = shift,           next  if (                $_ eq '--name');
   $OP_version = shift,        next  if (                $_ eq '--vers');
   $OP_prefix = shift,         next  if (                $_ eq '--prefix');
   $OP_prefix = '',            next  if (                $_ eq '--no-prefix');
   $OP_packager = shift,       next  if ($_ eq '-p'  ||  $_ eq '--packager');
   $OP_rpmbuild = shift,       next  if (                $_ eq '--rpmbuild');
   $OP_clean_macros = 1,       next  if (                $_ eq '--clean-macros');
   $OP_build_type = shift,     next  if (                $_ eq '--build-type');
   $OP_group = shift,          next  if (                $_ eq '--group');
   push(@OP_config, shift),    next  if (                $_ eq '--config');
   push(@OP_build, shift),     next  if (                $_ eq '--build');
   $OP_release = shift,        next  if (                $_ eq '--release');
   $OP_disttag = shift,        next  if (                $_ eq '--disttag');
   $OP_epoch = shift,          next  if (                $_ eq '--epoch');
   $OP_macros = 1,             next  if ($_ eq '-m'  ||  $_ eq '--macros');
   $OP_spec_only = 1,          next  if (                $_ eq '--spec-only');
   $OP_inst_type = shift,      next  if ($_ eq '-T'  ||  $_ eq '--install-type');
   $OP_inst_base = shift,      next  if ($_ eq '-i'  ||  $_ eq '--install-base');
   $OP_mandir = shift,         next  if (                $_ eq '--mandir');
   $OP_no_clean = 1,           next  if (                $_ eq '--no-clean');
   $OP_sign = 1,               next  if ($_ eq '-s'  ||  $_ eq '--sign');
   $OP_gpg_path = shift,       next  if (                $_ eq '--gpg-path');
   $OP_gpg_name = shift,       next  if (                $_ eq '--gpg-name');
   $OP_gpg_passwd = shift,     next  if (                $_ eq '--gpg-passwd');
   $OP_gpg_passfile = shift,   next  if (                $_ eq '--gpg-passfile');
   $OP_install = 'upg',        next  if ($_ eq '-I'  ||  $_ eq '--install');
   $OP_install = 'new',        next  if (                $_ eq '--install-new');
   $OP_install = 'force',      next  if (                $_ eq '--install-force');
   $OP_yum = shift,            next  if ($_ eq '-y'  ||  $_ eq '--yum');
   $OP_script = shift,         next  if (                $_ eq '--script');
   $OP_script_dir = shift,     next  if (                $_ eq '--script-dir');
   $OP_patch = shift,          next  if (                $_ eq '--patch');
   $OP_patch_dir = shift,      next  if (                $_ eq '--patch-dir');
   $OP_runtime_rec = 1,        next  if (                $_ eq '--runtime-rec');
   $OP_build_rec = 1,          next  if (                $_ eq '--build-rec');
   $OP_test_rec = 1,           next  if (                $_ eq '--test-rec');
   $OP_extracted = shift,      next  if (                $_ eq '--extracted');

   push(@OP_add_require, shift),
                               next  if (                $_ eq '--add-require');
   push(@OP_add_provide, shift),
                               next  if (                $_ eq '--add-provide');
   push(@OP_rem_require, shift),
                               next  if (                $_ eq '--rem-require');
   push(@OP_rem_provide, shift),
                               next  if (                $_ eq '--rem-provide');

   die "ERROR: unknown arguments: $_ @ARGV\n"  if (@ARGV);
}

if ($OP_build_type  &&
    $OP_build_type ne 'build'  &&
    $OP_build_type ne 'make') {
   log_message('ERR',"Invalid --build-type option: $OP_build_type");
}

$OP_inst_type = 'perl'  if ($OP_inst_type eq 'core');
if ($OP_inst_type  &&
    $OP_inst_type ne 'perl'  &&
    $OP_inst_type ne 'site'  &&
    $OP_inst_type ne 'vendor') {
   log_message('ERR',"Invalid --install-type option: $OP_inst_type");
}

# To determine whether man pages go in PREFIX/man or PREFIX/share/man,
# we'll analyze them here.

use vars qw($MAN);

if ($OP_mandir) {
   $MAN = $OP_mandir;
} else {
   my $prefix = $Config{'installprefix'};
   my $man1   = $Config{'installman1dir'};

   # Make sure man1 is DIR/man1 (allow man1p, man1xxx, etc.)
   if ($man1 !~ s,/man1[^/]*,,) {
      log_message('ERR',"man page installation directory cannot be determined",
                        "completely.  Use the --mandir option.");
   }
   # Make sure man1 is PREFIX/... where PREFIX is the installation prefix.
   if ($man1 !~ s,^$prefix/,,) {
      log_message('ERR',"man page installation directory cannot be determined",
                        "completely.  Use the --mandir option.");
   }
   $MAN = $man1;
}

if ($OP_no_deps) {
   $OP_no_tests = 1  if (! $OP_no_tests);
}

$package{'incl_tests'} = ($OP_no_tests == 2 ? 0 : 1);
$package{'incl_deps'}  = ($OP_no_deps == 2 ? 0 : 1);

############################################################################
# MAIN PROGRAM
############################################################################

# %package =
#   # Argument parsing step:             Notes
#   # ------------------------------     --------------------------------
#   CMD        => cpantorpm              This script
#   VERSION    => 1.00                   Version of this script
#   command    => STRING                 The command executed
#   args       => STRING                 The command line arguments
#   date       => STRING                 Current timestamp
#   incl_tests => 0/1                    1 if we'll be adding tests to SPEC
#   incl_deps  => 0/1                    1 if we'll be included requires
#                                        in the SPEC
#
#   # Init step:
#   # ------------------------------     --------------------------------
#   TMP        => $TMPDIR                Directory where we'll store things
#
#   # Package retrieval step
#   # ------------------------------     --------------------------------
#   from       => file/dir/url/CPAN
#   fromsrc    => Foo::Bar               how it was passed in
#   DIR        => $TMPDIR/Foo-Bar-1.0
#   dir        => Foo-Bar-1.0
#   dist       => Foo-Bar
#   vers       => 1.0
#   archive    => Foo-Bar-1.0.tgz        Set for URL/File/CPAN
#   ext        => tgz                    Set for URL/File/CPAN
#   filetype   => tar.gz                 Set for URL/File/CPAN
#                                        One of: tar.gz, tar.bz2, zip
#   cpandir    => S/SB/SBECK             Set for CPAN
#
#   # Reading metadata step
#   # ------------------------------     --------------------------------
#   build      => Build.PL               The Build.PL file (if one exists)
#   make       => Makefile.PL            The Makefile.PL file (if one exists)
#   m_name     => Foo-Bar                Should be the same as 'dist'
#   m_version  => 1.0                    Should be the same as 'vers'
#   m_abstract => Summary text
#   m_description => Description
#   m_keywords => [ WORD, WORD, ... ]
#   m_author   => [ AUTHOR, AUTHOR, ... ]
#   m_provides => { FEATURE => { version => VERS,
#                                file    => FILE } ... }
#                                        FILE = lib/Foo/Bar.pm
#   m_license  => perl_5,apache          Comma separated list of licenses
#
#   # Derived from the metadata
#   # -------------------------
#   build_type => make|build
#   name       => Foo-Bar
#   version    => 1.0
#   author     => [ AUTHOR, AUTHOR, ...]
#   desc       => DESCRIPTION
#   summary    => SUMMARY
#   arch       => noarch|%{_arch}
#   arch_val   => noarch|x86_64
#   prefix     => perl-
#   rpmname    => perl-Foo-Bar
#   specname   => perl-Foo-Bar.spec
#
#   # Categorize package files
#   # -------------------------
#   files      => { pm      => { FILE => '' },
#                   pod     => { FILE => '' },   or FILE => NAME
#                   t       => { FILE => '' },
#                   xs      => { FILE => '' },
#                   scripts => { FILE => '' },
#                   build   => { FILE => '' },
#                   mainpod => [ FILE, NAME, SUMMARY, DESCRIPTION ]
#                 }
#                                        FILE is the path relative to the top
#                                        directory of the package.
#   instfiles  => { pm      => { FILE => '' },
#                   scripts => { FILE => '' },
#                   man1    => { FILE => '' },
#                   man3    => { FILE => '' },
#                 }
#                                        Similar to files but limited to the
#                                        files that are actually installed.
#   lib_inst   => 1                      if we're installing .pm files
#   arch_inst  => 1                      if we're installing architecture .pm files
#   bin_inst   => 1                      if we're installing scripts
#   man1_inst  => 1
#   man3_inst  => 1
#
#   # Prereqs and provides
#   # -------------------------
#   requires   => { build   => { FEATURE => VERS },
#                   test    => { FEATURE => VERS },
#                   runtime => { FEATURE => VERS }, }
#                                        Requirements for building the
#                                        module, running tests, or runtime.
#                                        FEATURE is Foo::Bar
#   build_req  => { FEATURE => VERS }
#   runtime_req=> { FEATURE => VERS }
#                                        Same as 'requires', but FEATURE
#                                        formatted as it will be in the SPEC file.
#                                        FEATURE is perl(Foo::Bar)
#   provides   => { FEATURE => VERS }
#
#   # Gotten from the build step
#   # -------------------------
#   bin_dir    => /usr/bin               The directories we're installing too
#   lib_dir    => /usr/lib/perl5/5.14.2
#   arch_dir   => /usr/lib/perl5/5.14.2/x86_64-linux-thread-multi
#   man_dir    => /usr/share/man
#   config_cmd => perl Makefile.PL       The commands used for each step
#   build_cmd  => make
#   test_cmd   => make test
#   install_cmd=> make install
#
#   # From the SPEC creation step
#   # -------------------------
#   release    => 1
#   disttag    => %{?dist}
#   epoch      =>                        The epoch number (set by --epoch)
#   group      => Development/Libraries
#   url        => http://search.cpan.org/dist/Foo-Bar/
#   license    => GPL+ or Artistic
#   source     => http://www.cpan.org/authors/id/S/SB/SBECK/Foo-Bar-1.0.tar.gz
#   packager   => PACKAGER
#   restore    => 1                      if .rpmmacros file should be restored
#   remove     => 1                      if .rpmmacros file should be removed
#   topdir     => /usr/src/redhat        Top of the RPM build tree
#   rpmarch    => x86_64                 The RPM architecture
#   post_build => [ LINE, LINE, ... ]    Lines (sh commands) added to %build
#
#   # From the build RPM step
#   # -------------------------
#   rpmfile    => /usr/src/redhat/RPMS/noarch/cpantorpm-1.00-1.noarch.rpm
#   srpmfile   => /usr/src/redhat/SRPMS/cpantorpm-1.00-1.src.rpm

init();

get_package($OP_package);
chdir($package{'DIR'});
get_meta_pre_build();
check_deps()                   unless ($OP_no_deps);
build();
get_meta();
make_spec();
exit  if ($OP_spec_only);

build_rpm();
sign_rpm()       if ($OP_sign);
install_rpm()    if ($OP_install);
install_yum()    if ($OP_yum);

############################################################################
############################################################################

# Initialize the run.
sub init {

   #
   # Make sure that the scratch directory exists and is empty.
   #

   log_message('HEAD',"Initializing cpantorpm ($VERSION)");

   log_message('INFO',"Checking cpantorpm dir: $TMPDIR");
   log_indent(+1);

   if (-d $TMPDIR) {

      # If it already exists, remove it so we can start fresh.

      log_message('INFO','Exists.  Removing it...');

      my $func = sub { return ! -d $TMPDIR };

      my $succ = multiple_methods( [$func],
                                   ['module', 'File::Path', ['remove_tree'],
                                    "remove_tree('$TMPDIR')" ],
                                   ['system-null','rm',
                                    "{rm} -rf '$TMPDIR'"]
                                 );

      log_message('ERR',
                  "Unable to clean temporary directory: $TMPDIR",
                  "Make sure that File::Path is installed, or the 'rm' command",
                  "is in your path.  Also, check permissions on the directory.")
        if (! $succ);

   } elsif (-e $TMPDIR) {
      log_message('ERR',
                  'File exists.  Directory cannot be created.');
   } else {
      log_message('INFO','Does not exist.');
   }

   log_message('INFO','Creating it...');

   make_dir($TMPDIR);
   $package{'TMP'} = $TMPDIR;
   log_indent(-1);
}

sub make_dir {
   my($dir) = @_;

   my $func = sub { return -d $dir };

   my $succ = multiple_methods( [$func],
                                ['module', 'File::Path', ['make_path'],
                                 "make_path('$dir')" ],
                                [($dir eq $TMPDIR ? 'system-null' : 'system'),
                                 'mkdir',
                                 "{mkdir} -p '$dir'"]
                              );

   log_message('ERR',
               "Unable to create directory: $dir",
               "Make sure that File::Path is installed, or the 'mkdir' command",
               "is in your path.  Also, make sure that permissions on the",
               "parent directory are correct.")
         if (! $succ);
}

############################################################################
# Get the module into TMPDIR and extract it.

# This gets a package and creates a directory in the temporary directory
# containing it.
#
sub get_package {
   my($package) = @_;

   log_message('HEAD',"Obtaining package: $package");

   if      ($package =~ m,^(http|ftp)://,) {
      get_package_url($package);
   } elsif (-d $package) {
      get_package_dir($package);
   } elsif (-e $package) {
      get_package_file($package);
   } else {
      get_package_cpan($package);
   }

   log_message('INFO',
               "Package    : $package",
               "  Archive  : " . ($package{'archive'} ? $package{'archive'} : ''),
               "  DIR      : $package{DIR}",
               "  Dir      : $package{dir}",
               "  Dist     : $package{dist}",
               "  Vers     : $package{vers}",
               "  Ext      : " . ($package{'ext'} ? $package{'ext'} : ''),
               "  CPAN dir : " . ($package{'cpandir'} ? $package{'cpandir'} : '')
              );

   apply_patch();
   run_script();
}

# This will copy the directory unmodified into the temporary directory.
# It can use any of the following methods:
#    File::Copy::Recursive
#    system(cp -r)
#
sub get_package_dir {
   my($package) = @_;
   my($err);

   log_message('INFO',"Package type: directory");

   $package{'from'}    = 'dir';
   $package{'fromsrc'} = $package;

   # If directory ends in '.' or '..', then we'll have to do a pwd
   # to handle it.

   $package =~ m,^(.*/)?(.*)$,;
   my $dir  = $2;

   if ($dir eq '.'  ||  $dir eq '..') {
      log_message('INFO',"Diretory name not specified. Assuming '.'");
      my $succ = multiple_methods( [ sub { 1; } ],
                                   ['system','pwd',
                                    "cd '$package'; {pwd}"],
                                 );

      if (! $succ  ||  ! @OUTPUT) {
         log_message('ERR',"Unable to determine package directory: $package");
      }

      $package = $OUTPUT[0];
      $package =~ m,^(.*/)?(.*)$,;
      $dir     = $2;
   }

   my ($dist,$vers);
   if ($dir =~ /^(.+)\-(.+)$/) {
      ($dist,$vers) = ($1,$2);
   } else {
      log_message('ERR','Invalid directory name: $dir');
   }
   $package{'DIR'}  = "$TMPDIR/$dir";
   $package{'dir'}  = $dir;
   $package{'dist'} = $dist;
   $package{'vers'} = $vers;

   # Copy in the directory

   log_message('INFO',"Copying diretory");
   my $succ = multiple_methods( [ sub { -d "$TMPDIR/$dir" } ],
                                ['module','File::Copy::Recursive',['dircopy'],
                                 "\$File::Copy::Recursive::CPRFComp = 1; " .
                                 "dircopy('$package','$TMPDIR')" ],
                                ['system','cp',
                                 "{cp} -r '$package' '$TMPDIR'"],
                              );

   if (! $succ) {
      log_message('ERR',"Unable to copy directory: $package");
   }
}

# This takes an archive file containing a package and copies it into
# the temporary directory.  It can use any of the following methods:
#    File::Copy
#    system(cp)
#
sub get_package_file {
   my($package) = @_;
   my $err;

   log_message('INFO',"Package type: archive file");

   $package{'from'}    = 'file';
   $package{'fromsrc'} = $package;

   my($valid,$dir,$dist,$vers,$archive,$ext,$filetype) = is_archive($package);
   if (! $valid) {
      log_message('ERR',"Package file not a valid archive: $package");
   }
   $package{'DIR'}       = "$TMPDIR/$dir";
   $package{'dir'}       = $dir;
   $package{'dist'}      = $dist;
   $package{'vers'}      = $vers;
   $package{'archive'}   = $archive;
   $package{'ext'}       = $ext;
   $package{'filetype'}  = $filetype;

   # Copy in the file

   log_message('INFO',"Copying file");

   backup_file($package,$TMPDIR,1);

   # Extract it.

   extract_archive();
}

sub apply_patch {
   return  if (! $OP_patch  &&  ! $OP_patch_dir);

   my $file;

   if ($OP_patch) {
      $file = $OP_patch;

   } elsif (-f "$OP_patch_dir/$package{fromsrc}.diff") {
      $file = "$OP_patch_dir/$package{fromsrc}.diff";

   } else {
      return;
   }

   # Run the patch.

   log_message('INFO',"Applying patch: $file");

   my $patch = find_exe('patch');
   if (! $patch) {
      log_message('ERR','patch executable not found when trying to apply patch');
   }
   my $cmd = "cd $package{DIR}; $patch -p0 < $file";
   if (system($cmd) != 0) {
      log_message('ERR',"pre-package patch failed: $file");
   }
}

sub run_script {
   return  if (! $OP_script  &&  ! $OP_script_dir);

   my $script;

   if ($OP_script) {
      $script = $OP_script;

   } elsif (-f "$OP_script_dir/$package{fromsrc}.sh") {
      $script = "$OP_script_dir/$package{fromsrc}.sh";

   } else {
      return;
   }

   # Run the script.

   log_message('INFO',"Running script: $script");

   my $cmd = "cd $package{DIR}; sh $script";
   if (system($cmd) != 0) {
      log_message('ERR',"pre-package script failed: $script");
   }
}

# We'll support lots of different ways to download an archive from a URL
# including:
#    LWP::UserAgent
#    HTTP::Lite
#    system(curl)
#    system(wget)
#    system(lynx)
#    system(links)
#    system(lftp)
#
sub get_package_url {
   my($package) = @_;

   log_message('INFO',"Package type: URL");

   $package{'from'}    = 'url';
   $package{'fromsrc'} = $package;

   my($valid,$dir,$dist,$vers,$archive,$ext,$filetype) = is_archive($package);
   if (! $valid) {
      log_message('ERR',"Package file not a valid archive: $package");
   }
   $package{'DIR'}       = "$TMPDIR/$dir";
   $package{'dir'}       = $dir;
   $package{'dist'}      = $dist;
   $package{'vers'}      = $vers;
   $package{'archive'}   = $archive;
   $package{'ext'}       = $ext;
   $package{'filetype'}  = $filetype;

   # Download the URL

   log_message('INFO',"Downloading from URL");

   my $succ = multiple_methods
     ( [ sub { -f "$TMPDIR/$package{archive}" } ],
       ['module','LWP::UserAgent',[],
        qq{ my \$ua = LWP::UserAgent->new;
           \$ua->timeout(10);
           \$ua->env_proxy;
           \$ua->get('$package',':content_file'=>'$TMPDIR/$package{archive}'); }
       ],
       ['module','HTTP::Lite',[],
        qq{ my \$x = HTTP::Lite->new();
           \$x->request('$package');
           open OUT,"> $TMPDIR/$package{archive}";
           print OUT \$x->body();
           close OUT; }
       ],
       [ 'system', 'curl',
         "{curl} -s -o '$TMPDIR/$package{archive}' '$package'" ],
       [ 'system', 'wget',
         "cd '$TMPDIR'; {wget} -q '$package'" ],
       [ 'system', 'lynx',
         "{lynx} -dump '$package' > '$TMPDIR/$package{archive}'" ],
       [ 'system', 'links',
         "{links} -source '$package' > '$TMPDIR/$package{archive}'" ],
       [ 'system', 'lftp',
         "{lftp} -c get '$package' -o '$TMPDIR/$package{archive}'" ],
     );

   if (! $succ) {
      log_message('ERR',"Unable to dowload URL: $package");
   }

   # Extract it.

   extract_archive();
}

sub get_package_cpan {
   my($package) = @_;

   log_message('INFO',"Package type: CPAN module");

   $package{'from'}    = 'CPAN';
   $package{'fromsrc'} = $package;

   CPAN:
   while (1) {

      #
      # Use CPANPLUS to get the module.
      #

      if ($OP_cpan eq 'cpanplus') {

         log_message('INFO',"Using CPANPLUS");
         my $err = load_module("CPANPLUS::Backend");
         if (! $err) {
            my $cb   = CPANPLUS::Backend->new;
            my @mods = $cb->search ( type  => 'module',
                                     allow => [ qr/^$package$/ ] );

            if (@mods == 0) {
               log_message('ERR',"CPANPLUS: Module not found: $package");
            } elsif (@mods > 1) {
               log_message('ERR',"CPANPLUS: Multiple versions exist: $package");
            }

            my $mod = $mods[0];
            if (! $mod->{'package'}  ||
                ! $mod->{'path'}) {
               log_message('ERR',"CPANPLUS: Metadata incomplete: $package");
            }

            my($valid,$dir,$dist,$vers,$archive,$ext,$filetype) =
              is_archive($mod->{'package'});

            if (! $valid) {
               log_message('ERR',"CPANPLUS: Metadata invalid (package): $package");
            }

            $package{'DIR'}       = "$TMPDIR/$dir";
            $package{'dir'}       = $dir;
            $package{'dist'}      = $dist;
            $package{'vers'}      = $vers;
            $package{'archive'}   = $archive;
            $package{'ext'}       = $ext;
            $package{'filetype'}  = $filetype;
            $package{'cpandir'}   = $mod->{'path'};

            my $succ = $mod->fetch( module   => $mod,
                                    fetchdir => "$TMPDIR" );

            if (! $succ  ||  ! -f "$TMPDIR/$archive") {
               log_message('ERR',"CPANPLUS: Unable to fetch module: $package");
            }

            last CPAN;
         }
      }

      #
      # Use CPAN to get the module.
      #

      log_message('INFO',"Using CPAN");
      my $err = load_module("CPAN::Shell");
      $err    = load_module("CPAN")  if (! $err);
      if (! $err) {

         my $mod = CPAN::Shell->expand('Module', $package);

         if (! $mod) {
            log_message('ERR',"CPAN: Module not found: $package");
         }

         $mod    = $mod->{'RO'}  if ($mod->{'RO'});

         if (! $mod->{'CPAN_FILE'}) {
            log_message('ERR',"CPAN: Metadata incomplete: $package");
         }

         my $cpan_file   = $mod->{'CPAN_FILE'};
         $cpan_file      =~ m,(.*)/(.*),;
         my($cpandir,$file) = ($1,$2);

         my($valid,$dir,$dist,$vers,$archive,$ext,$filetype) =
           is_archive($file);

         if (! $valid) {
            log_message('ERR',"CPAN: Metadata invalid (package): $package");
         }

         $package{'DIR'}       = "$TMPDIR/$dir";
         $package{'dir'}       = $dir;
         $package{'dist'}      = $dist;
         $package{'vers'}      = $vers;
         $package{'archive'}   = $archive;
         $package{'ext'}       = $ext;
         $package{'filetype'}  = $filetype;
         $package{'cpandir'}   = $cpandir;

         CPAN::get($cpan_file);

         if (! $CPAN::Config->{'keep_source_where'}) {
            log_message('ERR',"CPAN: Unable to determine source dir: $package");
         }

         my $srcdir = $CPAN::Config->{'keep_source_where'} . "/authors/id";

         if (! -f "$srcdir/$cpandir/$archive") {
            log_message('ERR',"CPAN: Unable to fetch module: $package");
         }

         # Copy the file into the main directory.

         log_message('INFO',"Copying file");
         my $succ = multiple_methods
           ( [ sub { -f "$TMPDIR/$archive" } ],
             ['module','File::Copy',['copy'],
              "copy('$srcdir/$cpandir/$archive','$TMPDIR')" ],
             ['system','cp',
              "{cp} '$srcdir/$cpandir/$archive' '$TMPDIR'"],
           );

         if (! $succ) {
            log_message('ERR',"Unable to copy file: $package");
         }

         last CPAN;
      }

      log_message('ERR',"Unable to dowload CPAN module: $package");
   }

   # Extract the file.

   extract_archive();
}

sub is_archive {
   my($file) = @_;

   my $tmp =  $file;
   $tmp    =~ s,^.*/,,;

   if ($tmp =~ /^(.+)\-(.+)\.(tar|tar\.gz|tgz|tar\.bz2|zip)$/) {
      my ($dist,$vers,$ext) = ($1,$2,$3);
      my $dir               = "$dist-$vers";
      my $pack              = $tmp;
      my %filetype          = ('tar'      => 'tar',
                               'tar.gz'   => 'tar.gz',
                               'tgz'      => 'tar.gz',
                               'tar.bz2'  => 'tar.bz2',
                               'zip'      => 'zip');
      my $filetype          = $filetype{$ext};

      return (1,$dir,$dist,$vers,$pack,$ext,$filetype);
   }
   return (0);
}

# For tar files, it can use the following methods:
#    Archive::Extract
#    Archive::Tar
#    system(tar)
#
# For zip files, it can use the following methods:
#    Archive::Extract
#    Archive::Zip
#    unzip
#
sub extract_archive {
   my($type) = $package{'filetype'};
   my $succ;

   # The expected directory that will be extracted.
   my $dir   = ($OP_extracted ? $OP_extracted : $package{'dir'});

   if      ($type eq 'zip') {

      $succ = multiple_methods
        ( [ sub { -d "$TMPDIR/$dir" } ],
          ['module','Archive::Extract',[],
           qq{ chdir('$TMPDIR');
              my \$arch=Archive::Extract->new(archive=>'$TMPDIR/$package{archive}');
              \$arch->extract(); }
          ],
          ['module','Archive::Zip',[],
           qq{ chdir('$TMPDIR');
              my \$zip = Archive::Zip->new('$TMPDIR/$package{archive}');
              \$zip->extractTree(); }
          ],
          ['system','unzip',
           "cd $TMPDIR; {unzip} -qq $package{archive}" ]
        );

   } else {

      my $comp = ($type eq 'tar' ? 0 : 1);
      my $opt  = ($type eq 'tar'    ? ''  :
                  $type eq 'tar.gz' ? 'z' :
                  'j');

      $succ = multiple_methods
        ( [ sub { -d "$TMPDIR/$dir" } ],
          ['module','Archive::Extract',[],
           qq{ chdir('$TMPDIR');
              my \$arch=Archive::Extract->new(archive=>'$TMPDIR/$package{archive}');
              \$arch->extract(); }
          ],
          ['module','Archive::Tar',[],
           qq{ chdir('$TMPDIR');
              Archive::Tar->extract_archive('$TMPDIR/$package{archive}',$comp); }
          ],
          ['system','tar',
           "cd $TMPDIR; {tar} xf$opt $package{archive}" ]
        );
   }

   if (! $succ) {
      log_message('ERR',"Unable to extract archive: $package{archive}");
   }

   if ($OP_extracted) {
      if (! rename("$TMPDIR/$dir","$TMPDIR/$package{dir}")) {
         log_message('ERR',
                     "Unable to rename extracted directory: $package{archive}");
      }
   }
}

############################################################################
# Read meta data from the distrbution (this does only the bare minimum).
# A more comprehensive look will be done after building (so a META.* file
# can be generated as appropriate).

sub get_meta_pre_build {

   log_message('HEAD',"Reading package metadata (pre-build): $package{dir}");

   my %files = get_filelist($package{"DIR"});
   categorize_files("pre_build",$package{"DIR"},%files);

   #
   # Figure out if we'll be using a Build.PL or Makefile.PL method.
   #

   get_meta_build_pl($files{'build.pl'})        if (exists $files{'build.pl'});
   get_meta_makefile_pl($files{'makefile.pl'})  if (exists $files{'makefile.pl'});

   if (! $package{'build'}  &&
       ! $package{'make'}) {
      log_message('ERR',"Package has no Makefile.PL/Build.PL script: $package{dir}",
                  "It cannot be built automatically with this sript.");
   }

   if      ($package{'build'}  &&  $package{'make'}) {
      if ($OP_build_type eq 'make') {
         $package{'build_type'} = 'make';
      } else {
         $package{'build_type'} = 'build';
      }

   } elsif ($package{'build'}) {
      if ($OP_build_type eq 'make') {
         log_message('ERR',
                     'Makefile.PL specified with --build-type does not exist.');
      }
      $package{'build_type'} = 'build';
   } else {
      if ($OP_build_type eq 'build') {
         log_message('ERR',
                     'Build.PL specified with --build-type does not exist.');
      }
      $package{'build_type'} = 'make';
   }

   log_message('INFO',"Build type: $package{build_type}");

   #
   # If any META files exist, we'll get a list of requires from them now.
   # If a distribution is totally broken and does not include any, we'll
   # check again after the build.
   #

   foreach my $f (qw(meta.json mymeta.json meta.yml mymeta.yml)) {
      my $type = ($f =~ /json/ ? 'json' : 'meta');
      get_meta_meta($type,$files{$f})           if (exists $files{$f});
   }

   requires('files');
}

# Get a list of all of the files in the package.  We'll ignore directories.
# It will return a hash:
#    { LC_FILE => { FILE => 1 } }
# where LC_FILE is a file (all lowercased) and FILE is the same file (or files)
# in the case they actually exist.  All FILE and LC_FILE are the paths
# relative to the top directory in the package.
#
#    {
#      manifest  => { MANIFEST => 1 }
#      t/readme  => { t/README => 1,
#                     t/Readme => 1 }
#    }
#
sub get_filelist {
   my($dir) = @_;

   log_message('INFO',"Listing package files");

   my $succ = multiple_methods
     ( [ sub { 1; } ],
       [ 'module','File::Find',['find'],
         qq< find(sub { push(\@OUTPUT,\$File::Find::name) if (-f) },"$dir"); >
       ],
       [ 'system','find',
         "{find} '$dir' -type f" ]
     );

   my %files;
   foreach my $file (@OUTPUT) {
      $file =~ s,^$dir/,,;
      next  if (! $file);

      $files{lc($file)}{$file} = 1;
   }

   return %files;
}

# Get as much information from the Build.PL file as possible.
#
sub get_meta_build_pl {
   my($filehash) = @_;
   if (exists $filehash->{'Build.PL'}) {
      $package{'build'} = 'Build.PL';
      my @tmp = keys %$filehash;
      if (@tmp != 1) {
         log_message('WARN',"Multiple Build.PL files exist (with different cases).",
                            "The one with the correct case will be used");
      }
   } else {
      my @tmp = keys %$filehash;
      if (@tmp == 1) {
         log_message('WARN',"Build.PL exists, but is the wrong case.",
                            "It will used.");
         $package{'build'} = $tmp[0];
      } else {
         log_message('WARN',"Multiple Build.PL files exist (with different cases).",
                            "None have the correct case, so they will be ignored.");
      }
   }
}

# Get as much information from the Makefile.PL file as possible.
#
sub get_meta_makefile_pl {
   my($filehash) = @_;
   if (exists $filehash->{'Makefile.PL'}) {
      $package{'make'} = 'Makefile.PL';
      my @tmp = keys %$filehash;
      if (@tmp != 1) {
         log_message('WARN',
                     "Multiple Makefile.PL files exist (with different cases).",
                     "The one with the correct case will be used");
      }
   } else {
      my @tmp = keys %$filehash;
      if (@tmp == 1) {
         log_message('WARN',"Makefile.PL exists, but is the wrong case.",
                            "It will used.");
         $package{'make'} = $tmp[0];
      } else {
         log_message('WARN',
                     "Multiple Makefile.PL files exist (with different cases).",
                     "None have the correct case, so they will be ignored.");
      }
   }
}

############################################################################
# We'll check the dependencies to make sure we can build it.

sub check_deps {

   # We'll check build dependencies (which includes all runtime tests).
   # We'll also check test dependencies IF we're going to be running
   # tests.

   my %deps = %{ $package{'requires'}{'build'} }
     if (exists $package{'requires'}  &&
         exists $package{'requires'}{'build'});

   if ($OP_no_tests == 0) {
      foreach my $feat (keys %{ $package{'requires'}{'test'} }) {
         next  if (exists $deps{$feat});
         $deps{$feat} = $package{'requires'}{'test'}{$feat};
      }
   }

   # Check them.

   if (exists $deps{'perl'}) {
      my $v = $deps{'perl'};
      delete $deps{'perl'};
      if ($v) {
         my $err = load_module('',$v);
         if ($err) {
            log_message('ERR',
                        "Perl version $v is required to build this module.");
         }
      }
   }

   my $error = 0;
   foreach my $feat (sort keys %deps) {
      next  if ($feat !~ /^perl\((.*)\)/);
      my $mod = $1;
      my $v   = $deps{$feat};
      my $err = load_module($mod,$v);
      if ($err) {
         $error = 1;
         log_message('WARN',
                     "Unable to load module $feat" . ($v ? " [$v]" : ''));
      }
   }
   if ($error) {
      log_message('ERR',
                  "Unable to load required modules.  Aborting.");
   }
}

############################################################################
# This is the most complicated step of the process.  We actually need to
# build the package for the following reasons:
#   o  To make sure that we CAN build it non-interactively
#   o  To determine what the module provides for this architecture
#   o  To determine what the module requires for this architecture
#
# With Build.PL, it is especially complicated because we want to be able
# to override the install directories, but to do so, we want to use 'installdirs'
# from the Build.PL script.  In order to not parse the file, we'll actually
# do a 'perl Build.PL' without directory arguements.  This will create a
# _build/build_params file which contains the information.

sub build {

   log_message('HEAD',"Building package: $package{dir}");
   my $type = $package{'build_type'};

   #
   # Get the config command (perl Makefile.PL), the build command
   # (make), and the install command (make install).
   #
   # First, we'll get the commands ignoring an alternate directory.
   # We'll do this so we can figure out what installation type (if any)
   # is hardcoded into the module.
   #
   # We'll run the configure and build commands to make sure
   # everything works, and to verify where the module wants to be
   # installed by default.
   #

   log_message('INFO',"Generating commands to build the module");

   my($config_cmd,$build_cmd,$test_cmd,$install_cmd) =
     commands($type,$OP_inst_type,'',0);

   my $status = run_command("$TMPDIR/config",@$config_cmd);
   if ($status eq 'WAITING') {
      my @err = `cat $TMPDIR/config.out`;
      chomp(@err);
      log_message('ERR',
                  'Config command failed waiting on input.',
                  'Output is as follows:',
                  '#'x70,
                  @err);
   } elsif ($status eq 'ERROR') {
      my @err = `cat $TMPDIR/config.err`;
      chomp(@err);
      log_message('ERR',
                  'Config command failed with an exit code.',
                  'Output is as follows:',
                  '#'x70,
                  @err);
   }

   $status = run_command("$TMPDIR/config",@$build_cmd);
   if ($status eq 'WAITING') {
      my @err = `cat $TMPDIR/config.out`;
      chomp(@err);
      log_message('ERR',
                  'Build command failed waiting on input.',
                  'Output is as follows:',
                  '#'x70,
                  @err);
   } elsif ($status eq 'ERROR') {
      my @err = `cat $TMPDIR/config.err`;
      chomp(@err);
      log_message('ERR',
                  'Build command failed with an exit code.',
                  'Output is as follows:',
                  '#'x70,
                  @err);
   }

   #
   # If we didn't pass get an installation type, we need to figure out
   # what it is now.  This is because Build.PL uses the same variables
   # to set the locations of all types of builds, so we need to know
   # what installation type so we can set them appropriately in the
   # final config command.
   #
   # This is only technically necessary if we're installing in an
   # alternate directory, but it doesn't hurt even if we're not.
   #

   if (! $OP_inst_type) {
      if ($type eq 'build') {
         # _build/build_params contains:
         #    'installdirs' => 'core',

         my @tmp = `cat _build/build_params | grep "'installdirs' =>"`;
         chomp(@tmp);
         if (@tmp != 1) {
            log_message('ERR',
                        'perl Build.PL did not produce a _build/build_params',
                        'file of the expected format.');
         }
         $tmp[0] =~ /=> '(.*?)'/;
         $OP_inst_type = $1;
         $OP_inst_type = 'perl'  if ($OP_inst_type eq 'core');

      } else {
         # Makefile contains:
         #    INSTALLDIRS = perl

         my @file = `cat Makefile`;
         chomp(@file);

         my @tmp = grep /^INSTALLDIRS =/,@file;
         if (@tmp != 1) {
            log_message('ERR',
                        'perl Makefile.PL did not produce a Makefile file of',
                        'the expected format.');
         }
         $tmp[0] =~ /= (.*)$/;
         $OP_inst_type = $1;
      }
   }

   log_message('INFO',"Determined installation type: $OP_inst_type");
   log_message('INFO',"Generating commands to build the module to that location");

   ($config_cmd,$build_cmd,$test_cmd,$install_cmd) =
     commands($type,$OP_inst_type,$OP_inst_base,1);

   #
   # Now, update the file list based on the files that will actually
   # be installed.  Get the modules and scripts.
   #

   my %files = get_filelist("$package{DIR}/blib");
   categorize_files('build',"$package{DIR}/blib",%files);
}

# This figures out the config, build, and install commands. It takes
# the type (build, make), $insttype (perl/core, site, vendor), an
# alternate install directory ($dir), and $for_spec.
#
# If $for_spec is 1, the config command will be suitable for the
# SPEC file.  Otherwise, it will be suitable for running.
#
sub commands {
   my($type,$insttype,$dir,$for_spec) = @_;

   my(@config_cmd,@build_cmd,@test_cmd,@install_cmd);

   #
   # Configure the module.
   #

   if ($type eq 'build') {
      if ($for_spec) {
         @config_cmd  = (qw(%{__perl} <build> optimize="<_optimize>"));
      } else {
         @config_cmd  = ('perl',$package{build});
      }
      @build_cmd   = (qw(./Build));
      @test_cmd    = (qw(./Build test));
      @install_cmd = (qw(./Build pure_install destdir=<_buildroot> create_packlist=0));
   } else {
      if ($for_spec) {
         @config_cmd  = (qw(%{__perl} <make> OPTIMIZE="<_optimize>"));
         @build_cmd   = (qw(make %{?_smp_mflags}));
      } else {
         @config_cmd  = ('perl',$package{make});
         @build_cmd   = ('make');
      }
      @test_cmd    = (qw(make test));
      @install_cmd = (qw(make pure_install PERL_INSTALL_ROOT=<_buildroot>));
   }

   # Handle directory arguments
   #
   # We have to record which directories things can get installed into.

   DIR_ARGS:
   {
      # If we don't specify an installation directory or type,
      # we don't have to add any arguments.
      #
      # Note: this will never be the information that goes in the SPEC
      # file because the installation type will always be determined,
      # so we don't have to record installation directories here.

      if (! $dir  &&  ! $insttype) {
         last DIR_ARGS;
      }

      # If we know the installation type, but we're not using
      # a special directory, it's a simple case.

      if (! $dir  &&  $insttype) {

         if ($type eq 'build') {
            if ($insttype eq 'perl') {
               push(@config_cmd,"installdirs=core");
            } else {
               push(@config_cmd,"installdirs=$insttype");
            }
         } else {
            push(@config_cmd,"INSTALLDIRS=$insttype");
         }

         $package{'bin_dir'}  = '%{_bindir}';
         $package{'man_dir'}  = '%{_mandir}';
         if      ($insttype eq 'perl') {
            $package{'lib_dir'}  = '%{perl_privlib}';
         } elsif ($insttype eq 'site') {
            $package{'lib_dir'}  = '%{perl_sitelib}';
         } else {
            $package{'lib_dir'}  = '%{perl_vendorlib}';
         }
         $package{'arch_dir'} = "$package{lib_dir}/$ARCH";

         last DIR_ARGS;
      }

      # If we're installing in an alternate location, we have made
      # sure to always pass in $insttype.

      if ($dir) {
         my $d = $OP_inst_base;
         my $t = $insttype;
         my $T = uc($t);

         if ($type eq 'build') {
            if ($t eq 'perl') {
               push(@config_cmd,
                    "--installdirs core",
                    "--install_path arch=$d/lib/perl5/$VERS/$ARCH",
                    "--install_path lib=$d/lib/perl5/$VERS",
                    "--install_path script=$d/bin",
                    "--install_path bin=$d/bin",
                    "--install_path libdoc=$d/$MAN/man3",
                    "--install_path bindoc=$d/$MAN/man1",
                   );

            } else {
               push(@config_cmd,
                    "--installdirs $t",
                    "--install_path arch=$d/lib/perl5/${t}_perl/$VERS/$ARCH",
                    "--install_path lib=$d/lib/perl5/${t}_perl/$VERS",
                    "--install_path script=$d/bin",
                    "--install_path bin=$d/bin",
                    "--install_path libdoc=$d/$MAN/man3",
                    "--install_path bindoc=$d/$MAN/man1",
                   );
            }

         } else {
            if ($t eq 'perl') {
               push(@config_cmd,
                    "INSTALLDIRS=perl",
                    "PERLPREFIX=$d",
                    "INSTALLARCHLIB=$d/lib/perl5/$VERS/$ARCH",
                    "INSTALLPRIVLIB=$d/lib/perl5/$VERS",
                    "INSTALLBIN=$d/bin",
                    "INSTALLSCRIPT=$d/bin",
                    "INSTALLMAN1DIR=$d/$MAN/man1",
                    "INSTALLMAN3DIR=$d/$MAN/man3",
                   );
            } else {
               push(@config_cmd,
                    "INSTALLDIRS=$t",
                    "${T}PREFIX=$d",
                    "INSTALL${T}ARCH=$d/lib/perl5/${t}_perl/$VERS/$ARCH",
                    "INSTALL${T}LIB=$d/lib/perl5/${t}_perl/$VERS",
                    "INSTALL${T}BIN=$d/bin",
                    "INSTALL${T}SCRIPT=$d/bin",
                    "INSTALL${T}MAN1DIR=$d/$MAN/man1",
                    "INSTALL${T}MAN3DIR=$d/$MAN/man3",
                    "INSTALLSCRIPT=$d/bin",            # necessary due to a bug
                   );
            }
         }

         $package{'bin_dir'}  = "$d/bin";
         $package{'man_dir'}  = "$d/$MAN";
         if      ($insttype eq 'perl') {
            $package{'lib_dir'}  = "$d/lib/perl5/$VERS";
         } else {
            $package{'lib_dir'}  = "$d/lib/perl5/${t}_perl/$VERS"
         }
         $package{'arch_dir'} = "$package{lib_dir}/$ARCH";
      }
   }

   #
   # Now handle everything else.
   #

   push(@config_cmd,@OP_config);
   push(@build_cmd,@OP_build);

   $package{'config_cmd'}  = join(' ',@config_cmd);
   $package{'build_cmd'}   = join(' ',@build_cmd);
   $package{'test_cmd'}    = join(' ',@test_cmd);
   $package{'install_cmd'} = join(' ',@install_cmd);

   return (\@config_cmd,\@build_cmd,\@test_cmd,\@install_cmd);
}

# Some Makefile.PL and Build.PL scripts are interactive!  That really sucks.
# We have to run the commands and check on them, and if they ever enter a
# 'waiting for input' state, we'll kill them and report that this module
# can't be made.
#

sub run_command {
   my($output_file,@cmd) = @_;

   my $pid = fork();

   if (! $pid) {
      # We'll run the command in a child process.
      open(STDOUT, ">$output_file.out");
      open(STDERR, ">$output_file.err");
      exec(@cmd);
   }

   my $status;
   local $SIG{CHLD} = sub { $status = 'DONE'; };

   while (1) {
      $status = strace($pid);
      last  if ($status eq 'DONE'  ||
                $status eq 'WAITING');
   }

   if (-f "$output_file.exit") {
      $status = 'ERROR';
   }

   return $status;
}


sub strace {
   my($pid) = @_;

   my $strace = find_exe('strace');
   if (! $strace) {
      log_message('ERR','strace executable not found');
   }

   my($strace_pid,$strace_fh);
   $strace_pid = open $strace_fh, "-|", "$strace -qp $pid 2>&1"  ||
     log_message('ERR',"Unable to run strace: $!");

   local $SIG{ALRM} = sub { kill INT => $strace_pid };
   alarm 3;
   my $trace;
   $trace  .= $_ while <$strace_fh>;
   alarm 0;
   close $strace_fh;

   if (! $trace) {
      return "RUNNING";

   } elsif ($trace =~ /^read\(.*$/) {   # check strace output
      kill TERM => $pid;
      return "WAITING";

   } elsif ($trace =~ /No such process/  ||
            $trace =~ /Operation not permitted/) {
      # If the process is done or in defunct state
      return "DONE";
   }
   return "RUNNING";
}

############################################################################
# After building the package, missing META files will have been created, so
# we can get information from them rather than try to get that information
# from the Build.PL or Makefile.PL files.

sub get_meta {

   log_message('HEAD',"Reading package metadata (post-build): $package{dir}");

   my %files = get_filelist($package{"DIR"});
   categorize_files("post_build",$package{"DIR"},%files);

   # Get rid of any requirements previously deduced since they may not
   # have come from a META file.

   foreach my $type (keys %{ $package{'requires'} }) {
      delete $package{"${type}_req"};
   }
   delete $package{'requires'};

   foreach my $f (qw(meta.json mymeta.json meta.yml mymeta.yml)) {
      my $type = ($f =~ /json/ ? 'json' : 'meta');
      get_meta_meta($type,$files{$f})           if (exists $files{$f});
   }

   $package{'arch'}     = (exists $package{'files'}{'xs'} ? '%{_arch}' : 'noarch');
   my $tmp              = `rpm --eval '$package{arch}'`;
   chomp($tmp);
   $package{'arch_val'} = $tmp;

   #
   # If we passed in --name, we'll use that.  Otherwise, we'll get it from
   # the package name (which MUST match the META name).
   #

   if ($OP_name) {
      $package{'name'} = $OP_name;
   } else {
      if ($package{'m_name'}  &&
          $package{'m_name'} ne $package{'dist'}) {
         log_message('ERR',
                     "The name obtained from metadata is different: $package{dir}",
                     "The name of the package obtained from the archive file",
                     "and the one obtained from the metadata are not the same.",
                     "   Archive:  $package{dist}",
                     "   Metadata: $package{m_name}",
                     "They must be the same, OR you must manually set the name",
                     "with the --name option.");
      }

      $package{'name'} = $package{'dist'};
   }

   #
   # If we passed in --vers, we'll use that.  Otherwise, we'll get it from
   # the package version (which MUST match the META version).
   #

   if ($OP_version) {
      $package{'version'} = $OP_version;
   } else {
      if ($package{'m_version'}  &&
          $package{'m_version'} ne $package{'vers'}) {
         log_message('ERR',
                     "The version obtained from metadata is different: $package{dir}",
                     "The version of the package obtained from the archive file",
                     "and the one obtained from the metadata are not the same.",
                     "   Archive:  $package{vers}",
                     "   Metadata: $package{m_version}",
                     "They must be the same, OR you must manually set the version",
                     "with the --version option.");
      }

      $package{'version'} = $package{'vers'};
   }

   #
   # Make sure we've got authors.
   #

   if (@OP_author) {
      $package{'author'} = [@OP_author];
   } else {
      if (! $package{'m_author'}) {
         log_message('WARN',
                     "Unable to determine author(s): $package{dir}",
                     "This can be specified them using the --author option.");
         $package{'author'} = ['No author information listed in META file.'];
      } elsif (! ref($package{'m_author'})) {
         $package{'author'} = [ $package{'m_author'} ];
      } else {
         $package{'author'} = [ @{ $package{'m_author'} } ];
      }
   }

   #
   # Make sure we've got a module description.  If we included a --description
   # option, use it.  Otherwise, we'll see if it was included in the META
   # files or the main POD file.
   #

   if ($OP_description) {

      if (-r $OP_description) {
         my $in = new IO::File;
         $in->open($OP_description);
         $package{'desc'} = join('',<$in>);
      } else {
         log_message('ERR',"--description option invalid: $OP_description",
                     "--description must contain the path to a readable file",
                     "containing the package description.");
      }

   } elsif ($package{'m_description'}) {
      $package{'desc'} = $package{'m_description'};

   } elsif ($package{'files'}{'mainpod'}  &&
            $package{'files'}{'mainpod'}[3]) {
      $package{'desc'} = $package{'files'}{'mainpod'}[3];

   } else {
      $package{'desc'} = 'A perl module';
   }


   #
   # Same thing with the summary.
   #

   if ($OP_summary) {
      $package{'summary'} = $OP_summary;

   } elsif ($package{'m_abstract'}) {
      $package{'summary'} = $package{'m_abstract'};

   } elsif ($package{'files'}{'mainpod'}  &&
            $package{'files'}{'mainpod'}[2]) {
      $package{'summary'} = $package{'files'}{'mainpod'}[2];

   } else {
      $package{'summary'} = 'A perl module';
   }

   #
   # Handle the prefix and get various file names.
   #

   $package{'prefix'}   = $OP_prefix;
   my $pkgname          = ($OP_prefix ? $OP_prefix . '-' . $package{'name'} :
                           $package{'name'});
   $package{'rpmname'}  = $pkgname;
   $package{'specname'} = "$pkgname.spec";

   #
   # Check the requires/provides for this package.
   #

   provides();
   requires('instfiles');

   #
   # Now clean up the directory.
   #

   if ($package{'build_type'} eq 'build') {
      system('./Build distclean');
   } else {
      system('make distclean');
   }
}

# Get as much information from a META file as possible.
#
sub get_meta_meta {
   my($type,$filehash) = @_;
   my $meta;

   my @tmp = keys %$filehash;
   if (@tmp != 1) {
      my $tmp = $tmp[0];
      log_message('WARN',"Multiple '$tmp' files exist (with different cases).",
                  "This is not supported, so they will be ignored.");
      return;
   }
   my $file = "$package{DIR}/$tmp[0]";
   $OUTPUT = '';

   log_message('INFO',"Reading META file: $tmp[0]");

   my $succ;
   if ($type eq 'json') {

      $succ = multiple_methods
        ( [ sub { 1; } ],
          [ 'module', 'Parse::CPAN::Meta', '1.41', [],
            "\$OUTPUT = Parse::CPAN::Meta->load_file('$file')" ],
          [ 'module', 'JSON', ['from_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = from_json(\$json_text);" ],
          [ 'module', 'JSON::XS', ['decode_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = decode_json(\$json_text);" ],
          [ 'module', 'JSON::PP', ['decode_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = decode_json(\$json_text);" ],
          [ 'module', 'JSON::DWIW', ['from_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = from_json(\$json_text);" ],
        );

   } else {

      $succ = multiple_methods
        ( [ sub { 1; } ],
          [ 'module', 'Parse::CPAN::Meta', [],
            "\$OUTPUT = Parse::CPAN::Meta::LoadFile('$file')" ],
          [ 'module', 'CPAN::Meta::YAML', [],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$yaml_text = do { local \$/; <\$fh> }; " .
            "my \$tmp = CPAN::Meta::YAML->read_string(\$yaml_text);" .
            "\$OUTPUT = \$tmp->[0]" ],
          [ 'module', 'YAML', [],
            "my \@tmp = YAML::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
          [ 'module', 'YAML::Syck', [],
            "my \@tmp = YAML::Syck::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
          [ 'module', 'YAML::XS', [],
            "my \@tmp = YAML::XS::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
          [ 'module', 'YAML::Tiny', [],
            "my \@tmp = YAML::Tiny::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
        );
   }

   if (! $succ) {
      log_message('WARN',"Unable to read META file: $tmp[0]");
      return;
   }
   if (! $OUTPUT) {
      log_message('ERR',"META file empty or corrupt: $tmp[0]");
      return;
   }

   # Now get the meta information:

   foreach my $f (qw(name version keywords abstract description author provides)) {
      get_meta_field($f,"m_$f");
   }

   # License information is stored in multiple places:
   #   license     => VALUE
   #   license     => [ VALUE, VALUE, ... ]
   #   resources   => license => [ VALUE, VALUE, ... ]
   #   license_uri => VALUE

   if (! $package{'m_license'}) {

      my $lic = '';

      if ($OUTPUT->{'license'}) {
         my @lic;

         if (ref($OUTPUT->{'license'})) {
            @lic = @{ $OUTPUT->{'license'} };
         } else {
            @lic = ($OUTPUT->{'license'});
         }

         foreach my $l (@lic) {
            if ($l =~ /^perl$/i  ||
                $l =~ /^perl_5$/i) {
                $l="GPL+ or Artistic";
             } elsif ($l =~ /^apache$/i) {
                $l="Apache Software License";
             } elsif ($l =~ /^artistic$/i) {
                $l="Artistic";
             } elsif ($l =~ /^artistic_?2$/i) {
                $l="Artistic 2.0";
             } elsif ($l =~ /^bsd$/i) {
                $l="BSD";
             } elsif ($l =~ /^gpl$/i) {
                $l="GPL+";
             } elsif ($l =~ /^lgpl$/i) {
                $l="LGPLv2+";
             } elsif ($l =~ /^mit$/i) {
                $l="MIT";
             } elsif ($l =~ /^mozilla$/i) {
                $l="MPL";
             } elsif ($l =~ /^open_source$/i) {
                $l="OSI-Approved";                  # rpmlint will complain
             } elsif ($l =~ /^unrestricted$/i) {
                $l="Distributable";
             } elsif ($l =~ /^restrictive$/i) {
                $l="Non-distributable";
                log_message('WARN',
                            'License is "restrictive".',
                            'This package should not be redistributed.');
             } else {
                $l="Unknown license: $l";
                log_message('WARN',
                            "Unknown license: $l",
                            'Check to make sure this package is distributable.');
             }
         }

         $lic = join(', ',@lic);

      } elsif ($OUTPUT->{'resources'}  &&
               $OUTPUT->{'resources'}->{'license'}) {
         $lic = join(' ',@{ $OUTPUT->{'resources'}->{'license'} });

      } elsif ($OUTPUT->{'license_uri'}) {
         $lic = $OUTPUT->{'license_uri'};
      }


      $package{'m_license'} = $lic  if ($lic);
   }

   # Requires can come from an old-style META.yml file:
   #   requires           => FEATURE => VERSION
   #   build_requires     => FEATURE => VERSION
   #   configure_requires => FEATURE => VERSION
   # or a new style META.json file:
   #   prereqs =>
   #      LEVEL =>                 LEVEL = configure, build, test, runtime
   #         KEY =>                KEY   = requires, recommends
   #            FEATURE => VERSION
   #
   # If we find prereqs in multiple files, we'll merge them
   # (but we'll use the VERSION from the first file they're
   # found in so we'll assume that we're examining the most
   # accurate file first).

   my %requires;
   if ($OUTPUT->{'prereqs'}) {
      my %lev = ( 'configure'  => [ 'build' ],
                  'build'      => [ 'build' ],
                  'test'       => [ 'test' ],
                  'runtime'    => [ 'build', 'runtime' ],
                );

      foreach my $lev (keys %lev) {
         foreach my $t (@{ $lev{$lev} }) {
            my @key = ('requires');
            push(@key,'recommends')  if ( ($t eq 'build'    &&  $OP_build_rec)  ||
                                          ($t eq 'test'     &&  $OP_test_rec)   ||
                                          ($t eq 'runtime'  &&  $OP_runtime_rec) );

            foreach my $key (@key) {

               if ($OUTPUT->{'prereqs'}->{$lev}  &&
                   $OUTPUT->{'prereqs'}->{$lev}->{$key}) {

                  foreach my $f (keys %{ $OUTPUT->{'prereqs'}->{$lev}->{$key} }) {
                     my $v = $OUTPUT->{'prereqs'}->{$lev}->{$key}->{$f};
                     $requires{$t}{$f} = $v;
                  }
               }
            }
         }
      }

   } else {

      # Requires

      my %lev = ( 'configure_requires'  => [ 'build' ],
                  'build_rquires'       => [ 'build' ],
                  'requires'            => [ 'build', 'runtime' ],
                );

      foreach my $lev (keys %lev) {
         if ($OUTPUT->{$lev}) {
            foreach my $f (keys %{ $OUTPUT->{$lev} }) {
               my $v = $OUTPUT->{$lev}->{$f};
               foreach my $t (@{ $lev{$lev} }) {
                  $requires{$t}{$f} = $v;
               }
            }
         }
      }

      # Recommends

      %lev = ( 'recommends'          => [ 'build', 'runtime' ],
             );

      foreach my $lev (keys %lev) {
         if ($OUTPUT->{$lev}) {
            foreach my $f (keys %{ $OUTPUT->{$lev} }) {
               my $v = $OUTPUT->{$lev}->{$f};
               foreach my $t (@{ $lev{$lev} }) {

                  if ( ($t eq 'build'    &&  $OP_build_rec)  ||
                       ($t eq 'test'     &&  $OP_test_rec)   ||
                       ($t eq 'runtime'  &&  $OP_runtime_rec) ) {
                     $requires{$t}{$f} = $v;
                  }
               }
            }
         }
      }

   }

   if (%requires) {
      foreach my $t (keys %requires) {
         foreach my $f (keys %{ $requires{$t} }) {
            my $v = $requires{$t}{$f};

            $package{'requires'}{$t}{$f} = $v  if (! $package{'requires'}{$t}{$f});
         }
      }

   } else {
      $package{'requires'} = \%requires;
   }
}

sub get_meta_field {
   my($meta_field,$pack_field) = @_;

   return  if ($package{$pack_field}  ||
               ! exists $OUTPUT->{$meta_field});
   $package{$pack_field} = $OUTPUT->{$meta_field};
}

# This looks at the filelist determines which are pod files, which are
# .pm files, which are test files, etc.
#
sub categorize_files {
   my($op,$dir,%files) = @_;

   log_message('INFO',"Categorizing $op package files");

   # First pass based on some simple tests.

   my $in = new IO::File;

   foreach my $file (keys %files) {
      foreach my $f (keys %{ $files{$file} }) {

         if ($op eq 'build') {

            # Files in the blib directory:
            #
            # Ignored:
            #   */*.exists
            #
            # PM files:
            #   **/*.pm
            #
            # Bin files
            #   bin/*
            #   script/*
            #
            # man1 files:
            #   man1/*
            #   bindoc/*
            #
            # man3 files:
            #   man3/*
            #   libdoc/*

            if      ($f =~ /\.exists$/) {
               next;

            } elsif ($f =~ m,^lib/\Q$ARCH\E/.*\.pm$,) {
               $package{'instfiles'}{'pm'}{$f}      = 1;
               $package{'arch_inst'}                = 1;

            } elsif ($f =~ m,.*\.pm$,) {
               $package{'instfiles'}{'pm'}{$f}      = 1;
               $package{'lib_inst'}                 = 1;

            } elsif ($f =~ m,^(script|bin)/,) {
               $package{'instfiles'}{'scripts'}{$f} = 1;
               $package{'bin_inst'}                 = 1;

            } elsif ($f =~ m,^man1/,  ||
                     $f =~ m,^bindoc/,) {
               $package{'instfiles'}{'man1'}{$f}    = 1;
               $package{'man1_inst'}                = 1;

            } elsif ($f =~ m,^man3/,  ||
                     $f =~ m,^libdoc/,) {
               $package{'instfiles'}{'man3'}{$f}    = 1;
               $package{'man3_inst'}                = 1;
            }

         } else {

            # Package files:
            #
            # Test files:
            #   t/*.t
            #   t/*.pl
            #   t/*.pm
            #
            # Build files:
            #   Makefile.PL
            #   Build.PL
            #   inc/*.pm
            #
            # POD files:
            #   *.pod
            #
            # PM files:
            #   *.pm
            #
            # XS files:
            #   *.c
            #   *.xs
            #
            # Scripts:
            #   *.pl
            #   Anything that starts with '#!'

            if ($f =~ m,^t/.*\.(t|pl|pm)$,) {
               $package{'files'}{'t'}{$f} = '';

            } elsif ($f =~ m,^t/,) {
               next;

            } elsif ($f =~ m,^blib/,  ||
                     $f =~ /pm_to_blib/) {
               next;

            } elsif ($f =~ m,^inc/.*\.pm$,  ||
                     lc($f) eq 'makefile.pl'  ||
                     lc($f) eq 'build.pl') {
               $package{'files'}{'build'}{$f} = '';

            } elsif ($f =~ m,^inc/,) {
               next;

            } elsif ($f =~ m,\.pod$,) {
               $package{'files'}{'pod'}{$f} = '';

            } elsif ($f =~ m,\.pm$,) {
               $package{'files'}{'pm'}{$f} = '';

            } elsif ($f =~ m,\.(c|xs)$,) {
               $package{'files'}{'xs'}{$f} = '';

            } elsif ($f =~ m,\.pl$,) {
               $package{'files'}{'scripts'}{$f} = '';

            } else {
               $in->open("$dir/$f");
               my $line = <$in>;
               if ($line  &&  $line =~ /^\#\!/) {
                  $package{'files'}{'scripts'}{$f} = '';
               }
            }
         }
      }
   }

   #
   # We need to decide (if possible) which is the 'main' pod file.
   #
   # This only has to be done once (at the post_build step).
   #

   return  if ($op ne 'post_build');

   # Scripts and .pm files may also be pod.

   $package{'files'}{'pm'}      = {}  if (! exists $package{'files'}{'pm'});
   $package{'files'}{'scripts'} = {}  if (! exists $package{'files'}{'scripts'});
   $package{'files'}{'pod'}     = {};

   foreach my $f (keys %{ $package{'files'}{'pm'} },
                  keys %{ $package{'files'}{'scripts'} }) {

      $in->open("$dir/$f");
      my @in = <$in>;
      if (grep /^(=pod|=head1)/,@in) {
         $package{'files'}{'pod'}{$f} = 1;
      }
   }

   log_message('INFO',"Determining the main POD file");

   my($mainpod,$name,$summary,$description);

   POD:
   while (1) {
      my @pod = keys %{ $package{'files'}{'pod'} };

      #
      # If it was specified using the --mainpod option.
      #

      if ($OP_mainpod) {
         if (! -f "$dir/$OP_mainpod") {
            log_message('WARN',
                        "No data obtained from POD file; $OP_mainpod",
                        "POD file specified with --mainpod does not exist.",
                        "Automatic detection of main POD file will be attempted.");

         } else {
            if (! exists $package{'files'}{'pod'}{$OP_mainpod}) {
               log_message
                 ('WARN',
                  "No data obtained from POD file; $OP_mainpod",
                  "File specified with --mainpod does not appear to be a POD file.",
                  "It will be tried in spite of this.");
            }
            ($name,$summary,$description) = get_meta_pod($OP_mainpod);

            if (! $name) {
               log_message
                 ('WARN',
                  "No data obtained from POD file; $OP_mainpod",
                  "Unable to read POD data from file specified with --mainpod",
                  "Automatic detection of main POD file will be attempted.");
            } else {
               $mainpod = $OP_mainpod;
               last POD;
            }
         }
      }


      #
      # If there's only one pod file, then it's the main one.
      #

      if (@pod == 1) {
         ($name,$summary,$description) = get_meta_pod($pod[0]);

         if (! $name) {
            log_message
              ('WARN',
               "No data obtained from POD file; $pod[0]",
               "There is only one POD file, but it does not appear to be valid.",
               "Automatic detection of main POD file failed.");
         } else {
            $mainpod = $pod[0];
         }
         last POD;
      }

      #
      # If we're working with a package named Foo-Bar and there
      # is exactly one pod file named Bar.pod (or Bar.pm), use
      # it if it is valid.
      #

      my @dist = split(/\-/,$package{'dist'});
      my $last = $dist[$#dist];
      my @tmp  = grep /(^|\/)$last\.(pm|pod)$/,@pod;
      if (@tmp == 1) {

         ($name,$summary,$description) = get_meta_pod($tmp[0]);

         if (! $name) {
            log_message
              ('WARN',
               "No data obtained from POD file; $tmp[0]",
               "There is only one correctly named POD file, but it does not",
               "appear to be valid.",
               "Automatic detection of main POD file will be attempted.");
         } else {
            $mainpod = $tmp[0];
            last POD;
         }
      }

      #
      # Next, look at all POD files.  If a pod file has a NAME that is
      # the same as the the package name (with :: changed to dashes),
      # this is the main one.  i.e. in a package named Foo-Bar, the
      # first POD file who's NAME is Foo::Bar will be used.
      #

      my $mod_exp = $package{'dist'};
      $mod_exp    =~ s/\-/::/g;

      foreach my $pod (sort keys %{ $package{'files'}{'pod'} }) {
         my($n,$s,$d) = get_meta_pod($pod);
         $n = ''   if (! $n);

         if ($n eq $mod_exp) {
            ($name,$summary,$description) = ($n,$s,$d);
            $mainpod = $pod;
            last POD;
         }

         $package{'files'}{'pod'}{$pod} = $n;
      }

      #
      # One final attempt will be to see if there is one POD file that
      # is 'shallowest'.  In other words, if the POD files in the distribution
      # are named:
      #   Foo
      #   Foo::Bar
      #   Foo::Bar2
      # we'll take 'Foo' one since it is the least number of levels.  This
      # will only occur if there is exactly 1 POD file at that level.
      #

      my ($n,$f);
      $n = 100;
      foreach my $pod (sort keys %{ $package{'files'}{'pod'} }) {
         my $name = $package{'files'}{'pod'};
         next  if (! $name);

         my @tmp = split(/::/,$name);
         if (@tmp == $n) {
            $f = '';
         } elsif (@tmp < $n) {
            $n = @tmp;
            $f = $pod;
         }
      }

      if ($f) {
         ($name,$summary,$description) = get_meta_pod($f);
         $mainpod = $f;
         last POD;
      }

      #
      # We weren't able to determine the main POD file.
      #

      log_message
        ('WARN',"Automatic detection of main POD file failed.");
      last POD;
   }

   if ($mainpod) {
      $package{'files'}{'mainpod'} = [ $mainpod,$name,$summary,$description ];
   }
}

# This will get the NAME, SUMMARY, and DESCRIPTION sections of a POD
# file.  It will return () if it is not a valid POD file.
#
# This will use the following perl modules:
#   Pod::Parser
#   Pod::Simple::TextContent
#
sub get_meta_pod {
   my($file) = @_;
   my($name,$summary,$description);

   POD:
   while (1) {

      #
      # Try Pod::Parser
      #

      log_message('INFO',"Analyzing pod file with Pod::Parser: $file");
      my $err = load_module("Pod::Select");
      if (! $err) {

         # NAME - SUMMARY

         Pod::Select::podselect( { -output     => "$TMPDIR/pod_select",
                                   -selections => ["NAME"]
                                 }, "$package{DIR}/$file");

         my $fh = new IO::File;
         $fh->open("$TMPDIR/pod_select");
         my @in = <$fh>;
         $fh->close();
         chomp(@in);

         if (@in) {
            shift(@in);
            while (@in  &&  ! $in[0]) {
               shift(@in);
            }
            if (@in  &&  $in[0] =~ /^(\S+)\s+\-\s+(.*)$/) {
               ($name,$summary) = ($1,$2);
            }
         }

         # DESCRIPTION
         Pod::Select::podselect( { -output     => "$TMPDIR/pod_select",
                                   -selections => ["DESCRIPTION"]
                                 }, "$package{DIR}/$file");

         $fh->open("$TMPDIR/pod_select");
         @in = <$fh>;
         chomp(@in);
         $fh->close();

         # Although not great, we're going to keep only the first paragraph.
         # The description stuff gets too long otherwise.
         if (@in) {
            shift(@in);
            while (@in  &&  ! $in[0]) {
               shift(@in);
            }
            my(@tmp);
            while (@in  &&  $in[0]) {
               push(@tmp,shift(@in));
            }
            $description = join("\n",@tmp);
         }

         last POD;
      }

      #
      # Try Pod::Simple::TextContent
      #

      log_message('INFO',"Analyzing pod file with Pod::Simple::TextContent: $file");
      $err = load_module("Pod::Simple::TextContent");
      if (! $err) {

         my $output;
         my $parser = Pod::Simple::TextContent->new();
         $parser->no_whining(1);
         $parser->output_string(\$output);

         my $fh = new IO::File;
         $fh->open("$package{DIR}/$file");
         $parser->parse_file($fh);

         my @in = split(/\n/,$output);

         while (@in  &&  $in[0] !~ /^(NAME|DESCRIPTION)$/) {
            shift(@in);
         }

         if (@in  &&  $in[0] =~ /NAME/) {
            shift(@in);
            while (@in  &&  ! $in[0]) {
               shift(@in);
            }
            if (@in  &&  $in[0] =~ /^(\S+)\s+\-\s+(.*)$/) {
               ($name,$summary) = ($1,$2);
            }
         }

         while (@in  &&  $in[0] !~ /^(DESCRIPTION)$/) {
            shift(@in);
         }

         # Although not great, we're going to keep only the first paragraph.
         # The description stuff gets too long otherwise.
         if (@in) {
            shift(@in);
            while (@in  &&  ! $in[0]) {
               shift(@in);
            }
            my(@tmp);
            while (@in  &&  $in[0]) {
               push(@tmp,shift(@in));
            }
            $description = join("\n",@tmp);
         }

         last POD;
      }

      log_message('WARN',"Analyzing pod file failed: $file");
      last POD;
   }

   # Do a simple format into 75-character lines.

   $description = ''  if (! $description);
   my $max = 75;
   my @tmp = split(/\n/,$description);
   my $tmp = join(' ',@tmp);
   $tmp    =~ s/\s+/ /g;
   @tmp    = ();
   while ($tmp) {
      if (length($tmp) <= $max) {
         push(@tmp,$tmp);
         last;
      }

      my $i = rindex($tmp,' ',$max);
      if ($i == -1) {
         $i = index($tmp,' ');
         if ($i == -1) {
            push(@tmp,$tmp);
            $tmp = '';
         } else {
            push(@tmp,substr($tmp,0,$i,''));
            substr($tmp,0,1,'');
         }
      } else {
         push(@tmp,substr($tmp,0,$i,''));
         substr($tmp,0,1,'');
      }
   }
   $description = join("\n",@tmp);

   return ($name,$summary,$description);
}

# Figure out what this package provides.
#
# If one of the META files has an 'provides' section, we'll use it.
# Otherwise, we have to look at all of the .pm files to see what
# packages are provided.
#
sub provides {

   PROVIDES_METHOD:
   {
      if (exists $package{'m_provides'}) {

         #
         # If the META file defines the provides, it'll be of the form:
         #    FEATURE => { version => VERS,
         #                 file    => FILE,      lib/Foo/Bar.pm
         #               }
         #
         # We'll use this list.
         #

         foreach my $mod (keys %{ $package{'m_provides'}}) {
            my $m = "perl($mod)";
            my $v = $package{'m_provides'}{'version'}  ||  $package{'version'};
            $v    =~ s/^v//;
            $package{'provides'}{$m} = $v;
         }
         last PROVIDES_METHOD;
      }

      #
      # I'd like to rely on the standard RPM tools for getting the
      # list of provides and dependencies.  Unfortunately, I've found
      # in at least one case (openSuSE 12.1) that the tools are broken.
      # Since that is actually one of the platforms that I want to
      # build RPMs for, we're going to have to do two things:
      #   1) first, we'll try the RPM tools, but if they don't work,
      #      we'll read the files manually
      #   2) we'll explicitly put in the provides and requires into the
      #      SPEC file, even though this is officially discouraged
      #
      # If the standard tools don't work, use the included cpantorpm-depreq
      # script.
      #
      # perldeps is the last resort since it doesn't give versions.
      #

      my $cd     = "cd $package{DIR}/blib";
      my @files  = sort keys %{ $package{'instfiles'}{'pm'} };
      my @prov;

      if (@files) {
         my $bin    = find_exe('rpmdeps','/usr/lib/rpm');
         @prov      = `$cd; $bin --provides @files`  if ($bin);

         $bin       = find_exe('find-provides','/usr/lib/rpm');
         @prov      = `$cd; echo @files | $bin`      if (! @prov  &&  $bin);

         $bin       = find_exe('find-provides.perl','/usr/lib/rpm');
         @prov      = `$cd; echo @files | $bin`      if (! @prov  &&  $bin);

         $bin       = find_exe('perl.prov','/usr/lib/rpm');
         @prov      = `$cd; $bin @files`             if (! @prov  &&  $bin);

         $bin       = find_exe('cpantorpm-depreq',$DIR);
         @prov      = `$cd; $bin -p @files`          if (! @prov  &&  $bin);

         $bin       = find_exe('perldeps.pl','/usr/lib/rpm');
         @prov      = `$cd; $bin --provides @files`  if (! @prov  &&  $bin);
      }

      if (@prov) {
         chomp(@prov);
         foreach my $prov (@prov) {
            if ($prov =~ /^\s*(.*?)\s*=\s*(.*?)\s*$/) {
               my($mod,$ver) = ($1,$2);
               $package{'provides'}{$mod} = $ver;
            } else {
               $prov =~ s/\s*$//;
               $prov =~ s/^\s*//;
               $package{'provides'}{$prov} = $package{'version'};
            }
         }
      }
   }

   #
   # If we're removing provides.
   #

   foreach my $feat (@OP_rem_provide) {
      delete $package{'provides'}{$feat};
   }

   #
   # Handle any added provides.
   #

   foreach my $feat (@OP_add_provide) {
      my($mod,$ver);
      if ($feat =~ /^(.+)=(.+)$/) {
         ($mod,$ver) = ($1,$2);
      } else {
         ($mod,$ver) = ($feat,$package{'version'});
      }

      $package{'provides'}{$mod} = $ver;
   }
}

# This formats the figures out what the package requires to build, test, or
# run.
#
# $type is 'files' or 'instfiles' and tells which list of files to
# examine for requirements.
#
sub requires {
   my($type) = @_;

   if (! $package{'requires'}) {

      #
      # No META file was available to provide information on requirements,
      # so we're going to do it by hand.
      #
      # See the note in the provides subroutine above.  We won't depend
      # on the standards rpm tools... but we'll use them if they work.
      #

      # Since test files are not installed, we'll always use the 'files' type
      _requires(['test'],   keys %{ $package{'files'}{'t'} });
      _requires(['build'],  keys %{ $package{'files'}{'build'} });

      _requires(['build','runtime'], keys %{ $package{$type}{'pm'} },
                                     keys %{ $package{$type}{'script'} });
   }

   #
   # Now format the requirements for use in the SPEC file.
   #

   if ($package{'requires'}) {

      foreach my $type (keys %{ $package{'requires'} }) {
         foreach my $feature (keys %{ $package{'requires'}{$type} }) {
            my $v = $package{'requires'}{$type}{$feature};

            if ($feature ne 'perl') {
               $feature = "perl($feature)";
            }

            $package{"${type}_req"}{$feature} = $v;
         }
      }
   }

   #
   # If we're removing requirements.
   #

   foreach my $feat (@OP_rem_require) {
      foreach my $lev (qw(build runtime test)) {
         delete $package{"${lev}_req"}{$feat};
      }
   }

   #
   # Handle any added requirements.
   #

   foreach my $feat (@OP_add_require) {
      my($mod,$ver);
      if ($feat =~ /^(.+)>?=(.+)$/) {
         ($mod,$ver) = ($1,$2);
      } else {
         ($mod,$ver) = ($feat,0);
      }

      foreach my $lev (qw(build runtime)) {
         $package{"${lev}_req"}{$mod} = $ver;
      }
   }
}

# This reads @files and figures out what their requirements are.  It sets
# this information for each level in the @$level.
#
sub _requires {
   my($level,@files) = @_;
   return  if (! @files);

   my %tmp = map { $_,1 } @files;
   @files  = sort(keys %tmp);

   my @req;

   my $bin    = find_exe('rpmdeps','/usr/lib/rpm');
   @req       = `$bin --requires @files`                    if ($bin);

   $bin       = find_exe('find-requires','/usr/lib/rpm');
   @req       = `echo @files | $bin`                        if (! @req  &&  $bin);

   $bin       = find_exe('find-requires.perl','/usr/lib/rpm');
   @req       = `echo @files | $bin`                        if (! @req  &&  $bin);

   $bin       = find_exe('cpantorpm-depreq',$DIR);
   @req       = `$bin -r @files`                            if (! @req  &&  $bin);

   $bin       = find_exe('perldeps.pl','/usr/lib/rpm');
   @req       = `$bin --requires @files`                    if (! @req  &&  $bin);

   if (@req) {
      chomp(@req);
      foreach my $req (@req) {

         # Should return a list of:
         #    perl               [ OP VERSION ]
         #    perl(Class::Mod)   [ OP VERSION ]

         next  if ($req !~ /^perl/);

         if ($req =~ /^\s*(.*?)\s*>=\s*(.*?)\s*$/) {
            my($mod,$ver) = ($1,$2);

            if ($mod ne 'perl') {
               if ($mod =~ /^perl\((.*)\)$/) {
                  $mod = $1;
               } else {
                  log_message
                    ('ERR',"Dependency malformed: $mod");
               }
            }

            # For some reason, rpmdeps sometimes gives a version number
            # for perl as #:xxx.  Fix this

            $ver =~ s/^\d+://  if ($mod eq 'perl');

            foreach my $lev (@$level) {
               $package{'requires'}{$lev}{$mod} = $ver;
            }
         } else {
            $req =~ s/\s*$//;
            $req =~ s/^\s*//;

            if ($req =~ /^perl\((.*)\)$/) {
               $req = $1;
            } else {
               log_message
                 ('ERR',"Dependency malformed: $req");
            }

            foreach my $lev (@$level) {
               $package{'requires'}{$lev}{$req} = 0;
            }
         }
      }
   }
}

############################################################################
# The process of generating the spec file is taken in large part from the
# cpanspec script.

sub make_spec {

   log_message('HEAD',"Writing spec file: $package{name}");

   setlocale(LC_ALL, "en_US.UTF-8");

   #
   # A few more package values.
   #

   $package{'release'} = $OP_release;
   $package{'disttag'} = $OP_disttag;
   $package{'url'}     = ($package{'from'} eq 'url' ?
                          $package{'fromsrc'} :
                          "http://search.cpan.org/dist/$package{name}/");
   $package{'epoch'}   = $OP_epoch  if ($OP_epoch ne '');
   $package{'group'}   = $OP_group;
   $package{'license'} = ($package{'m_license'} ?
                          $package{'m_license'} :
                          'GPL+ or Artistic');
   $package{'source'} =
     ($package{'from'} eq 'CPAN' ?
      "http://search.cpan.org/authors/id/$package{cpandir}/$package{archive}" :
      $package{'fromsrc'} );

   foreach my $key (keys %{ $Macros{$OP_macros} }) {
      my $val = $Macros{$OP_macros}{$key};
      $package{$key} = $val;
   }

   #
   # Find out if there are is a post-build script.
   #

   post_build();

   #
   # Make sure we can run rpm.
   #

   if (system('rpm --version > /dev/null') != 0) {
      log_message('ERR','Unable to run rpm.');
   }

   #
   # Make sure that the RPM build hierarchy exists.
   #

   check_rpm_build();
   log_message('INFO',"SPEC file: $package{topdir}/SPECS/$package{specname}");

   #
   # Every package needs a packager.
   #

   if ($OP_packager) {
      $package{'packager'} = $OP_packager;
   } else {
      my $tmp = `rpm --eval '\%packager'`;
      chomp($tmp);

      if (! $tmp  ||  $tmp eq "\%packager") {
         log_message('ERR','%packager not defined in ~/.rpmmacros.',
                           'Add it or use the --packager option.');
      }
      $package{'packager'} = $tmp;
   }

   #
   # Start spec file creation...
   #

   my $out = new IO::File;
   $out->open("> $package{topdir}/SPECS/$package{specname}")  ||
     log_message('ERR',"Unable to create spec file: $package{specname}: $!");

   #
   # The SPEC file is a stored in the __DATA__ section of this script.
   #

   my @tmp = <DATA>;
   chomp(@tmp);
   my @lines;

   # @stack contains listrefs:
   #    [ KEEP, END_STRING, TOGGLE_STRING ]
   # KEEP is 1 if we're using these lines.
   # When a line is found with END_STRING, the structure ends.
   # When a line is foud with TOGGLE_STRING, KEEP is toggled.

   my(@stack) = ();

   LINE:
   while (@tmp) {
      my $line = shift(@tmp);

      last  if ($line eq '<eof>');

      # If we're currently in an <if> <else> <endif> conditional, look
      # for <else> and <endif>.

      if (@stack) {
         my($keep,$end,$toggle) = @{ $stack[$#stack] };

         if      ($line =~ /$end/) {
            pop(@stack);
            next LINE;
         } elsif ($line =~ /$toggle/) {
            $stack[$#stack][0] = 1 - $keep;
            next LINE;
         }

         next LINE  if (! $keep);
      }

      # Set up a new:
      #   <if:VAR>
      # structure.

      if ($line =~ /<if:([^>]+)>/) {
         my $var  = $1;
         my $keep = (exists $package{$var}  &&  $package{$var} ? 1 : 0);
         push @stack,[$keep,"<endif:$var>","<else:$var>"];
         next LINE;
      }

      if ($line =~ /(<list:([^>]+)>)/) {
         my($tag,$var) = ($1,$2);
         my @l;
         foreach my $ele (@{ $package{$var} }) {
            my $l = $line;
            $l    =~ s/$tag/$ele/g;
            push(@l,$l);
         }
         unshift(@tmp,@l);
         next LINE;
      }

      if ($line =~ /(<hash:(?:(true|false):)?([^>]+)>)/) {
         my($tag,$flag,$var) = ($1,$2,$3);
         $flag          = ''  if (! $flag);

         my @l;
         foreach my $key (sort keys %{ $package{$var} }) {
            my $val = $package{$var}{$key};
            next  if ( ($flag eq 'true'   &&  ! $val)  ||
                       ($flag eq 'false'  &&  $val) );
            my $tmp = $line;
            $tmp =~ s/$tag/$key/g;
            $tmp =~ s/<val>/$val/g;
            push(@l,$tmp);
         }
         unshift(@tmp,@l);
         next LINE;
      }

      while ($line =~ /<skip:([^>]+)>/) {
         my $var = $1;

         if (exists $package{$var}) {
            $line =~ s/<skip:$var>/$package{$var}/g;
         } else {
            next LINE;
         }
      }

      while ($line =~ /(<(?:(quiet):)?([^>]+)>)/) {
         my ($tag,$flag,$var) = ($1,$2,$3);
         $flag = ''  if (! $flag);

         if (exists $package{$var}) {
            $line =~ s/$tag/$package{$var}/g;
         } else {
            $line =~ s/$tag//g;
            log_message('WARN',"Package variable not defined: $var",
                               "   Line: $line")
              if ($flag ne 'quiet');
         }
         # The description/summary may contain POD markup (B<text>) that
         # we don't want to interpret as macros.

         if ($var eq 'desc'  ||  $var eq 'summary') {
            $line =~ s/(?:[IBCFS])<(.*?)>/$1/sg;
            $line =~ s/(?:[XZ])<(.*?)>//sg;
            last;
         }
      }

      push(@lines,$line);
   }

   foreach my $line (@lines) {
      print $out "$line\n";
   }

   $out->close();
}

sub post_build {
   return  if (! $OP_script_dir);

   my $script;

   if (-f "$OP_script_dir/$package{fromsrc}.build-sh") {
      $script = "$OP_script_dir/$package{fromsrc}.build-sh";

   } else {
      return;
   }

   # Run the script.

   log_message('INFO',"Post %build script: $script");

   my @cmd = `cat $script`;
   chomp(@cmd);
   $package{'post_build'} = [ @cmd ];
}

sub check_rpm_build {

   log_message('INFO',"Checking RPM build dir");

   #
   # Check to see if there is a conflict between %_topdir in .rpmmacros
   # file and $OP_rpmbuild .  We won't use 'rpm --eval' because it's only
   # a conflict if the value in MY rpm macro file differs.
   #

   my $macros = "$ENV{HOME}/.rpmmacros";

   if ($OP_clean_macros) {
      if (-f $macros) {
         backup_file($macros,"$macros.cpantorpm",0);
         $package{'restore'} = 1;
      }
   }

   my $macroval;
   if (-f $macros) {
      my $in = new IO::File;
      $in->open($macros)  ||
        log_message('ERR',"Unabble to open .rpmmacros file: $!");
      my @in = <$in>;
      $in->close();

      # Multiple %_topdir lines are allowed... last one is used
      my @tmp = grep /^\s*\%_topdir\s+/,@in;
      if (@tmp) {
         my $tmp = pop(@tmp);
         $tmp =~ /^\s*\%_topdir\s+(.*)\s*$/;
         $macroval = $1;

         # If the macro is found and it differs from the one we specified
         # on the command line, we're going to error out.

         if ($OP_rpmbuild  &&  $macroval ne $OP_rpmbuild) {
            log_message('ERR',
                        'RPM build tree conflict',
                        "   ~/.rpmmacros : $macroval",
                        "   --rpmbuild   : $OP_rpmbuild",
                        'Use the --clean-macros option to proceed.');
         }
      }
   }

   #
   # If the RPM build dir was specified on the command line (--rpmbuild), but
   # was not found in the macrofile, we'll back up the file and add the
   # %_topdir macro.
   #

   if ($OP_rpmbuild  &&  ! $macroval) {
      add_macro($macros,'%_topdir',$OP_rpmbuild);
   }

   #
   # Now make sure that the RPM build tree exists, and is writable.
   #

   my $topdir = `rpm --eval '%_topdir'`;
   chomp($topdir);
   my $arch   = `rpm --eval '%_arch'`;
   chomp($arch);

   $package{'topdir'}  = $topdir;
   $package{'rpmarch'} = $arch;

   log_message('INFO',"RPM build dir:  $topdir");
   log_message('INFO',"RPM build arch: $arch");

   log_message('INFO',"Creating directory: $topdir");
   make_dir($topdir)  if (! -d $topdir);
   if (! -w $topdir) {
      log_message('ERR',"Unable to write to directory: $topdir",
                        'Make sure permissions are correct.');
   }

   foreach my $subdir (qw( BUILD
                           SOURCES
                           SPECS
                           SRPMS
                           RPMS
                           RPMS/noarch
                        ),
                       "RPMS/$arch") {
      if (! -d "$topdir/$subdir") {
         log_message('INFO',"Creating directory: $subdir");
         make_dir("$topdir/$subdir");
      }
   }
}

END:
{
   my $macros = "$ENV{HOME}/.rpmmacros";
   if      ($package{'restore'}) {
      rename "$macros.cpantorpm",$macros  ||
        log_message('WARN',"Unable to restore .rpmmacros file: $!");
   } elsif ($package{'remove'}) {
      unlink $macros  ||
        log_message('WARN',"Unable to remove temporary .rpmmacros file: $!");
   }
}

# This either renames or copies a file.
sub backup_file {
   my($file1,$file2,$copy) = @_;

   if ($copy) {

      if (-d $file2) {
         my @f  = split(/\//,$file1);
         my $f  = pop(@f);
         $file2 = "$file2/$f";
      }

      if (-f $file2) {
         if (! unlink $file2) {
            log_message('ERR',
                        "Unable to remove/overwrite file: $file2: $!");
         }
      }

      my $succ = multiple_methods( [ sub { -f "$file2" } ],
                                   ['module','File::Copy',['copy'],
                                    "copy('$file1','$file2')" ],
                                   ['system','cp',
                                    "{cp} '$file1' '$file2'"],
                                 );

      if (! $succ) {
         log_message('ERR',"Unable to copy file: $file1 -> $file2");
      }

   } else {
      if (! rename $file1,$file2) {
         log_message('ERR',"Unable to back up file: $file1");
      }
   }
}

# This adds a macro to the rpmmacro file in such a way that at the end, it
# will be restored.
#
sub add_macro {
   my($file,$macro,$val) = @_;

   if (! -f $file) {

      # If the macros file is new, we'll remove it once we're done.
      $package{'remove'} = 1;


   } elsif ($package{'remove'}  ||  $package{'restore'}) {

      # If we've already created a backup of the macros file
      # which will be restore, or if we've already determined
      # that the macros file will be removed, we don't have
      # redetermine anything.

   } else {

      # This is the first time we're adding a macro to
      # the macros file, so we want to save it so that it
      # can be restored at the end.

      backup_file($file,"$file.cpantorpm",1);
      $package{'restore'} = 1;

   }

   my $out = new IO::File;
   $out->open(">> $file")  ||
     log_message('ERR',"Unable to write to .rpmmacros file: $!");
   print $out "\n$macro $val\n";
   $out->close();
}

############################################################################

sub build_rpm {
   log_message('HEAD',"Creating RPM: $package{name}");

   my $rpmbuild = find_exe("rpmbuild");
   if (! $rpmbuild) {
      log_message('ERR','Unable to locate rpmbuild command');
   }

   if ($OP_no_tests == 1) {
      $ENV{'RPMBUILD_NOTESTS'} = 1;
   }

   my @cmd = ($rpmbuild,"-ba",
              "--define","_builddir $TMPDIR",
              "--define","_sourcedir $TMPDIR",
              ($OP_no_clean  ? ()                                  : ("--clean")),
              ($OP_no_deps   ? ("--nodeps")                        : ()),
              ($OP_rpmbuild  ? ("--define","_topdir $OP_rpmbuild") : ()),
              "$package{topdir}/SPECS/$package{specname}");

   if (system(@cmd) != 0) {
      log_message('ERR',"While executing $rpmbuild: $!");
   }
   chdir($TMPDIR);

   my $disttag = `rpm --eval '$OP_disttag'`;
   chomp($disttag);

   $package{'rpmfile'} = "$package{topdir}/RPMS/$package{arch_val}/$package{rpmname}-$package{version}-$package{release}$disttag.$package{arch_val}.rpm";
   $package{'srpmfile'} = "$package{topdir}/SRPMS/$package{rpmname}-$package{version}-$package{release}$disttag.src.rpm";
}

############################################################################

sub sign_rpm {
   log_message('HEAD',"Signing RPM: $package{name}");

   my $gpg = find_exe('gpg');
   if (! $gpg) {
      log_message('ERR',"gpg program not found in path.");
   }

   #
   # First, let's get the value of the GPG path
   #

   my $path  = '';    # The gpg option to set the path to use (if not the default)
   my $macro = '';    # The value of the rpm macro.

   $macro    = `rpm --eval '%_gpg_path'`;
   chomp($macro);
   $macro    = ''  if ($macro eq '%_gpg_path');

   if ($OP_gpg_path) {

      if (! -d $OP_gpg_path) {
         log_message('ERR',"GPG directory does not exist: $OP_gpg_path");
      }

      if ($macro) {

         if ($OP_gpg_path ne $macro) {
            # We're overriding a value set in the rpm macro file.

            log_message('WARN',
                        '--gpg-path option overriding value in RPM macro file',
                        "   --gpg-path = $OP_gpg_path",
                        "   \%_gpg_path = $macro");
            $path = "--homedir $OP_gpg_path";

            # We have to add it to the macros file.  We'll just tack
            # it on the end since this will effectively override the
            # value there.

            my $macros = "$ENV{HOME}/.rpmmacros";
            add_macro($macros,'%_gpg_path',$OP_gpg_path);
         }
      }

   } elsif ($macro) {

      if (! -d $macro) {
         log_message('ERR',"GPG directory set in rpmmacros does not exist: $macro");
      }

   }

   log_message('INFO',"GPG path = $path");

   #
   # Next, let's get the value of the GPG user.
   #

   my $name  = '';   # The gpg option to set the user to use.
   $macro    = '';   # The value of the rpm macro.

   $macro    = `rpm --eval '%_gpg_name'`;
   chomp($macro);
   $macro    = ''  if ($macro eq '%_gpg_name');

   if ($OP_gpg_name) {

      if ($macro) {

         if ($OP_gpg_name ne $macro) {
            # We're overriding a value set in the rpm macro file.

            log_message('WARN',
                        '--gpg-name option overriding value in RPM macro file',
                        "   --gpg-name = $OP_gpg_name",
                        "   \%_gpg_name = $macro");
            $name = "'$OP_gpg_name'";

            # We have to add it to the macros file.  We'll just tack
            # it on the end since this will effectively override the
            # value there.

            my $macros = "$ENV{HOME}/.rpmmacros";
            add_macro($macros,'%_gpg_name',$OP_gpg_name);
         }
      }
   }

   $name = "'$macro'"  if ($macro  &&  ! $name);

   log_message('INFO',"GPG name = $name");

   #
   # Now let's make sure that we actually have exactly one key.
   #

   my @out = `$gpg $path --list-keys $name | grep '^uid'`;
   if (! @out) {
      log_message('ERR','No keys found in this GPG keyring.',
                        'Use --gpg-path to specify an alternate GPG path',
                        'or create a key in this keyring.');
   }
   if (@out != 1) {
      log_message('ERR','Multiple keys found in this keyring',
                        'Use --gpg-user to specify a single user.');
   }

   #
   # Sign it.
   #

   SIGN:
   {

      if ($OP_gpg_passwd  ||  $OP_gpg_passfile) {

         my $err = load_module("Expect");
         if (! $err) {
            $err = sign_perlexpect();
            if ($err) {
               log_message('ERR','PGP passphrase incorrect');
            }
            last SIGN;
         }

         my $expect = find_exe('expect');
         if ($expect) {
            $err = sign_expect($expect);
            if ($err) {
               log_message('ERR','PGP passphrase incorrect');
            }
            last SIGN;
         }
      }

      sign_interactive();
      last SIGN;
   }
}

sub sign_expect {
   my($expect) = @_;

   log_message('INFO',"Signing with non-interactive expect script");

   my $pass;
   if ($OP_gpg_passwd) {
      $pass = $OP_gpg_passwd;
   } else {
      $pass = `cat $OP_gpg_passfile`;
      chomp($pass);
   }

   my $out  = new IO::File;
   my $file = "$TMPDIR/cpantorpm-expect-sign-wrapper";
   $out->open("> $file");

   print $out <<"EOF";
#!$expect

spawn rpm --addsign $package{rpmfile} $package{srpmfile}
expect -exact "Enter pass phrase: "
send -- "$pass\\r"

expect {
  "Pass phrase check failed" { puts "Failed" }
  eof { puts "Success" }
}
EOF

   $out->close();
   chmod 0755,$file;

   my @out = `$file`;
   unlink $file;
   if ( grep /Failed/,@out ) {
      return 1;
   }
   return 0;
}

{
   my $flag;

   sub sign_perlexpect {
      log_message('INFO',"Signing with non-interactive perl Expect script");

      my $pass;
      if ($OP_gpg_passwd) {
         $pass = $OP_gpg_passwd;
      } else {
         $pass = `cat $OP_gpg_passfile`;
         chomp($pass);
      }

      my $exp = Expect->spawn('rpm','--addsign',
                              $package{rpmfile},$package{srpmfile});
      $exp->expect(undef, "Enter pass phrase:");
      $exp->send("$pass\n");

      $exp->expect(undef,
                   [ "Pass phrase check failed" => sub { $flag = 1; } ],
                   [ "eof"                      => sub { $flag = 0; } ],
                  );

      return $flag;
   }
}

sub sign_interactive {
   log_message('INFO',"Signing with interactive rpm command");

   system('rpm','--addsign',
          $package{rpmfile},$package{srpmfile});
}

############################################################################

sub install_rpm {
   log_message('HEAD',"Installing RPM: $package{name}");

   my @args = qw(-U);
   if      ($OP_install eq 'new') {
      @args = qw(-i);
   } elsif ($OP_install eq 'force') {
      @args = qw(-U --force);
   }

   my @cmd = ('rpm',@args,$package{rpmfile});

   if ($<) {
      my $sudo = find_exe('sudo');
      if (! $sudo) {
         log_message('ERR','sudo not found.  The rpm will not be installed.');
      }
      unshift (@cmd,$sudo);
   }

   if (system(@cmd) != 0) {
      log_message('ERR','Installation failed.');
   }
}

############################################################################

sub install_yum {
   log_message('HEAD',"Installing in yum repository: $package{name}");

   if (! -d $OP_yum) {
      log_message('ERR',"Yum directory does not exist: $OP_yum");
   }

   if (! -d "$OP_yum/RPMS"  ||
       ! -d "$OP_yum/SRPMS") {
      log_message('ERR',"Yum directory invalid (no RPMS/SRPM subdir): $OP_yum");
   }

   # Copy in the binary RPM

   my $dir;
   if (-d "$OP_yum/RPMS/$package{arch_val}") {
      $dir = "$OP_yum/RPMS/$package{arch_val}";
   } else {
      $dir = "$OP_yum/RPMS";
   }

   backup_file($package{'rpmfile'},$dir,"copy");

   # Copy in the source RPM

   backup_file($package{'srpmfile'},"$OP_yum/SRPMS","copy");
}

############################################################################

# This function will try to accomplish a simple task using several
# different methods.  It will try each method in order until success
# is achieved, or if all of them fail, an error condition will be
# noted.
#
# This is useful for simple tasks which can be trivially checked for
# success, but for which there are multiple possible ways to perform
# it, not all of which may be available.
#
#   $success = multiple_methods($test,$method1,$method2,...);
#
# $test is a listref of:
#
#   $test   = [ CODEREF, ARGS ]
#
# where CODEREF is a reference to a subroutine to test to see if a method
# succeeded.  ARGS is an optional list of arguments to pass to the function.
#
# Each method is a listref of one of the following forms:
#
#   $method = [ 'system', EXECUTABLE, COMMAND, ARGS ]
#             Run COMMAND as a system command (with ARGS).  If EXECUTABLE
#             (which is the main command) can't be found, this method is
#             ignored.
#
#   $method = [ 'module', MODULE, IMPORT_LIST, EVAL_STRING ]
#   $method = [ 'module', MODULE, VERSION, IMPORT_LIST, EVAL_STRING ]
#             First tries to load MODULE.  If it succeeds, it imports
#             the functions in IMPORT_LIST (which is a listref).
#             Once done, it evaluates the string stored in EVAL_STRING.
#
#   $method = [ 'function', CODEREF, ARGS ]
#             Run &CODEREF (with ARGS)
#
# It returns 1 if one of the methods succeed, 0 otherwise.  Also, the
# global varialbe @OUTPUT contains the output from the command.
#
sub multiple_methods {
   my($test,@method)   = @_;
   my($testfunc,@args) = @$test;

   log_indent(+1);
   my(@print);

   METHOD:
   foreach my $method (@method) {
      my($type,@tmp) = @$method;
      @OUTPUT        = ();

      if ($type eq 'ignore') {
         next METHOD;

      } elsif ($type eq 'module') {
         my ($module,$vers,$import_list,$eval_string);

         if (ref($tmp[1])) {
            ($module,$import_list,$eval_string) = @tmp;
            $vers = '';
         } else {
            ($module,$vers,$import_list,$eval_string) = @tmp;
         }

         push(@print,log_message('INFO',"Attempting module method: $module"));

         my $err = load_module($module,$vers,@$import_list);
         if ($err) {
            log_indent(+1);
            push(@print,log_message('INFO',"Failed to load module: $module"));
            log_indent(-1);
            next METHOD;
         }

         push(@OUTPUT,eval "$eval_string");

      } elsif ($type eq 'system'  ||
               $type eq 'system-null') {
         my($bin,$command,@args) = @tmp;

         my $exe = find_exe($bin);
         if (! $exe) {
            push(@print,log_message('INFO',"System command not found: $command"));
            next METHOD;
         }

         push(@print,log_message('INFO',"Attempting system command: $command"));

         my $cmd;
         if ($type eq 'system-null') {
            $cmd = '(' . join(' ',$command,@args) . ") > /dev/null";
         } else {
            $cmd = '(' . join(' ',$command,@args) . ") > $TMPDIR/cmd.out";
         }
         $cmd =~ s/\{$bin\}/$exe/g;

         if (system($cmd) != 0) {
            log_indent(+1);
            push(@print,log_message('INFO',"Failed system command: $command"));
            log_indent(-1);
            next METHOD;
         }

         if ($type eq 'system-null') {
            @OUTPUT = ();
         } else {
            my $in = new IO::File;
            $in->open("$TMPDIR/cmd.out");
            @OUTPUT = <$in>;
            chomp(@OUTPUT);
            $in->close();
         }

      } elsif ($type eq 'function') {
         my($coderef,@args) = @$method;

         @OUTPUT = &$coderef(@args);
      }

      if (&$testfunc(@args)) {
         log_indent(-1);
         return 1;
      }
   }

   if (@print) {
      log_message('NONE',@print);
   }
   log_message('WARN','All methods for this task failed',
               'Please make sure one of the above methods works.');

   log_indent(-1);
   return 0;
}

# This will load a module (and import the given functions).  If it fails,
# an error code will be returned.
#
# This will work to check the perl version (if $mod is empty).  If $vers
# is empty, it will attempt to load the module but not check the version
# number.
#
sub load_module {
   my($mod,$vers,@funcs) = @_;
   $mod  = ''  if (! $mod);
   $vers = ''  if (! $vers);

   if (@funcs) {
      my $funcs = join(' ',@funcs);
      eval "use $mod $vers qw($funcs)";
      return 1  if ($@);

   } elsif ($mod) {
      eval "use $mod $vers ()";
      return 1  if ($@);

   } else {
      eval "use $vers";
      return 1  if ($@);
   }
   return 0;
}

BEGIN
{
   my $indent = 0;

   # $level can be:
   #    NONE
   #    INFO
   #    WARN
   #    ERR
   #    HEAD
   sub log_message {
      my($level,@line) = @_;

      my(@print);
      my $print = 1;

      if ($level eq 'HEAD') {

         #
         # HEAD
         #

         $indent = 0;
         push(@print,"*" x 60);
         foreach my $line (@line) {
            push(@print,"* $line");
         }
         push(@print,"*" x 60);

      } elsif ($level eq 'NONE') {

         foreach my $line (@line) {
            print "$line\n";
         }

      } else {

         #
         # INFO  - only prints during debuggin
         # WARN
         # ERR   - prints then exits
         #

         $print = 0  if ($level eq 'INFO'  &&  ! $OP_debug);

         my $head = "$level:" . " "x(4-length($level)) . ' 'x($indent*3);
         my $line = shift(@line);
         push(@print,"$head $line");
         $head = " "x(length($head));
         foreach $line (@line) {
            push(@print,"$head $line");
         }
      }

      if ($print) {
         foreach my $line (@print) {
            print "$line\n";
         }
      }

      if ($level eq 'ERR') {
         exit 1;
      }

      return @print  if (! $print);
      return;
   }

   sub log_indent {
      my($mod) = @_;

      $indent += $mod;
   }
}

# Read in an opts file
#
sub opt_file {
   my($file) = @_;

   if (! -r $file) {
      die "ERROR: Options file not readable: $file\n";
   }

   my $succ;
   if ($file =~ /\.(yml|yaml)$/i) {

      $succ = multiple_methods
        ( [ sub { 1; } ],
          [ 'module', 'YAML', [],
            "my \@tmp = YAML::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
          [ 'module', 'YAML::Syck', [],
            "my \@tmp = YAML::Syck::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
          [ 'module', 'YAML::XS', [],
            "my \@tmp = YAML::XS::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
          [ 'module', 'YAML::Tiny', [],
            "my \@tmp = YAML::Tiny::LoadFile('$file'); " .
            "\$OUTPUT = \$tmp[0]" ],
        );

   } elsif ($file =~ /\.json$/i) {

      $succ = multiple_methods
        ( [ sub { 1; } ],
          [ 'module', 'JSON', ['from_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = from_json(\$json_text);" ],
          [ 'module', 'JSON::XS', ['decode_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = decode_json(\$json_text);" ],
          [ 'module', 'JSON::PP', ['decode_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = decode_json(\$json_text);" ],
          [ 'module', 'JSON::DWIW', ['from_json'],
            "my \$fh; " .
            "open \$fh,'<:utf8','$file'; " .
            "my \$json_text = do { local \$/; <\$fh> }; " .
            "\$OUTPUT = from_json(\$json_text);" ],
        );

   } else {
      die "ERROR: Options file must be YAML or JSON: $file\n";
   }

   if (! $succ) {
      log_message('ERR',"Unable to read options file: $file");
   }

   return ()  if (! exists $OUTPUT->{$OP_package});

   my @opts;

   foreach my $line (@{ $OUTPUT->{$OP_package} }) {
      if ($line =~ /^(.+?)(?:\s+|=)(.+?)\s*$/) {
         push(@opts,$1,$2);
      } else {
         push(@opts,$line);
      }
   }

   return @opts;
}

# Find $exe in ENV{PATH} or one of the directories in @extra_path .
#
sub find_exe {
   my($exe,@extra_path) = @_;

   my @path = split(/:/,$ENV{PATH});
   unshift(@path,@extra_path);

   foreach my $d (@path) {
      return "$d/$exe"  if (-x "$d/$exe");
   }

   return '';
}

=pod

=head1 NAME

cpantorpm - An RPM packager for perl modules

=head1 SYNOPSIS

   cpantorpm [OPTIONS] MODULE

This script takes a perl module and creates an RPM for it.

=head1 DESCRIPTION

This script automates the entire process of obtaining a perl module
and turning it into an RPM package.  This includes the steps of
obtaining the module distribution, creating an RPM from it, and then
making the package available in various ways.

The following steps are involved in this process, and are discussed
in more detail below:

=over 4

=item B<Obtain the perl module>

=item B<Parse various perl modules files for necessary information>

=item B<Build the package>

=item B<Generate a spec file>

=item B<Create the RPM packages>

=item B<Sign the RPM packages>

=item B<Install the RPM (optional)>

=item B<Store the RPM in a local yum repository (optional)>

=back

=head1 OPTIONS

=head2 General Options

The following general purpose options exist.

=over 4

=item B<-h/--help>

Prints a help message describing command usage.

=item B<-v/--version>

Prints the version of this program.

=item B<-D/--debug>

Enable verbose debugging output.

=item B<-t/--tmpdir DIR>

The cpantorpm script makes use of a default directory to store all of it's
working files in.  It defaults to:

   /tmp/cpantorpm

but can be set explicitly with this option.

=item B<-f/--optfile FILE>

All of the options below that may be specified on the command line may
also come from a config file.  The config file may contain the options
for any number of modules and is described below.

=back

=head2 Download Options

The following options affect how a module is downloaded.

=over 4

=item B<-c/--cpan>

When downloading modules from CPAN, the script will first try to use CPANPLUS
and, if that is not available, it will use CPAN.  If this option is included,
only CPAN will be tried.

=item B<--extracted DIR>

Occasionally, the archive file on CPAN is broken in that the archive file
(minus the relevent suffixes) is not the same as the archived directory.

For example, the archive Foo-Bar-1.00.tar.gz contains the directory
Foo-Bar instead of Foo-Bar-1.00 .

Set B<DIR> to be the name of the directory that it contains.

=back

=head2 Module Description Options

Once the module is downloaded, it will be analyzed and various information
about the module which will be used in creating the RPM is gathered.  This
includes looking at the perl META files, the main POD document, and the
build scripts (Makefile.PL or Build.PL).

The following options impact these operations:

=over 4

=item B<--name NAME>

By default, the name of the package will be obtained from the
distribution name.  This option can be used to explicitly set the
name, overriding the distribution name.

NOTE: the name of the RPM will be based on this, but will typically
have a prefix added.  See the B<--prefix> and B<--no-prefix> options below
for more details.

=item B<--summary TEXT>

Every package has a 1-line summary description.  By default, this comes
from the main POD document or the META files, but can be explicitly
set using this option.

=item B<--description FILE>

Every package has a multi-line description.  To override the description
that comes from the POD document, put the description in a local file,
and pass that file name to this option.

=item B<--mainpod FILE>

The description and summary of the module typically come from the main
POD document, if it can be determined using the normal methods described
below.

In a few cases (where the POD document is named in some non-standard
way), it may not be possible to determine which is the main POD document.
In this case, you can specify it using this option.

FILE is the path to the file relative to the top level in the module
distribution. For example, it might be:

   lib/Foo/Bar.pm

=item B<--author AUTHOR>

This lists an author for the module, overriding the values from the META
files.  This option can be included multiple times for multiple authors.

=item B<--vers VERSION>

This specifies the version of the RPM.  It defaults to the version of
the package, but can be overridden here.

=back

=head2 SPEC File Options

The following options are used during the SPEC file creation step:

=over 4

=item B<-n/--no-tests>

=item B<--NO-TESTS>

When creating a module RPM, typically, the module tests are run
as part of the process.  These two options can be used to modify
this behavior.

The first will add the lines necessary to run the tests to the
SPEC file, but (by use of an environment variable), the tests will
not be run when the RPM is created.  In this instance, if the
SPEC file is used to create an RPM at some later date, the tests
will run (unless the environment variable RPMBUILD_NOTESTS is
set).

With the second option, the lines necessary to run the tests will
not be added to the SPEC file at all.

=item B<-d/--no-deps>

=item B<--NO-DEPS>

By default, when building an RPM, the prerequisites for the module
will be tested.

There are three types of prerequisites:

   prerequisites to build the module
   prerequisites to run the module tests
   prerequisites to use the installed module

It is slightly unfortunate that RPM only recognizes two types.  There
is no way to specify requirements to run tests.

As such, the build requirements will include those requirements to
run the tests unless the --NO-TESTS option is given.  In this case,
requirements to run the tests will be omitted.

If the B<--no-deps> option is given, dependencies will not be tested
(though they will be added to the SPEC file).

If the B<--NO-DEPS> option is given, dependencies will not be added
to the SPEC file at all.

In addition, if either of these are given, B<--no-tests> is implied.

=item B<--prefix PREFIX>

=item B<--no-prefix>

By default, a prefix of 'perl-' is added to the name of the package
(or the name supplied using the B<--name> option).

To specify that no prefix be added, use the B<--no-prefix> option.  To
specify an alternate prefix, use the B<--prefix> option.

=item B<-p/--packager PACKAGER>

Use this option to specify the name of the packager.  The name of the
packager may be suplied using the '%packager' macro in the ~/.rpmmacros
file.  If it is not there, this option must be included.

=item B<--rpmbuild DIR>

RPMs are built in the RPM build hierarchy.  This defaults to the value
of the '%_topdir' macro, or it can be specified using this option.

The directory will have the following subdirectories:

   BUILD
   SOURCES
   SPECS
   SRPMS
   RPMS

=item B<--clean-macros>

By default, macros included in the existing ~/.rpmmacros file will be
used.  With this option, that file is temporarily removed (it will be
restored when the script exits).

=item B<--group GROUP>

Every package is a member of a group.  If this is not specified, it defaults
to:

   Development/Libraries

=item B<--release STRING>

=item B<--disttag STRING>

The full name of an RPM is something like:

   foo-bar-1.00-1a-noarch.rpm

The string '1a' here consists of the release (1) and a disttag (a).
By default, release is '1' and disttag is the macro '%{?dist}', but
these can be overridden with these options.

=item B<--epoch EPOCH>

This sets an epoch number in the RPM when the version number is not
sufficient to determine the relative age of two different versions.

=item B<--add-require FEATURE[=VERS]>

=item B<--add-provide FEATURE[=VERS]>

Every RPM has a list of features that are required in order to use it,
and a list of features that it provides.

In some cases, you may need to add featurs to these two lists.  Both options
may include a version:

   --add-requires Foo::Bar=0.45

=item B<--rem-require FEATURE>

=item B<--rem-provide FEATURE>

Related to the previous options, these options allow you to remove a feature
from the requirements list, or the list of features provided.

Unfortunately, there is no cross-platform way of doing this, so these options
will only function on a RHEL or Fedora computer.  Functionality on other
platforms may be added in the future.

=item B<-m, --macros>

Use the macro form of common SPEC constructs over the environment variable
form (e.g. %{buildroot} vs $RPM_BUILD_ROOT).

=item B<--build-rec, --test-rec, --runtime-rec>

Many modules have a list of modules that are recommended to be
installed at build time, test time, or at run time, but they are not
absolutely required.  By default, these modules will not be included
as requirements for the various steps.  Adding these options will
require them.

=back

=head2 Module Build Options

The perl module must be built as part of the process.  The following
options are used during the build:

=over 4

=item B<--build-type TYPE>

TYPE must be 'make' or 'build' and specifies that the build must be done
using the Makefile.PL or Build.PL files respectively (for those modules
that have both).  If that file does not exist, an error is triggered.

=item B<--config STRING>

The given string is passed to either the 'perl Build.PL' or 'perl
Makefile.PL' command used to configure the module and create a Build
script or a Makefile.  This option can be passed in any number of
times, but only a single option should be included in each STRING.

Since the arguments passed in differ when using a Makefile.PL and a
Build.PL procedure, for safety, you should always include the B<--build-type>
option when using this option.

=item B<--build STRING>

Similar to the B<--config> option except this passes strings which
are passed to either the './Build' or 'make' command used to actually
build the module.  This option can be passed in any number of times.

=item B<-T/--install-type TYPE>

=item B<-i/--install-base DIR>

These options allow you to specify where the module will be installed.
By default, the module will be built to install in the standard
perl location.  In most cases, that would mean installing the module,
documentation, and scripts in:

   BASEDIR/lib/perl5/PERLVERS
   BASEDIR/man
   BASEDIR/bin

where BASEDIR is the place where perl is installed (which is typically
/usr) and PERLVERS is the version directory (i.e. 5.14.2).  To install
in /usr/local instead of /usr, just use the option:

   --install-base /usr/local

To change the module installation directory (but not the directory of the
documentation or scripts) to either the site_perl or vendor_perl location,
use:

   --install-type site
   --install-type vendor

to set the module directory to be:

   BASEDIR/lib/perl5/site_perl/PERLVERS
   BASEDIR/lib/perl5/vendor_perl/PERLVERS

respectively.

The B<--install-type> value must be one of:

   perl  (or core)
   site
   vendor

and defaults to 'perl'.  'perl' and 'core' are synonyms.  If this is
passed in, it will override any default value set in the Makefile.PL
or Build.PL scripts (so be careful about rebuilding core modules).

=item B<--mandir STRING>

When specifying a prefix (using the B<--install-base> option), it is
necessary to determine where man pages should be installed relative
to this directory.

Most of the time, this can be determined automatically, but if your
version of perl installs man pages by default in a completely separate
location from where it installs libraries, it may not be able to be
determined correctly and should be specified using this.

The only time this would happen would be if the man pages were installed
in one hierarchy and the libraries in a completely different hierarchy
(i.e. man pages in /usr and libraries in /opt for example).

=item B<--patch FILE>

=item B<--patch-dir DIR>

=item B<--script FILE>

=item B<--script-dir DIR>

In a few cases, a distribution cannot be properly packaged unless it
is first modified.  The modification can be done by applying a patch,
or by running a script, or both.  Patches are applied first, followed
by scripts.

To specify a patch file or script file, use the B<--patch> or B<--script>
options.  Alternately, you can specify a directory containing files named
PACKAGE.sh or PACKAGE.diff where PACKAGE is the string that was passed
in on the command line.

By default, no patch or script will be used.  They will only be used
if one of these options is given.

Scripts and patches will both be applied while in the top directory
of the package (i.e. the directory where a Makefile.PL or Build.PL
script exists).

The B<--script-dir> option has a second use.  If there is a file named
PACKAGE.build-sh in it, the lines in that file are added to the SPEC
file at the end of the %build step.

=back

=head2 Options Controlling Cpantorpm Steps

To control what steps get done, the following options are available:

=over 4

=item B<--spec-only>

By default, the script creates a SPEC file, and then builds
RPMs (both source and binary).

With the B<--spec-only> option, the SPEC file is created, but no
further action is taken.

=item B<--no-clean>

By default, the build tree will be removed after the RPM is built.
If this option is given, it will be left in place.

=item B<-s/--sign>

If this option is given, a GPG signature will be added to the package.

It should be noted that this step is often interactive, so if the
installation process is scripted in any way, adding this option may
interfere with the process.

Please refer to the secrtion SIGN THE RPM PACKAGE for more information.

=item B<-I/--install>

=item B<--install-new>

=item B<--install-force>

If any of these options are given, cpantorpm will attempt to install
the RPM on the system after it is built.  If you are running as root,
this will be done by simply running the appropriate rpm command.  If
you are running as any other user, the command will be run using
B<sudo>.

By default, the '-U' flag is given to the B<rpm> command which will cause
it to install the RPM if it is a new package, or an upgrade to an existing
package.

If the B<--install-new> option is given, the '-i' option will be passed
to the B<rpm> command and the RPM will only be installable if it is a new
package.

If the B<--install-force> option is used, the flags '-U --force' will
be used which will replace an existing package, even if the same
version is already installed.

=item B<-y/--yum DIR>

If this option is given, the RPMs (both binary and source) will be
copied to a local yum repository once they are built.

=back

=head2 Misc Options

The following misc. options are also available:

=over 4

=item B<--gpg-path PATH>

=item B<--gpg-name NAME>

These options are used to set the path the the GPG directory (which
contains the keyring) and the name of the key that will be used.

=item B<--gpg-password PASSWORD>

=item B<--gpg-passfile FILE>

When signing a package, this script become interactive unless B<expect>
(or perl B<Expect>) is available.  If one of these is available, the
password can be passed in at the command line (or a file containing the
password) using one of these two commands.

=back

=head1 OBTAIN THE PERL MODULE

The perl module may be obtained in a number of different ways.  The
perl module may exist on local disk either as an archive file or a
directory, or it can be retrieved from a URL or from CPAN.

For example, any of the following ways could be used:

   cpantorpm Foo::Bar
   cpantorpm http://some.host.com/some/path/Foo-Bar-1.00.tar.gz
   cpantorpm /tmp/Foo-Bar-1.00.tar.gz
   cpantorpm /tmp/Foo-Bar-1.00

When working with a CPAN module, you must use the form:

   Foo::Bar

instead of

   Foo-Bar

When downloading from a URL, both ftp:// and http:// URLs are
supported (though others such as file:// and https:// are not
supported at this time).

For this script to work, the perl module must meet a few validity
requirements:

=over 4

=item Valid name format

The name of the distribution must be of the form:

   PACKAGE-VERS

if obtained from a local directory, or

   PACKAGE-VERS.EXT

if obtained from an archive (a local file, a URL, or from CPAN).  Here
VERS is any string which does NOT contain a dash (-).  EXT may be any
of the following extensions:

   .tar
   .tar.gz
   .tgz
   .tar.bz2
   .zip

=over Standard install script

The module must contain either a Build.PL or Makefile.PL script.  A
module using some other non-standard build procedure cannot be built
with this script.

=back

Getting the module in each of the 4 ways requires different system
requirements.  In general, the script will try several different ways
to get the module, and will only fail if all of the different methods
fail.

The following system requirements exist for the different ways of
obtaining a module:

=over 4

=item From a local directory

You must be able to run the system command 'cp -r' (to recursively copy
a directory) or be able to load the module File::Copy::Recursive.

=item From a local file

You must be able to run the system command 'cp' (to copy a file) or be
able to load the module File::Copy.

In addition, you must meet additional requirements for working with the
different types of archives as described next.

=item From a URL

To get a module from a URL, you have to have one of the following
packages installed:

   curl
   wget
   lynx
   links
   lftp

or be able to load one of the modules:

   LWP::UserAgent
   HTTP::Lite

In addition, you must meet additional requirements for working with the
different types of archives as described next.

=item From CPAN

To get a module from CPAN, you must be able to load one of the perl
modules:

   CPANPLUS::Backend
   CPAN

In addition, you must meet additional requirements for working with the
different types of archives as described next.

=back

In each case (except for obaining a module from a local directory),
once you have obtained the archive, you need to be able to extract it.

To do this, you need to meet the system requirements for the appropriate
type of archive:

=over 4

=item .tar, .tar.gz, .tgz files

You need to be able to run the system 'tar' command, or be able to
load one of the perl modules:

   Archive::Extract
   Archive::Tar

These modules will make use of other modules to handle .gz or .bz2
compression.

=item .zip files

You need to be able to run the system 'unzip' command, or be able to
load one of the perl modules:

   Archive::Extract
   Archive::Zip

=back

Once the package is obtained, in some cases it may be necessary to
apply patches or run a script in it to fix things that make it
not suitable for packaging.

=head1 PARSE VARIOUS PERL MODULES FILES FOR NECESSARY INFORMATION

Building an RPM correctly involves getting a great deal of information
from the module.  We have to know what features are provided by this
module, what features are required by the module to run, as well as
the description of the module, the author, etc.

This information can be obtained by a number of different files
including:

=over 4

=item Makefile.PL, Build.PL

Currently, these are only used to determine how the module should
be built.  Although they typically contain a great deal more information,
it is written as perl code and there is no reasonable way to get the
information from them.

However, one of the steps done by this script is to actually build
a Build script or Makefile (this ensures that the perl module can
be correctly built), and information can be extracted from them
since they do follow regular formats.

=item META.json, MYMETA.json

For a description of the type of data stored here, please refer to the
CPAN-Meta documentation on CPAN.

In order to interpret a JSON file, you have to be able to load one of
the following perl modules:

   Parse::CPAN::Meta 1.40
   JSON
   JSON::XS
   JSON::PP
   JSON::DWIW

Most of the information can be obtained from a complete JSON file.

=item META.yml, MYMETA.yml

For a description of the type of data stored here, please refer to the
CPAN-Meta documentation on CPAN.

In order to interpret a YAML file, you have to be able to load one of
the following perl modules:

   Parse::CPAN::Meta
   CPAN::Meta::YAML
   YAML
   YAML::Syck
   YAML::XS
   YAML::Tiny

Most of the information can be obtained from a complete YAML file.

=item Pod file

In most instances, some of the information (primarily the summary and
description of the module) must be obtained from a pod document.  This
will require one of the modules:

   Pod::Select
   Pod::Simple::TextContent

The script will need to determine which POD file to get this informaion
from (the primary POD file for the package).  Most of the time, the
script is able to determine which file to use, but if it fails, it
can be manually specified using the B<--mainpod> option.

=back

=head1 BUILD THE PACKAGE

The next step is to actually build the module.

This step is a departure from the way cpanspec and cpan2rpm work.  In
both of these scripts, the SPEC file contains the procedure for
building the perl module, but it is never tested to see if it works.

This has a couple significant advantages:

=over 4

=item It ensures that the package builds

A number of perl modules cannot be built automatically because the
scripts are interactive.  Unfortunately, the RPM build process does
not handle this well, so what you end up with is a hanging process
that (eventually) you will have to kill by hand.  In other cases, the
build process fails for other reasons.

When the build process is put in the SPEC file untested, the
RPM build process will either fail or hang.

This script avoids many of those problems.

=item It generates additional meta data

Both cpanspec and cpan2rpm would interpret the Makefile.PL and
Build.PL scripts directly to obtain information from them.
Since there is no guarantee that these scripts follow any
convention, I considered this a very poor option.

By actually building the module, it creates either a Makefile
or a _build hierarchy, and these DO follow regular conventions,
and information can be obtained from them with a much greater
chance of success.

=back

This script actually builds the module to ensure that it can be done.
It watches the process to see if it enters a state where it's waiting
for user input, and if it does, the process ends and the RPM is not built,
and you can then go in and correct the problem (typically by installing
some build prerequisite, or supplying a non-standard option to the
build process, or in the worst case, by providing a patch to the module
source that removes the interactive nature.

=head1 GENERATE A SPEC FILE

Much of the process of generating a spec file is taken from the
cpanspec package.

The first step in creating a SPEC file is to determine where the
RPM build hierachy lives (since that is where the SPEC file will live).
This script supports using the standard build hierarchy, or specifying
an alternate location.

If the B<--rpmbuild> option is used, it is used to specify the location
of the build hierarchy.  Otherwise, the standard location will be used.
If a location is specified, and if there is a B<~/.rpmmacros> file present,
the B<~/.rpmmacros> file must not contain the macro B<%_topdir> that is
different than the one specified by the B<--rpmbuild> option.  If
the macro does exist, you can use the B<--rpm-clean> option to specify a
clean version of the B<.rpmmacros> file be used.

The SPEC file created by this script does deviate from the recommended
form in one respect.  The recommended way to handle the list of
requirements and the list of features provided by an RPM is to leave
out these lists in the SPEC file and allow rpmbuild to generate them
automatically.  In the SPEC file, you only list changes to the
defaults.  In other words, you can add features that are required or
that the package provides that were not picked up automatically, or you
can add lines to the SPEC file to filter out features that you do not
want the rpm to depend on or provide.

Unfortunately, even though adding prerequisites and provided features
works well, removing them does not work nearly as smoothly.  The
methods for filtering prerequisites and features does not work well
cross platform (attempts that worked for redhat would not work for
openSuSE for example).

As a result, I do not let the SPEC file tell rpmbuild to generate these
lists.  Instead, I generate the lists (using the standard rpm utilities
when available, or using an included script when they are not) and
explicitly put them in the SPEC file.

=head1 CREATE THE RPM PACKAGES

Once the SPEC file is done, the RPM can be created using the standard
RPM tool 'rpmbuild'.

It uses the standard RPM file structure and creates both a source RPM
and a binary RPM.

=head1 SIGN THE RPM PACKAGES

This is an optional step.  If can be used to embed a GPG signature in
the package.

In order sign a package, you must have a GPG key available.  You must
have the gpg package installed on your system and you must have at least
one GPG key created.

The path to the GPG directory be specified by one of the following:

   the value of the --gpg-path option

   the value of the %_gpg_path rpm macro

   the value determined by gpg using any currently
   set environment variables

If no keyring is found, signing is not available.

The key to use is specified by:

   the value of the --gpg-name option

   the value of the %_gpg_name rpm macro

   the only key in the keyring (if the keyring
   contains exactly one key)

If the key cannot be uniquely determined, signing is not available.

The rpm command to sign a package is interactive.  In order to script
everything, it is necessary to use a tool like expect.  If such a tool
is not available, and if you are signing packages, this script will be
interactive.  Currently, if the B<expect> program is installed or the
perl B<Expect> module is available, signing can be done non-interactively
if either the --gpg-passwd or --gpg-passfile options are passed in.

=item INSTALL THE RPM

This is an optional step.

After the RPM is successfully built, it can be installed on the system.
This will be done in one of two ways.  If you are running this as root,
it will simply use the rpm command.  Otherwise, it will use B<sudo> to
run the rpm command.

=item STORE THE RPM IN A LOCAL YUM REPOSITORY

This is an optional step.

If the B<--yum DIR> option is passed in, the RPMs (both source and
binary) are copied in to a local yum repository.  The repository is
stored at B<DIR> and should have the following directories:

   RPMS
   SRPMS

RPMs will be stored in either the RPMS/<arch> directory (if it exists)
or directly in the RPMS directory.  <arch> is typicall something like
'noarch' or 'x86_64'.

=head1 CONFIG FILE

A config file can be created which sets options on a per-module
basis.  It can be either a YAML file (ending in .yaml or .yml) or
a JSON file (ending in .json).

A sample YAML file is:

   ---
   Crypt::SSLeay:
      - --config=--default

   Foo::Bar:
      - --name Foobar

Each line should contain one option of any of the forms:

   --opt=val
   --opt val
   -o    val

If val contains spaces, you should NOT put quotes around it.  Use:

   --summary This is the summary

instead of:

   --summary "This is the summary"

=head1 SYSTEM REQUIREMENTS

This script will try to function under many different situations, and
it will often try multiple methods to accomplish a task, and many of
those methods will be available on any common linux configuration.  As
such, a rigorous list of system requirements is overly complicated and
won't be listed here.  In the event that the script fails, it will list
the methods tried and you can make sure that one of them will function
on your host.

The most common requirements will be listed here.  In all probability, if
you meet these requirements, this script will run.

Since the most common way to obtain a module will be from CPAN, you will
need one of the following modules installed and correctly configured:

   CPAN
   CPANPLUS

To make sure it's configured, make sure you can run B<cpan> or B<cpanp>
at the command line and have it work.

If you will be applying patches to a package, you will need the B<patch>
command.

You also need to be able to read both YAML and JSON files included
in almost every module.  This means that you will need one JSON
module installed out of the following:

   JSON
   JSON::XS
   JSON::PP
   JSON::DWIW

and one YAML module from the following:

   YAML
   YAML::Syck
   YAML::XS
   YAML::Tiny

You will also need to be able to examine POD files using one of the
following modules:

   Pod::Select
   Pod::Simple::TextContent

You'll should have both:

   Module::Build
   ExtUtils::MakeMaker

installed in order to build modules that use the Build.PL and Makefile.PL
scripts.

In order to build the rpm, you need the B<rpmbuild> program.

This script also relies on the B<strace> program.  This is necessary
because many Makefile.PL and Build.PL scripts are interactive so when
you run them, they hang waiting for input.  Unfortunately, I was not
able to find any pure perl way to run a program as a child (or in a
thread) and monitor it to see if it's still running because it's doing
work, or still running because it's waiting on user input.  Though
somewhat crude, B<strace> can be used to determine that.

In order to sign packages, you must have the B<gpg> program installed,
and you must have a key set up to sign with.  In order to do this
non-interactively, you also need either the B<Expect> module or the
B<expect> program installed.

In order to install the package, you either must be running as root, or
have the B<sudo> program.  The B<sudo> command may be interactive,
depending on how you have it set up.

In order to install RPMs in a yum repository, the repository must exist.

=head1 HISTORY

This script is based loosely on Erick Calder's cpan2rpm script and
Steven Pritchard's cpanspec script.  Initially, I set out to modify
one or the other of them, but I found that the modifications that I
felt necessary were extensive enough that I decided a fresh
implementation was both faster and cleaner.

=over 4

=item cpan2rpm

cpan2rpm had basically the full functionality that I wanted.  It would
download a module, write a spec file for it, create and RPM, and then
install it.  The only functionality that was missing was some simple
functionality to add it to a local yum repository.  That would have been
very simple to add.  However, it suffered from several other significant
problems.

cpan2rpm is old.  It has not been supported since 2003.  It have
virtually no support for modules built using Build.PL scripts, and
adding it would have been quite complicated.

cpan2rpm is also not written as cleaning, or in a style that I'd like
to maintain, so it would take a bit of cleaning up to turn it into
something I'd want to maintain.

The main problem though is how it gets information from the
Makefile.PL script.  In order to get all of the information necessary
to create a SPEC file, there's a lot of information about the module
that needs to be examined.  Much of that information is stored in the
various META files in any new module distribution.  None of that is
used in cpan2rpm (which predates most of them), so that would have to
be added.  However, even with the META files, some information comes
from the Makefile.PL (or Build.PL script) such as the default install
location.

Since there data is in a script, cpan2rpm tries to be intelligent about
extracting the information.  It loads in the Makefile.PL script, modifies
it (by turning 'exit' into 'return') and evals it.  The theory is that
by eval'ing it, you end up with the appropriate data structure that you
can examine.

The modifications that it makes are completely unjustified though.  It
makes drastic assumptions about what the Makefile.PL file looks like,
and I can think of any number of cases where turning 'exit' into
'return' won't produce the result you want.

As such, cpan2rpm's handling of the Makefile.PL file needed to be
replaced entirely (and since that makes up a significant portion of
the script, that justified a complete rewrite).

In addition, if the script contained in Makefile.PL is interactive,
cpan2rpm hangs silently while trying to eval it, and there is no
easy way to determine what is causing it to hang.

=item cpanspec

cpanspec is much cleaner in most respects.  It is well written, handles
both Makefile.PL and Build.PL installs, and handles all of the new
META files.

However, it has a few other problems.

It was written specifically for redhat distributions (redhat, centos,
fedora) and hardcodes some of the redhat specific paths in it.  Other
RPM based distributions (such as OpenSuSE which I use) use different
paths.  At the very lease, the cpanspec file would need to be modified
to add options to override the defaults.

cpanspec also makes assumptions about where you want to install the
modules.  It will only install in the vendor location of the primary
perl installation.  If you want to install them anywhere else, you
are out of luck.

But the single biggest weakness was how it handles the Makefile.PL and
Build.PL scripts.  Rather than evaluating the code, cpanspec just opens
them and tries to parse information from them.

Again, given that these are perl scripts, the only reliable way to
parse them is to actually use the perl interpreter.  Although most
modern modules include Makefile.PL or Build.PL scripts that follow
certain conventions, it is by no means guaranteed, so I was not
satisfied with this assumption.

As with cpan2rpm, cpanspec does not deal with interactive installs.  It
simply shuffles the problem to another location.  In this case, once
the SPEC file is created, it is necessary to run rpmbuild, and this
will hang.

=back

Due to the weaknesses in both of the existing alternatives, I decided
a clean rewrite was in order.  The goals were:

=over 4

=item Beginning-to-end functionality

cpantorpm will download the module from CPAN, create the SPEC file,
generate an RPM, install it, and store the RPM in a local YUM repository
for other hosts to use.

=item Cross platform

cpantorpm will work on any RPM based distribution (though this is only
tested on redhat and OpenSuSE to date).  Also, many of the steps can
be done in many different ways, different one of which may be
available by default platforms, so most steps will try more than one
way to accomplish the task.

For example, to download a module, cpantorpm will use the CPAN module,
the CPANPLUS module, and various tools to download via HTTP (such as
wget, curl, etc.).

=item Correctly handle Makefile.PL and Build.PL

The only way to correctly handle these scripts is to actually build the
module.  The files generated contain all of the information in a
standard format, and we can get it without making any assumptions about
the format of these scripts.

=item Handle interactive installs

Many installs are potentially interactive.  If you are missing
prerequisites, many modules will stop and ask you if you want to
install them first.

cpantorpm traps this behavior and allows you to handle it, rather than
hanging for an unknown location.

Note: by 'handle it', it will not try to install them.  Rather, the
cpantorpm script will let you know what was being asked, and then
exit, and at that point, it's up to you to correct the problem.  A
future version of cpantorpm will include some automatic handling of
missing prerequisites which is the primary cause of interactive
installs.

=back

All that being said, I have borrowed ideas (and in rare instances,
code) freely from cpanspec and cpan2rpm.  I'm very grateful to the
authors of both cpan2rpm and cpanspec who's work has made mine much
easier.

Hopefully, cpantorpm takes the best of both worlds and improves on
that.

=head1 KNOWN BUGS

None known.

=head1 BUGS AND QUESTIONS

If you find a bug in cpantorpm, please send it directly to me
(see the AUTHOR section below).  Alternately, you can submit it
on CPAN using the URL:

   http://rt.cpan.org/Public/Dist/Display.html?Name=cpantorpm

Please do not use other means to report bugs (such as Usenet newsgroups,
or forums for a specific OS or Linux distribution) as it is impossible
for me to keep up with all of them.

When filing a bug report, please include the version of cpantorpm you
are using.  You can get this by running:

   cpantorpm -v

=item SEE ALSO

cpan2rpm  - Erick Calder's script to generate RPMs

cpanspec  - Steven Pritchard's script to generate spec files

=head1 LICENSE

This script is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 AUTHOR

Sullivan Beck (sbeck@cpan.org)

=cut

1;

#
# The SPEC file is a stored in the __DATA__ section of this script.  It
# is unmodified except that macros are replaced as follows:
#
# <eof>         Signals the end of the file.
# <VAR>         <VAR> is replaced by $package{VAR}.  If it does not exist,
#               a warning is issued and it is replaced by an empty string.
# <quiet:VAR>   Similar to <VAR> but no warning will be issued.
# <skip:VAR>    This line is skipped if $package{VAR} does not exist.
#
# <hash:VAR>    This line is repeated for each key/value pair in the hash.
#               <hash:VAR> is replace by the key and <val> is replaced by
#               the value.
# <hash:true:VAR>
# <hash:false:VAR>
#               These are similar to <hash:VAR> except that the line is
#               only included for key/value pairs where the value evaluates
#               to true (in the first case) or false (in the second case).
# <list:VAR>    This line is repeated once for each value in a list with
#               <list:VAR> replaced by each value.
#
# <if:VAR>
# ... lines A ...
# <else:VAR>
# ... lines B ...
# <endif:VAR>   This will include the A set of lines if $package{VAR} is true
#               and the B set of lines otherwise.
#

__DATA__
#
# This SPEC file was automatically generated using the cpantorpm
# script.
#
#    Package:           <rpmname>
#    Version:           <version>
#    cpantorpm version: <VERSION>
#    Date:              <date>
#    Command:
# <command> <args>
#

Name:           <rpmname>
Version:        <version>
Release:        <release><disttag>
Epoch:          <skip:epoch>
Summary:        <summary>
License:        <license>
Group:          <group>
URL:            <url>
BuildRoot:      <DIR>-inst
BuildArch:      <arch>
Source0:        <skip:source>

#
# Unfortunately, the automatic provides and requires do NOT always work (it
# was broken on the very first platform I worked on).  We'll get the list
# of provides and requires manually (using the RPM tools if they work, or
# by parsing the files otherwise) and manually specify them in this SPEC file.
#

AutoReqProv:    no
AutoReq:        no
AutoProv:       no

Provides:       <hash:true:provides> = <val>
Provides:       <hash:false:provides>
<if:incl_deps>
Requires:       <hash:true:runtime_req> >= <val>
Requires:       <hash:false:runtime_req>
BuildRequires:  <hash:true:build_req> >= <val>
BuildRequires:  <hash:false:build_req>
Requires:       perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
<endif:incl_deps>

%description
<desc>

%prep

%setup -T -D -n <name>-<version>
chmod -R u+w %{_builddir}/<name>-%{version}

if [ -f pm_to_blib ]; then rm -f pm_to_blib; fi

%build

<config_cmd>
<build_cmd>
<list:post_build>

<if:incl_tests>
#
# This is included here instead of in the %check section because
# older versions of rpmbuild (such as the one distributed with RHEL5)
# do not do %check by default.
#

if [ -z "$RPMBUILD_NOTESTS" ]; then
   <test_cmd>
fi
<endif:incl_tests>

%install

rm -rf <_buildroot>
<install_cmd>
find <_buildroot> -type f -name .packlist -exec rm -f {} \;
find <_buildroot> -type f -name '*.bs' -size 0 -exec rm -f {} \;
find <_buildroot> -depth -type d -exec rmdir {} 2>/dev/null \;
%{_fixperms} <_buildroot>/*

%clean

rm -rf <_buildroot>

%files

%defattr(-,root,root,-)
<if:bin_inst>
<bin_dir>/*
<endif:bin_inst>
<if:lib_inst>
<lib_dir>/*
<endif:lib_inst>
<if:arch_inst>
<arch_dir>/*
<endif:arch_inst>
<if:man1_inst>
<man_dir>/man1/*
<endif:man1_inst>
<if:man3_inst>
<man_dir>/man3/*
<endif:man3_inst>

<eof>
# Local Variables:
# mode: cperl
# indent-tabs-mode: nil
# cperl-indent-level: 3
# cperl-continued-statement-offset: 2
# cperl-continued-brace-offset: 0
# cperl-brace-offset: 0
# cperl-brace-imaginary-offset: 0
# cperl-label-offset: 0
# End:
