Search code examples
perlpackagingsoftware-distribution

Packaging a perl app so that it will work outside of perl's default prefix


I'm using Module::Build (although I'm flexible on build environments) to package up some perl software I'm writing for internal use where I work. It includes a handful of scripts, and some helper modules. My plan is to make it so you can specify a prefix of whatever you want (ie. something outside of perl's default @INC) during the build process and the built scripts should still be able to find their helper modules without any problems.

I want to do this because I want to distribute this software internally with "Encap", which is a packaging tool that by default can not install anything outside of /usr/local, and being on RedHat, our perl does not search /usr/local/lib for modules by default.

This leaves me with the prospect of either telling the user to manually set PERL5LIB to /usr/local/lib every time they want to run the app, or to do something intelligent with the build system to have it fiddle with each script's use lib line after a --prefix is specified.

Right now I'm just setting use lib to point straight to /usr/local/lib manually in each of my scripts, but I'm not really liking that as a solution. Chiefly because of the testing process: I want to override @INC during testing so that it uses my working directory first for perl modules, but upon being built, the working directory should be removed from @INC and replaced with the user's specified prefix. But also because I would like this software to be installed to arbitrary locations (such as its own little island somewhere on NFS with its own bin/ and lib/ dirs) and still work without issue.

The question:

Can Module::Build allow me to fiddle with my scripts' use lib lines during build steps? I notice MakeMaker has a pm_filter option that lets you specify a search-and-replace that can arbitrarily modify your .pm files as they're being built, but that only works with .pm files, not scripts. Module::Build is supposed to be more flexible but I'm drowning in the documentation trying to figure out where you'd specify that.


Solution

  • >>> daxim@champion:/tmp/Foo-Bar$ tree
    .
    ├── bin
    │   └── foobar
    ├── Build.PL
    ├── inc
    │   └── Local
    │       └── Module
    │           └── Build
    │               └── Fnord.pm
    └── lib
        └── Foo
            └── Bar.pm
    
    7 directories, 4 files
    
    >>> daxim@champion:/tmp/Foo-Bar$ cat bin/foobar
    use lib "DUMMY";
    use Foo::Bar;
    print "It works!\n";
    
    >>> daxim@champion:/tmp/Foo-Bar$ cat Build.PL
    use lib 'inc';
    use Local::Module::Build::Fnord;
    
    my $build = Local::Module::Build::Fnord->new(
        module_name => 'Foo::Bar',
        license     => 'restricted',
    );
    $build->add_build_element('bin');
    $build->create_build_script;
    
    >>> daxim@champion:/tmp/Foo-Bar$ cat inc/Local/Module/Build/Fnord.pm
    package Local::Module::Build::Fnord;
    use parent 'Module::Build';
    sub process_bin_files {
        my ($self) = @_;
        my $lib = $self->install_base . '/lib/perl5';
        system "chmod u+w blib/script/*";
        my $call = qq($^X -p -i -e's[use lib "DUMMY";][use lib "$lib";]' blib/script/*);
        print "$call\n";
        system $call;
    };
    1;
    
    >>> daxim@champion:/tmp/Foo-Bar$ cat lib/Foo/Bar.pm
    package Foo::Bar;
    1;
    
    >>> daxim@champion:/tmp/Foo-Bar$ perl Build.PL --install_base=/tmp/usr/local
    ⋮
    
    >>> daxim@champion:/tmp/Foo-Bar$ ./Build install
    Building Foo-Bar
    /home/daxim/local/bin/perl -p -i -e's[use lib "DUMMY";][use lib "/tmp/usr/local/lib/perl5";]' blib/script/*
    Installing /tmp/usr/local/lib/perl5/Foo/Bar.pm
    Installing /tmp/usr/local/bin/foobar
    
    >>> daxim@champion:/tmp/Foo-Bar$ cat blib/script/foobar
    use lib "/tmp/usr/local/lib/perl5";
    use Foo::Bar;
    print "It works!\n";
    
    >>> daxim@champion:/tmp/Foo-Bar$ cd /tmp/usr/local/bin/
    
    >>> daxim@champion:/tmp/usr/local/bin$ perl foobar
    It works!