package PPM::Make; use strict; use warnings; use PPM::Make::Config qw(:all); use PPM::Make::Util qw(:all); use PPM::Make::Meta; use PPM::Make::Search; use Cwd; use Pod::Find qw(pod_find contains_pod); use File::Basename; use File::Path; use File::Find; use File::Copy; use Config; use Net::FTP; use LWP::Simple qw(getstore is_success); require File::Spec; use Pod::Html; use Safe; use File::HomeDir; use version; our $VERSION = '0.97'; my $protocol = $PPM::Make::Util::protocol; my $ext = $PPM::Make::Util::ext; my $no_case = 0; my $html = 'blib/html'; sub new { my ($class, %opts) = @_; die "\nInvalid option specification" unless check_opts(%opts); $opts{zip_archive} = 1 if ($opts{binary} and $opts{binary} =~ /\.zip$/); my ($arch, $os) = arch_and_os($opts{arch}, $opts{os}, $opts{noas}); my $has = what_have_you($opts{program}, $arch, $os); my %cfg; # $opts{no_cfg} = 1 if $opts{install}; 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; $no_case = 1 if defined $opts->{no_case}; my $search = PPM::Make::Search->new(); my $self = { opts => $opts || {}, cwd => '', has => $has, args => {}, ppd => '', archive => '', zip => '', prereq_pm => {}, file => '', version => '', use_mb => '', ARCHITECTURE => $arch, OS => $os, cpan_meta => $opts->{cpan_meta}, search => $search, fetch_error => '', no_remote_lookup => $opts->{no_remote_lookup}, }; bless $self, $class; } sub make_ppm { my $self = shift; die 'No software available to make a zip archive' if ( ($self->{opts}->{zip_archive} or $self->{opts}->{zipdist}) and not $self->{has}->{zip}); my $dist = $self->{opts}->{dist}; if ($dist) { my $build_dir = File::Spec->tmpdir; chdir $build_dir or die "Cannot chdir to $build_dir: $!"; print "Working directory: $build_dir\n"; die $self->{fetch_error} unless ($dist = $self->fetch_file($dist, no_case => $no_case)); # if ($dist =~ m!$protocol! # or $dist =~ m!^\w/\w\w/! or $dist !~ m!$ext!); print "Extracting files from $dist ....\n"; my $name = $self->extract_dist($dist, $build_dir); chdir $name or die "Cannot chdir to $name: $!"; $self->{file} = $dist; } die "Need a Makefile.PL or Build.PL to build" unless (-f 'Makefile.PL' or -f 'Build.PL'); my $force = $self->{opts}->{force}; $self->{cwd} = cwd; print "Working directory: $self->{cwd}\n"; my $mb = -e 'Build.PL'; $self->{mb} = $mb; die "This distribution requires Module::Build to build" if ($mb and not HAS_MB); $self->check_script() if $self->{opts}->{script}; $self->check_files() if $self->{opts}->{add}; $self->adjust_binary() if $self->{opts}->{arch_sub}; $self->build_dist() unless (-d 'blib' and (-f 'Makefile' or ($mb and -f 'Build' and -d '_build')) and not $force); my $meta = PPM::Make::Meta->new(dir => $self->{cwd}, search => $self->{search}, no_remote_lookup => $self->{no_remote_lookup}, ); die qq{Creating PPM::Make::Meta object failed} unless ($meta and (ref($meta) eq 'PPM::Make::Meta')); $meta->meta(); foreach my $key( keys %{$meta->{info}}) { next unless defined $meta->{info}->{$key}; $self->{args}->{$key} ||= $meta->{info}->{$key}; } if ($self->{version} = $self->{args}->{VERSION}) { my $version = version->new($self->{version}); $self->{version} = $version; $self->{version} =~ s/^v//x; } else { warn "Could not extract version information"; } unless ($self->{opts}->{no_html}) { $self->make_html() unless (-d 'blib/html' and not $force); } $dist = $self->make_dist(); $self->make_ppd($dist); # if ($self->{opts}->{install}) { # die 'Must have the ppm utility to install' unless HAS_PPM; # $self->ppm_install(); # } $self->make_cpan() if $self->{opts}->{cpan}; $self->make_zipdist($dist) if ($self->{opts}->{zipdist} and not $self->{opts}->{no_upload}); if (defined $self->{opts}->{upload} and not $self->{opts}->{no_upload}) { die 'Please specify the location to place the ppd file' unless $self->{opts}->{upload}->{ppd}; $self->upload_ppm(); } return 1; } sub check_script { my $self = shift; my $script = $self->{opts}->{script}; return if ($script =~ m!$protocol!); my ($name, $path, $suffix) = fileparse($script, '\..*'); my $file = $name . $suffix; $self->{opts}->{script} = $file; return if (-e $file); copy($script, $file) or die "Copying $script to $self->{cwd} failed: $!"; } sub check_files { my $self = shift; my @entries = (); foreach my $file (@{$self->{opts}->{add}}) { my ($name, $path, $suffix) = fileparse($file, '\..*'); my $entry = $name . $suffix; push @entries, $entry; next if (-e $entry); copy($file, $entry) or die "Copying $file to $self->{cwd} failed: $!"; } $self->{opts}->{add} = \@entries if @entries; } sub extract_dist { my ($self, $file, $build_dir) = @_; my $has = $self->{has}; my ($tar, $gzip, $unzip) = @$has{qw(tar gzip unzip)}; my ($name, $path, $suffix) = fileparse($file, $ext); if (-d "$build_dir/$name") { rmtree("$build_dir/$name", 1, 0) or die "rmtree of $name failed: $!"; } EXTRACT: { if ($suffix eq '.zip') { ($unzip eq 'Archive::Zip') && do { my $arc = Archive::Zip->new(); die "Read of $file failed" unless $arc->read($file) == Archive::Zip::AZ_OK(); $arc->extractTree(); last EXTRACT; }; ($unzip) && do { my @args = ($unzip, $file); print "@args\n"; system(@args) == 0 or die "@args failed: $?"; last EXTRACT; }; } else { ($tar eq 'Archive::Tar') && do { my $arc = Archive::Tar->new($file, 1); $arc->extract($arc->list_files); last EXTRACT; }; ($tar and $gzip) && do { my @args = ($gzip, '-dc', $file, '|', $tar, 'xvf', '-'); print "@args\n"; system(@args) == 0 or die "@args failed: $?"; last EXTRACT; }; } die "Cannot extract $file"; } return $name; } sub adjust_binary { my $self = shift; my $binary = $self->{opts}->{binary}; my $archname = $self->{ARCHITECTURE}; return unless $archname; if ($binary) { if ($binary =~ m!$ext!) { if ($binary =~ m!/!) { $binary =~ s!(.*?)([\w\-]+)$ext!$1$archname/$2$3!; } else { $binary = $archname . '/' . $binary; } } else { $binary =~ s!/$!!; $binary .= '/' . $archname . '/'; } } else { $binary = $archname . '/'; } $self->{opts}->{binary} = $binary; } sub build_dist { my $self = shift; my $binary = $self->{opts}->{binary}; my $script = $self->{opts}->{script}; my $exec = $self->{opts}->{exec}; my $has = $self->{has}; my ($make, $perl) = @$has{qw(make perl)}; my $mb = $self->{mb}; my $makepl = $mb ? 'Build.PL' : 'Makefile.PL'; my @args = ($perl, $makepl); if (not $mb and my $makepl_arg = $CPAN::Config->{makepl_arg}) { push @args, (split ' ', $makepl_arg); } print "@args\n"; system(@args) == 0 or die qq{@args failed: $?}; # if ($mb) { # my $file = 'Build.PL'; # unless (my $r = do $file) { # die "Can't parse $file: $@" if $@; # die "Can't do $file: $!" unless defined $r; # die "Can't run $file" unless $r; # } # } # else { # $self->write_makefile(); # } my $build = 'Build'; @args = $mb ? ($perl, $build) : ($make); if (not $mb and my $make_arg = $CPAN::Config->{make_arg}) { push @args, (split ' ', $make_arg); } print "@args\n"; system(@args) == 0 or die "@args failed: $?"; unless ($self->{opts}->{skip}) { @args = $mb ? ($perl, $build, 'test') : ($make, 'test'); print "@args\n"; unless (system(@args) == 0) { die "@args failed: $?" unless $self->{opts}->{ignore}; warn "@args failed: $?"; } } return 1; } sub make_html { my $self = shift; my $args = $self->{args}; my $cwd = $self->{cwd}; unless (-d $html) { mkpath($html, 1, 0755) or die "Couldn't mkdir $html: $!"; } my %pods = pod_find({-verbose => 1}, "$cwd/blib/"); if (-d "$cwd/blib/script/") { finddepth( sub {$pods{$File::Find::name} = "script::" . basename($File::Find::name) if (-f $_ and not /\.bat$/ and contains_pod($_)); }, "$cwd/blib/script"); } foreach my $pod (keys %pods){ my @dirs = split /::/, $pods{$pod}; my $isbin = shift @dirs eq 'script'; (my $infile = File::Spec->abs2rel($pod)) =~ s!^\w+:!!; $infile =~ s!\\!/!g; my $outfile = (pop @dirs) . '.html'; my @rootdirs = $isbin? ('bin') : ('site', 'lib'); (my $path2root = "../" x (@rootdirs+@dirs)) =~ s|/$||; (my $fulldir = File::Spec->catfile($html, @rootdirs, @dirs)) =~ s!\\!/!g; unless (-d $fulldir){ mkpath($fulldir, 1, 0755) or die "Couldn't mkdir $fulldir: $!"; } ($outfile = File::Spec->catfile($fulldir, $outfile)) =~ s!\\!/!g; my $htmlroot = "$path2root/site/lib"; my $podroot = "$cwd/blib"; my $podpath = join ":" => map { $podroot . '/' . $_ } ($isbin ? qw(bin lib) : qw(lib)); (my $package = $pods{$pod}) =~ s!^(lib|script)::!!; my $abstract = parse_abstract($package, $infile); my $title = $abstract ? "$package - $abstract" : $package; my @opts = ( '--header', "--title=$title", "--infile=$infile", "--outfile=$outfile", "--podroot=$podroot", "--htmlroot=$htmlroot", "--css=$path2root/Active.css", ); print "pod2html @opts\n"; pod2html(@opts);# or warn "pod2html @opts failed: $!"; } ################################### } sub make_dist { my $self = shift; my $args = $self->{args}; my $has = $self->{has}; my ($tar, $gzip, $zip) = @$has{qw(tar gzip zip)}; my $force_zip = $self->{opts}->{zip_archive}; my $binary = $self->{opts}->{binary}; my $name; if ($binary and $binary =~ /$ext/) { ($name = $binary) =~ s!.*/(.*)$ext!$1!; } else { $name = $args->{DISTNAME} || $args->{NAME}; $name =~ s!::!-!g; } $name .= "-$self->{version}" if ( ($self->{opts}->{vs} or $self->{opts}->{vsr}) and $self->{version}); my $is_Win32 = (not $self->{OS} or $self->{OS} =~ /Win32/i or not $self->{ARCHITECTURE} or $self->{ARCHITECTURE} =~ /Win32/i); my $script = $self->{opts}->{script}; my $script_is_external = $script ? ($script =~ /$protocol/) : ''; my @files; if ($self->{opts}->{add}) { @files = @{$self->{opts}->{add}}; } my $arc = $force_zip ? ($name . '.zip') : ($name . '.tar.gz'); # unless ($self->{opts}->{force}) { # return $arc if (-f $arc); # } unlink $arc if (-e $arc); DIST: { ($tar eq 'Archive::Tar' and not $force_zip) && do { $name .= '.tar.gz'; my @f; my $arc = Archive::Tar->new(); if ($is_Win32) { finddepth(sub { push @f, $File::Find::name unless $File::Find::name =~ m!blib/man\d!; print $File::Find::name,"\n"}, 'blib'); } else { finddepth(sub {push @f, $File::Find::name; print $File::Find::name,"\n"}, 'blib'); } if ($script and not $script_is_external) { push @f, $script; print "$script\n"; } if (@files) { push @f, @files; print join "\n", @files; } $arc->add_files(@f); $arc->write($name, 1); last DIST; }; ($tar and $gzip and not $force_zip) && do { $name .= '.tar'; my @args = ($tar, 'cvf', $name); if ($is_Win32) { my @f; finddepth(sub { push @f, $File::Find::name if $File::Find::name =~ m!blib/man\d!;}, 'blib'); for (@f) { push @args, "--exclude", $_; } } push @args, 'blib'; if ($script and not $script_is_external) { push @args, $script; } if (@files) { push @args, @files; } print "@args\n"; system(@args) == 0 or die "@args failed: $?"; @args = ($gzip, $name); print "@args\n"; system(@args) == 0 or die "@args failed: $?"; $name .= '.gz'; last DIST; }; ($zip eq 'Archive::Zip') && do { $name .= '.zip'; my $arc = Archive::Zip->new(); if ($is_Win32) { die "zip of blib failed" unless $arc->addTree('blib', 'blib', sub{$_ !~ m!blib/man\d/! && print "$_\n";}) == Archive::Zip::AZ_OK(); } else { die "zip of blib failed" unless $arc->addTree('blib', 'blib', sub{print "$_\n";}) == Archive::Zip::AZ_OK(); } if ($script and not $script_is_external) { die "zip of $script failed" unless $arc->addFile($script, $script); print "$script\n"; } if (@files) { for (@files) { die "zip of $_ failed" unless $arc->addFile($_, $_); print "$_\n"; } } die "Writing to $name failed" unless $arc->writeToFileNamed($name) == Archive::Zip::AZ_OK(); last DIST; }; ($zip) && do { $name .= '.zip'; my @args = ($zip, '-r', $name, 'blib'); if ($script and not $script_is_external) { push @args, $script; print "$script\n"; } if (@files) { push @args, @files; print join "\n", @files; } if ($is_Win32) { my @f; finddepth(sub { push @f, $File::Find::name unless $File::Find::name =~ m!blib/man\d!;}, 'blib'); for (@f) { push @args, "-x", $_; } } print "@args\n"; system(@args) == 0 or die "@args failed: $?"; last DIST; }; die "Cannot make archive for $name"; } return $name; } sub make_ppd { my ($self, $dist) = @_; my $has = $self->{has}; my ($make, $perl) = @$has{qw(make perl)}; my $binary = $self->{opts}->{binary}; if ($binary) { unless ($binary =~ /$ext/) { $binary =~ s!/$!!; $binary .= '/' . $dist; } } (my $name = $dist) =~ s!$ext!!; if ($self->{opts}->{vsr} and not $self->{opts}->{vsp}) { $name =~ s/-$self->{version}// if $self->{version}; } if ($self->{opts}->{vsp} and $name !~ m/-$self->{version}/) { $name .= "-$self->{version}"; } my $ppd = $name . '.ppd'; my $args = $self->{args}; my $os = $self->{OS}; my $arch = $self->{ARCHITECTURE}; my $d; $d->{SOFTPKG}->{NAME} = $d->{TITLE} = $name; $d->{SOFTPKG}->{VERSION} = cpan2ppd_version($self->{version} || 0); $d->{OS}->{NAME} = $os if $os; $d->{ARCHITECTURE}->{NAME} = $arch if $arch; $d->{ABSTRACT} = $args->{ABSTRACT}; $d->{AUTHOR} = (ref($args->{AUTHOR}) eq 'ARRAY') ? (join ', ', @{$args->{AUTHOR}}) : $args->{AUTHOR}; $d->{CODEBASE}->{HREF} = $self->{opts}->{no_upload} ? $dist : ($binary || $dist); ($self->{archive} = $d->{CODEBASE}->{HREF}) =~ s!.*/(.*)!$1!; if ( my $script = $self->{opts}->{script}) { if (my $exec = $self->{opts}->{exec}) { $d->{INSTALL}->{EXEC} = $exec; } if ($script =~ m!$protocol!) { $d->{INSTALL}->{HREF} = $script; (my $name = $script) =~ s!.*/(.*)!$1!; $d->{INSTALL}->{SCRIPT} = $name; } else { $d->{INSTALL}->{SCRIPT} = $script; } } my $search = $self->{search}; my $no_remote_lookup = $self->{no_remote_lookup}; unless ($no_remote_lookup) { if ($search->search($name, mode => 'dist')) { my $mods = $search->{dist_results}->{$name}->{mods}; if ($mods and (ref($mods) eq 'ARRAY')) { foreach my $mod (@$mods) { my $mod_name = $mod->{mod_name}; next unless $mod_name; my $mod_vers = $mod->{mod_vers}; if ($] < 5.10) { $mod_name .= '::' unless ($mod_name =~ /::/); } push @{$d->{PROVIDE}}, {NAME => $mod_name, VERSION => $mod_vers}; } } } else { $search->search_error(); warn qq{Cannot obtain the modules that '$name' provides}; } } my $mod_ref; foreach my $dp (keys %{$args->{PREREQ_PM}}) { next if ($dp eq 'perl' or is_core($dp)); $dp =~ s{-}{::}g; $d->{REQUIRE}->{$dp} = $args->{PREREQ_PM}->{$dp} || 0; push @$mod_ref, $dp; } my %deps = map {$_ => 1} @$mod_ref; unless ($no_remote_lookup) { if ($mod_ref and ref($mod_ref) eq 'ARRAY') { if ($search->search($mod_ref, mode => 'mod')) { my $matches = $search->{mod_results}; if ($matches and ref($matches) eq 'HASH') { foreach my $dp(keys %$matches) { next unless $deps{$dp}; my $results = $matches->{$dp}; next unless (defined $results and defined $results->{mod_name}); my $dist = $results->{dist_name}; next if (not $dist or $dist =~ m!^perl$! or $dist =~ m!^Test! or is_ap_core($dist)); $self->{prereq_pm}->{$dist} = $d->{DEPENDENCY}->{$dist} = cpan2ppd_version($args->{PREREQ_PM}->{$dp} || 0); } } else { $search->search_error(); warn qq{Cannot find information on prerequisites for '$name'}; } } } } foreach (qw(OS ARCHITECTURE)) { delete $d->{$_}->{NAME} unless $self->{$_}; } $self->print_ppd($d, $ppd); $self->{ppd} = $ppd; } sub print_ppd { my ($self, $d, $fn) = @_; open (my $fh, ">$fn") or die "Couldn't write to $fn: $!"; my $title = xml_encode($d->{TITLE}); my $abstract = xml_encode($d->{ABSTRACT}); my $author = xml_encode($d->{AUTHOR}); print $fh <<"END"; {SOFTPKG}->{NAME}\" VERSION=\"$d->{SOFTPKG}->{VERSION}\"> $title $abstract $author END foreach (keys %{$d->{DEPENDENCY}}) { print $fh qq{ \n}; } if ($] > 5.008) { foreach (keys %{$d->{REQUIRE}}) { print $fh qq{ \n}; } } foreach (qw(OS ARCHITECTURE)) { next unless $d->{$_}->{NAME}; print $fh qq{ <$_ NAME="$d->{$_}->{NAME}" />\n}; } if (my $script = $d->{INSTALL}->{SCRIPT}) { my $install = 'INSTALL'; if (my $exec = $d->{INSTALL}->{EXEC}) { $install .= qq{ EXEC="$exec"}; } if (my $href = $d->{INSTALL}->{HREF}) { $install .= qq{ HREF="$href"}; } print $fh qq{ <$install>$script\n}; } print $fh qq{ \n}; my $provide = $d->{PROVIDE}; unless ($self->{opts}->{no_ppm4}) { if ($provide and (ref($provide) eq 'ARRAY')) { foreach my $mod(@$provide) { my $string = qq{ {VERSION}) { $string .= qq{ VERSION="$mod->{VERSION}"}; } $string .= qq{ />\n}; print $fh $string; } } } print $fh qq{ \n}; print $fh qq{\n}; $fh->close; $self->{codebase} = $d->{CODEBASE}->{HREF}; } sub make_zipdist { my ($self, $dist) = @_; my $ppd = $self->{ppd}; (my $zipdist = $ppd) =~ s!\.ppd$!.zip!; if (-f $zipdist) { unlink $zipdist or warn "Could not unlink $zipdist: $!"; } my $cb = $self->{codebase}; my ($path, $archive, $local); if ($cb =~ m!/!) { ($path, $archive) = $cb =~ m!(.*)/(.*)!; $local = ($path !~ m!(http|ftp)://! and not File::Spec->file_name_is_absolute($path) ) ? 1 : 0; } else { $archive = $cb; } my $readme = 'README.ppm'; 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 install $ppd END close $fh; my $ppd_zip = $ppd . '.copy'; open(my $rfh, '<', $ppd) or die "Cannot open $ppd: $!"; open(my $wfh, '>', $ppd_zip) or die "Cannot open $ppd_zip: $!"; while (my $line = <$rfh>) { $line =~ s{HREF=\"(http|ftp)://.*/([^/]+)\"}{HREF="$2"}; print $wfh $line; } close($rfh); close($wfh); my $zip = $self->{has}->{zip}; my $copy = $local ? File::Spec::Unix->catfile($path, $archive) : $archive; print qq{\nCreating $zipdist ...\n}; if ($zip eq 'Archive::Zip') { my %contents = ($ppd_zip => $ppd, $archive => $copy, $readme => 'README'); my $arc = Archive::Zip->new(); foreach (keys %contents) { print "Adding $_ as $contents{$_}\n"; unless ($arc->addFile($_, $contents{$_})) { die "Failed to add $_"; } } die "Writing to $zipdist failed" unless $arc->writeToFileNamed($zipdist) == Archive::Zip::AZ_OK(); } else { if ($path and $local) { unless (-d $path) { mkpath($path, 1, 0777) or die "Cannot mkpath $path: $!"; } copy($archive, $copy) or die "Cannot cp $archive to $copy: $!"; } rename($ppd, "$ppd.tmp") or die "Cannnot rename $ppd to $ppd.tmp: $!"; rename($ppd_zip, $ppd) or die "Cannnot rename $ppd_zip to $ppd: $!"; my @args = ($zip, $zipdist, $ppd, $copy, $readme); print "@args\n"; system(@args) == 0 or die "@args failed: $?"; rename($ppd, $ppd_zip) or die "Cannnot rename $ppd to $ppd_zip: $!"; rename("$ppd.tmp", $ppd) or die "Cannnot rename $ppd.tmp to $ppd: $!"; if ($path and $local and -d $path) { rmtree($path, 1, 1) or warn "Cannot rmtree $path: $!"; } } $self->{zip} = $zipdist; unlink $readme; unlink $ppd_zip; } sub make_cpan { my $self = shift; my ($ppd, $archive) = ($self->{ppd}, $self->{archive}); my %seen; my $man = 'MANIFEST'; my $copy = $man . '.orig'; unless (-e $copy) { rename($man, $copy) or die "Cannot rename $man: $!"; } open(my $orig, $copy) or die "Cannot read $copy: $!"; open(my $new, ">$man") or die "Cannot open $man for writing: $!"; while (<$orig>) { $seen{ppd}++ if $_ =~ /$ppd/; $seen{archive}++ if $_ =~ /$archive/; print $new $_; } close $orig; print $new "\n$ppd\n" unless $seen{ppd}; print $new "$archive\n" unless $seen{archive}; close $new; my @args = ($self->{has}->{make}, 'dist'); print "@args\n"; system(@args) == 0 or die qq{system @args failed: $?}; return; } sub upload_ppm { my $self = shift; my ($ppd, $archive, $zip) = ($self->{ppd}, $self->{archive}, $self->{zip}); my $upload = $self->{opts}->{upload}; my $ppd_loc = $upload->{ppd}; my $zip_loc = $upload->{zip}; my $ar_loc = $self->{opts}->{arch_sub} ? $self->{ARCHITECTURE} : $upload->{ar} || $ppd_loc; if (defined $ar_loc) { if (not File::Spec->file_name_is_absolute($ar_loc)) { ($ar_loc = File::Spec->catdir($ppd_loc, $ar_loc)) =~ s!\\!/!g; } } if (defined $zip_loc) { if (not File::Spec->file_name_is_absolute($zip_loc)) { ($zip_loc = File::Spec->catdir($ppd_loc, $zip_loc)) =~ s!\\!/!g; } } if (my $host = $upload->{host}) { print qq{\nUploading files 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($ppd_loc) or die "cwd to $ppd_loc failed: ", $ftp->message; if ($Net::FTP::VERSION eq '2.77') { $ftp->binary; } else { $ftp->ascii; } $ftp->put($ppd) or die "Cannot upload $ppd: ", $ftp->message; $ftp->cwd($ar_loc) or die "cwd to $ar_loc failed: ", $ftp->message; $ftp->binary; $ftp->put($archive) or die "Cannot upload $archive: ", $ftp->message; if ($self->{opts}->{zipdist} and -f $zip) { $ftp->cwd($zip_loc) or die "cwd to $zip_loc failed: ", $ftp->message; $ftp->put($zip) or die "Cannot upload $zip: ", $ftp->message; } $ftp->quit; print qq{Done!\n}; } else { print qq{\nCopying files ....\n}; copy($ppd, "$ppd_loc/$ppd") or die "Cannot copy $ppd to $ppd_loc: $!"; unless (-d $ar_loc) { mkdir $ar_loc or die "Cannot mkdir $ar_loc: $!"; } copy($archive, "$ar_loc/$archive") or die "Cannot copy $archive to $ar_loc: $!"; if ($self->{opts}->{zipdist} and -f $zip) { unless (-d $zip_loc) { mkdir $zip_loc or die "Cannot mkdir $zip_loc: $!"; } copy($zip, "$zip_loc/$zip") or die "Cannot copy $zip to $zip_loc: $!"; } print qq{Done!\n}; } } sub fetch_file { my ($self, $dist, %args) = @_; my $no_case = $args{no_case}; my $to; if (-f $dist) { $to = basename($dist, $ext); unless ($dist eq $to) { copy($dist, $to) or die "Cannot cp $dist to $to: $!"; } return $to; } if ($dist =~ m!$protocol!) { ($to = $dist) =~ s!.*/(.*)!$1!; print "Fetching $dist ....\n"; my $rc = is_success(getstore($dist, $to)); unless ($rc) { $self->{fetch_error} = qq{Fetch of $dist failed.}; return; } return $to; } my $search = $self->{search}; my $no_remote_lookup = $self->{no_remote_lookup}; my $results; unless ($no_remote_lookup or $dist =~ /$ext$/) { my $mod = $dist; $mod =~ s!-!::!g; if ($search->search($mod, mode => 'mod')) { $results = $search->{mod_results}->{$mod}; } unless ($results) { $mod =~ s!::!-!g; if ($search->search($mod, mode => 'dist')) { $results = $search->{dist_results}->{$mod}; } } unless ($results->{cpanid} and $results->{dist_file}) { $self->{fetch_error} = qq{Cannot get distribution name of '$mod'}; return; } $dist = cpan_file($results->{cpanid}, $results->{dist_file}); } my $id = dirname($dist); $to = basename($dist, $ext); my $src = HAS_CPAN ? File::Spec->catdir($src_dir, 'authors/id', $id) : $src_dir; my $CS = 'CHECKSUMS'; my $get_cs = 0; for my $file( ($to, $CS)) { my $local = File::Spec->catfile($src, $file); if (-e $local and $src_dir ne $build_dir and not $get_cs) { copy($local, '.') or do { $self->{fetch_error} = "Cannot copy $local: $!"; return; }; next; } else { my $from; $get_cs = 1; foreach my $url(@url_list) { $url =~ s!/$!!; $from = $url . '/authors/id/' . $id . '/' . $file; print "Fetching $from ...\n"; last if is_success(getstore($from, $file)); } unless (-e $file) { $self->{fetch_error} = "Fetch of $file from $from failed"; return; } if ($src_dir ne $build_dir) { unless (-d $src) { mkpath($src) or do { $self->{fetch_error} = "Cannot mkdir $src: $!"; return; }; } copy($file, $src) or warn "Cannot copy $to to $src: $!"; } } } return $to unless $to =~ /$ext$/; my $cksum; unless ($cksum = load_cs($CS)) { $self->{fetch_error} = qq{Checksums check disabled - cannot load $CS file.}; return; } unless (verifyMD5($cksum, $to)) { $self->{fetch_error} = qq{Checksums check for "$to" failed.}; return; } unlink $CS or warn qq{Cannot unlink "$CS": $!\n}; return $to; } 1; __END__ =head1 NAME PPM::Make - Make a ppm package from a CPAN distribution =head1 SYNOPSIS my $ppm = PPM::Make->new( [options] ); $ppm->make_ppm(); =head1 DESCRIPTION See the supplied C script for a command-line interface. This module automates somewhat some of the steps needed to make a I (Perl Package Manager) package from a CPAN distribution. It attempts to fill in the I and I attributes of F, if these are not supplied, and also uses C to generate a set of html documentation. It also adjusts I of I to reflect the generated I or I archive. Such packages are suitable both for local installation via C:\.cpan\build\package_src> ppm install and for distribution via a repository. Options can be given as some combination of key/value pairs passed to the I constructor (described below) and those specified in a configuration file. This file can either be that given by the value of the I environment variable or, if not set, a file called F<.ppmcfg> at the top-level directory (on Win32) or under I (on Unix). If the I argument is passed into C, this file will be ignored. The configuration file is of an INI type. If a section I is specified as [ default ] option1 = value1 option2 = value2 these values will be used as the default. Architecture-specific values may be specified within their own section: [ MSWin32-x86-multi-thread-5.8 ] option1 = new_value1 option3 = value3 In this case, an architecture specified as I within PPM::Make will have I, I, and I, while any other architecture will have I and I. Options that take multiple values, such as C, can be specified as reps = < method of PPM::Make. Valid options that may be specified within the configuration file are those of PPM::Make, described below. For the I and I options (which take hash references), the keys (make, zip, unzip, tar, gzip), or (ppd, ar, zip, host, user, passwd), respectively, should be specified. For binary options, a value of I in the configuration file will be interpreted as true, while I will be interpreted as false. =head2 OPTIONS The available options accepted by the I constructor are =over =item no_cfg =E 1 If specified, do not attempt to read a F<.ppmcfg> configuration file. =item no_html =E 1 If specified, do not not build the html documentation. =item no_ppm4 =E 1 If specified, do not add ppm4 extensions to the ppd file. =item no_remote_lookup =E 1 If specified, do not consult remote databases nor CPAN.pm for information not contained within the files of the distribution. =item dist =E value If I is not specified, it will be assumed that one is working inside an already unpacked source directory, and the ppm distribution will be built from there. A value for I will be interpreted either as a CPAN-like source distribution to fetch and build, or as a module name, in which case I will be used to infer the corresponding distribution to grab. =item no_case =E boolean If I is specified, a case-insensitive search of a module name will be performed. =item binary =E value The value of I is used in the I attribute passed to C, and arises in setting the I attribute of the I field in the ppd file. =item arch_sub =E boolean Setting this option will insert the value of C<$Config{archname}> (or the value of the I option, if given) as a relative subdirectory in the I attribute of the I field in the ppd file. =item script =E value The value of I