Bwmat
Bwmat

Reputation: 4578

Exporting subroutines from a module 'used' via a 'require'

I'm working with a set of perl scripts which our build system is written with. Unfortunately they were not written as a set of modules, but instead a bunch of .pl files which 'require' each other.

After making some changes to a 'LogOutput.pl' which was used by almost every other file, I started to suffer from some issues caused by the file being 'require'd multiple times.

In an effort to fix this, while not changing every file (some of which are not under my direct control), I did the following:

-Move everything in LogOutput.pl to a new file LogOutput.pm, this one having everything needed to make it a module (based on reading http://www.perlmonks.org/?node_id=102347 ).

-Replace the existing LogOutput.pl with the following

BEGIN
{
    use File::Spec;

    push @INC, File::Spec->catfile($BuildScriptsRoot, 'Modules');
}

use COMPANY::LogOutput qw(:DEFAULT);

1;

This works, except that I need to change calling code to prefix the sub names with the new package (i.e. COMPANY::LogOutput::OpenLog instead of just OpenLog)

Is there any way for me to export the new module's subroutine's from within LogOutput.pl?

Upvotes: 1

Views: 148

Answers (2)

Bwmat
Bwmat

Reputation: 4578

This turned out to just be a stupid mistake on my part, I didn't put the subs into the @EXPORT list, only into @EXPORT_OK.

Upvotes: 0

Schwern
Schwern

Reputation: 164809

The well named Import::Into can be used to export a module's symbols into another package.

use Import::Into;

# As if Some::Package did 'use COMPANY::LogOutput'
COMPANY::LogOutput->import::into("Some::Package");

However, this shouldn't be necessary. Since LogOutput.pl has no package, its code is in the package it was required from. use COMPANY::LogOutput will export into the package which required LogOutput.pl. Your code, as written, should work to emulate a bunch of functions in a .pl file.

Here's what I assume LogOutput.pl looked like (using the subroutine "pass" as a stand in for whatever subroutines you had in there)...

sub pass { print "pass called\n" }

1;

And what I assume LogOutput.pl and LogOutput.pm look like now...

# LogOutput.pl
BEGIN
{
    use File::Spec;

    push @INC, File::Spec->catfile($BuildScriptsRoot, 'Modules');
}

use COMPANY::LogOutput qw(:DEFAULT);

1;


# LogOutput.pm
package test;

use strict;
use warnings;

use Exporter "import";

our @EXPORT_OK = qw(pass);
our %EXPORT_TAGS = (
    ':DEFAULT'    => [qw(pass)],
);

sub pass { print "pass called\n" }

1;

Note this will not change the basic nature of require. A module will still only be required once, after that requiring it again is a no-op. So this will still not work...

{
    package Foo;

    require "test.pl";  # this one will work
    pass();
}

{
    package Bar;

    require "test.pl";  # this is a no-op
    pass();
}

You can make it work. Perl stores the list of what files have been required in %INC. If you delete and entry, Perl will load the file again. However, you have to be careful that all the code in the .pl file is ok with this. That @INC hack has to make sure its only run once.

BEGIN
{
    use File::Spec;

    # Only run this code once, no matter how many times this
    # file is loaded.
    push @INC, File::Spec->catfile($BuildScriptsRoot, 'Modules')
        if $LogOutput_pl::only_once++;
}

use COMPANY::LogOutput qw(:DEFAULT);

# Allow this to be required and functions imported more
# than once.
delete $INC{"LogOutput.pl"};

1;

This is one of the few cases that a global variable is justified. A lexical (my) variable must be declared and would be reset with each loading of the library. A global variable does not need to be declared and will persist between loading.

Upvotes: 2

Related Questions