3
votes

I want to detect when a user is missing a required module and print a friendly error message explaining what they need to install.

So far I tried putting this at the beginning of my script:

eval {
    use IO::Uncompress::Gunzip qw(gunzip $GunzipError) ;
};
if ($@) {
    die "Error: IO::Uncompress::Gunzip not installed: $@";
}

But Perl seems to die on the "use" line instead of the "die" line and never prints my error message.

3

3 Answers

10
votes
use IO::Uncompress::Gunzip qw( gunzip $GunzipError );

is short for

BEGIN {
   require IO::Uncompress::Gunzip;
   import IO::Uncompress::Gunzip qw( gunzip $GunzipError );
}

and BEGIN blocks are evaluated as soon as they are compiled. That means your code does:

  1. Compilation phase:
    1. Compile eval statement.
      1. Compiled BEGIN block.
        1. Compile require IO::Uncompress::Gunzip;
        2. Compile import IO::Uncompress::Gunzip qw( gunzip $GunzipError );
      2. Evaluate BEGIN block.
        1. Evaluate require IO::Uncompress::Gunzip;
        2. Evaluate import IO::Uncompress::Gunzip qw( gunzip $GunzipError );
    2. Compile if statement.
  2. Run phase:
    1. Evaluate (empty) eval statement.
    2. Evaluate if statement.

If an exception happens in step 1.1.2.1, the eval run in step 2.1 won't catch it!


Solutions:

You started with something equivalent to

BEGIN {
   require IO::Uncompress::Gunzip;
   import IO::Uncompress::Gunzip qw( gunzip $GunzipError );
}

It's errors from require you want to catch, so just add an eval around the require:

BEGIN {
   eval { require IO::Uncompress::Gunzip }
      or die "Error: IO::Uncompress::Gunzip not installed: $@";

   import IO::Uncompress::Gunzip qw( gunzip $GunzipError );
}

You could also delay the use getting compiled (and thus evaluated) by using eval EXPR instead of eval BLOCK:

BEGIN {
   eval 'use IO::Uncompress::Gunzip qw( gunzip $GunzipError ); 1'
      or die "Error: IO::Uncompress::Gunzip not installed: $@";
}

(I wish there was a good way of finding out if a module is installed. Even the first solution will catch other errors, the second even more.)

5
votes

What's happening here is that the module is being used at compile-time, regardless of the fact that it is inside the eval block.

This is also why naab's suggestion to change from eval BLOCK form to eval EXPR form works as well; the expression is evaluated at run-time. Changing the use to require will attempt to load the module at run-time:

eval {
    require IO::Uncompress::Gunzip;
    IO::Uncompress::Gunzip->import( qw/gunzip $GunzipError/ ) ;
};
if ($@) {
    die "Error: IO::Uncompress::Gunzip not installed: $@";
}

Output

Error: IO::Uncompress::Gunzip not installed: Can't locate IO/Uncompress/Gunzip.pm in @INC (@INC contains: C:/Perl/site/lib C:/Perl/lib .) at - line 2.

0
votes

check out Class::Load's try_load_class (CPAN)

use Class::Load;
die "Error: IO::Uncompress::Gunzip not installed" if 
    (! try_load_class (IO::Uncompress::Gunzip));

But as far as I can tell, you can't import qw(gunzip $GunzipError) with Class::Load.