Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Re: Multiple Packages in a Module? (manipulate %INC)

by LanX (Saint)
on Jun 25, 2018 at 18:45 UTC ( [id://1217380]=note: print w/replies, xml ) Need Help??


in reply to Multiple Packages in a Module?

> I want to have a set of files test_v1.pm .. test_vN.pm that each have definitions of package Foo and package Foo::Bar

After useing test_v*.pm you have to make Perl believe that package Foo and package Foo::Bar have already been loaded ...

i.e. you have to flag it in %INC: °

  • %INC

The hash %INC contains entries for each filename included via the do, require, or use operators. The key is the filename you specified (with module names converted to pathnames), and the value is the location of the file found. The require operator uses this hash to determine whether a particular file has already been included.

I had a similar discussion some time ago, I'll dig it up and update it here.

EDIT

Couldn't find it, here a little demo:

use strict; use warnings; use Data::Dump qw/pp dd/; package Foo::Bar; sub import { warn __PACKAGE__." was imported!" } BEGIN { $INC{'Foo/Bar.pm'}=1; # actually a path needed, but 1 is true eno +ugh˛ ;-) } package main; use Foo::Bar; warn pp \%INC;
Foo::Bar was imported! at d:/Users/lanx/pm/inc.pl line 10. { "C:/Perl_64/site/lib/sitecustomize.pl" => "C:/Perl_64/site/lib/sitec +ustomize.pl", "Data/Dump.pm" => "C:/Perl_64/lib/Data/Dump. +pm", "Exporter.pm" => "C:/Perl_64/lib/Exporter.p +m", "Foo/Bar.pm" => 1, "overload.pm" => "C:/Perl_64/lib/overload.p +m", "overloading.pm" => "C:/Perl_64/lib/overloadin +g.pm", "strict.pm" => "C:/Perl_64/lib/strict.pm" +, "subs.pm" => "C:/Perl_64/lib/subs.pm", "vars.pm" => "C:/Perl_64/lib/vars.pm", "warnings.pm" => "C:/Perl_64/lib/warnings.p +m", "warnings/register.pm" => "C:/Perl_64/lib/warnings/r +egister.pm", } at d:/Users/lanx/pm/inc.pl line 21.

HTH! :)

update

added dump of \%INC for illustration

update

˛) using __FILE__ instead of 1 is even better, because the origin of the code will become obvious when debugging.

BEGIN { $INC{'Foo/Bar.pm'}=__FILE__; }

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

°) %INC the hash not @INC the array!

