App-Fetchware

 view release on metacpan or  search on metacpan

bin/fetchware  view on Meta::CPAN

        # Also, add cmdline options to control what to do when this happens???
        vmsg 'Determining which command to run based on command line options.';
        my $command;
        @ARGV ? ($command = shift @ARGV) : ($command = '');
        if ($command eq 'install') {
            cmd_install(@ARGV);
        } elsif ($command eq 'uninstall') {
            cmd_uninstall(@ARGV);
        } elsif ($command eq 'new') {
            cmd_new(@ARGV);
        } elsif ($command eq 'upgrade') {
            cmd_upgrade(@ARGV);
        } elsif ($command eq 'upgrade-all') {
            cmd_upgrade_all(@ARGV);
        } elsif ($command eq 'list') {
            cmd_list(@ARGV);
        } elsif ($command eq 'look') {
            cmd_look(@ARGV);
        } elsif ($command eq 'clean') {
            cmd_clean(@ARGV);
        } elsif ($command eq 'help') {
            cmd_help(@ARGV);
        } else {
            cmd_help(@ARGV);
        }
        # Exit success, because if any of the main subroutines run into any
        # problems they die() exceptions, which get caught in eval above, and
        # warn()ed below, and fetchware exits 1 for failure.
        vmsg 'Fetchware ran successfully! Exiting with status of 0 for success!';
        exit 0;
    };
    # If a fatal error was thrown print it to STDERR and exit indicating failure.
    if ($@) {
        # Set File::Temp's $KEEP_ALL so user can troubleshoot what happend
        # without having to bother to use --keep-all.
        $File::Temp::KEEP_ALL = 1;
        msg <<EOM;
Fetchware threw an exception! Exiting with an exit status of 1 for failure.
Fetchware failed inside directory [@{[cwd()]}].
EOM
        warn $@;
        exit 1;
    }
}





###BUGALERT### cmd_install() does *not* actually do this. Consider implementing
#it.
#If no filename was
#provided or the filename doesn't exist then, cmd_install() calls new() to create
#and install a new fetchware package.


