Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

help with versioning modules

by Special_K (Monk)
on Nov 20, 2020 at 17:08 UTC ( [id://11123913]=perlquestion: print w/replies, xml ) Need Help??

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

I am trying to incorporate version numbers into modules that will only be used internally and cannot get it to work. As a basic testcase, I have the following file Foo.pm, located at '/home/user/perl_modules/lib/perl5/My/Foo.pm':

package Foo; our $VERSION = '1.0'; use strict; use warnings; sub new { my $class = shift(); my ($arguments) = @_; my $self = {}; bless $self, $class; }

I also have the following script, foo_version_test.pl, used to test the version number of Foo.pm:

#!/tool/bin/perl -w use strict; use lib '/home/user/perl_modules/lib/perl5'; use My::Foo 1.0; printf("version = %s\n", $Foo::VERSION);

Running the script produces the following output:

My::Foo defines neither package nor VERSION--version check failed at . +/foo_version_test.pl line 4. BEGIN failed--compilation aborted at ./foo_version_test.pl line 4.

I'm not sure what's going wrong here given that both the package and VERSION statements exist in Foo.pm.

On a related note, if I have modules that will only be used internally and that I want to version (as opposed to modules I download from CPAN, in which subsequent module updates overwrite the previous module version), suppose I have the following directory structure:

/home/user/perl_modules/lib/perl5/My/FooVersion 1.0/ Foo.pm 1.1/ Foo.pm 1.2/ Foo.pm Devel/ Foo.pm

How would I use a particular version of Foo.pm in my script? Perl is fine with:

use My::FooVersion::Devel::Foo;

but trying to reference any of the numbered versions in the same manner, even when I quote either the numerical portion or the entire string, produces syntax errors. For example, none of the following work:

use My::FooVersion::1.1::Foo; use "My::FooVersion::1.1::Foo"; use My::FooVersion::"1.1"::Foo; use My::FooVersion::'1.1'::Foo; use 'My::FooVersion::1.1::Foo';

Replies are listed 'Best First'.
Re: help with versioning modules
by afoken (Chancellor) on Nov 20, 2020 at 18:59 UTC
    /home/user/perl_modules/lib/perl5/My/Foo.pm:
    package Foo; our $VERSION = '1.0'; use strict; use warnings; sub new { my $class = shift(); my ($arguments) = @_; my $self = {}; bless $self, $class; }
    foo_version_test.pl:
    #!/tool/bin/perl -w use strict; use lib '/home/user/perl_modules/lib/perl5'; use My::Foo 1.0; printf("version = %s\n", $Foo::VERSION);

    Filename and package name do not match.

    If the file name (relative to the include path) is My::Foo, and you load the module in foo_version_test.pl by use My::Foo, then the package name should be My::Foo, not Foo. Loading the module creates a global variable named $Foo::VERSION, but use My::Foo 1.0 tests for a variable named $My::Foo::VERSION.

    It is perfectly legal to have package names in a module that to not match the file name, so perl does not warn. But it causes a lot of trouble (see Breaking Tie::Hash into three modules for an example). As a rule of thumb, make sure that package names and file names match.

    Update: Modern versions of perl also allow package My::Foo 1.0;, implicitly setting $My::Foo::VERSION and matching use My::Foo 1.0. See also package.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

      I'm still somewhat confused. If the problem is due to the fact that the filename and the package name don't match, then why does the following not also give an error, given that I'm loading Foo the same (incorrect) way:


      #!/tool/bin/perl -w use strict; use lib '/home/user/perl_modules/lib/perl5'; use My::Foo; my $new_foo = Foo->new();

      I thought that generally speaking, whatever prefixed the :: of a module name (e.g. Foo) was used to denote directories that the module's file (Foo.pm) was within, relative to whatever was in my @INC. In my example above, the /home/user/perl_modules/lib/perl5 folder contains the 'My' folder containing my own personal modules (such as Foo.pm), but it also contains all of the folders of modules I downloaded from CPAN (it's a local perl installation), e.g.:

      /home/user/perl_modules/lib/perl5 My/ CGI/ Devel/ Pod/ ...

      and so on. Are you saying that instead of what I did above, I should instead do the following:


      use lib '/home/user/perl_modules/lib/perl5'; use lib '/home/user/perl_modules/lib/perl5/My'; use Foo;

      Or am I misunderstanding?

        Here's the thing... Package name and filesystem path have (almost1) no intrinsic connection at all. The only reason they're normally the same is because of convention. But you should absolutely follow this convention because, even though Perl can handle it without problems, it easily becomes very, very confusing for humans trying to use the code, as your post here illustrates.

        I suspect the reason you're not quite seeing the significance of the distinction is that, in this case, your package name and filesystem path are almost, but not quite, the same, so it's easy to gloss over that difference in your head. So let's rewrite things a bit to make it more obvious. In a file at My/Bar.pm, put

        package Xyzzy; sub say_it { print "It works!"; }
        Now, in your code, you load this file with use My::Bar; but there is no package named My::Bar, or even a package named Bar. The package is named Xyzzy, so you must call the sub with Xyzzy::say_it (or Xyzzy->say_it if you prefer, since it will just ignore all parameters passed anyhow).

        If you move the module file to Some/Random/Place.pm, you will then load it with use Some::Random::Place;, but the package inside is still package Xyzzy, so you still have to call the code with Xyzzy::say_it, not Some::Random::Place::say_it. The path you use the module at and the package within the module are two separate things.


        1 There is one exception. The one and only time that Perl cares whether the path and the package name match up is when you export symbols. This is because use Whatever; is equivalent to require Whatever; Whatever->import(); so, if the file Whatever.pm contains package Something::Else, and Something::Else exports symbols, that won't work because the use will implicitly call Whatever->import() (which doesn't exist) and not Something::Else->import().

