Automatically List and Install Perl Modules

requirements.pl

Automatically List and Install Perl Modules

Abstract

We have ye olde Perl code (in ye olde mess) and we need to get it running on some or other environment, this requires installing the modules that are used by the scripts. (See what I did there?)

Introduction

In Python - a programming language that sacrifices speed for readability - we'd normally list the modules in requirements.txt and simply go pip install -r requirements.txt. In so doing, although we avoid the booby trap of using a YAML file, we still need to make the list of modules used.

In node.js we could use npm and end up completely insane.

So let us proceed to innovatively brain storm some sane, automated way of getting the perl modules installed...

Methods

We could just run the scripts and install the modules one by one as the interpreter complains about them: nah - not automated.

We could search the internet for previously used examples or perl modules that do the job for us: nah - I'm GenX.

We could write a Perl script that scans the Perl scripts and installs the modules that they use!

Results

#!/usr/bin/perl
use strict;
use warnings;

use File::Find;
use CPAN;


sub get_modules_in_files {

    my %modules;

    foreach (@_) {
        my @file_modules;
        open my $fh, '<:encoding(UTF-8)', $_ or next;
        while (my $line = <$fh>) {
            push @file_modules, $1 if ($line =~ /^use ([\w|:]+)[ |;]/);
        }

        foreach (@file_modules) {
            $modules{$_}++;
        }
    }

    return keys %modules
}


sub get_all_files {

    my ($dir) = @_;
    my @files;

    find(sub { push @files, $File::Find::name if ($File::Find::name =~ /.+\.p[l|m]$/) }, $dir);
    print "\nLocated Perl files:\n";
    print "$_\n" for @files;

    return @files
}

my $dir = shift;
my $scan = shift;
if ($dir eq 'scan') {
    $dir = '.';
    $scan = 'scan';
}

$dir = '.' unless defined($dir);
my @modules = get_modules_in_files(get_all_files($dir));

print "\nModules used:\n";
print "$_\n" for @modules;

print "\nChecking modules:\n";
foreach (@modules) {
    eval "use $_";
    if ($@ and $@) {
        print "Need to install: $_\n";
        CPAN->install("$_") unless ($scan eq 'scan');
    } else {
        print "Found and skipping: $_\n";
    }
}
perl requirements.pl [dir] [scan]

dir is the directory to (recursively) scan, will use . if ommitted.
scan will only scan for modules and not attempt to install them.

Discussion

I ran into a few hassles between different versions of Perl and some modules not being compatible with a later version - hence the scan option for when a module bombs on installation and there's a river of text in the terminal leaving me in the dark as to what the modules required and their statuses are.

Another issue is the need to explicitly dereference hashes in later (5.3x) versions of Perl (as opposed to, say v5.18).

It'd be an improvement to use CPAN Minus.

CPAN and File::Find seem to come packaged with Perl, at least on Linux.

Conclusion

This was the remaining step from my previous blog post Old School LAMP on Old School VM Setup.