PPM::Make::Bundle - make a bundle of ppm packages


PPM-Make documentation Contained in the PPM-Make distribution.

Index


Code Index:

NAME

Top

PPM::Make::Bundle - make a bundle of ppm packages

SYNOPSIS

Top

  my $bundle = PPM::Make::Bundle->new(%opts);
  $bundle->make_bundle();

DESCRIPTION

Top

PPM::Make::Bundle is used to build a bundled zip file of a package and all of it's required prerequisites. It will first search through a list of specified repositories to see if a required package is present there, and if not, will use PPM::Make to build one. See PPM::Make for a discussion of details on how the ppm package is built, as well as the available options. The bundled zip file will be placed in the current directory from where it is invoked, unless a bundle key to upload of PPM::Make specifies where to upload bundled files.

The options accepted for PPM::Make::Bundle include those of PPM::Make. If a dist option is not given, it will be assumed that one is in a valid CPAN distribution directory, and attempt to build a zipped bundle file based on that distribution. Additional options specific to PPM::Make::Bundle are

bundle_name => $bundle_name

This options specifes the name of the zip file containing all of the bundled ppm packages. If this is not specified, a default of Bundle-dist_name.zip will be used, where dist_name is the name of the main distribution being built.

no_upload => 1

By default, if a required package is built by PPM::Make, and if the configuration file specifies that such ppm packages are to be uploaded to a repository, this upload will take place. The no_upload option specifies that such individual package uploads not take place, although the bundled zip file will still be uploaded, if specified.

reps => \@repositories

This specifies a list of repositories to search for needed ppm packages.

clean => 1

The ppm packages are placed in a temporary directory for eventual inclusion in the zipped bundle file. The clean option specifies that this temporary directory be removed after the bundle file is built.

BUGS

Top

The needed prerequisites will be followed recursively; however, for packages built with PPM::Make, the tests will be run before this has taken place, which probably will result in build failures. Future versions will address this problem. In the meantime, you may want to use the ignore option, to ignore failing tests, or the skip option, to skip running the tests.

COPYRIGHT

Top

SEE ALSO

Top

PPM::Make and PPM.


PPM-Make documentation Contained in the PPM-Make distribution.

package PPM::Make::Bundle;

use strict;
use warnings;
use Cwd;
use File::Spec::Functions qw(:ALL);
use File::Copy;
use File::Path;
use Config qw(myconfig %Config);
use PPM::Make;
use PPM::Make::Util qw(:all);
use PPM::Make::Config qw(:all);
use PPM::Make::Search;
use LWP::Simple;

our $VERSION = '0.97';