Re: help with versioning modules
by NetWallah (Canon) on Nov 20, 2020 at 19:16 UTC
    Expanding on afoken's explanation:

    Your declaration of

    use My::Foo 1.0;
    is looking for the subroutine My::Foo::VERSION
    which does not exist (because your package name is Foo, not My::Foo).

    Update: The VERSION subroutine , if absent, is implicitly imported from UNIVERSAL - into your package, which , again, is named "Foo" and not "My::Foo" - hence "My::Foo::VERSION" continues to not exist, whereas Foo::VERSION would exist as a result of "package Foo;".

                    "Imaginary friends are a sign of a mental disorder if they cause distress, including antisocial behavior. Religion frequently meets that description"

Re: help with versioning modules
by afoken (Chancellor) on Nov 20, 2020 at 19:20 UTC

    On a related note, if I have modules that will only be used internally and that I want to version (as opposed to modules I download from CPAN, in which subsequent module updates overwrite the previous module version), suppose I have the following directory structure:

    /home/user/perl_modules/lib/perl5/My/FooVersion 1.0/ Foo.pm 1.1/ Foo.pm 1.2/ Foo.pm Devel/ Foo.pm

    How would I use a particular version of Foo.pm in my script? Perl is fine with:

    use My::FooVersion::Devel::Foo;

    but trying to reference any of the numbered versions in the same manner, even when I quote either the numerical portion or the entire string, produces syntax errors. For example, none of the following work:

    use My::FooVersion::1.1::Foo; use "My::FooVersion::1.1::Foo"; use My::FooVersion::"1.1"::Foo; use My::FooVersion::'1.1'::Foo; use 'My::FooVersion::1.1::Foo';

    use expect a bareword, not a string. Period. If you want module names that are not barewords, you are already begging for trouble, as package expects a namespace, not a string.

    Anyway, it is possible to load a module from such "unperlish" names. This is slightly hidden in use:

    Imports some semantics into the current package from the named module, generally by aliasing certain subroutine or variable names into your package. It is exactly equivalent to

    BEGIN { require Module; Module->import( LIST ); }

    except that Module must be a bareword.

    require allows to use a bareword, but you can alternatively provide a filename to be loaded. To generate a filename from a bareword, convert all :: and ' to / and append .pm.

    BUT

    I think the idea to load several versions of the same module into the same namespace is really, really begging for trouble.

    There may be reasons for having versioned module name spaces, for example to support APIs or protocols with different versions. Consider an imaginary set of HTTP protocol modules:

    • My::Protocol::HTTP for the user interface
    • My::Protocol::HTTP::v0_9 for implementing the first public version
    • My::Protocol::HTTP::v1_0 for implementing HTTP/1.0
    • My::Protocol::HTTP::v1_1 for implementing HTTP/1.1
    • My::Protocol::HTTP::v2_0 for implementing HTTP/2.0

    Note that version numbers were changed to allow their use in barewords. Also note that those modules would load into different namespaces.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

      > I think the idea to load several versions of the same module into the same namespace is really, really begging for trouble.

      The intent is to have multiple versions of the module so that when a new version of the module is developed, any other scripts that called a previous version can continue calling it without automatically having to start using the new version and risk something breaking. There would never be a case in which multiple versions of the same module would be loaded into the same namespace.

      > Note that version numbers were changed to allow their use in barewords. Also note that those modules would load into different namespaces.

      Something like that would probably work for what I'm doing.

