Sjoerd
Sjoerd

Reputation: 75679

How can I check whether all Perl modules are imported?

Say I have a Perl module:

use Foo::Bar;

sub add {
    return Foo::Bar::func() + Foo::Buzz::func();
}

There is an error in this module, because it forgets to use Foo::Buzz. This error will not always be catched by my unit tests, for example if the test for Foo::Buzz runs earlier and imports Foo::Buzz before add() is run. If I use this module in production code, it will fail with the error that Foo::Buzz is not imported.

How can I check whether all modules that I use in the code are also imported?

Edit: I want to check the code before deploying it in production, to avoid that any errors occur. The example will fail in production, and I want to catch the error before that, for example when I run my unit tests. I want a tool or some code that I can run before deployment that catches this error, like flake8 for python.

Upvotes: 3

Views: 745

Answers (2)

David W.
David W.

Reputation: 107090

If Perl was strictly a dynamic language, you could easily check whether or not a module is installed in the program. The problem is that Perl isn't 100% dynamic. It does some compilation and part of that compilation work is done after modules are checked.

Bulrush is on the right track. Unfortunately, you can use use clause in order to do this. use is checked pre-compile, so you'll get an error before your eval executes.

However, there's a clue in the use perldoc page:

  • use Module LIST
  • use Module
  • use VERSION

    Imports some semantics into the current package from the named module, generally by aliasing certain subroutine or variable names into your package. It is exactly equivalent to

    BEGIN { require Module; Module->import( LIST ); }

There you go! You can use require inside a BEGIN clause which is executed even before the rest of the file is parsed. You can use your eval there to see if this works or not too. You need to use a package variable as a flag to see whether or not this worked because of the scope issues. A regular lexically scoped variable will disappear when you leave the BEGIN clause.

BEGIN {
    our $FOO_BAR_available = 0;        # Must be a package variable
    eval {
        require Foo::Bar;
        Module->import( qw(...) );     # No need if you don't import any subroutines
    };
    if (not $@ ) {
        $FOO_BAR_AVAILABLE = 0;
    }
}

Then in your program, you'd have:

our $FOO_BAR_available;
if ( not $FOO_BAR_available ) {
    # Here be dragons...
}
else {
    # Back to your normal code...
}

The our $FOO_BAR_available is a bit confusing. You're not declaring this variable again, you're merely stating that you want to use this variable without prefixing it with the full package name. The variable was set in the BEGIN clause, and this won't affect the value.

You can skip the use of a package variable entirely, if this module was written correctly. Modules are suppose to set a package variable called $VERSION. You can use this variable as your flag:

BEGIN {
    eval {
        require Foo::Bar;
        Module->import( qw(...) );   # No need if you don't import any subroutines
    };
}

Note I not only don't have to declare a package variable, I don't even have to verify if the eval statement worked or not.

Then in your program...

if ( not $FOO::BAR::VERSION )  {
    # Here be dragons...
}
else {
    # Back to your normal code...
}

If the module set the $VERSION variable, you know it loaded. Otherwise, you know the module was not loaded.


Addendum

I want to check the code before deploying it in production, to avoid that any errors occur. The example will fail in production, and I want to catch the error before that, for example when I run my unit tests.

Here's my recommendations. It isn't as simple as running a script, but it's much better:

  • First, define your production environment: What version of Perl does it have? What modules are used? This will help developers know what to expect. I know Foo::Bar is good, but I shouldn't use Far::Bu because production doesn't have that. It's the first step. I'm surprised at the number of places that have no idea what's on their production environment.
  • Use Vagrant. This defines a virtual machine that matches your production environment. Developers can download it to their system, and have on their desktop a copy of the production environment.
  • Use Jenkins. Jenkins is a continuous build engine. Yes, you don't compile Perl, but you can still benefit from Jenkins:

    • Jenkins can run your unit tests for you. Automatic testing with each and every change in the code. You catch your errors early on.
    • Your Jenkins system can match your production machines - Same Perl version, same Perl modules. If it doesn't run on your Jenkins build machine because something's not installed, there's a good chance it won't run on Production.
    • You install via Jenkins. Jenkins can package your release, and you can use that to install known releases. No pulling code from a developer's system and finding out that there's something on the system that's not in your version control system. I don't know how many times I've seen a developer spool something up from their machine for production (thoroughly tested! Trust me!), and then we discover that we don't have that code in our version control system because the developer forgot to check something in.

You don't normally run flake8 in a production environment. By then, it's a wee bit late.

Perl has a lot of nice tools that perform a similar function:

  • Perlbrew: This allows your developers to install a separate Perl program with its own CPAN module library into their development system. They can use this to match the Perl version and the modules required to the production environment. This way, they're playing with the same set of rules.
  • Perlcritic: This checks your module against coding standard set forth by Damian Conway in his Perl Best Practices.
  • B::Lint: This is like the old lint program in C and can catch coding issues.

However, this is stuff to do before you're all set to run in Production. Use Vagrant to help developers setup their own private production environment for testing. Use Jenkins to make sure you test in a very production like environment and catch errors as soon as they happen rather than after UAT testing.

Upvotes: 0

Vadim Pushtaev
Vadim Pushtaev

Reputation: 2363

The short answer is you can't. Since Perl is a dynamic language, you can't check whether you load all modules before runtime as you can't check whether there are some other bugs in your code.

You still can use some static code analysis, trying to find This::Pattern in files where use This::Pattern; is not presented, but it doesn't guarantee anything.

Upvotes: 2

Related Questions