my @cpan_mirrors = url_list();
my $protocol = qr{^(http|ftp)://};
my $ext = qr{\.(tar\.gz|tgz|tar\.Z|zip)};

sub new {
  my ($class, %opts) = @_;

  my $bundle_name = delete $opts{bundle_name};
  if ($bundle_name) {
    $bundle_name =~ s{$ext$}{} if $bundle_name;
    $bundle_name .= '.zip';
  }

  my $clean = delete $opts{clean};

  my ($arch, $os) = arch_and_os($opts{arch}, $opts{os}, $opts{noas});
  my $has = what_have_you($opts{program}, $arch, $os);

  die "\nInvalid option specification" unless check_opts(%opts);
  my %cfg;
  unless ($opts{no_cfg}) {
    if (my $file = get_cfg_file()) {
      %cfg = read_cfg($file, $arch) or die "\nError reading config file";
    }
  }
  my $opts = %cfg ? merge_opts(\%cfg, \%opts) : \%opts;
  my $search = PPM::Make::Search->new();

  my $cwd = cwd;
  my $build_dir = catdir(tmpdir, "ppm_make-$$");
  mkdir $build_dir or die qq{Cannot mkdir $build_dir: $!};
  my $self = {cwd => $cwd, opts => $opts, files => {}, name => '',
	      build_dir => $build_dir, has => $has, zipdist => $bundle_name,
	      clean => $clean, arch => $arch, os => $os,
	      search => $search, no_remote_lookup => $opts->{no_remote_lookup},
	      };
  bless $self, $class;
}

sub make_bundle {
  my $self = shift;
  $self->make_package($self->{opts}->{dist}) or return;
  $self->make_zip() or return;
  if ($self->{opts}->{upload}) {
    $self->upload_zip() or return;
  }
  my $cwd = $self->{cwd};
  chdir($cwd) or die qq{Cannot chdir to $cwd: $!};
  if ($self->{clean}) {
    chdir($self->{cwd}) or die qq{Cannot chdir to $self->{cwd}: $!};
    my $build_dir = $self->{build_dir};
    if (-d $build_dir) {
      rmtree($build_dir, 1, 1) or warn qq{Cannot rmtree $build_dir: $!};
    }
  }
  return 1;
}

sub make_package {
  my ($self, $dist, $info) = @_;

  my ($dist_name, $cpan_file);
  if ($dist and $dist !~ /$ext$/) {
    return 1 if (defined $self->{files}->{$dist} or is_ap_core($dist));
    $info = $self->get_info($dist) unless ($info and (ref($info) eq 'HASH'));
    $dist_name = $info->{dist_name};
    $cpan_file = $info->{cpan_file};
  }
  my $name;
 TRY: {
    (not $dist and (-e 'Makefile.PL' || -e 'Build.PL')) and do {
      last TRY if ($name = $self->from_cpan());
    };
    ($dist =~ /$ext$/) and do {
      last TRY if ($name = $self->from_cpan($dist));
    };
    ($dist_name) and do {
      last TRY if ($name = $self->from_repository($dist_name));
    };
    ($cpan_file) and do {
      my $url = $cpan_mirrors[0] . '/authors/id/' . $cpan_file;
      last TRY if ($name = $self->from_cpan($url));
    };
    last TRY if ($name = $self->from_cpan($dist));
    die qq{Cannot build "$dist"};
  }
  $self->{name} ||= $name;
  my $prereqs = $self->{files}->{$name}->{prereqs};
  if ($prereqs and (ref($prereqs) eq 'ARRAY')) {
    foreach my $item(@$prereqs) {
      $self->make_package($item->{dist_name}, $item);
    }
  }
  return 1;
}

sub get_info {
  my ($self, $dist) = @_;
  return if (-f $dist or $dist =~ /^$protocol/ or $dist =~ /$ext$/);
  my $search = $self->{search};
  my $no_remote_lookup = $self->{no_remote_lookup};
  $dist =~ s{::}{-}g;
  unless ($no_remote_lookup) {
    if ($search->search($dist, mode => 'dist')) {
      my $results = $search->{dist_results}->{$dist};
      my $cpan_file = cpan_file($results->{cpanid}, $results->{dist_file});
      my $info = {cpan_file => $cpan_file, dist_name => $results->{dist_name}};
      return $info;
    }
    else {
      $search->search_error();
      warn qq{Cannot obtain information on '$dist'};
      return;
    }
  }
  return;
}

sub from_cpan {
  my ($self, $pack) = @_;
  my $ppm = PPM::Make->new(%{$self->{opts}}, dist => $pack, no_cfg => 1);
  $ppm->make_ppm();
  my $name;
  if (defined $ppm->{ppd} and defined $ppm->{codebase}) {
    ($name = $ppm->{ppd}) =~ s{\.ppd$}{};
    (my $ar = $ppm->{codebase}) =~ s{.*/([^/]+)$}{$1};
    $self->{files}->{$name} = {cwd => $ppm->{cwd},
			       ppd => $ppm->{ppd},
			       ar => $ar};
  }
  else {
    return;
  }
  my @full_prereqs = keys %{$ppm->{args}->{PREREQ_PM}};
  return $name unless (scalar @full_prereqs > 0);
  my @prereqs = ();
  foreach my $mod(@full_prereqs) {
    push @prereqs, $mod unless ($mod eq 'perl' or is_core($mod));
  }
  my $search = $self->{search};
  my $no_remote_lookup = $self->{no_remote_lookup};
  unless ($no_remote_lookup) {
    if (scalar @prereqs > 0) {
      my $matches = $search->search(\@prereqs, mode => 'mod');
      if ($matches and (ref($matches) eq 'HASH')) {
        foreach my $mod (keys %$matches) {
	      my $item = $matches->{$mod};
	      my $dist_name = $item->{dist_name};
	      next if is_ap_core($dist_name);
	      my $cpan_file = cpan_file($item->{cpanid}, $item->{dist_file});
	      push @{$self->{files}->{$name}->{prereqs}}, 
	        {dist_name => $dist_name,
	        cpan_file => $cpan_file};
        }
      }
    }
  }
  return $name;
}

sub from_repository {
  my ($self, $pack) = @_;
  return if (-f $pack or $pack =~ /^$protocol/ or $pack =~ /$ext$/);
  my $cwd = $self->{build_dir};
  $pack =~ s/::/-/g;
  my $reps = $self->{opts}->{reps};
  return unless $reps;
  my @reps = ref($reps) eq 'ARRAY' ? @$reps : ($reps);
  chdir($cwd) or die qq{Cannot chdir to $cwd: $!};

  my $dist_name = $pack;
  my $ppd_local = $dist_name . '.ppd';
  my $arch = $self->{arch};
  my ($url, $ppd_remote, $info);
  foreach my $item (@reps) {
    if ($item !~ /^$protocol/) {
      $ppd_remote = catfile($item, $ppd_local);
      if (-f $ppd_remote) {
	copy($ppd_remote, $ppd_local) or do {
	  warn qq{Cannot copy "$ppd_remote" to "$ppd_local": $!};
	  return;
	};
	$info = parse_ppd(catfile($cwd, $ppd_local), $arch);
	next unless ($info and (ref($info) eq 'HASH'));
	my $info_arch = $info->{ARCHITECTURE}->{NAME};
	if ($info_arch  and ($info_arch eq $arch)) {
	  $url = $item;
	  print qq{\nUsing $ppd_local from $url\n};
	  last;
	}
      }
    }
    else {
      $item .= '/' unless $item =~ m{/$};
      my $ppd_remote = $item . $ppd_local;
      if (head($ppd_remote)) {
        if (is_success(getstore($ppd_remote, $ppd_local))) {
	  $info = parse_ppd(catfile($cwd, $ppd_local), $arch);
	  next unless ($info and (ref($info) eq 'HASH'));
	  my $info_arch = $info->{ARCHITECTURE}->{NAME};
	  if ($info_arch  and ($info_arch eq $arch)) {
	    $url = $item;
	    print qq{\nUsing $ppd_local from $url\n};
	    last;
	  }
	}
      }
    }
  }
  return unless (-f $ppd_local);
  return unless ($info and (ref($info) eq 'HASH'));

  my $codebase = $info->{CODEBASE}->{HREF};
  (my $ar_local = $codebase) =~ s{.*?/([^/]+)$}{$1};
  if ($codebase =~ /^$protocol/) {
    my $ar_remote = $codebase;
    return unless is_success(getstore($ar_remote, $ar_local));
  }
  elsif ($url !~ /^$protocol/) {
    my $ar_remote = catfile($url, $codebase);
    if (-f $ar_remote) {
      copy($ar_remote, $ar_local) or do {
	warn qq{Cannot copy "$ar_remote" to "$ar_local": $!};
	return;
      };
    }
  }
  else {
    my $ar_remote = $url . $codebase;
    return unless is_success(getstore($ar_remote, $ar_local));
  }
  unless (-f $ar_local) {
    warn qq{Cannot get "$ar_local"};
    return;
  }
  (my $name = $ppd_local) =~ s{\.ppd$}{};
  $self->{files}->{$name} = {cwd => $cwd,
			     ppd => $ppd_local,
			     ar => $ar_local};

  my $deps = $info->{DEPENDENCY};
  return 1 unless ($deps and (ref($deps) eq 'ARRAY'));
  foreach my $item (@$deps) {
    my $dist_name = $item->{NAME};
    next if is_ap_core($dist_name);
    push @{$self->{files}->{$name}->{prereqs}}, {dist_name => $dist_name};
  }
  return $name;
}

sub fetch_prereqs {
  my ($self, $ppm) = @_;
  die qq{Please supply a PPM::Make object} 
    unless ($ppm and (ref($ppm) eq 'PPM::Make'));
  
  my @full_prereqs = keys %{$ppm->{args}->{PREREQ_PM}};
  my @prereqs = ();
  foreach my $mod(@full_prereqs) {
    push @prereqs, $mod unless ($mod eq 'perl' or is_core($mod));
  }
  my $search = $self->{search};
  my $no_remote_lookup = $self->{no_remote_lookup};
  unless ($no_remote_lookup) {
    if (scalar @prereqs > 0) {
      my $matches = $search->search(\@prereqs, mode => 'mod');
      if ($matches and (ref($matches) eq 'HASH')) {
        foreach my $mod(keys %$matches) {
	    next if is_ap_core($matches->{$mod}->{dist_name});
	    print qq{\nFetching prerequisite "$mod"\n};
	    my $download = $cpan_mirrors[0] . '/authors/id/' . 
	      $matches->{$mod}->{download};
	    my $ppm = PPM::Make->new(%{$self->{opts}},
	        			 no_cfg => 1, dist => $download);
	    $ppm->make_ppm();
	    (my $name = $ppm->{ppd}) =~ s{\.ppd$}{};
	    $self->{files}->{$name} = {cwd => $ppm->{cwd},
	        			   ppd => $ppm->{ppd},
		        		   ar => $ppm->{codebase}};
	    $self->fetch_prereqs($ppm);
        }
      }
    }
  }
}

sub make_zip {
  my $self = shift;
  my $cwd = $self->{build_dir};
  chdir($cwd) or die qq{Cannot chdir to $cwd: $!};
  my $files = $self->{files};
  my $bundle_name = $self->{name};
  foreach my $name(keys %$files) {
    my $item = $self->{files}->{$name};
    my $item_cwd = $item->{cwd};
    next if ($item_cwd eq $cwd);
    my $ppd = $item->{ppd};
    my $ar = $item->{ar};
    copy(catfile($item_cwd, $ppd), $ppd)
      or die qq{Cannot copy $ppd from $item_cwd: $!};
    copy(catfile($item_cwd, $ar), $ar)
      or die qq{Cannot copy $ar from $item_cwd: $!};
  }
  my $ppd_master = $self->{files}->{$bundle_name}->{ppd};
  my $zipdist = $self->{zipdist} ||
    ($bundle_name =~ /^(Bundle|Task)/ ?
     $bundle_name : ('Bundle-' . $bundle_name)) . '.zip';
  if (-f $zipdist) {
    unlink $zipdist or warn "Could not unlink $zipdist: $!";
  }
  my $readme = 'README';
  open(my $fh, '>', $readme) or die "Cannot open $readme: $!";
  print $fh <<"END";
To install this ppm package, run the following command
in the current directory:

   ppm rep add temp_repository file://C:/Path/to/current/directory
   ppm install $ppd_master
   ppm rep del temp_repository_id_number

END
  close $fh;

  my %contents = ($readme => 'README');
  foreach my $name(keys %$files) {
    my $item = $self->{files}->{$name};
    my $item_cwd = $item->{cwd};
    my $ppd = $item->{ppd};
    my $ar = $item->{ar};
    my $ppd_orig = $ppd . '.orig';
    rename($ppd, $ppd_orig) or die "Cannot rename $ppd to $ppd_orig: $!";
    open(my $rfh, '<', $ppd_orig) or die "Cannot open $ppd_orig: $!";
    open(my $wfh, '>', $ppd) or die "Cannot open $ppd: $!";
    while (my $line = <$rfh>) {
      $line =~ s{HREF=\".*/([^/]+)\"}{HREF="$1"};
      print $wfh $line;
    }
    close($rfh);
    close($wfh);
    $contents{$ar} = $ar;
    $contents{$ppd} = $ppd;
  }

  my $zip = $self->{has}->{zip};
  print qq{\nCreating $zipdist ...\n};
  if ($zip eq 'Archive::Zip') {
    my $arc = Archive::Zip->new();
    foreach (sort keys %contents) {
      print "Adding $contents{$_}\n";
      unless ($arc->addFile($_, $contents{$_})) {
	die "Failed to add $_";
      }
    }
    die "Writing to $zipdist failed" 
      unless $arc->writeToFileNamed($zipdist) == Archive::Zip::AZ_OK();
  }
  else {
    my @args = ($zip, $zipdist, keys %contents);
    print "@args\n";
    system(@args) == 0 or die "@args failed: $?";
  }
  unless ($self->{opts}->{upload}) {
    my $cwd = $self->{cwd};
    copy($zipdist, $cwd) or warn qq{Cannot copy $zipdist to $cwd: $!};
    print qq{\nCopying $zipdist to $cwd.\n};
  }
  $self->{zipdist} = $zipdist;
  return 1;
}

sub upload_zip {
  my $self = shift;
  my $upload = $self->{opts}->{upload};
  my $bundle_loc = $upload->{bundle};
  my $zipdist = $self->{zipdist};
  my $cwd = $self->{build_dir};
  chdir($cwd) or die qq{Cannot chdir to $cwd: $!};

  if (my $host = $upload->{host}) {
    print qq{\nUploading $zipdist to $host ...\n};
    my ($user, $passwd) = ($upload->{user}, $upload->{passwd});
    die "Must specify a username and password to log into $host"
      unless ($user and $passwd);
    my $ftp = Net::FTP->new($host)
      or die "Cannot connect to $host: $@";
    $ftp->login($user, $passwd)
      or die "Login for user $user failed: ", $ftp->message;
    $ftp->cwd($bundle_loc) or die
      "cwd to $bundle_loc failed: ", $ftp->message;
    $ftp->binary;
    $ftp->put($zipdist)
      or die "Cannot upload $zipdist: ", $ftp->message;
    $ftp->quit;
  }
  else {
    print qq{\nCopying $zipdist to $bundle_loc\n};
    copy($zipdist, "$bundle_loc/$zipdist") 
      or die "Cannot copy $zipdist to $bundle_loc: $!";
  }
  print qq{Done!\n};
  return 1;
}

1;

__END__