Replies are listed 'Best First'.
Re^2: Multiple Packages in a Module? (%INC)
by haj (Vicar) on Jun 25, 2018 at 20:56 UTC
    LanX: After useing test_v1.pm you have to make Perl believe that package Foo and package Foo::Bar have already been loaded ...

    That makes only sense if you want to prevent Foo/Bar.pm being read from disk if a use Foo::Bar; is executed after the usurping module test_v1.pm has been loaded. Depending on load order can be tricky if Foo/Bar.pm initializes stuff or runs BEGIN blocks.

    Unfortunately we don't know the intentions of the original poster, so apparently the lot of us have made different assumptions. I have seen (and used) multiple packages in one module in these cases:

    • For "internal" classes which are not seen from the outside, where no one is ever supposed to "use" the module. In that case, modifying %INC isn't necessary.
    • To modify the behaviour of classes in a test: In that case, my test_v1.pm would explicitly use Foo::Bar and then do the desired modifications in a block { package Foo::Bar; no warnings "redefine"; ...; }. Modifying %INC isn't necessary either.

    Cheers,
    haj

      I have problems to see a case where my approach doesn't work.°

      A use is a require + import and the require part is skipped after checking %INC

      (Of course you need to place Foo::Bar before Foo if the latter uses the former.)

      But if you have a special example in mind where this breaks, please share.

      update

      > if a use Foo::Bar ; is executed after the usurping module test_v1.pm has been loaded

      as demonstrated you can use Foo::Bar as soon as the BEGIN block sets %INC.

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

      update

      °) well if the module is written in a way that BEGIN blocks expect to be run after the use (i.e. in the inherent do FILE), then yes could get bitten by order problems. The OP didn't sound like his intention was to simulate meta programming magics.

      Otherwise he should do a BEGIN { eval $MODULE_CODE; package->import() }

      Please note that this code could be encapsulated in a special module.

      Something like use FromData "__MODULE_NAME__" could extract the Modules_Code from the DATA section (delimited by __MODULE_NAME__ ) and import it into the upper namespace.

      ... or ...

      Another - probably more elegant - option is to provide a loader function as value in %INC . use would evaluate this function to get the MODULE_CODE.

      You can also insert hooks into the import facility by putting Perl code directly into the @INC array. There are three forms of hooks: subroutine references, array references, and blessed objects.

        I have problems to see a case where my approach doesn't work.

        I can't see a case where it wouldn't "work", but I also can't see any typical case in which it would do anything useful.

        Assuming an unambiguous file system, if you have a xyzzy.pm file defining a package Foo and its structure/content and a bunch of other namespaces and their contents, the only way to establish the Foo namespace (and all the others) is by a  use xyzzy; statement. Once the  use xyzzy; statement is executed, all the namespaces are established, and xyzzy cannot be use-ed again with any effect because it is present in the  %INC structure. If one then wants to access one of these namespaces, a  packagenamespace; statement will do the trick.

        OTOH, if you have a Perl inclusion directory structure with a slew of Foo.pm files in it, any  use Foo; statement is an open invitation to a major brain tumor. Surely, the best approach is to have a clean, unambiguous inclusion path to begin with. If you have some weird monkey patching/phasing/path resolution/??? problem such as haukex alludes to here, patching  %INC in this way may be your only salvation, but again, isn't studious avoidance rather than acceptance the best way to deal with such an unusual (one would hope) situation?


        Give a man a fish:  <%-{-{-{-<

        Lanx: I have problems to see a case where my approach doesn't work.
        I have been referring to your initial sentence (emphasis added):
        LanX: After useing test_v1.pm you have to make Perl believe that package Foo and package Foo::Bar have already been loaded ...

        Your approach works in most cases, but you don't have to modify %INC in the cases I've shown. Can I whip up an example where it actually breaks? Probably. I've done some security testing in my career, and to do that you often need to find an economic way to simulate an error situation which "does not happen normally" and check how an application behaves. It isn't really short, but hopefully self-contained and correct enough. Let's consider this application App.pm which needs some critical resource, which in our example is the Perlmonks website:

        # App.pm package App; use Foo; sub new { my $class = shift; bless { ua => Foo->new }, $class; } sub check { my $self = shift; my $res = $self->{ua}->get('https://perlmonks.org'); if ($res->code != 200) { return "Monks are offline"; } return "Everything works just fine"; } 1;
        The Foo module is pretty short.
        # Foo.pm package Foo; use parent 'LWP::UserAgent'; 1;

        Now how do we get test coverage in the branch where it deals with "Monks are offline"? I can't shut down the Perlmonks server, and let's for the moment assume that I can't just manipulate the network, because other parts of the application need their connections. But I can make sure that Foo will behave as if Perlmonks were down. I'll do this with a test class test_v1:

        # test_v1.pm package Foo; use HTTP::Response; sub get { return HTTP::Response->new(500); } # Add more packages ad libidum 1;
        A testfile would look like this:
        # errorcode.t use test_v1; # %INC modification, as prescribed by LanX #BEGIN { # $INC{'Foo.pm'} = __FILE__; #} use Test::More; use App; my $app = App->new; is($app->check,"Monks are offline"); done_testing;

        If you comment out the behaviour modification, then the test will fail (unless Perlmonks are really down). If you uncomment the %INC modification, then the test will fail with Can't locate object method "new" via package "Foo" at App.pm line 7.

        I usually avoid such a construct and instead explicitly use Foo; before doing any behaviour modification. In that case, the %INC modification would be harmless, but still useless and by no means required.

        Cheers,
        haj

        P.S.: Good that I'm typing so slow. You're editing your posts so often that I have to occasionally check what I'm actually responding to.

Re^2: Multiple Packages in a Module? (manipulate %INC)
by Anonymous Monk on Jun 25, 2018 at 22:13 UTC

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1217380]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (5)
As of 2024-03-28 11:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found