kaif has asked for the wisdom of the Perl Monks concerning the following question:

I am planning on writing a Perl script that will be run on a variety of different platforms and Perl installations (both version numbers and module configurations). Because sometimes I can not or do not wish to control the installation and because some things work on Unix-type machines but do not on Windows, I would like to write my program so that it chooses which module or code to use for a particular task. So, I have two general questions:

How does a module such as CPAN test whether or not the user has Net::FTP or LWP, etc., installed and working? I tried looking through the code, but could not find the answer. In general, what methods should I use to determine whether or not I can use module Foo::Bar? Are there methods preferred to liberal use of eval? Also, I am considering not only checking whether or not a module is available but also running some (of my own) test cases against it. Is this a good idea and does anyone have any experience with this? (That is, running test cases and reacting to them within a single program.)

Secondly, I'd like for the program to check for module availability on its first run and then simply proceed to use what it determined was usable (as the first run may take a while). However, perhaps when a new module is installed or something, I would like to rerun the configuration procedure. Is there a configuration module that would help me here? How should I store the data of "this module works but this one doesn't" and then use it when necessary?

I tried searching Perlmonks but could not find anything. Thanks very much in advance for your assistance.

Replies are listed 'Best First'.
Re: Gracefully choosing which module to use
by samtregar (Abbot) on Jun 13, 2005 at 17:32 UTC
    I've used:

    if (eval "use Module::Foo") { # I've got foo! } else { die $@ if $@; }

    If evaling a string makes you squirmy, this should work the same:

    if (eval { require Module::Foo }) { Module::Foo->import() if Module::Foo->can('import'); # I've got foo! } else { die $@ if $@; }

    Note that this will not work:

    if (eval { use Module::Foo }) { # I've got foo! } else { die $@ if $@; }

    That's because use is a compile-time statement which can't be trapped by an eval. It'll blow up at compile time if Module::Foo can't be loaded.

    -sam

Re: Gracefully choosing which module to use
by nothingmuch (Priest) on Jun 13, 2005 at 17:57 UTC
    When I have to do something like that I usually write compatible warppers.

    What I mean by this is that I have a small module, one for each module my app knows how to use:

    package MyApp::URLFetcher::LWP; use base qw/MyApp::URLFetcher/; use LWP::Simple; # define the most convenient interface you can think of # if you need just the contents of a URL in a string, do that sub get_url_to_str { my $url = shift; get($url); # our chosen interface is simple with LWP }
    package MyApp::URLFetcher::NetFTP; use base qw/MyApp::URLFetcher/; use Net::FTP; sub get_url_to_str { my $self = shift; my $url = shift; # I don't know Net::FTP # either way, our interface is a bit harder to pull off # with Net::FTP # but this complexity will be in the software anyway # it's better to hide it here my $f = Net::FTP->new; # ... # ... $f->get("..."); }
    I usually write one driver, the simplest one, and then add others later.

    Then in your app, say you need a URL:

    use UNIVERSAL::require; my $fetcher_class = choose_fetcher_class(); my $fetcher = $fetcher_class->new; # this is the good part # this line of code does not care what driver it's using, as # long as the subclass of MyApp::URLFetcher is implemented correctly # isn't polymorphism great? my $content = $fetcher->get($url); sub choose_fetcher_class { foreach my $driver (qw/LWP NetFTP/){ my $class = "MyApp::URLFetcher::$driver"; # UNIVERSAL::require let's us avoid an eval return $class if $class->require; } die "couldn't load any driver"; }

    Update: Oops! I forgot the classes in the foreach loop

    Also, you might want to look into Module::Pluggable and friends, they might be useful for "finding" drivers, without the code that uses them even caring about their names. I don't think they have an API to skip failed modules, and furthermore to only get the first successful one.

    -nuffin
    zz zZ Z Z #!perl
        Yeah, I know it's guts. But good point =)

        What I meant is that you don't have to construct some perl code, eval it, and maybe get it wrong and stuff.

        Encapsulation let's me feel free to ignore most of what's underneath, but normally before I get to that stage I take a good look at the insides to make sure I can trust it.

        -nuffin
        zz zZ Z Z #!perl
Re: Gracefully choosing which module to use
by scmason (Monk) on Jun 13, 2005 at 17:38 UTC
    Something like this:
    my $hasLWP = eval{require LWP;}; if( $hasLWP ){ print "LWP is installed\n"; } else{ print "LWP not installed.\n"; }
    Good luck.
    "Never take yourself too seriously, because everyone knows that fat birds dont fly" -FLC
Re: Gracefully choosing which module to use
by sgifford (Prior) on Jun 13, 2005 at 22:00 UTC

    Rather than doing a series of functionality tests, CPAN generally looks at the module's version, in $Module::Name::VERSION. You may find this technique useful.

    eval is a perfectly acceptable way to test if a module can be loaded, but you can use the UNIVERSAL can method to test if particular methods exist.

Re: Gracefully choosing which module to use
by ChrisS (Monk) on Jun 14, 2005 at 16:57 UTC
    You might be able to determine if a module is installed without explicitly requiring or using it by using a technique I described in Re: Case Insensitivity on Windows.