view release on metacpan or search on metacpan
lib/App/Fetchware.pm view on Meta::CPAN
package App::Fetchware;
our $VERSION = '1.016'; # VERSION: generated by DZP::OurPkgVersion
# ABSTRACT: App::Fetchware is Fetchware's API used to make extensions.
###BUGALERT### Uses die instead of croak. croak is the preferred way of throwing
#exceptions in modules. croak says that the caller was the one who caused the
#error not the specific code that actually threw the error.
use strict;
use warnings;
# CPAN modules making Fetchwarefile better.
use File::Spec::Functions qw(catfile splitpath splitdir file_name_is_absolute);
use Path::Class;
use Data::Dumper;
use File::Copy 'cp';
use HTML::TreeBuilder;
use Scalar::Util qw(blessed looks_like_number);
use Digest::SHA;
use Digest::MD5;
#use Crypt::OpenPGP::KeyRing;
#use Crypt::OpenPGP;
use Archive::Tar;
use Archive::Zip qw(:ERROR_CODES :CONSTANTS);
use Cwd 'cwd';
use Sub::Mage;
use URI::Split qw(uri_split uri_join);
use Text::ParseWords 'quotewords';
use File::Temp 'tempfile';
use Term::ReadLine;
use Term::UI;
use App::Fetchware::Util ':UTIL';
use App::Fetchware::Config ':CONFIG';
# Enable Perl 6 knockoffs, and use 5.10.1, because smartmatching and other
# things in 5.10 were changed in 5.10.1+.
use 5.010001;
# Set up Exporter to bring App::Fetchware's API to everyone who use's it
# including fetchware's ability to let you rip into its guts, and customize it
# as you need.
use Exporter qw( import );
# By default fetchware exports its configuration file like subroutines.
#
# These days popular dogma considers it bad to import stuff without being asked
# to do so, but App::Fetchware is meant to be a configuration file that is both
# human readable, and most importantly flexible enough to allow customization.
# This is done by making the configuration file a perl source code file called a
# Fetchwarefile that fetchware simply executes with eval.
our @EXPORT = qw(
program
filter
temp_dir
fetchware_db_path
user
prefix
configure_options
make_options
build_commands
install_commands
uninstall_commands
lookup_url
lookup_method
gpg_keys_url
gpg_sig_url
lib/App/Fetchware.pm view on Meta::CPAN
###BUGALERT### Recommend installing http://gpg4win.org if you use fetchware on
# Windows so you have gpg support.
# _make_config_sub() is an internal subroutine that only App::Fetchware and
# App::Fetchware::CreateConfigOptions should use. Use
# App::Fetchware::CreateConfigOptions to create any configuration option
# subroutines that you want your fetchware extensions to have.
#=head2 _make_config_sub()
#
# _make_config_sub($name, $one_or_many_values)
#
#A function factory that builds many functions that are the exact same, but have
#different names. It supports three types of functions determined by
#_make_config_sub()'s second parameter. It's first parameter is the name of that
#function. This is the subroutine that builds all of Fetchwarefile's
#configuration subroutines such as lookupurl, mirror, fetchware, etc....
#
#=over
#=item LIMITATION
#
#_make_config_sub() creates subroutines that have prototypes, but in order for
#perl to honor those prototypes perl B<must> know about them at compile-time;
#therefore, that is why _make_config_sub() must be called inside a C<BEGIN> block.
#
#=back
#
#=over
#=item NOTE
#_make_config_sub() uses caller to determine the package that _make_config_sub()
#was called from. This package is then prepended to the string that is eval'd to
#create the designated subroutine in the caller's package. This is needed so that
#App::Fetchware "subclasses" can import this function, and enjoy its simple
#interface to create custom configuration subroutines.
#
#=back
#
#=over
#
#=item $one_or_many_values Supported Values
#
#=over
#
#=item * 'ONE'
#
#Generates a function with the name of _make_config_sub()'s first parameter that
#can B<only> be called one time per Fetchwarefile. If called more than one time
#will die with an error message.
#
#Function created with C<$CONFIG{$name} = $value;> inside the generated function that
#is named $name.
#
#=item * 'ONEARRREF'
#
#Generates a function with the name of _make_config_sub()'s first parameter that
#can B<only> be called one time per Fetchwarefile. And just like C<'ONE'> above
#if called more than once it will throw an exception. However, C<'ONEARRREF'> can
#be called with a list of values just like C<'MANY'> can, but it can still only
#be called once like C<'ONE'>.
#
#=item * 'MANY'
#
#Generates a function with the name of _make_config_sub()'s first parameter that
#can be called more than just once. This option is only used by fetchware's
#C<mirror()> API call.
#
#Function created with C<push @{$CONFIG{$name}}, $value;> inside the generated function that
#is named $name.
#
#=item * 'BOOLEAN'
#
#Generates a function with the name of _make_config_sub()'s first parameter that
#can be called only once just like 'ONE' can be, but it also only support true or
#false values. What is true and false is the same as in perl, with the exception
#that /false/i and /off/i are also false.
#
#Function created the same way as 'ONE''s are, but with /false/i and /off/i
#mutated into a Perl accepted false value (they're turned into zeros.).
#
#=back
#
#=back
#
#All API subroutines fetchware provides to Fetchwarefile's are generated by
#_make_config_sub() except for fetchware() and override().
#
#=cut
my @api_functions = (
[ program => 'ONE' ],
[ filter => 'ONE' ],
[ temp_dir => 'ONE' ],
[ fetchware_db_path => 'ONE' ],
[ user => 'ONE' ],
[ prefix => 'ONE' ],
[ configure_options=> 'ONEARRREF' ],
[ make_options => 'ONEARRREF' ],
[ build_commands => 'ONEARRREF' ],
[ install_commands => 'ONEARRREF' ],
[ uninstall_commands => 'ONEARRREF' ],
[ lookup_url => 'ONE' ],
[ lookup_method => 'ONE' ],
[ gpg_keys_url => 'ONE' ],
[ gpg_sig_url => 'ONE' ],
[ sha1_url => 'ONE' ],
[ md5_url => 'ONE' ],
[ user_agent => 'ONE' ],
[ verify_method => 'ONE' ],
[ mirror => 'MANY' ],
[ no_install => 'BOOLEAN' ],
[ verify_failure_ok => 'BOOLEAN' ],
[ stay_root => 'BOOLEAN' ],
[ user_keyring => 'BOOLEAN' ],
);
# Loop over the list of options needed by _make_config_sub() to generated the
lib/App/Fetchware.pm view on Meta::CPAN
and defined $available_verify_methods{sha1}
and $available_verify_methods{sha1} > 0
) {
msg <<EOM;
SHA1 checksums found. Using SHA1 verification.
EOM
$options{verify_method} = 'sha1';
} elsif (exists $available_verify_methods{md5}
and defined $available_verify_methods{md5}
and $available_verify_methods{md5} > 0
) {
msg <<EOM;
MD5 checksums found. Using MD5 verification.
EOM
$options{verify_method} = 'md5';
} else {
# Print a huge long nasty warning even include links to news stories
# of mirrors actually getting hacked and serving malware, which
# would be detected and prevented with proper verification enabled.
# Ask user if they would like to continue installing fetchware even if
# verification fails, and then enable the verify_failure_ok option.
if (
$term->ask_yn(prompt => <<EOP,
Would you like fetchware to ignore the fact that it is unable to verify the
authenticity of any downloads it makes? Are you ok with possibly downloading
viruses, worms, rootkits, or any other malware, and installing it possibly even
as root?
EOP
default => 'n',
print_me => <<EOP,
Automatic verification of your fetchware package has failed! Fetchware is
capable of ignoring the error, and installing software packages anyway using its
verify_failure_ok configuration option. However, installing software packages
without verifying that they have not been tampered with could allow hackers to
potentially install malware onto your computer. Don't think this is *not*
possible or do you think its extremely unlikely? Well, it's actually
surprisingly common:
1. http://arstechnica.com/security/2012/09/questions-abound-as-malicious-phpmyadmin-backdoor-found-on-sourceforge-site/
Discusses how a mirror for sourceforge was hacked, and the phpMyAdmin
software package on that mirror was modified to spread malware.
2. http://www.geek.com/news/major-open-source-code-repository-hacked-for-months-says-fsf-551344/
Discusses how FSF's gnu.org ftp download site was hacked.
3. http://arstechnica.com/security/2012/11/malicious-code-added-to-open-source-piwik-following-website-compromise/
Discusses how Piwiki's wordpress software was hacked, and downloads of
Piwiki had malicious code inserted into them.
4. http://www.theregister.co.uk/2011/03/21/php_server_hacked/
Discusses how php's wiki.php.org server was hacked yielding credentials to
php's source code repository.
Download mirrors *do* get hacked. Do not make the mistake, and think that it is
not possible. It is possible, and it does happen, so please properly configure
your Fetchwarefile to enable fetchware to verify that the downloaded software is
the same what the author uploaded.
EOP
)
) {
# If the user is ok with not properly verifying downloads, then
# ignore the failure, and install anyway.
$options{verify_failure_ok} = 'On';
} else {
# Otherwise, throw an exception.
die <<EOD;
fetchware: Fetchware *must* be able to verify any software packages that it
downloads. The Fetchwarefile that you were creating could not do this, because
you failed to specify how fetchware can verify its downloads. Please rerun
fetchware new again, and this time be sure to specify a gpg_keys_url, specify
user_keyring to use your own gpg keyring, or answer yes to the question
regarding adding verify_failure_ok to your Fetchwarefile to make failing
verificaton acceptable to fetchware.
EOD
}
}
}
return \%options;
}
sub get_filter_option {
my $term = shift;
# $filename_listing is an array of [$filename, $timestamp] arrays.
my $filename_listing = shift;
msg <<EOS;
Analyzing the lookup_url you provided to determine if fetchware can use it to
successfully determine when new versions of your software are released.
EOS
my $filter;
if (grep {$_->[0] =~ /^(CURRENT|LATEST)[_-]IS[_-].+/} @$filename_listing) {
# There is only one version in the lookup_url directory listing, so
# I do not need a filter option.
msg <<EOS;
* The lookup_url you gave fetchware includes a CURRENT_IS or a LATEST_IS file
that tells fetchware and regular users what the latest version is. Because of
this we can be reasonable sure that a filter option is not needed, so I'll skip
asking for one. You can provide one later if you need to provide one, when
fetchware prompts you for any custom options you may want to use.
EOS
} else {
# There is a CURRENT_IS_<ver_num> or LATEST_IS_<ver_num> file that tells
# you what the latest version is.
###BUGALERT### Why is this line in both sections of the if statement??? Inside
#this else block means that a CURRENT_IS or LATEST-IS was *not* found??? Fix
#this!!!!!!
msg <<EOS;
* The directory listing of your lookup_url has a CURRENT_IS_<ver_num> or
LATEST_IS_<ver_num> file that specifies the latest version, which means that
your program's corresponding Fetchwarefile does not need a filter option. If you
still would like to provide one, you can do so later on, when fetchware allows
you to define any additional configuration options.
EOS
my $what_a_filter_is = <<EOA;
Fetchware needs you to provide a filter option, which is a pattern that fetchware
compares each file in the directory listing of your lookup_url to to determine
which version of your program to install.
Directories will have other junk files in them or even completely different
programs that could confuse fetchware, and even potentially cause it to install
a different program. Therefore, you should also add the program name to the
begining of your filter. For example if you program is apache, then your filter
lib/App/Fetchware.pm view on Meta::CPAN
and is either a bug in fetchware or some sort of race condition.
EOD
# Replace scalar filename with a arrayref of the filename with its
# assocated timestamp for later processing for lookup().
#
# Use Path::Class's file() constructor & basename() method to strip out
# all unneeded directory information leaving just the file's name.
# Add all of the timestamp numbers together, so that only one numberical
# sort is needed instead of a descending list of numerical sorts.
$file = [file($file)->basename(), $mtime ];
}
return $file_listing;
}
sub lookup_by_timestamp {
my $file_listing = shift;
# Sort the timstamps to determine the latest one. The one with the higher
# numbers, and put $b before $a to put the "bigger", later versions before
# the "lower" older versions.
# Sort based on timestamp, which is $file_listing->[0..*][1][0..6].
# Note: the crazy || ors are to make perl sort each timestamp array first by
# year, then month, then day of the month, and so on.
my @sorted_listing = sort { $b->[1] <=> $a->[1] } @$file_listing;
return \@sorted_listing;
}
sub lookup_by_versionstring {
my $file_listing = shift;
# Implement versionstring algorithm.
my @versionstrings;
for (my $i = 0; $i <= $#{$file_listing}; $i++) {
# Split the filename on "Not a numbers", so remove all "not
# numbers", but keep a list of things that actually are numbers.
my @iversionstring = split(/\D+/, $file_listing->[$i][0]);
# Use grep to strip leading empty strings (eg: '').
@iversionstring = grep {$_ ne ''} @iversionstring;
if (@iversionstring == 0) {
# Let the usr know we're skipping this filename, but only if they
# really want to know (They turned on verbose output.).
vmsg <<EOM;
File [$file_listing->[$i][0]] has no version number in it. Ignoring.
EOM
# And also skip adding this @iversionstring to @versionstrings,
# because this @iversionstring is empty, and how do I sort an empty
# array? Return undef--nope causes "value undef in sort fatal errors
# and warnings." Return 0--nope causes a file with no version number
# at beginning of listing to stay at listing, and cause fetchware to
# fail picking the right version. Return -1--nope, because that's
# hackish and lame. Instead, just not include them in the lookup
# listing, and if that means that the lookup listing is empty throw
# an exception.
next;
}
# Add $i's version string to @versionstrings.
push @versionstrings, [$i, @iversionstring];
# And the sort below sorts them into highest number first order.
}
die <<EOD if @versionstrings == 0;
App-Fetchware: The lookup_url your provided [@{[config('lookup_url')]}] does not
have any filenames with detectable version numbers in them. Fetchware's
'versionstring' lookup algorithm depends on files having version numbers in them
such as [httpd-2.2.22.tar.gz] notice the [2.2.22] version number. Fetchware
failed to find any of those in the lookup_url you provided. Consider a different
lookup_url or try switching to the default 'timestamp' lookup algorithm adding
the "lookup_method" configuration option to your Fetchwarefile.
EOD
# LIMITATION: The sort block below can not have any undef values in its
# input. If there are any, then perl will give a warning about a value being
# undef in a sort, if you are not lucky, then it will actually trigger a
# fatal error. There are CPAN Testers reports with this problem, so it really
# can happen. But you do not have to worry about this, because the for loop
# above that creates @versionstrings
@versionstrings = sort {
# Figure out whoose ($b or $a) is larger and set $last_index to it.
my $last_index;
if ($#{$b} > $#{$a}) {
$last_index = $#{$b};
} else {
$last_index = $#{$a};
}
# Loop over the indexes of both $b and $a at the same time comparing
# them one by one with <=>...
# ...and be sure to start at index 1, because index 0 is the index of
# $file_listing that this entry in @versionstrings belongs to...
for my $x (1..$last_index) {
# If one of $b or $a has more numbers in it ($#{$a_or_b} is smaller than
# $x), then if it's $b we should return -1, because $b is smaller
# than $a, and if it's $a, we should return 1, because $b is bigger
# than $a.
return -1 if $x > $#{$b};
return 1 if $x > $#{$a};
my $spaceship_result = $b->[$x] <=> $a->[$x];
# ...and as soon as they no longer equal each other return whatever
# result (-1 or 1) <=> gives.
return $spaceship_result if $spaceship_result != 0;
}
# Return 0 for equal, because if the two versions were not equal, then
# the for loop above would have caught it, and returned the appropriate
# -1 or 1.
return 0;
} @versionstrings;
# Now, "sort" $file_listing into the order @versionstrings was sorted into
lib/App/Fetchware.pm view on Meta::CPAN
}
if (! $retval or $gpg_err) {
msg <<EOM;
Trying SHA1 verification of downloaded package.
EOM
eval {$retval = sha1_verify($download_path, $package_path)};
$sha_err = $@;
if ($sha_err) {
msg <<EOM;
SHA1 verification failed!
SHA1 verificaton error [
$@
]
EOM
warn $sha_err;
}
if (! $retval or $sha_err) {
msg <<EOM;
Trying MD5 verification of downloaded package.
EOM
eval {$retval = md5_verify($download_path, $package_path)};
$md5_err = $@;
if ($md5_err) {
msg <<EOM;
MD5 verification failed!
MD5 verificaton error [
$@
]
EOM
warn $md5_err;
}
}
if (! $retval or $md5_err) {
die <<EOD unless config('verify_failure_ok');
App-Fetchware: run-time error. Fetchware failed to verify your downloaded
software package. You can rerun fetchware with the --force option or add
[verify_failure_ok 'True';] to your Fetchwarefile. See the section VERIFICATION
FAILED in perldoc fetchware.
EOD
}
if (config('verify_failure_ok')) {
warn <<EOW;
App-Fetchware: run-time warning. Fetchware failed to verify the integrity of you
downloaded file [$package_path]. This is ok, because you asked Fetchware to
ignore its errors when it tries to verify the integrity of your downloaded file.
You can also ignore the errors Fetchware printed out abover where it tried to
verify your downloaded file. See perldoc App::Fetchware.
EOW
vmsg <<EOM;
Verification Failed! But you asked to ignore verification failures, so this
failure is not fatal.
EOM
return 'warned due to verify_failure_ok'
}
}
} elsif (config('verify_method') =~ /gpg/i) {
vmsg <<EOM;
You selected gpg cryptographic verification. Verifying now.
EOM
###BUGALERT### Should trap the exception {gpg,sha1,md5}_verify()
#throws, and then add that error to the one here, otherwise the
#error message here is never seen.
gpg_verify($download_path)
or die <<EOD unless config('verify_failure_ok');
App-Fetchware: run-time error. You asked fetchware to only try to verify your
package with gpg or openpgp, but they both failed. See the warning above for
their error message. See perldoc App::Fetchware.
EOD
} elsif (config('verify_method') =~ /sha1?/i) {
vmsg <<EOM;
You selected SHA1 checksum verification. Verifying now.
EOM
sha1_verify($download_path, $package_path)
or die <<EOD unless config('verify_failure_ok');
App-Fetchware: run-time error. You asked fetchware to only try to verify your
package with sha, but it failed. See the warning above for their error message.
See perldoc App::Fetchware.
EOD
} elsif (config('verify_method') =~ /md5/i) {
vmsg <<EOM;
You selected MD5 checksum verification. Verifying now.
EOM
md5_verify($download_path, $package_path)
or die <<EOD unless config('verify_failure_ok');
App-Fetchware: run-time error. You asked fetchware to only try to verify your
package with md5, but it failed. See the warning above for their error message.
See perldoc App::Fetchware.
EOD
} else {
die <<EOD;
App-Fetchware: run-time error. Your fetchware file specified a wrong
verify_method option. The only supported types are 'gpg', 'sha', 'md5', but you
specified [@{[config('verify_method')]}]. See perldoc App::Fetchware.
EOD
}
msg 'Verification succeeded.';
}
sub gpg_verify {
my $download_path = shift;
my $keys_file;
# Attempt to download KEYS file in lookup_url's containing directory.
# If that fails, try gpg_keys_url if defined.
# Import downloaded KEYS file into a local gpg keyring using gpg command.
# Determine what URL to use to download the signature file *only* from
# lookup_url's host, so that we only download the signature from the
# project's main mirror.
# Download it.
# gpg verify the sig using the downloaded and imported keys in our local
# keyring.
# Skip downloading and importing keys if we're called from inside a
# fetchware package, which should already have a copy of our package's
# KEYS file.
unless (config('user_keyring')
lib/App/Fetchware.pm view on Meta::CPAN
return check_config_options(
BothAreDefined => [ [qw(build_commands)],
[qw(prefix configure_options make_options)] ],
Mandatory => [ 'program', <<EOM ],
App-Fetchware: Your Fetchwarefile must specify a program configuration
option. Please add one, and try again.
EOM
Mandatory => [ 'mirror', <<EOM ],
App-Fetchware: Your Fetchwarefile must specify a mirror configuration
option. Please add one, and try again.
EOM
Mandatory => [ 'lookup_url', <<EOM ],
App-Fetchware: Your Fetchwarefile must specify a lookup_url configuration
option. Please add one, and try again.
EOM
ConfigOptionEnum => ['lookup_method', [qw(timestamp versionstring)] ],
ConfigOptionEnum => ['verify_method', [qw(gpg sha1 md5)] ],
);
}
sub check_config_options {
my @args = @_;
my @both_are_defined;
my @mandatory;
my @config_option_enum;
# Process arguments, and check that they were specified correctly.
# Loop over @args 2 at a time hence the $i += 2 instead of $i++.
for( my $i = 0; $i < @args; $i += 2 ) {
my( $type, $AnB ) = @args[ $i, $i+1 ];
die <<EOD unless ref $AnB eq 'ARRAY';
App-Fetchware: check_config_options()'s even arguments must be an array
reference. Please correct your arguments, and try again.
EOD
die <<EOD unless @$AnB == 2;
App-Fetchware: check_config_options()'s even arguments must be an array
reference with exactly two elements in it. Please correct and try again.
EOD
if ($type eq 'BothAreDefined') {
push @both_are_defined, $AnB;
} elsif ($type eq 'Mandatory') {
push @mandatory, $AnB;
} elsif ($type eq 'ConfigOptionEnum') {
push @config_option_enum, $AnB;
} else {
die <<EOD;
App-Fetchware: check_config_options() only supports types 'BothAreDefined',
'Mandatory', and 'ConfigOptionEnum.' Please specify one of these, and try again.
EOD
}
}
# Process @both_are_defined by checking if both of the elements in the
# provided arrayrefs are "both defined", and if they are "both defined"
# throw an exception.
for my $AnB (@both_are_defined) {
my ($A, $B) = @$AnB;
my @A_defined;
my @B_defined;
# Check which ones are defined in both $A and $B
{
# the config() call will call the specified strings of which many
# are expected to be uninitialized. Because we expect them to be
# uninitialized, we use that behavior to determine if they have been
# specified in the users Fetchwarefile, and if an option was not
# specified, then undef is returned by config(). Since, we expect
# lots of undef warnings, we'll disable them.
no warnings 'uninitialized';
@A_defined = grep {config($_)} @$A;
@B_defined = grep {config($_)} @$B;
}
if (@A_defined > 0 and @B_defined > 0) {
die <<EOD;
App-Fetchware: Your Fetchwarefile has incompatible configuration options.
You specified configuration options [@$A] and [@$B], but these options are not
compatible with each other. Please specifiy either [@$A] or [@$B] not both.
EOD
}
}
# Process @mandatory options by checking if they're defined, and if not
# throwing the specified exception.
for my $AnB (@mandatory) {
my ($option, $error_message) = @$AnB;
die $error_message if not defined config($option);
}
# Process @config_option_enum.
for my $AnB (@config_option_enum) {
my ($option, $enumerations) = @$AnB;
# Ditch uninitialized warnings, because I'm using undef to mean
# unspecified, so undef is not something unexpected to bother warning
# about, but something that will happen all the time.
{
no warnings 'uninitialized';
# Only test the @enumerations if $option was specified in the
# Fetchwarefile.
if (config($option)) {
# Only one @enumerations should equal $option not more than one, hence
# the == 1 part.
die <<EOD unless (grep {config($option) eq $_} @$enumerations) == 1;
App-Fetchware: You specified the option [$option], but failed to specify only
one of its acceptable values [@$enumerations]. Please change the value you
specified [@{[config($option)]}] to one of the acceptable ones listed above, and try again.
EOD
}
}
}
return 'Syntax Ok';
}
sub end {
# Use cleanup_tempdir() to cleanup your tempdir for us.
cleanup_tempdir();
}
1;
lib/App/Fetchware.pm view on Meta::CPAN
php.Fetchwarefile: Failed to chdir to git repo at
[$git_repo_dir].
OS error [$!].
EOD
# Pull latest changes from php git repo.
run_prog('git pull');
# First determine latest version that is *not* a development version.
# And chomp off their newlines.
chomp(my @tags = `git tag`);
# Now sort @tags for only ones that begin with 'php-'.
@tags = grep /^php-/, @tags;
# Ditch release canidates (RC, alphas and betas.
@tags = grep { $_ !~ /(RC\d+|beta\d+|alpha\d+)$/ } @tags;
# Sort the tags to find the latest one.
# This is quite brittle, but it works nicely.
@tags = sort { $b cmp $a } @tags;
# Return $download_path, which is only just the latest tag, because that's
# all I need to know to download it using git by checking out the tag.
my $download_path = $tags[0];
return $download_path;
};
# Just checkout the latest tag to "download" it.
hook download => sub {
my ($temp_dir, $download_path) = @_;
# The latest tag is the download path see lookup.
my $latest_tag = $download_path;
# checkout the $latest_tag to download it.
run_prog('git checkout', "$latest_tag");
my $package_path = cwd();
return $package_path;
};
# You must manually add php's developer's gpg keys to your gpg keyring. Do
# this by going to the page: http://us1.php.net/downloads.php . At the
# bottom the gpg key "names are listed such as "7267B52D" or "5DA04B5D."
# These are their key "names." Use gpg to download them and import them into
# your keyring using: gpg --keyserver pgp.mit.edu --recv-keys [key id]
hook verify => sub {
my ($download_path, $package_path) = @_;
# the latest tag is the download path see lookup.
my $latest_tag = $download_path;
# Run git verify-tag to verify the latest tag
my $success = eval { run_prog('git verify-tag', "$latest_tag"); 1;};
# If the git verify-tag fails, *and* verify_failure_ok has been turned on,
# then ignore the thrown exception, but print an annoying message.
unless (defined $success and $success) {
unless (config('verify_failure_ok')) {
msg <<EOM;
Verification failure ok, becuase you've configured fetchware to continue even
if it cannot verify its downloads. Please reconsider, because mirror and source
code repos do get hacked. The exception that was caught was:
[$@]
EOM
}
}
};
hook unarchive => sub {
# there is nothing to archive due to use of git.
do_nothing(); # But return the $build_path, which is the cwd().
my $build_path = $git_repo_dir;
return $build_path;
};
# It's a git tag, so it lacks an already generated ./configure, so I must use
# ./buildconf to generate one. But it won't work on php releases, so I have to
# force it with --force to convince ./buildconf to run autoconf to generate the
# ./configure program to configure php for building.
build_commands './buildconf --force', './configure', 'make';
# Add any custom configure options that you may want to add to customize
# your build of php, or control what php extensions get built.
#configure_options '--whatever you --need ok';
# start() creates a tempdir in most cases this is exactly what you want, but
# because this Fetchwarefile is using git instead. I don't need to bother with
# creating a temporary directory.
hook start => sub {
# But checkout master anyway that way the repo can be in a known good state
# so lookup()'s git pull can succeed.
run_prog('git checkout master');
};
# Switch the local php repo back to the master branch to make using it less
# crazy. Furthermore, when using git pull to update the repo git uses what
# branch your on, and if I've checked out a tag, I'm not actually on a branch
# anymore; therefore, I must switch back to master, so that the git pull when
# this fetchwarefile is run again will still work.
hook end => sub {
run_prog('git checkout master');
};
=back
=head2 MariaDB Database
This example MariaDB Fetchwarefile parses the MariaDB download page to determine
what the latest version is based on what C<filter> option you set up. Once this
is determined, the download path is created based on the weird path that MariaDB
uses on its mirrors.
Like PHP MariaDB uses some annoying software on their Web site to presumably
track downloads. This software makes use of AJAX, which is vastly beyone the
lib/App/Fetchware.pm view on Meta::CPAN
that the archive will not overwrite any system files, and contains no absolute
paths that could cause havok on your system. unarchive_package() does the actual
unarchiving of software packages.
=item L<build()'s OVERRIDE_BUILD export tag.|build() API REFERENCE>
Provides run_star_commands(), which is meant to execute common override commands
that fetchware provides with the C<build_commands>, C<install_commands>, and
C<uninstall_commands> configuration file directives. These directives are of
type C<ONEARRREF> where they can only be called once, but you can supply a comma
separated list of commands that fetchware will install instead of the standard
commands default AutoTools commands (build() => ./configure, make; install() =>
make install; uninstall() => ./configure, make uninstall). See its
L<documentation|run_star_commands(config('*_commands'));> for more details.
=item L<install()'s OVERRIDE_INSTALL export tag.|install() API REFERENCE>
install() only exports chdir_unless_already_at_path(), which is of limited use.
install() also uses build()'s run_star_commands().
=item uninstall()'s OVERRDIE_UNINSTALL export tag.
uninstall() actually has no exports of its own, but it does make use of
build() and install()'s exports.
=item upgrade()'s OVERRIDE_UNINSTALL export tag.
upgrade() also has no exports of its own, and does not use anyone elses.
=back
=back
=head2 Write your fetchware extension's documentation
Fill in all of the skeleton's missing POD to ensure that fetchware extension has
enough documentation to make it easy for user's to use your fetchware extension.
Be sure to document:
=over
=item * All of Perl's standard POD sections such as SYNOPSIS, DESCRIPTION, AUTHOR, and all of the others. See L<perlpodstyle> for more details.
=item * Give each subroutine its own chunk of POD before it explaining its arguments, any App::Fetchware configuration options it uses, and what its return value is.
=item * Be sure to document both its external interface, its Fetchwarefile, and its internal interface, what subroutines it has and uses.
=back
=head2 Write tests for your fetchware extension
Use perls venerable Test::More, and whatever other Perl TAP testing modules you
need to be sure your fetchware extension works as expected.
L<Test::Fetchware/> has a few testing subroutines that fetchware itself uses
in its test suite that you may find helpful. These include:
=over
=item L<Test::Fetchware/eval_ok()> - A poor man's Test::Exception. Captures any
exceptions that are thrown, and compares them to the provided exception text or
regex.
=item L<Test::Fetchware/print_ok()> - A poor man's Test::Output. Captures
STDOUT, and compares it to the provided text.
=item L<Test::Fetchware/skip_all_unless_release_testing()> - Fetchware is a
package manager, but who wants software installed on their computer just to test
it? This subroutine marks test files or subtests that should be skipped unless
fetchware's extensive FETCHWARE_RELEASE_TESTING environement variables are set.
This funtionality is described next.
=item L<Test::Fetchware/make_clean()> - Just runs C<make clean> in the current
directory.
=item L<Test::Fetchware/make_test_dist()> - Creates a temporary distribution
that is used for testing. This temporary distribution contains a C<./configure>
and a C<Makefile> that create no files, but can still be executed in the
standard AutoTools way.
=item L<Test::Fetchware/md5sum_file()> - Just md5sum's a file so verify() can be
tested.
=item L<Test::Fetchware/expected_filename_listing()> - Returns a string of crazy
Test::Deep subroutines to test filename listings. Not quite as useful as the
rest, but may come in handy if you're only changing the front part of lookup().
=back
Your tests should make use of fetchware's own C<FETHWARE_RELEASE_TESTING>
environment variable that controls with the help of
skip_all_unless_release_testing() if and where software is actually installed.
This is done, because everyone who installs fetchware or your fetchware
extension is really gonna freak out if its test suite installs apache or ctags
just to test its package manager functionality. To use it:
=over
=item 1. Set up an automated way of enabling FETCHWARE_RELEASE_TESTING.
Just paste the frt() bash shell function below. Translating this to your
favorite shell should be pretty straight forward. Do not just copy and paste it.
You'll need to customize the specific C<FETCHWARE_*> environment variables to
whatever mirrors you want to use or whatever actual programs you want to test
with. And you'll have to point the local (file://) urls to directories that
actually exist on your computer.
# Sets FETCHWARE_RELEASE_TESTING env vars for fully testing fetchware.
frt() {
if [ -z "$FETCHWARE_RELEASE_TESTING" ]
then
echo -n 'Setting fetchware_release_testing environment variables...';
export FETCHWARE_RELEASE_TESTING='***setting this will install software on your computer!!!!!!!***'
export FETCHWARE_FTP_LOOKUP_URL='ftp://carroll.cac.psu.edu/pub/apache/httpd'
export FETCHWARE_HTTP_LOOKUP_URL='http://www.apache.org/dist/httpd/'
export FETCHWARE_FTP_MIRROR_URL='ftp://carroll.cac.psu.edu/pub/apache/httpd'
export FETCHWARE_HTTP_MIRROR_URL='http://mirror.cc.columbia.edu/pub/software/apache//httpd/'
export FETCHWARE_FTP_DOWNLOAD_URL='ftp://carroll.cac.psu.edu/pub/apache/httpd/httpd-2.2.26.tar.bz2'
export FETCHWARE_HTTP_DOWNLOAD_URL='http://mirrors.ibiblio.org/apache//httpd/httpd-2.2.26.tar.bz2'
export FETCHWARE_LOCAL_URL='file:///home/dly/software/httpd-2.2.22.tar.bz2'
export FETCHWARE_LOCAL_ZIP_URL='file:///home/dly/software/ctags-zip/ctags58.zip'
lib/App/Fetchware.pm view on Meta::CPAN
=back
new() uses Term::UI, which in turn uses Term::ReadLine to implement the
character based question and anwser wizard interface. A Term::ReadLine/Term::UI
object is passed to new() as its first argument. new() also uses
L<App::Fetchware::Fetchwarefile> to help create and help generate the
Fetchwarefile for the user.
new()'s argument is the program name that the user has specified on the command
line. It will be undef if the user did not specify one on the command line.
Whatever scalars (not references just regular strings) that new() returns will
be shared with new()'s sister API subroutine new_install() that is called after
new() is called by cmd_install(), which implements fetchware's new command.
new_install() is called in the parent process, so it does have root permissions,
so be sure to test it as root as well.
=over
=item drop_privs() NOTES
This section notes whatever problems you might come accross implementing and
debugging your Fetchware extension due to fetchware's drop_privs mechanism.
See L<Util's drop_privs() subroutine for more info|App::Fetchware::Util/drop_privs()>.
=over
=item *
This subroutine is B<not> run as root; instead, it is run as a regular user
unless the C<stay_root> configuration option has been set to true.
=back
=back
=head2 new() API REFERENCE
Below are the API routines that new() uses to create the question and answer
interface for helping to build new Fetchwarefiles and fetchware packages.
=head3 extension_name();
extension_name(__PACKAGE__);
This subroutine sets the name of the extension that this implementation of new()
wants to be called by. It should be C<App::FetchwareX::ExtensionName> the full
name of your extension to make looking it up in documentaiton easier.
All of new()'s API subroutines (everything in App::Fetchware's OVERRIDE_NEW
export tag) use extension_name() to deterime what the this extension should be
called. This is really only used in error messages and occasionally in some of
the questions that new's API subroutines will ask the user. But this subroutine
is important, because it allows extension authors to change all of the
C<App::Fetchware> references in the error messages to their own fetchware
extensions name.
extension_name() is a singleton, and can only be set once. After being set only
once any attempts to set it again will result in an exception being thrown.
Furthermore, any calls to it without arguments will result in it returning the
one scalar argument that was set the first time it was called.
=head3 opening_message();
opending_message($opening_message);
opening_message() takes the specified $opening_message and just prints it to
C<STDOUT>. This subroutine may seem useless, you could just use print, but using
it instead of print helps document that what you're doing is printing the
opening message to the user.
=head3 fetchwarefile_name();
# $fetchwarefile_name comes from the first command line option to fetchware
# new: fetchware new $fetchwarefile_name, which is optional, hence the need
# for fetchwarefile_name().
sub new {
my $fetchwarefile_name = shift;
my $program_name = fetchwarefile_name($term, program => $fetchwarefile_name)
If when the user called C<fetchware new $fetchwarefile_name>, if they provided a
$fetchwarefile_name name on the command line, then fetchwarefile_name() simply
returns what the user provided, but if they did not, then $fetchwarefile_name in
the call to fetchwarefile_name() is going to be undef. In that case,
fetchwarefile_name() uses the provided Term::UI in the provided $term variable,
to ask the user what the name of their fetchwarefile should be, which it returns
back to the caller.
=head3 get_lookup_url()
my $lookup_url = get_lookup_url($term);
Uses $term argument as a L<Term::ReadLine>/L<Term::UI> object to interactively
explain what a lookup_url is, and to ask the user to provide one and press
enter.
=head3 download_lookup_url()
my $filename_listing = download_lookup_url($term, $lookup_url);
Attempts to download the lookup_url the user provides. Returns it after parsing
it using parse_directory_listing() from L<App::Fetchware> that lookup() itself
uses.
=head3 get_mirrors()
my $mirrors_hashref = get_mirrors($term, $filename_listing);
# $mirrors_hashref = (
# mirrors => [
# 'ftp://some.mirror/mirror',
# 'http://some.mirror/mirror',
# 'file://some.mirror/mirror',
# ],
# );
Asks the user to specify at least one mirror to use to download their archives.
It also reiterates to the user that the C<lookup_url> should point to the
lib/App/Fetchware.pm view on Meta::CPAN
=over
=item Configuration subroutines used:
=over
=item lookup_url
=item lookup_method
=item filter
=item user_agent
=back
=back
Accesses C<lookup_url> as a http/ftp directory listing, and uses C<lookup_method>
to determine what the newest version of the software is available. This is
combined with the path given in C<lookup_url>, and return as $download_path for
use by download().
=over
=item LIMITATIONS
C<lookup_url> is a web browser like URL such as C<http://host.com/a/b/c/path>,
and it B<must> be a directory listing B<not> a actual file. This directory
listing must be a listing of all of the available versions of the program this
Fetchwarefile belongs to.
Only ftp://, http://, and file:// URL scheme's are supported.
And the HTML directory listings Apache and other Web Server's return I<are>
exactly what lookup() uses to determine what the latest version available for
download is.
=back
=over
=item drop_privs() NOTES
This section notes whatever problems you might come accross implementing and
debugging your Fetchware extension due to fetchware's drop_privs mechanism.
See L<Util's drop_privs() subroutine for more info|App::Fetchware::Util/drop_privs()>.
=over
=item *
Under drop_privs() lookup() is executed in the child with reduced privileges.
=back
=back
C<lookup_method> can be either C<'timestamp'> or C<'versionstring'>, any other
values will result in fetchware throwing an exception.
=head2 lookup() API REFERENCE
The subroutines below are used by lookup() to provide the lookup functionality
for fetchware. If you have overridden the lookup() handler, you may want to use
some of these subroutines so that you don't have to copy and paste anything from
lookup.
App::Fetchware is B<not> object-oriented; therefore, you B<can not> subclass
App::Fetchware to extend it!
=head3 get_directory_listing()
my $directory_listing = get_directory_listing();
Downloads a directory listing that lookup() uses to determine what the latest
version of your program is. It is returned.
=over
=item SIDE EFFECTS
get_directory_listing() returns a SCALAR REF of the output of HTTP::Tiny
or a ARRAY REF for Net::Ftp downloading that listing. Note: the output is
different for each download type. Type 'http' will have HTML crap in it, and
type 'ftp' will be the output of Net::Ftp's dir() method.
Type 'file' will just be a listing of files in the provided C<lookup_url>
directory.
=back
=head3 parse_directory_listing()
my $file_listing = parse_directory_listing($directory_listing);
Based on URL scheme of C<'file'>, C<'http'>, or C<'ftp'>,
parse_directory_listing() will call file_parse_filelist(), ftp_parse_filelist(),
or http_parse_filelist(). Those subroutines do the heavy lifting, and the
results are returned.
=over
=item SIDE EFFECTS
parse_directory_listing() returns to a array of arrays of the filenames and
timestamps that make up the directory listing.
=back
=head3 determine_download_path()
my $download_path = determine_download_path($filename_listing);
Runs the C<lookup_method> to determine what the lastest filename is, and that
one is then concatenated with C<lookup_url> to determine the $download_path,
which is then returned to the caller.
Also calls lookup_determine_downloadpath() to determine the actual download path
from the $sorted_filename_listing returned by C<lookup_by_*()>.
=over
lib/App/Fetchware.pm view on Meta::CPAN
=back
=back
Verifies the downloaded package stored in $package_path by downloading
$download_path.{asc,sha1,md5}> and comparing the two together. Uses the
helper subroutines C<{gpg,sha1,md5,digest}_verify()>.
=over
=item LIMITATIONS
Uses gpg command line, and the interface to gpg is a little brittle.
Crypt::OpenPGP is buggy and not currently maintainted again, so fetchware cannot
make use of it, so were stuck with using the command line gpg program.
=back
=over
=item drop_privs() NOTES
This section notes whatever problems you might come accross implementing and
debugging your Fetchware extension due to fetchware's drop_privs mechanism.
See L<Util's drop_privs() subroutine for more info|App::Fetchware::Util/drop_privs()>.
=over
=item *
Under drop_privs() verify() is executed in the child with reduced privileges.
=back
=back
=head2 verify() API REFERENCE
The subroutines below are used by verify() to provide the verify
functionality for fetchware. If you have overridden the verify() handler, you
may want to use some of these subroutines so that you don't have to copy and
paste anything from verify().
App::Fetchware is B<not> object-oriented; therefore, you B<can not> subclass
App::Fetchware to extend it!
=head3 gpg_verify()
'Package Verified' = gpg_verify($download_path);
Uses the command-line program C<gpg> to cryptographically verify that the file
you download is the same as the file the author uploaded. It uses public-key
priviate-key cryptography. The author signs his software package using gpg or
some other OpenPGP compliant program creating a digital signature file with the
same filename as the software package, but usually with a C<.asc> file name
extension. gpg_verify() downloads the author's keys, imports them into
fetchware's own keyring unless the user sets C<user_keyring> to true in his
Fetchwarefile. Then Fetchware downloads a digital signature that usually
ends in C<.asc>. Afterwards, fetchware uses the gpg command line program to
verify the digital signature. gpg_verify returns true if successful, and throws
an exception otherwise.
You can use C<gpg_keys_url> to specify the URL of a file where the author has
uploaded his keys. And the C<gpg_sig_url> can be used to setup an alternative
location of where the C<.asc> digital signature is stored.
=head3 sha1_verify()
'Package verified' = sha1_verify($download_path, $package_path);
undef = sha1_verify($download_path, $package_path);
Verifies the downloaded software archive's integrity using the SHA Digest
specified by the C<sha_url 'ftp://sha.url/package.sha'> config option. Returns
true for sucess dies on error.
=over
=item SECURITY NOTE
If an attacker cracks a mirror and modifies a software package, they can also
modify the MD5 sum of that software package on that B<same mirror>. Because of
this limitation MD5 sums can only tell you if the software package was corrupted
while downloading. This can actually happen as I've had it happen to me once.
If your stuck with using MD5 sum, because your software package does not provide
gpg signing, I recommend that you download your SHA1 sums (and MD5 sums) from
your software package's master mirror. For example, Apache provides MD5 and SHA1
sums, but it does not mirror them--you must download them directly from Apache's
servers. To do this specify a C<sha1_url 'master.mirror/package.sha1';> in your
Fetchwarefile.
=back
=head3 md5_verify()
'Package verified' = md5_verify($download_path, $package_path);
undef = md5_verify($download_path, $package_path);
Verifies the downloaded software archive's integrity using the MD5 Digest
specified by the C<md5_url 'ftp://sha.url/package.sha'> config option. Returns
true for sucess and dies on error.
=over
=item SECURITY NOTE
If an attacker cracks a mirror and modifies a software package, they can also
modify the MD5 sum of that software package on that B<same mirror>. Because of
this limitation MD5 sums can only tell you if the software package was corrupted
while downloading. This can actually happen as I've had it happen to me once.
If your stuck with using MD5 sum, because your software package does not provide
gpg signing, I recommend that you download your MD5 sums (and SHA1 sums) from
your software package's master mirror. For example, Apache provides MD5 and SHA1
sums, but it does not mirror them--you must download them directly from Apache's
servers. To do this specify a C<md5_url 'master.mirror/package.md5';> in your
Fetchwarefile.
=back
=head3 digest_verify()
lib/App/Fetchware.pm view on Meta::CPAN
=head3 list_files()
# Remember $files is a array ref not a regular old scalar.
my $files = list_files($package_path;
list_files() takes $package_path as an argument to a tar'ed and compressed
package or to a zip package, and calls C<list_files_{tar,zip}()> accordingly.
C<list_files_{tar,zip}()> in turn uses either Archive::Tar or Archive::Zip to
list all of the files in the archive and return a arrayref to that list.
=head3 list_files_tar()
my $tar_file_listing = list_files_tar($path_to_tar_archive);
Returns a arrayref of file names that are found in the given, $path_to_tar_archive,
tar file. Throws an exception if there is an error.
It uses C<Archive::Tar-E<gt>iter()> to avoid reading the entire tar archive
into memory.
=head3 list_files_zip()
my $zip_file_listing = list_files_zip($path_to_zip_archive);
Returns a arrayref of file names that are found in the given, $path_to_zip_archive,
zip file. Throws an exception if there is an error.
=head3 unarchive_package()
unarchive_package($format, $package_path)
unarchive_package() unarchive's the $package_path based on the type of $format
the package is. $format is determined and returned by list_archive(). The
currently supported types are C<tar> and C<zip>. Nothing is returned, but
C<unarchive_{tar,zip}()> is used to to use Archive::Tar or Archive::Zip to
unarchive the specified $package_path.
=head3 unarchive_tar()
my @extracted_files = unarchive_tar($path_to_tar_archive);
Extracts the given $path_to_tar_archive. It must be a tar archive. Use
unarchive_zip() for zip archives. It returns a list of files that it
extracted.
=head3 unarchive_zip()
'Extraced files successfully.' = unarchive_zip($path_to_zip_archive);
Extraces the give $path_to_zip_archive. It must be a zip archive. Use
unarchive_tar() for tar archives. It I<only> returns true for success. It
I<does not> return a list of extracted files like unarchive_tar() does, because
Archive::Zip's extractTree() method does not.
=head3 check_archive_files()
my $build_path = check_archive_files($files);
Checks if all of the files in the archive are contained in one B<main>
directory, and spits out a warning if they are not. Also checks if
B<one or more> of the files is an absolute path, and if so it throws an
exception, because absolute paths could potentially overwrite important system
files messing up your computer.
=head2 build()
'build succeeded' = build($build_path)
=over
=item Configuration subroutines used:
=over
=item prefix
=item configure_options
=item make_options
=item build_commands
=back
=back
Changes directory to $build_path, and then executes the default build
commands of C<'./configure', 'make', 'make install'> unless your Fetchwarefile
specifies some build options like C<build_commands 'exact, build, commands';>,
C<make_options '-j4';>, C<configure_options '--prefix=/usr/local';>, or
C<prefix '/usr/local'>.
You can only specify C<build_commands> or any of the other three build options.
You cannot combine C<build_commands> with any of the other build options.
=over
=item LIMITATIONS
build() like install() inteligently parses C<build_commands>, C<prefix>,
C<make_options>, and C<configure_options> by just using Test::Parsewords to
parse out the string considering quotes, and then execute them using fetchware's
built in run_prog() to properly support -q.
Also, simply executes the commands you specify or the default ones if you
specify none. Fetchware will check if the commands you specify exist and are
executable, but the kernel will do it for you and any errors will be in
fetchware's output.
=back
=over
=item drop_privs() NOTES
This section notes whatever problems you might come accross implementing and
debugging your Fetchware extension due to fetchware's drop_privs mechanism.
See L<Util's drop_privs() subroutine for more info|App::Fetchware::Util/drop_privs()>.
=over
lib/App/Fetchware.pm view on Meta::CPAN
=back
=back
Executes C<make install>, which installs the specified software, or executes
whatever C<install_commands 'install, commands';> if its defined.
install() takes $build_path as its argument, because it must chdir() to this
path if fetchware drops privileges.
=over
=item LIMITATIONS
install() like build() inteligently parses C<install_commands> by C<split()ing>
on C</,\s+/>, and then C<split()ing> again on C<' '>, and then execute them
fetchware's built-in run_prog(), which supports -q.
Also, simply executes the commands you specify or the default ones if you
specify none. Fetchware will check if the commands you specify exist and are
executable, but the kernel will do it for you and any errors will be in
fetchware's output.
=back
=over
=item drop_privs() NOTES
This section notes whatever problems you might come accross implementing and
debugging your Fetchware extension due to fetchware's drop_privs mechanism.
See L<Util's drop_privs() subroutine for more info|App::Fetchware::Util/drop_privs()>.
=over
=item *
install() is run in the B<parent> process as root, because most server programs
must be installed as root. install() must be called with $build_path as its
argument, because it may need to chdir to $build_path, the same path that
build() built the package in, in order to be able to install the program.
=back
=back
=head2 install() API REFERENCE
Below are the subroutines that install() exports with its C<INSTALL_OVERRIDE>
export tag. fillmein!!!!!!!!
=head3 chdir_unless_already_at_path()
chdir_unless_already_at_path() takes a $path as its argument, and determines if
that path is currently part of the current processes current working directory.
If it is, then it does nothing. Buf if the given $path is not in the current
working directory, then it is chdir()'d to.
If the chdir() fails, an exception is thrown.
=head2 uninstall()
'uninstall succeeded' = uninstall($build_path)
=over
=item Configuration subroutines used:
=over
=item uninstall_commands
=back
=back
Cd's to $build_path, and then executes C<make uninstall>, which installs the
specified software, or executes whatever C<uninstall_commands 'uninstall, commands';>
if its defined.
=over
=item LIMITATIONS
uninstall() like install() inteligently parses C<install_commands> by C<split()ing>
on C</,\s+/>, and then C<split()ing> again on C<' '>, and then execute them
fetchware's built-in run_prog(), which supports -q.
Also, simply executes the commands you specify or the default ones if you
specify none. Fetchware will check if the commands you specify exist and are
executable, but the kernel will do it for you and any errors will be in
fetchware's output.
=back
=over
=item drop_privs() NOTES
This section notes whatever problems you might come accross implementing and
debugging your Fetchware extension due to fetchware's drop_privs mechanism.
See L<Util's drop_privs() subroutine for more info|App::Fetchware::Util/drop_privs()>.
=over
=item *
uninstall() is run in the B<parent> process as root, because most server
programs must be uninstalled as root. uninstall() must be called with
$build_path as its argument, because it may need to chdir to $build_path, the
same path that build() built the package in, in order to be able to uninstall
the program.
=back
=back
=head2 upgrade()
lib/App/Fetchware.pm view on Meta::CPAN
temporary directory is available for use.
=back
=back
=head2 check_syntax() API REFERENCE
check_syntax() uses check_config_options() to do the heavy lifting of Set Theory
to check that you do not use some configuration options that are not compatible
with other ones that you have used.
=head3 check_config_options()
check_config_options(
BothAreDefined => [ [qw(build_commands)],
[qw(prefix configure_options make_options)] ],
Mandatory => [ 'program', <<EOM ],
App-Fetchware: Your Fetchwarefile must specify a program configuration
option. Please add one, and try again.
EOM
Mandatory => [ 'mirror', <<EOM ],
App-Fetchware: Your Fetchwarefile must specify a mirror configuration
option. Please add one, and try again.
EOM
Mandatory => [ 'lookup_url', <<EOM ],
App-Fetchware: Your Fetchwarefile must specify a lookup_url configuration
option. Please add one, and try again.
EOM
ConfigOptionEnum => ['lookup_method', [qw(timestamp versionstring)] ],
ConfigOptionEnum => ['verify_method', [qw(gpg sha1 md5)] ],
);
Uses config() to test that no configuration options that clash with each other
are used.
It's parameters are specified in a list with an even number of parameters. Each
group of 2 parameters specifies a type of test that check_config_options() will
test for. There are three types of tests. Also, note that the parameters are
specified as a list not as a hash, because multiple "keys" are allowed, and hash
keys must be unique; therefore, the parameters are a list instead of a hash.
=over
=item BothAreDefined
Is used to test if one or more elements from both provided arrayrefs are defined
at the same time. This is needed, because if you specify C<build_commands> any
value you specify for C<prefix>, C<configure_options>, C<make_options> will be
ignored, which may not be what you expect or want, so BothAreDefined is used to
check for this.
Also, unlike C<Mandatory> and C<ConfigOptionEnum> this syntax checker does not
take a string argument that specifies an error message, because it takes the two
other values that you specifiy, and uses them to fill in its own error message.
=item Mandatory
Is used to check for mandatory options, which just means that these options
absolutely must be specified in user's Fetchwarefiles, and if they are not, then
the provided error message is thrown as an exception.
=item ConfigOptionEnum
Tests that enumerations are valid. For example, C<lookup_method> can only take
two values C<timestamp> or C<versionstring>, and ConfigOptionEnum is used to
test for this.
=back
=head2 end()
end();
=over
=item Configuration subroutines used:
=over
=item none
=back
=back
end() is called after all of the other main fetchware subroutines such as
lookup() are called. It's job is to cleanup after everything else. It just calls
cleanup_tempdir(), which mostly just closes the C<fetchware.sem> fetchware
semaphore file used to lock each fetchware temporary directory so C<fetchware
clean> does not delete it out from under an concurrently running Fetchware
process.
It also calls the very internal only __clear_CONFIG() subroutine that clears
App::Fetchware's internal %CONFIG variable used to hold your parsed
Fetchwarefile.
=over
=item EXTENSION OVERRIDE NOTES
end() calls L<App::Fetchware::Util>'s cleanup_tempdir() subroutine that cleans
up the temporary directory. If your fetchware extension overrides end() or
start(), you must call cleanup_tempdir() or name your temproary directories
in a manner that fetchware clean won't find them, so something that does not
start with C<fetchware-*>.
If you fail to do this, and you use some other method to create temporary
directories that begin with C<fetchware-*>, then fetchware clean may delete your
temporary directories out from under your feet. To fix this problem:
=over
=item *
Use L<App::Fetchware::Util>'s create_tempdir() in your start() and
cleanup_tempdir() in your end().
=item *
Or, be sure not to name your temprorary directory that you create and manage
lib/App/Fetchware.pm view on Meta::CPAN
needless abstration that would just make fetchware more complicated. It is a
simple command line program, so it should be written as one.
Moose was out, because I don't need any of its amazing features. I could use
those amazing features, but fetchware's simple nature does not demand them.
Also, unless Moose and its large chunk of dependencies are in your OS's file
system cache, Moose based command line apps take a few seconds to start,
because perl has to do a bunch of IO on slow disks to read in Moose and its
dependencies. I don't want to waste those few seconds. Plus fetchware is a
program not intended for use by experienced Perl developers like dzil is, which
does use Moose, and has a few second start up overhead, which is acceptable to
its author and maintainer. I also use it, and I'm ok with it, but those few
seconds might be off putting to users who have no Perl or Moose knowledge.
=head2 What's up with the insane fetchware extension mechanism?
Extension systems are always insane. dzil, for example, uses a configuration
file where you list names of dzil plugins, and for each name dzil loads that
plugin, and figures out what dzil roles it does, then when dzil executes any
commands you give it, dzil executes all of those roles that the plugins
registered inside you configuration file.
I wanted a configuration file free of declaring what plugins you're using. I
wanted it to be easy to use. dzil is for Perl programmers, so it requiring some
knowledge of Perl and Moose is ok. But fetchware is for end users or perhaps
system administrators not Perl programmers, so something easier is needed.
The extension mechanism was designed for ease of use by people who use your
fetchware extension. And it is. Just "use" whatever fetchware extension you want
in your Fetchwarefile, and then supply whatever configuration options you
need.
This extension mechanism is also very easy for Perl programmers, because you're
basically I<subclassing> App::Fetchware, only you do it using
L<App::Fetchware::ExportAPI> and L<App::Fetchware::CreateConfigOptions>. See
section L<Implement your fetchware extension.> for full details.
=head2 How do I fix the verification failed error.
Fetchware is designed to always attempt to verify the software archives it
downloads even if you failed to configure fetchware's verification settings. It
will try to guess what those setting should be using simple heuristics. First it
will try gpg verificaton, then sha1 verification, and finally md5 verification.
If all fail, then fetchware exit failure with an appropriate error message.
When you get this error message
L<read fetchware's documentation on how to set this up|/4. Add mandatory verification settings>.
=head2 How do I make fetchware log to a file instead of STDOUT?
You can't fetchware does not have any log file support. However, you can simply
redirect STDOUT to a file to make your shell redirect STDOUT to a file for you.
fetchware install <some-program.Fetchwarefile> > fetchware.log
This would not prevent any error messages from STDERR being printed to your
screen for that:
fetchware install <some-program.Fetchwarefile> 2>&1 fetchware.log
And to throw away all messages use:
fetchware -q install <some-progra.Fetchwarefile>
or use the shell
fetchware install <some-program.Fetchwarefile 2>&1 /dev/null
=head2 Why don't you use Crypt::OpenPGP instead of the gpg command line program?
I tried to use Crypt::OpenPGP, but I couldn't get it to work. And getting gpg to
work was a breeze after digging through its manpage to find the right command
line options that did what I need it to.
Also, unfortunately Crypt::OpenPGP is buggy, out-of-date, and seems to have
lost another maintainer. If it ever gets another maintainer, who fixes the newer
bugs, perhaps I'll add support for Crypt::OpenPGP again. Because of how
fetchware works it needs to use supported but not popular options of
Crypt::OpenPGP, which may be where the bugs preventing it from working reside.
Supporting Crypt::OpenPGP is still on my TODO list. It's just not very high on
that list. Patches are welcome to add support for it, and the old code is still
there commented out, but it needs updating if anyone is interested.
In the meantime if you're on Windows without simple access to a gpg command line
program, try installing gpg from the L<gpg4win project|http://gpg4win.org/>,
which packages up gpg and a bunch of other tools for easier use on Windows.
=head2 Does fetchware support Windows?
Yes and no. I intend to support Windows, but right now I'm running Linux, and my
Windows virtual machine is broken, so I can't easily test it on Windows. The
codebase makes heavy use of File::Spec and Path::Class, so all of its file
operations should work on Windows.
I currently have not tested fetchware on Windows. There are probably some test
failures on Windows, but Windows support should be just a few patches away.
So the real answer is no, but I intend to support Windows in a future release.
=head2 How do I configure a HTTP proxy for use with Fetchware?
Fetchware uses Perl's L<HTTP::Tiny> module to download files over HTTP. So, all
you need to do is configure HTTP::Tiny for use with proxies. This is done with
environment variables that can easily be set from your shell.
Set C<http_proxy> in the format C<http://host:port>. You can do this permanantly
for this session with:
export http_proxy='http://example.com:8080'
Or once just for this invocation of fetchware:
http_proxy='http://example.com:8080 fetchware new
See your OS's shell for more details regarding using and exporting environment
variables.
=head2 How do I get Fetchware to I<not> delete its temporary directory, so I can troubleshoot a fetchware failure.
Use fetchware's C<-v> (verbose) and C<--keep-temp> command-line options. C<-v>