Re: help with versioning modules
by NetWallah (Canon) on Nov 20, 2020 at 21:12 UTC
    For your issue with "Numbered version directories", I was able to get it to "work" by :

    * mkdir My/1.2
    * Move Foo.pm to My/1.2/
    * Change the internal $VERSION to 1.2
    * change the "use" declarations in foo_version_test.pl to:

    use lib 'My/1.2'; use Foo 1.2;
    Notice that the folder name goes into the "use lib", and avoids issues with bareword requirements for the module name.

                    "Imaginary friends are a sign of a mental disorder if they cause distress, including antisocial behavior. Religion frequently meets that description"

      Also, to prevent duplication, note that both the path in "use lib" and the module version in "use Foo" can contain variables, but you need to populate them in the compile time.
      my $required_version; BEGIN { $required_version = '1.2'; } use lib "My/$required_version"; use Foo $required_version;
      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Thanks, that should probably work for my case.

Re: help with versioning modules
by tobyink (Canon) on Nov 21, 2020 at 18:46 UTC

    Part of the reason I created MooX::Press and portable::loader was to create classes that were divorced from the Perl namespace, so you can have different versions of the same API which don't stomp on each others' namespaces.

    Try this for an example:

    git clone git@github.com:tobyink/misc-versioned-library-example-p5.git cd misc-versioned-library-example-p5 curl -fsSL --compressed https://git.io/cpm | perl - install perl -Ilocal example.pl

    (Assumes you have Perl 5.10+, git, and curl.)

    This is the contents of example.pl:

    #!/usr/bin/env perl use strict; use warnings; use feature 'say'; use portable::lib './lib'; use portable::alias 'Foo-1.0' => 'FooOld'; use portable::alias 'Foo-1.1' => 'Foo'; # Foo 1.0 my $bar = FooOld->new_bar( name => 'X', desc => 'Y' ); say $bar->desc; # Foo 1.1 changed 'desc' to 'description' $bar = Foo->new_bar( name => 'X', description => 'Y' ); say $bar->description;

    And here's the definition of Foo 1.1:

    use strict; use warnings; return { version => '1.1', class => { 'Bar' => { has => { 'name' => { type => 'Str' }, 'description' => { type => 'Str' }, }, }, }, }

    You could add a method to the Bar class like this:

    'Bar' => { has => { 'name' => { type => 'Str' }, 'description' => { type => 'Str' }, }, can => { 'do_something' => sub { my $self = shift; return 42 }, }, },
Re: help with versioning modules
by siberia-man (Friar) on Nov 23, 2020 at 08:31 UTC
    I think that the only module is developed exactly to cover the module versioning.

    Quotation from its pod:
    The only.pm facility allows you to load a MODULE only if it satisfies a given CONDITION. Normally that condition is a version. If you just specify a single version, 'only' will only load the module matching that version. If you specify multiple versions, the module can be any of those versions. ...

    only.pm will also allow you to load a particular version of a module, when many versions of the same module are installed. ...

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others chanting in the Monastery: (3)
As of 2024-04-18 23:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found