http://qs321.pair.com?node_id=1228230

bliako has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

I have come across a situation where I have a module and a test script which uses it and all is fine when done using the perl interpreter from the command line. However, when I read the module file into a string and the test script into another string with the intend to eval() the strings one after the other, the operation collapses at the eval() of the test script string.

After some time, I realised why I was getting the error Can't locate Module.pm in @INC at the second eval which indeed has a use Module; statement. Obviously Module.pm is not in @INC and any use statement for it will fail even if said module has already been eval'ed successfully.

So, I am asking whether there is an easier solution to this kind of problem than analysing each script with PPI and removing use/require statements ONLY of modules already eval'ed. Because obviously there will be some use/require statements in there which must remain as they refer to modules which should be in @INC. I am doing the PPI way right now but I hope there is a more natural way.

Here is a test which demonstrates the problem when the marked statements uncomment:

#!/usr/bin/env perl use strict; use warnings; sub load_modules { my $mod1 = <<'EOM'; package Test::Module::Hello::Hello; our $VERSION = 0.1; use strict; use warnings; sub go { return "Hi iam ".__PACKAGE__ } 1; __END__ EOM my $mod2 = <<'EOM'; package Test::Module::Hello::Goodbye; our $VERSION = 0.1; use strict; use warnings; # >>> This will kill the eval if uncommented #use Test::Module::Hello::Hello; sub go { return "Hi iam ".__PACKAGE__ } 1; __END__ EOM eval($mod1) or die "$mod1\n\neval failed $@\n"; eval($mod2) or die "$mod2\n\neval failed $@\n"; } load_modules(); my $testscript = ' # >>> Following use/require will kill the eval #use Test::Module::Hello::Hello; require Test::Module::Hello::Goodbye; my $ret = Test::Module::Hello::Hello::go(); print "ret=$ret\n"; $ret = Test::Module::Hello::Goodbye::go(); print "ret=$ret\n"; '; eval($testscript) or die "eval failed, $@";

thanks, bliako

Replies are listed 'Best First'.
Re: use of already eval()ed module (from string)
by haukex (Archbishop) on Jan 08, 2019 at 21:14 UTC

    The typical solution would be to add fake entries to %INC, so require and therefore use thinks the module has already been loaded and doesn't need to be loaded again. If I put $INC{'Test/Module/Hello/Hello.pm'}=1; after the first eval and $INC{'Test/Module/Hello/Goodbye.pm'}=1; after the second, your code seems to run fine, even when I reenable the use lines. (If later parts of your program might depend on it, you might want to add a more useful value to %INC instead of 1.)

    An alternative (but equivalent) solution is to put use me::inlined; in each of the packages you're evaling.

      yes, thanks for this simple tip (i.e. modifying the %INC). It solved the problem.

        Note that module_notional_filename from Module::Runtime, when given a module name like "Foo::Bar", will tell you what the %INC key should be ("Foo/Bar.pm").

        I also like LanX's idea of an @INC hook, that's a bit more powerful.

Re: use of already eval()ed module (from string)
by LanX (Saint) on Jan 08, 2019 at 21:17 UTC
    The cleanest solution is to tell Perl not to retrieve the source from the file system but from the string.

    require describes various possibilities how to add a hook to @INC, which provides the needed source.( search for You can also insert hooks into the import facility )

    Maybe you might just want to have a look into this demo of mine:

    [WEBPERL] dynamically importing non-bundled modules via http

    and adjust it to your needs.

    HTH! :)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      thanks for the pointers. yes the code is in strings and not on file. so I do not have to use the hook yet

        I'm afraid you are missing the point here.

        But as long as you're happy, it's OK for me. :)

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: use of already eval()ed module (from string)
by Veltro (Hermit) on Jan 09, 2019 at 11:17 UTC

    Hi bliako,

    I think that something like this could work

    #!/usr/bin/env perl use strict; use warnings; sub updateINC { my ( $modfn, $code ) = @_ ; # code = ref push( @INC, sub { my ( $coderef, $filename ) = @_ ; if ( $filename eq $modfn ) { # print STDOUT "########### filename = $filename\n" ; open( my $fh, '<', $code ) or die "Open Hello failed\n" ; return $fh ; } } ) ; } sub load_modules { my $mod1 = <<'EOM'; package Test::Module::Hello::Hello; our $VERSION = 0.1; use strict; use warnings; sub go { return "Hi iam ".__PACKAGE__ } 1; __END__ EOM updateINC('Test/Module/Hello/Hello.pm', \$mod1) ; my $mod2 = <<'EOM'; package Test::Module::Hello::Goodbye; our $VERSION = 0.1; use strict; use warnings; # >>> This will kill the eval if uncommented use Test::Module::Hello::Hello; sub go { return "Hi iam ".__PACKAGE__ } 1; __END__ EOM updateINC('Test/Module/Hello/Goodbye.pm', \$mod2) ; } load_modules(); my $testscript = ' # >>> Following use/require will kill the eval #use Test::Module::Hello::Hello; require Test::Module::Hello::Goodbye; my $ret = Test::Module::Hello::Hello::go(); print "ret=$ret\n"; $ret = Test::Module::Hello::Goodbye::go(); print "ret=$ret\n"; '; eval($testscript) or die "eval failed, $@";

    I haven't looked at any of the other solutions, so I don't know what is better or not. I just tried it out and it seems to work

    I hope this may be of some help to you,

    Veltro

      Yes Veltro this works fine! Thank you.