sub cmd_install {
    # These variables must be shared back to the parent from the child using
    # pipe_{write,read}_newline().
    my $P_build_path;
    ###BUGALERT### After verifying basic functionality of cmd_install wrap
    #subroutine contents in a for my $filename (pop @ARGV) loop to try to
    #install all given arguments that arn't command line options as parsed by
    #GetOpt::Long.
    ### Add this loop in run(), so there is just one loop to test.
    my $filename = shift;
    
    my $output;
    if (defined($filename) and -e $filename) {

    msg "Starting fetchware install to install [$filename]";

    # If a fpkg extract out the Fetchwarefile into a scalar, and if not a
    # fetchware package to go ahead and open for reading only the Fetchware
    # right now while we're perhaps still root, so we can be sure we can
    # still access it.
    my $fetchwarefile;
    if ($filename =~ /\.fpkg$/) {
        $fetchwarefile = extract_fetchwarefile($filename);
        vmsg <<EOM;
Extracting out Fetchwarefile from [$filename] to [$$fetchwarefile]
EOM
    } else {
        my $fh = safe_open($filename, <<EOD);
fetchware: Fetchware failed to open the filename you specified to fetchware
install [$filename]. The OS error was [$!].
EOD
        vmsg "Opened file [$filename] for slurping.";
        # Add a \ to turn the slurped scalar into a scalar ref for calling
        # parse_fetchwarefile() properly.
        $fetchwarefile = \do {local $/; <$fh>};
        vmsg  "Slurped [$filename] into fetchware: [$$fetchwarefile]";
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";


    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

        # Drop privs, so only install() and  end() are called with root perms
        $output = drop_privs(
        sub {
            my $write_pipe = shift;

            # Run the App::Fetchware API subroutines to do everything to install
            # the program, but be mindful of drop_privs() requiring this coderef
            # to use write_dropprivs_pipe() to communicate needed changes back to
            # the parent process, for example, $P_build_path--the parent needs to
            # chdir() to that directory before it tries to execute install().

            ###BUGALERT### install installs no matter if the program is already
            #installed!!! Change this to parse out the package from the
            #download_urlif possible, compare with the one in the fetchware
            #package database, and call exit right here if the current version
            #is already installed unless of course --force is used!!!
            my $download_url = lookup();

            my $package_path = download($temp_dir, $download_url);

            ###BUGALERT### Add support for caching the key files gpg creates to
            #the fetchwarefile, and for actually using them later on inside the
            #fpkg.
            verify($download_url, $package_path);

            $P_build_path = unarchive($package_path);

            build($P_build_path);

            # Tell the parent, root, process the values of the variables the
            # child calculated in this coderef, and write them across this pipe
            # back to the parent
            write_dropprivs_pipe($write_pipe, $P_build_path);
        }, config('user')
        ); # End drop_privs().

        # Read from the pipe the child, the drop_privs()ed process, writes to to
        # read the necessary values that correspond to the variables that the
        # child must communicate back to the parent, so the parent can continue
        # processing as though no fork()ing or priv dropping took place.
        ($P_build_path)
            = read_dropprivs_pipe($output);

        my $installed_fetchware_package_path;
        if (not config('no_install')) {
            install($P_build_path);

            vmsg "Creating Fetchware package from [@{[cwd()]}].";
            my $fetchware_package_path
                =
                create_fetchware_package($fetchwarefile, cwd());
            vmsg "Created fetchware package at [$fetchware_package_path].";

            vmsg 'Installing created fetchware package to fetchware database.';
            $installed_fetchware_package_path
                = copy_fpkg_to_fpkg_database($fetchware_package_path);
            vmsg <<EOM;
Installed created fetchware package to [$installed_fetchware_package_path]
EOM
        }

        end();

        # Return the path of the created and installed fetchware package.
        return $installed_fetchware_package_path;
    } else {
        ###BUGALERT### Replace with warn for proposed for loop above to work???
        die <<EOD;
fetchware: You called fetchware install incorrectly. You must also specify
either a Fetchwarefile or a fetchware package that ends with [.fpkg].
EOD
    }
}




sub cmd_uninstall {

    my $uninstall_package_path = shift;

    msg "Uninstalling specified package [$uninstall_package_path]";

    my $fetchware_package_path

bin/fetchware  view on Meta::CPAN

fetchware packages please try fetchware's list command: fetchware list
EOD
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

    # "Download" the package using File::Copy's cp().
    my $package_path;
    if (cp($fetchware_package_path, $temp_dir)) {
        # Determine the output file that cp() used.
        ###BUGALERT### Open the file for cp(), and provide cp() with a
        #filehandle to write the data to to ensure the filename is exactly
        #what it needs to be.
        $package_path = catfile($temp_dir,
            file($fetchware_package_path)->basename());
    } else {
        die <<EOD;
fetchware: Fetchware failed to copy the file [$fetchware_package_path] to the
destination directory [$temp_dir]. OS error [$!].
EOD
    }


    vmsg "Copied installed package to temporary directory at [$package_path]";

    my $build_path = unarchive($package_path);

    uninstall($build_path);

    end();

    vmsg 'Uninstalling fetchware package from fetchware database.';
    uninstall_fetchware_package_from_database($fetchware_package_path);

    msg "Uninstalled fetchware package [$uninstall_package_path].";
    # Return the name of the uninstalled package's full path fetchware's
    # database.
    return $fetchware_package_path;
}



###BUGALERT### Move cmd_new() before install()?????
###BUGALERT### Print out fetchware's assumptions it makes about what FTP & hTTP
#lookup_url's look like, versionstring's assumptions, timestamp's assumptions,
#verify's assumptions, and so on. If not here in new() at least do it in the
#POD documentation.
###BUGALERT### Support ~/.Fetchwarefile, or whatever File::HomeDir wants it to
#be. Test if ~/.Fetchwarefile exists, if it does do nothing, but if it does not
#exist then prompt the user to fill one out!!!

############BUGALERT########################BUGALERT##################
############BUGALERT########################BUGALERT##################
###BUGALERT### Modify analyze_lookup_listing() to print the directory listing
#for the user to peruse, and have the user choose what program they want to
#install from the listing. Then use that as the basis for the filter option.
#httpd-2.4.1.tar.bz2 would simply be m/(\w+?)[.-_\d]+?/ And $1 is the filter
#option. If the match fails to the existing manual crap.
############BUGALERT########################BUGALERT##################
############BUGALERT########################BUGALERT##################

###BUGALERT### Add a command line option to fetchware new that allows users to
#only edit a blank file (Have helpful comments about what options are must
#haves), and then have fetchware ask the user if they would like to then install
#that new Fetchwarefile.
###BUGALERT### Rename lookup_url to main_mirror or master_mirror or author_mirror
#or something to better implicitly explain what it is.
###BUGALERT### Add the check to see if there is a MIRRORS file or some similar
#regex, and if so parse it, and auto add it to the list of mirrors? Is this
#really feasible?


sub cmd_new {

    # These are the variables that the child must share back with the parent.
    my $program_name = shift; # The child might change or define it again.

    my $term = Term::ReadLine->new('Fetchware new');

    # Must ask user what App::Fetchware extension they are going to create a
    # Fetchwarefile for, so I can load that extension's new() and new_install()
    # API subroutines, because *no* API subroutines are available until
    # parse_fetchwarefile() is called in cmd_install(), and I can't call
    # parse_fetchwarefile() before the user has answered the questons to
    # actually create a Fetchwarefile.
    my $fetchware_extension = $term->get_reply(
        print_me => <<EOP,
Unless you're using a Fetchware extension, press enter to continue along in the
creation of your new Fetchwarefile. If you are using a Fetchware extension,
please enter its name without the 'App::FetchwareX::' prefix.
EOP
        prompt => q{Unless you're using a Fetchware extension press enter to use default?},
        default => 'App::Fetchware',
    );

    ###Security Note### Whatever string the user supplies will be given
    #to Module::Load's load() subroutine, and then forwarded on to
    #Perl's require() function, which parses and executes it as far as
    #loading it goes, and then whatever new() and new_install()
    #subroutines will be imported in the current package, and later one
    #executed below new() with dropped privs, and new_install() as root
    #if fun as root. You may consider this a security hole as it is
    #never a good idea to execute user specified code, but considering
    #later on cmd_new() via new() will ask the user if they want to edit
    #the Fetchwarefile, where they can put whatever Perl code in it they
    #want to, and then ask_to_install_now_to_test_fetchwarefile() will

bin/fetchware  view on Meta::CPAN

    msg "Upgrading installed fetchware package [$upgrade_name].";

    $P_fetchware_package_path = determine_fetchware_package_path($upgrade_name);
    vmsg <<EOM;
Determined already installed fetchware package's path to be [$P_fetchware_package_path].
EOM

    # Parse out the Fetchwarefile from the fetchware package stored in the
    # fetchware database directory.
    my $fetchwarefile;
    if ($P_fetchware_package_path =~ /\.fpkg$/) {
        $fetchwarefile
            = extract_fetchwarefile($P_fetchware_package_path);            
        vmsg "Extracted Fetchwarefile temporarily into [$fetchwarefile]";
    } else {
        die <<EOD;
fetchware: fetchware upgrade failed to extract the Fetchwarefile from the
fetchware package that should be stored in fetchware's database.
EOD
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

    # Drop privs, so only install() is called with root permissions
    my $output = drop_privs(
    sub {
        my $write_pipe = shift;

        ###BUGALERT### Have lookup() replace the timestamp of what we should
        #download too to make upgrade() be able to use the lookup_by_timestamp
        #algorithm too, which is a better default anyway.
        $P_download_path = lookup();

        # Call upgrade() to determine if the currently available version
        # ($P_download_path) is newer than the currenlty installed version
        # ($P_fetchware_package_path).
        my $P_upgrade = upgrade($P_download_path, $P_fetchware_package_path);

        if ($P_upgrade) {
            msg 'New version available upgrading now.';

            my $package_path = download($temp_dir, $P_download_path);

            ###BUGALERT### Add support for caching the key files gpg creates to the
            #fetchwarefile, and for actually using them later on inside the fpkg.
            verify($P_download_path, $package_path);

            $P_build_path = unarchive($package_path);
            build($P_build_path);
        } else {
            # If a new version is not available, then the child should do
            # nothing, and let the parent call end() to clean up below.

            # Set $P_build_path to something that will fail, and give a decent
            # error message just in case.
            $P_build_path = 'Build Path not set because upgrade not needed.';
        }

        # Tell the parent, root, process the values of the variables the
        # child calculated in this coderef, and write them across this pipe
        # back to the parent
        write_dropprivs_pipe($write_pipe,
            $P_upgrade,
            $P_build_path,
            $P_download_path,
            $P_fetchware_package_path
        );
    }, config('user')
    ); # End drop_privs()

    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    ($P_upgrade,
    $P_build_path,
    $P_download_path,
    $P_fetchware_package_path) = read_dropprivs_pipe($output);

    # Test if a new version is available again due to drop_priv() ending
    # half way through this if statement.
    if ($P_upgrade) {
        install($P_build_path);

        my $updated_fetchware_package_path
            =
            create_fetchware_package($fetchwarefile, cwd());
        vmsg <<EOM;
Created a new fetchware package for the newly installed upgraded fetchware
package [$updated_fetchware_package_path].
EOM

        uninstall_fetchware_package_from_database($P_fetchware_package_path);
        vmsg 'Uninstalled the old fetchware package from the fetchware database.';

        my $installed_fetchware_package_path
            = copy_fpkg_to_fpkg_database($updated_fetchware_package_path);
        vmsg <<EOM;
Installed new fetchware package to fetchware package database
[$installed_fetchware_package_path].
EOM

        end();

        # Return the path of the created and installed fetchware package.
        return $installed_fetchware_package_path;

bin/fetchware  view on Meta::CPAN


###BUGALERT### Fix the bug that prevents look from check for an installed
#package first, then a filename or fetchwarefile.
sub cmd_look {
    my $filename = shift;

    my $P_look_path;

    my $fetchwarefile;
    if ($filename =~ /\.fpkg$/) {
        $fetchwarefile = extract_fetchwarefile($filename);
        vmsg <<EOM;
Extracting out Fetchwarefile from [$filename] to [$$fetchwarefile]
EOM
    } else {
        my $fh = safe_open($filename, <<EOD);
fetchware: Fetchware failed to open the filename you specified to fetchware
install [$filename]. The OS error was [$!].
EOD
        vmsg "Opened file [$filename] for slurping.";
        # Add a \ to turn the slurped scalar into a scalar ref for calling
        # parse_fetchwarefile() properly.
        $fetchwarefile = \do {local $/; <$fh>};
        vmsg  "Slurped [$filename] into fetchware: [$$fetchwarefile]";
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    #
    # Call start() with an option to have it keep the temp dir, and not
    # have File::Temp clean it up with an END handler.
    my $temp_dir = start(KeepTempDir => 1);

    # Drop privs to match up with cmd_install()'s behavior.
    my $output = drop_privs(
    sub {
        my $write_pipe = shift;

        msg 'Downloading and unarchiving specified distribution.';

        my $download_url = lookup();

        my $package_path = download(cwd(), $download_url);

        ###BUGALERT### Add support for caching the key files gpg creates to
        #the fetchwarefile, and for actually using them later on inside the
        #fpkg.
        verify($download_url, $package_path);

        my $build_path = unarchive($package_path);

        # end() is *not* run, because the point of look is to lookup,
        # download, and unarchive, and then actually "look" at the files,
        # and running end() would delete them.

        # Compose the $P_look_path. A simple catfile($temp_dir, $build_path)
        # should work, but don't forget about drop_privs() extra temporary
        # directory when run as root! To avoid this problem of the $P_look_path
        # being wrong when run as root due to the extra temporary directory;
        # instead, of a simple catfile(...) replace the last file portion of
        # $package_path, with $build_path.
        $P_look_path = catfile(dir($package_path)->parent(), $build_path);
        msg <<EOM;
Your package's contents are at [$P_look_path]. Please run [fetchware clean] to
delete these files and any other files fetchware may have left behind when you
are finished looking inside this package.
EOM

        # Tell the parent, root, process the values of the variables the
        # child calculated in this coderef, and write them across this pipe
        # back to the parent
        write_dropprivs_pipe($write_pipe, $P_look_path);

        # Does not need to execute anything as root, because cmd_look() does not
        # install anything or even call end(), because the suer is supposed to
        # look at its output in the tempdir it prints out.
    }, config('user')
    ); # End drop_privs()

    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    ($P_look_path)
        = read_dropprivs_pipe($output);

    return $P_look_path;
}



sub cmd_list {
    my @installed_packages = glob catfile(fetchware_database_path(), '*');
    
    if (@installed_packages == 0) {
        msg 'No fetchware packages are currently installed.';
        return;
    }

    msg 'Listing all currently installed packages:';
    for my $fetchware_package (@installed_packages) {
        # Clean up $fetchware_package.
        $fetchware_package = file($fetchware_package)->basename();
        $fetchware_package =~ s/\.fpkg$//;

        msg $fetchware_package;
    }
}

bin/fetchware  view on Meta::CPAN

OPTIONS:
    --help|-h|-? - prints this help message.
    --version|-V - prints a version message.
    --verbose|-v - prints additional logging information.
    --quiet|-q - prints *no* logging invormation. Determine success or
        failure with fetchware's exit status. 0 = success. Non-zero = failure.

For more information see perldoc fetchware and perldoc App::Fetchware.
HELP

    ###BUGALERT###Consider actually adding dry run functionality.
    #--dry-run|-d - turns on dry run functionality causing fetchware to not
    #actually download or install or create any packages.
	exit 0;
}







sub parse_fetchwarefile {
    my $fetchwarefile = shift;

    # Arg $fetchwarefile must be a SCALAR ref.
    die <<EOD unless ref($fetchwarefile) eq 'SCALAR';
fetchware: parse_fetchwarefile() was called with the wrong arguments. It only
accepts and scalar references of the text of your fetchwarefile.
EOD

    # Ensure the $fetchwarefile has a use App::Fetchware somewhere in it. And be
    # sure to support fetchware extensions such as App::FetchwareX::HTMLPageSync.
    die <<EOD unless $$fetchwarefile =~ /^\s*use\s+App::FetchwareX?(::)?/m;
fetchware: The fetchwarefile you provided did not have a [use App::Fetchware]
line in it. This line is required, because it is an important part of how
fetchware uses Perl for its configuration file. Your fetchware file was.
[$$fetchwarefile]
EOD

    # Do the potentially evil eval. No Safe compartment or use ops is used. This
    # is one gigantic security hole; however, it is also how fetchware works :)
    #
    # safe_open() is used to ensure that the file the user provides is "safe" to
    # use, and is the limit of fetchware's safety features.
    eval $$fetchwarefile;

    die <<EOD if $@;
fetchware: run-time error. fetchware failed to execute the Fetchwarefile
[$$fetchwarefile] you provieded on the command line or that was packaged
with your Fetchware package (*.fpkg). The error was [$@].
EOD


    # Ensure that the specified App::Fetchware implementation exports the proper
    # subroutines.
    my %api_subs = (
        start => 1,
        lookup => 1,
        download => 1,
        verify => 1,
        unarchive => 1,
        build => 1,
        install => 1,
        uninstall => 1,
        upgrade => 1,
        check_syntax => 1,
        end => 1,
    );

    # Determine if all of the @api_subs are in sublist, the list of all subs in
    # the current package.
    # Code adapted from Perl Cookbook pg. 129.
    my (%union, %intersection);
    for my $element (keys %api_subs, sublist()) {
        $union{$element}++ && $intersection{$element}++;
    }

    # Compares the number of %intersection's to the number of %api_subs, and if
    # they're *not* equal throw an exception, so the user knows which API subs
    # are not set up right.
    if ( (grep {exists $api_subs{$_} and exists $intersection{$_}
                and $api_subs{$_} eq $intersection{$_}}
                keys %api_subs) != scalar keys %api_subs) {
        my @missing_api_subs;
        for my $api_sub (keys %api_subs) {
            if (not exists $intersection{$api_sub}
                or not defined $intersection{$api_sub}
                or ($intersection{$api_sub} == 0)
            ) {
                push @missing_api_subs, $api_sub;
            }
        }
        die <<EOD;
fetchware: The App::Fetchware module you choose in your fetchwarefile does not
properly export the necessary subroutines fetchware needs it to. These include:
start(), lookup(), download(), verify, unarchive(), build(), install(),
uninstall(), and end().
The missing subroutines are [@missing_api_subs].
EOD
    }

    # Call App::Fetchware's check_syntax() (or a App::Fetchware extension's).
    check_syntax();

    return 'Evaled config file successfully';
}



sub create_fetchware_package {
    my ($fetchwarefile,
        $unarchived_package_path,
        $dir_for_new_fpkg) = @_;

    # chdir() to my cwd's parent directory, because my cwd is currently on linux
    # /tmp/fetchware-kd883ejfe/program-1.2, and I need the program-1.2 part to
    # be in the archive's @file_list. This needs to happen even when dropping
    # privs, because drop_privs() chdir()'s before it forks putting both parent
    # and child in the same directory.
    my $previous_cwd = cwd();
    my $new_dir = dir(cwd())->parent();
    chdir($new_dir) or die <<EOD;
fetchware: run-time error. Fetchware failed to change it's working
directory to
[$new_dir] from [$previous_cwd]. The os error was [$!].
EOD

    # Turn something like /tmp/fetchware-djdjkd8382/package-1.2/Fetchware (with
    # the "Fetchwarefile" filename only sometimes being there) into just
    # "package-1.2"
    my $pc = dir($unarchived_package_path);
    my $last_dir = $pc->dir_list(-1, 1);
    my $fetchware_package_name = "$last_dir.fpkg";

    # The dir the new fpkg goes in is the current working directory, or a user
    # provided alternate path to store it in.
    $dir_for_new_fpkg //= cwd();
    # Calculate the full absolute path of the fetchware package I create below.
    my $fetchware_package_full_path
        =
        catfile($dir_for_new_fpkg, $fetchware_package_name);

    # Determine @file_list, because Archive::Tar does not just automatically
    # include *all* files like bin/tar does.
    my @file_list;
    find(sub {
            push @file_list, $File::Find::name;
        }, $unarchived_package_path);

    # Convert absolute filenames into relative filenames, because Archive::Tar
    # will use the exact filenames that you provide, so I need to remove the
    # unneeded machine specific paths from the paths that will be stored in the
    # fetchware package.
    $_ = abs2rel($_) for @file_list;

    my $tar = Archive::Tar->new();

bin/fetchware  view on Meta::CPAN


    fetchware upgrade <name of already installed program>

    # Use fetchware list to see a list of already installed programs.
    fetchware list

=head2 L<Upgrade B<all> installed fetchware packages.|/upgrade-all>

    fetchware upgrade-all

=head2 L<Uninstall an installed fetchware package.|/uninstall>

    # Requires a "uninstall" make target, or customization of its Fetchwarefile
    # to specify what specific C<uninstall_commands> will uninstall this package.
    fetchware uninstall <name of already installed program>

=head2 L<List all installed fetchware packages.|/list>

    fetchware list 

    # Pipe to grep if you want to search for something specific.
    fetchware list | grep <something specific>

=head2 L<"Look" inside a fetchware package.|/look>

    fetchware look <name-of-program.fpkg> | <name-of-program.Fetchwarefile>

=head2 Put this in your /etc/cron.daily to make fetchware check for updates every night

    #!/bin/sh
    # Update all already installed fetchware packages.
    fetchware upgrade-all

=head2 Or use crontab -e to put this in a user crontab if you don't want to fetchware system wide

    # Check for updates using fetchware every night at 2:30AM.
    # Minute   Hour   Day of Month     Month          Day of Week     Command    
    # (0-59)  (0-23)     (1-31)  (1-12 or Jan-Dec) (0-6 or Sun-Sat)
        30      2          *              *               *           fetchware upgrade-all

=head1 MOTIVATION

While sysadmining I liked to install my own compiled from source versions of
popular programs like Apache, MySQL, or Perl without threading. However, doing
so means that you have to manually recompile everytime a new security hole comes
out, which is annoyingly frequent for Apache. So, fetchware was created to bring
the power of package management to source code distributions.

=head1 DESCRIPTION

Fetchware is a package manager for source code distributions. It takes advantage
of the fact that coincidentially I<most> source code distributions follow the same
conventions. Most use FTP and HTTP mirrors. Most use AutoTools or at least just
a few commands that you execute in sequence to configure, build, and install the
program.

Fetchware harnesses these conventions to create a powerful and flexible package
manager for source code distributions. It includes a simple, powerful, and
flexible configuration syntax stored in files called C<Fetchwarefile>s. These
C<Fetchwarefile>s specify the required mandatory configuration options,
C<program>, C<lookup>, C<mirror>, and a method of verifying your program. And
they also specify any additional optional configuration options.

To create a new Fetchwarefile to install a source code distribution use the
L<fetchware new|/new> command. It will ask you a bunch of questions, and based
on your answers and fetchware's assumptions fetchware will automagically create
a new Fetchwarefile for you. Then it will ask if you would like fetchware to
install it for you.

If your source code distribution exceeds fetchware's new command's capabilities,
then see the section L<MANUALLY CREATING A App::Fetchware FETCHWAREFILE> in
L<App::Fetchware>. It details how to create a Fetchwarefile manually in a text
editor of your choice.

Fetchware's commands are described next followed by its options. Following that
is the section L<HOW FETCHWARE WORKS>, which describes in some detail how
Fetchware does its magic, and documents how it all fits together.

See L<App::Fetchware> for more information on fetchware's Fetchwarefile syntax:

=over

=item *

L<App::Fetchware/"MANUALLY CREATING A App::Fetchware FETCHWAREFILE"> - Describes
how to create a appropriate Fetchwarefile manually using a text editor. This
can be skipped. You should try fetchware's L<new command|/new> first.

=item *

L<App::Fetchware/"USING YOUR App::Fetchwarefile WITH FETCHWARE"> - Shows how to
use your newly created fetchwarefile with fetchware.

=item *

L<App::Fetchware/"App::Fetcwhare'S FETCHWAREFILE CONFIGURATION OPTIONS"> - Details
all of fetchware's configuration options that you can use to further customize
your Fetchwarefile.

=item *

L<App::Fetchware/"FURTHER CUSTOMIZING YOUR FETCHWAREFILE"> - Shows you how to use
embed Perl inside your Fetchwarefile to change fetchware's behavior as needed
to make fetchware work with programs that use different conventions and
assumptions that fetchware makes.

=item *

L<App::Fetchware/"EXAMPLE FETCHWAREFILES"> - Details how to customize a
Fetchwarefile for popular programs such as Apache, Nginx, PHP, MariaDB, and
Postgres.

=item *

L<App::Fetchware/"CREATING A FETCHWARE EXTENSION"> - Details how to replace the
module that implements fetchware's behavior, App::Fetchware, with a completely
different module implementing completely different behavior. These fetchware
extensions can even be shared with everyone else on CPAN. See
L<App::FetchwareX::HTMLPageSync> for an example.

=back

bin/fetchware  view on Meta::CPAN

list of already installed fetchware packages run C<fetchware list>, or pipe it
through L<grep(1)>

    fetchware list | grep <keyword>

=head2 upgrade-all

    fetchware upgrade-all

C<upgrade-all> takes no arguments. Instead, it loops over the list of installed
programs as shown in C<fetchware list> and runs C<upgrade> on each one to upgrade all
currently installed programs.

=head2 uninstall

C<uninstall> removes all components of a currently installed program.
Afterwards, that program won't show up in a C<fetchware list> anymore.

=over

=item B<WARNING>

C<uninstall> is only capable of uninstalling programs that maintain a
C<uninstall> make target. For example, C<ctags> has a C<make uninstall>, while
Apache does not; therefore, without a prefix, fetchware can uninstall ctags, but
it cannot uninstall Apache.

The easiest way to be able to uninstall a program you install with fetchware
that does not have a C<make uninstall> is to use the C<prefix> configuration
option to use a separate prefix that everything is installed into this
directory. Then you could specify a custom C<uninstall_commands> that would
delete everything in that directory:

    # Set prefix so apache can be easily uninstalled.
    prefix '/usr/local/apache';

    # Set uninstall_commands to delete everything in the prefix directory when
    # apache is uninstalled.
    uninstall_commands 'rm -r /usr/local/apache';

Then when you uninstall apache, fetchware deletes its associated files, which
may include your Web site's Web files, so back them up before hand if you need to
keep them.

The other way around this limitation is to use one of the following programs
that use a cool C<LD_PRELOAD> trick to watch what files C<make install> or its
equivelent copy, and where they are copied to. Then these files are put into
some sort of vendor-specific package such as apt-get or rpm.

=over

=item L<checkinstall|http://www.debian-administration.org/articles/147>

Run like C<checkinstall make install> will detect what files are copied where
during installation, and will create a slackware, debian, or redhat package
based on this information.

=item L<paco|http://paco.sourceforge.net/>

Provides very similar functionality to fetchware, but lacks fetchware's lookup
and verify mechanisms. Includes its own package management functionality.

=back

=back

As far as fetchware one day supporting some sort of hack like checkinstall or
paco use, I'm against it. I'd prefer everyone just adding a C<make uninstall> to
their Makefiles. But it is on my todo list, and I may add similar functionality
in the future, but I'll make no promises. Until then consider using the
C<prefix> and C<uninstall_commands> hack.

=head2 list

    fetchware list

    fetchware list | grep <what are you looking for?>

C<list> just prints out the names of all fetchware packages that have been
installed. It takes no arguments, and currently does not support listing only
packages that match a certain criteria. However, you can just pipe it to
L<grep(1)> to using a regex to limit which packages you're looking for.

=head2 look

    fetchware look <package name>

C<look> looks up the specified program using your C<lookup_url>, downloads it,
verifies it, and unarchives it. Then it prints out the location of the
unarchived program, so you can take a look at its code, or install it manually
if you would like to.

=head2 clean

    fetchware clean

C<clean> deletes all fetchware temporary files and directories to clean up your
system temporary directory.

You can also specify one or more arguments to C<fetchware clean> to specify what
directories you want fetchware to search for fetchware's left over temp files to
clean up.

=head2 help

Prints out a brief screen full of help messages reminding you of fetchware's
command-line syntax.

=head1 OPTIONS

Fetchware's configuration file options are detailed below.

Most of its options are stored in its configuration file. If none of these
options suite what you need fetchware to do, consider using its Fetchwarefile
to meet your needs. See
L<App::Fetchware/"App::Fetchware'S FETCHWAREFILE CONFIGURATION OPTIONS>

=head2 -v or --verbose

    fetchware -v install <some-program.Fetchwarefile>

bin/fetchware  view on Meta::CPAN

Prints out a short message and says what version of fetchware is running.

=head2 -h or -? or --help

Prints out a brief screen full of help messages reminding you of fetchware's
command-line syntax.

=head1 HOW FETCHWARE WORKS

Fetchware works by having fetchware, the C<bin/fetchware> file and
App::Fetchware Perl package, do all of the "package manager" stuff: 

=over

=item *

Creating fetchware packages (create_fetchware_package())

=item *

Copying fetchware packages to the fetchware database
(copy_fpkg_to_fpkg_database())

=item *

Creating and managing the fetchware database
(determine_fetchware_package_path(), extract_fetchwarefile(),
and fetchware_database_path())

=item *

uninstalling installed packages from the fetchware database
(uninstall_fetchware_package_from_database())

=back

Fetchware I<delegates> all of the specifics on how to install, upgrade, and
uninstall the fetchware packages that fetchware manages to App::Fetchware or a
App::Fetchware extension:

=over

=item *

Implement Fetchware's new command's Q&A wizard interface (new() and new_install())

=item *

Checking Fetchwarefile's high-level syntax before execution (check_syntax())

=item *

Lookup to see if a new version is available (lookup())

=item *

Downloading the archive (download())

=item *

Verifying that the downloaded file is the same one the author uploaded (verify())

=item *

Unarchiving the package (unarchive())

=item *

Building and installing it (build() and install())

=item *

Uninstalling any already installed fetchware package (uninstall())

=item *

Determining if a newer version is available (upgrade())

=item *

Some before and after hooks (start() and end()).

=back

=head2 How fetchware's commands work

Fetchware's commands work by using fetchware's API, described in the section
L<INTERNAL LIBRARY SUBROUTINES>, to manage the package manager stuff. And
fetchware I<delegates> the heavy lifting of the steps needed to install,
upgrade, and uninstall fetchware packages to L<App::Fetchware> or a
L<App::Fetchware extension|App::Fetchware/"CREATING A FETCHWARE EXTENSION">.

=over

=item new

C<new> just asks the user a bunch of questions, and gives them an opportunity to
answer questions. Then it uses your answers to generate a Fetchwarefile for you,
so that you don't have to mess with creating one manually in a text editor.

=item install

Fetchware's install runs whatever fetchware API subroutines it needs to use, see
the section L<INTERNAL LIBRARY SUBROUTINES> for more. Then, install() will parse
a user provided Fetchwarefile or a Fetchwarefile fetchware finds in a fetchware
package. The act of parsing the Fetchwarefile will import the App::Fetchware API
subroutines into fetchware's namespace. This gives fetchware access to
App::Fetchwares API or whatever extension may have been used. Then, the API
subroutines are run providing whatever arguments they need and storing whatever
their important return values may be in a variable to probably later be given to
a later API subroutine as an argument.

=item upgrade

Cleverly reusues the same API subroutines that install uses, but in the middle
of all that uses the upgrade() API subroutine to determine if a newer version is
available. The upgrade() API subroutine allows Fetchware extensions to modify
how Fetcwhare determines if a new version is available to support using git or
something else to determine this.

=item uninstall

Uninstall parses the Fetcwharefile of the installed pacakges you specified. Then
it runs whatever C<uninstall_commands> you specified or the default,
C<make uninstall> if you specified none. Then the installed package is deleted
from the fetchware database.

=item list

List just globs all files in the fetchware database directory as returned by
fetchware_database_path(), and prints them to STDOUT. It does not let you
specify a Perl regex, or a keyword or anything yet, because I'm currently unsure
about the security ramifications of doing so. This feature may be added in the
future.

=item look

look just does the first part of install(). It parses whatever Fetchwarefile it
gets passed to it, then it does the start(), lookup(), download(), verify(), and
unarchive() parts of install(). Then look prints the path of this directory, and
exits.

=item clean

Clean just deletes all fetchware temp files and directories in the system
temp_dir. These files and directories all start with C<fetchware-*> or
C<Fetchwarefile-*>.

=item help

Just prints a simple, short, concise help message.

=back

=head2 How fetchware interfaces with App::Fetchware

Fetchware interfaces with App::Fetchware using the parse_fetchwarefile() API
subroutine. This subroutine simply eval()'s your Fetchwarefile and traps any
errors, and then rethrows that exception adding a helpful message about what
happened in addition to passing along the original problem from Perl.

The act of eval()ing your Fetchwarefile causes Perl to parse and execute as it
would any other Perl program. Only because its inside an eval any subroutines
that are imported are imported in the the caller of eval()'s package. In this
case fetchware.

Fetchware takes advantage of this by requiring all Fetchwarefile's to have a
C<use App::Fetchware...;> line. This line is what imports the default imports of
App::Fetchware into fetchware, which include App::Fetchware's API subroutines.

=head2 How fetchware intefaces with a fetchware extension

As explained above parse_fetchwarefile() eval()'s your Fetchwarefile, and this
causes Perl to parse and execute it. And any imports are imported into the
caller's package, which is fetchware.

That's how fetchware receives App::Fetchware's API subroutines, and it is also
how fetchware receives a fetchware extensions API subroutines, the fetchware
extension is simply use()d inside your Fetchwarefile instead of the default one
of App::Fetchware. Instead of:

    use App::Fetchware;

You would write:

    use App::FetchwareX::HTMLPageSync;

To use the fetchware extension HTMLPageSync.

=head1 INTERNAL SUBROUTINES IMPLEMENTING FETCHWARE COMMANDS

Below are all of subroutines that implement fetchware's main command line
options such as C<fetchware install> or C<fetchware new> and so on. These main
subroutines are called based on the options you pass to fetchware from the
command line.

=head2 cmd_install()

    my $installed_fetchware_package_path = cmd_install($filename|@ARGV)



( run in 0.743 second using v1.01-cache-2.11-cpan-39bf76dae61 )