James Stanley

An easy way to package Perl programs

Sat 16 May 2020

Tagged: software

The right way to package Perl programs is with RPM, or DEB, or on CPAN with ExtUtils::MakeMaker or similar. But this is a hassle.

For really-simple Perl scripts, where everything is in one file, we can just distribute that single file and tell people to put it in ~/bin or /usr/bin or wherever they want.

The difficulty comes when your Perl program is slightly more complicated than a single file, and some of the code is spread across a small handful of modules. We're not quite at the level where we want an entire build and packaging infrastructure around our project, but we want to be able to distribute it for others to use. Telling people to copy the contents of lib/ into their system's local Perl library directory is not really reasonable.

My solution: concatenate all your modules together into a single file and ship it as a single-file script! I know, I know. Hear me out here.

It's almost as simple as "just concatenate everything into one file", but with 2 issues:

  1. all our "use MyApp::Foo;" lines won't work because Perl can't find MyApp/Foo.pm anywhere in @INC
  2. we need a shebang at the start with "#!/usr/bin/perl"

But these are easily solved. Example Makefile:

build/myapp: lib/MyApp/*.pm lib/MyApp.pm bin/myapp
    mkdir -p build/
    echo "#!/usr/bin/perl" > build/myapp
    cat $^ | sed 's/^use MyApp/import MyApp/' >> build/myapp
    chmod +x build/myapp
    
install: build/myapp
    install -m 0755 build/myapp /usr/bin/myapp
    
clean:
    rm -f build/myapp

That concatenates all the library files first, followed by the main program. This means when the code is executed it will run through each of the packages before reaching the main code, just as it would if they were imported with use statements. To solve problems with all of the "use MyApp::Foo" lines we can just replace use with import. And we can manually add a shebang at the start.

I used this idea for Ngindock and it seems to work OK for that.

Pros

It's quick and easy and has no external dependencies.

You can still develop the program as separate modules.

There is precedent from the JavaScript ecosystem (although, maybe this is a con not a pro).

Cons

People will think you're crazy and/or stupid.

Your modules won't go into anywhere in @INC so they won't be available for other programs to use.

There's no dependency management.

The sed regex to match "use MyApp" anchors at the start of the line, so if you do anything weird like:

use Important::Module; use MyApp::Foo;
or
     use MyApp::Foo;

Then it won't replace use with import.

If you like my blog, please consider subscribing to the RSS feed or the mailing list:

James Stanley - james@incoherency.co.uk | ricochet:it2j3z6t6ksumpzd | jesblogfnk2boep4.onion | [rss]