3

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.

Greg
  • 39,830
  • 86
  • 217
  • 286
  • possible duplicate of [How can I check if I have a Perl module before using it?](http://stackoverflow.com/questions/251694/how-can-i-check-if-i-have-a-perl-module-before-using-it) – mob Jun 13 '12 at 18:50

3 Answers3

10
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.)

ikegami
  • 322,729
  • 15
  • 228
  • 466
5

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.

Community
  • 1
  • 1
Zaid
  • 35,070
  • 14
  • 81
  • 149
  • There's no need to move the loading to run-time, and you don't want to move the loading to run-time. If you do, you'll get strict errors accessing `$GunzipError`. – ikegami Jun 13 '12 at 18:48
  • @ikegami : Works for me (it's been `qw//`-ed) – Zaid Jun 13 '12 at 18:56
  • Then your test is broken. `Global symbol "$GunzipError" requires explicit package name at -e line 1` is received from `perl -e'use strict; eval { require IO::Uncompress::Gunzip; IO::Uncompress::Gunzip->import( qw/gunzip $GunzipError/ ); }; die $@ if $@; $GunzipError;'` – ikegami Jun 13 '12 at 18:59
  • (You would also have problems if `gunzip` had a prototype, but it doesn't.) – ikegami Jun 13 '12 at 19:01
  • @ikegami : Do you mean to say that wrapping your test in a `BEGIN` block would render `$GunzipError` defined or exempt from strictures? – Zaid Jun 13 '12 at 19:08
  • That would import `$GunzipError` at compile-time, and strict allows you to use imported variables. – ikegami Jun 13 '12 at 19:18
  • @ikegami : Perhaps I'm doing something wrong here, but it doesn't work for me: `$ perl -we 'use strict; BEGIN { eval { require IO::Uncompress::Gunzip; IO::Uncompress::Gunzip->import( qw/gunzip $GunzipError/ ); }; die $@ if $@; $GunzipError; }'` – Zaid Jun 13 '12 at 19:21
  • `Global symbol "$GunzipError" requires explicit package name at -e line 1. BEGIN not safe after errors--compilation aborted at -e line 1.` – Zaid Jun 13 '12 at 19:23
  • Your "}" is misplaced. You shouldn't be putting the BEGIN around teh whole script. :) `perl -we 'use strict; BEGIN { eval { require IO::Uncompress::Gunzip; IO::Uncompress::Gunzip->import( qw/gunzip $GunzipError/ ); }; die $@ if $@; } $GunzipError;'` – ikegami Jun 13 '12 at 19:24
0

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.

mokko
  • 132
  • 9