Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

require() @INC hooks problem

by kcott (Archbishop)
on Dec 27, 2020 at 19:54 UTC ( [id://11125817]=perlquestion: print w/replies, xml ) Need Help??

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

require describes inserting hooks into @INC.

I've been testing this out for use in a module I'm writing. Here's a very cutdown version of the code:

package RequireHookTest; use 5.032; use warnings; use Moose; use namespace::autoclean; sub dynamic_require { my ($self, $ns_extension) = @_; { my $class = join '::', __PACKAGE__, $ns_extension; my $source = <<~EOF; package $class; use Moose; extends 'RequireHookTest'; use namespace::autoclean; __PACKAGE__->meta->make_immutable; 1; EOF my sub for_inc { my ($coderef, $filename) = @_; return \$source }; my $for_inc_ref = \&for_inc; push @INC, $for_inc_ref; eval "require $class;"; } return; } __PACKAGE__->meta->make_immutable; 1;

Calling dynamic_require() worked a treat at first; however, when testing with an additional call, I got the quite unexpected message: Can't locate object method "new" ... for the second call (the first call still worked fine).

Here's an SSCCE:

#!perl use 5.032; use warnings; use FindBin; use lib "$FindBin::Bin/lib"; use Test::More tests => 6; use RequireHookTest; my $rht = RequireHookTest::->new(); is(defined $rht, 1, 'Test RequireHookTest::->new()'); isa_ok($rht, 'RequireHookTest', 'Test RequireHookTest::->new() ISA'); $rht->dynamic_require('Test1'); my $rht_test1 = RequireHookTest::Test1->new(); is(defined $rht_test1, 1, 'Test RequireHookTest::Test1->new()'); isa_ok($rht_test1, 'RequireHookTest::Test1', 'Test RequireHookTest::Te +st1->new() ISA'); $rht->dynamic_require('Test2'); my $rht_test2 = RequireHookTest::Test2->new(); is(defined $rht_test2, 1, 'Test RequireHookTest::Test2->new()'); isa_ok($rht_test2, 'RequireHookTest::Test2', 'Test RequireHookTest::Te +st2->new() ISA');

Example run:

$ prove -v sscce_require_hook_test.t sscce_require_hook_test.t .. 1..6 ok 1 - Test RequireHookTest::->new() ok 2 - 'Test RequireHookTest::->new() ISA' isa 'RequireHookTest' ok 3 - Test RequireHookTest::Test1->new() ok 4 - 'Test RequireHookTest::Test1->new() ISA' isa 'RequireHookTest:: +Test1' Can't locate object method "new" via package "RequireHookTest::Test2" +at sscce_require_hook_test.t line 23. # Looks like your test exited with 255 just after 4. Dubious, test returned 255 (wstat 65280, 0xff00) Failed 2/6 subtests Test Summary Report ------------------- sscce_require_hook_test.t (Wstat: 65280 Tests: 4 Failed: 0) Non-zero exit status: 255 Parse errors: Bad plan. You planned 6 tests but ran 4. Files=1, Tests=4, 0 wallclock secs ( 0.00 usr 0.03 sys + 0.20 cusr + 0.09 csys = 0.33 CPU) Result: FAIL

If I swap the last two blocks of code around, "Test2" works and "Test1" has the new() problem:

... ok 3 - Test RequireHookTest::Test2->new() ok 4 - 'Test RequireHookTest::Test2->new() ISA' isa 'RequireHookTest:: +Test2' Can't locate object method "new" via package "RequireHookTest::Test1" +at sscce_require_hook_test.t line 23. ...

In the module code, I tried pushing an ARRAYREF, instead of a CODEREF, onto @INC:

push @INC, [ $for_inc_ref ];

but the result was the same.

I've also tried various versions where use namespace::autoclean; and/or __PACKAGE__->meta->make_immutable; were excluded just in case that made any difference: it didn't.

This one's got me stumped. Any help would be very much appreciated.

Note: My (real) code is using version 5.32.0 so I've left that specified.

— Ken

Replies are listed 'Best First'.
Re: require() @INC hooks problem [SOLVED]
by kcott (Archbishop) on Dec 28, 2020 at 00:40 UTC
Re: require() @INC hooks problem
by LanX (Saint) on Dec 27, 2020 at 20:56 UTC
    Second guess, careful with private subs

    my sub for_inc { my ($coderef, $filename) = @_; return \$source }; my $for_inc_ref = \&for_inc;

    instead please try

    my $for_inc_ref = sub { my ($coderef, $filename) = @_; return \$source };

    unfortunately I can't test right now.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      Thanks for guessing; if nothing else, it eliminates possibilities.

      In earlier versions of the code, I did have what you suggest, as well as push @INC, sub { ... };. I tried both of those with and without the my ($coderef, $filename) = @_;.

      — Ken

Re: require() @INC hooks problem
by LanX (Saint) on Dec 27, 2020 at 20:18 UTC
    First guess: did you try without Moose?

    > I've also tried various versions where use namespace::autoclean; and/or __PACKAGE__->meta->make_immutable; were excluded just in case that made any difference: it didn't.

    well ... but still with Moose?

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      G'day Rolf,

      Short answer: no.

      The module that I'm working on is part of a series, all of which use the Moose framework. The dynamic_require() idea seemed like a nice thing to have; but, at the end of the day, if it doesn't work with Moose for some reason, I can live without it.

      In earlier testing with the real module, I did find that hard-coding the $source into a test file worked fine, even with multiple instances (e.g. the source generated with "Test1" and "Test2").

      The problem does seem to be somewhere in dynamic_require(); however, I've no idea where that might be. The fact that it works once, but not twice, is just very puzzling.

      — Ken

        Short answer: I'm willing to look into it as soon as Moose is not involved in your SSCCE ! :)

        (Moose doesn't qualify as "short" or "self contained" in my book ;-)

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Re: require() @INC hooks problem
by LanX (Saint) on Dec 28, 2020 at 06:28 UTC
    I understand now what's happening, but I have trouble getting what your intention is... (???)

    Why are you installing multiple hooks?

    Why do you even need a hook if your require happens right away?

    I'm puzzled ...

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

      "I understand now what's happening, ..."

      That's great. In "Re^6: require() @INC hooks problem [non-Moose]", I wrote: "The mechanism behind this is a mystery to me. Why is it calling the subroutine with all the previous CODEREF values then the newly created CODEREF?" Perhaps you would share your understanding.

      "... but I have trouble getting what your intention is... (???)"

      The subclasses, in this instance, are to be used for exception objects. There are multiple ways to handle this; this seemed like a handy way to do it. It follows the DRY principle and abstracts any required code to a single statement.

      "Why are you installing multiple hooks?"

      That's not actually a requirement at the present; although, I can see that it might be useful as code development progresses. The problem (1st hook working; 2nd hook not working) came up while testing. I don't like loose ends, or sweeping problems under the carpet, so I followed up with an investigation into this.

      "Why do you even need a hook if your require happens right away?"

      Technically, it's not a requirement; it's just, as I said above, "a handy way to do it". I don't need to write individual X/Y/Z.pm modules for require X::Y::Z statements.

      "I'm puzzled ..."

      Hopefully, less so now. :-)

      — Ken

        First things first

        > > "Why do you even need a hook if your require happens right away?"

        a require is basically a

        • load file into string
        • eval string
        • update %INC to avoid further loads
        instead of a hook you can simply eval your template and update %INC.

        > I don't need to write individual X/Y/Z.pm modules for require X::Y::Z statements.

        you don't need to save them to a file.

        Just eval the code and update %INC

        > > "Why are you installing multiple hooks?"

        A hook is an abstraction of a directory path in @INC, which are searched sequentially.

        Either a path includes a requested module (SUCCESS) or the next is searched (FAIL)

        But your hook in the OP always returned the same code of the first module, no matter which module was requested. It should have signaled a FAIL, to allow the next path/hook to handle the request.

        And each of your hooks is only returning one file, which is an overkill and "littering" @INC with hooks.

        > Perhaps you would share your understanding.

        If you want you can look into the working example I wrote for HaukeX' webperl project:

        [WEBPERL] dynamically importing non-bundled modules via http

        you just have to adapt the coderef $fetch and %INC_FETCH to your needs.

        BUT as I said, you don't even need hooks if you eval those modules right away.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11125817]
Approved by karlgoethebier
Front-paged by karlgoethebier
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (5)
As of 2024-04-19 10:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found