What is the real purpose of the $VERSION variable in Perl? - perl

I've read that it's considered good practice to always include a $VERSION number in all of your Perl scripts, but I never fully understood the purpose.
What do you really gain by including a version number in your script?

You can specify a minimum version of a module in the use statement:
use Foo 1.23;
When Perl loads that module, it looks at the $VERSION variable to check that it's equal to or greater than that number. That way you get the right set of features and fixes.
The CPAN clients use $VERSION to determine if you are up-to-date or if you should install the latest version of a module.
For programs or scripts, you typically don't need a $VERSION.

Only modules, and only those pushed to CPAN really benefit from specifying $VERSION. There's not much use for it in a script except to use a familiar name rather than inventing a new one if you want the script's version to be accessible.
Primary uses:
CPAN uses it as the version of the distribution, allowing it to index different versions of the same distribution.
cpan and cpanm uses it to check if an installed module is of sufficiently high level to meet the required minimum version of a dependency.
As brian d foy mentioned, it can also be checked by use Foo 1.23;, but most people avoid this in favour of specifying the required versions of dependencies in their distribution's META file. This allows cpan and cpanm to update the dependency if needed (where use using use Foo 1.23; would cause the installation to fail during testing). Due to the lack of use of the feature, it's hardly a primary use.

Related

How is local::lib supposed to handle XS and different versions of Perl?

I love the idea of local::lib. At least, I find it preferable to the default system-wide installs that cpan/perl defualts to in most every way. However, I'm having difficulties with modules that install XS and distribution upgrades to newer Perls. The instructions for local::lib call for adding this line to your .bashrc (or like file)
eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"
That executes arbitrary shell code, which is fine. You can see the code it executes by running
perl -I$HOME/perl5/lib/perl5 -Mlocal::lib
In my case it returns this code,
PATH="/home/ecarroll/perl5/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="/home/ecarroll/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="/home/ecarroll/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"/home/ecarroll/perl5\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=/home/ecarroll/perl5"; export PERL_MM_OPT;
In the above, we're setting the default options for the two most widely used module build systems,
MB is for Module::Build
MM is for ExtUtils::MakeMaker
We're telling those system to install to /home/ecarroll/perl5. The rest of the command sets
PERL5LIB which is used in setting #INC. The #INC variable in Perl is used to resolve calls with use.
and, PATH which prepends the bin/ directory that local::lib installs to.
So basically, install perl modules in your home directory, and look first for files installed in your home directory.
My question is what happens when you install something that has XS? In my case, I have an XS file,
/home/ecarroll/perl5/lib/perl5/x86_64-linux-gnu-thread-multi/auto/Class/Load/XS/XS.so
This was installed, with local::lib using an earlier version of Perl. I've since ran a Debian dist-upgrade and got a newer version of Perl (v5.36). Now a lot of utilities produce an obtuse error about this, including ones installed through the distro itself like in my case Dist::Zilla dzil which I installed with Debian's libdist-zilla-perl package.
$ dzil
XS.c: loadable library and perl binaries are mismatched (got first handshake key 0xeb00080, needed 0xeb80080)
Which is all a result of this module's XS,
perl -MClass::Load::XS -e1
XS.c: loadable library and perl binaries are mismatched (got first handshake key 0xeb00080, needed 0xeb80080)
This seems like very routine use of local::lib:
Am I using local::lib in the way intended? What else should I be doing?
How is local::lib supposed to handle XS?
Is there a way to make local::lib support multiple versions of Perl? My distro reads and writes the XS it manages to /usr/share/perl/$PERL_VERSION. This means a dist-upgrade leaves all the XS from the old version behind. It would be nice to have local::lib install not to /home/ecarroll/perl5/lib/perl5/ but instead to /home/ecarroll/perl5/lib/perl5.30/? Note the added minor version. Then at least a dist-upgrade wouldn't break the distribution.
Perl doesn't maintain ABI compatibility between versions. (5.34.x -> 5.36.y, for example.) After installing a new version of Perl, you will need to re-install modules that install themselves in arch dirs (XS modules and a few others). Might be easiest and safest to remove (rename) the local lib directory (/home/ecarroll/perl5) and start from scratch.
That's why I avoid using anything but distro-provided modules with distro-provided perl. The package manager ensures you always have compatible builds of the modules.
If that's not good enough, I recommend installing your own builds of perl instead of using local::lib. Tools like perlbrew makes this easy. You'll still have to re-install all the modules when you install a new perl, but you'll get the cleaner "Can't locate" error message if you forget one.
I rarely use local::lib, but I'm also a minimalist when it comes to administration. The more layers you have, the worse things get. As you see, you're own answer (now deleted) proposes even more complexity to deal with complexity that isn't likely appropriate for your situation.
But, local::lib has a way to set the locations of everything. You are trying to use the same location for everything instead of changing the locations based on what you are trying to do. However, even though local::lib lets you do that, you're likely to forget to switch things up.
My solution is simpler. I merely install a perl for whatever I'm doing (or reuse a suitable perl). I don't change the default locations at all. I never have to manage that. I simply call the perl I want:
$ ls -d /usr/local/perls/*
/usr/local/perls/perl-5.10.1 /usr/local/perls/perl-5.24.3 /usr/local/perls/perl-5.32.0
/usr/local/perls/perl-5.12.5 /usr/local/perls/perl-5.26.1 /usr/local/perls/perl-5.32.1
/usr/local/perls/perl-5.14.4 /usr/local/perls/perl-5.26.2 /usr/local/perls/perl-5.34.0
/usr/local/perls/perl-5.16.3 /usr/local/perls/perl-5.28.0 /usr/local/perls/perl-5.34.1
/usr/local/perls/perl-5.18.4 /usr/local/perls/perl-5.30.0 /usr/local/perls/perl-5.35.11
/usr/local/perls/perl-5.20.3 /usr/local/perls/perl-5.30.1 /usr/local/perls/perl-5.36.0
/usr/local/perls/perl-5.22.4 /usr/local/perls/perl-5.30.2 /usr/local/perls/perl-5.8.9
I have various symlinks and stable path adjustments to I use the tool name and the version tag I gave it:
% cpan5.36.0 ...
% cpan5.32.1 ...
People use all sorts of tools such as perlbrew, plenv, and now some other tool that you think might solve it. It's a big red flag if you have to constantly create new tools to deal with the tools that you are using. I'd rather spend my time doing something else.
I stand by my other answer—this is too much complexity and you should reduce that. But, lets work with that constraint. You want to use local::lib.
The problem is that you are trying to force the same set of modules on any particular perl, no matter how it was compiled. As ikegami explained, you can't assume that two different perls, or the same library compiled with different perls, are compatible. The version of the perls does not matter. Two different perl5.36, or any version, can be incompatible with each other.
But local::lib gives you what you need to set the appropriate values. The module itself can supply some starter values:
$ perl -I$HOME/perl5/lib/perl5 -Mlocal::lib
Attempting to create directory /Users/brian/perl5
PATH="/Users/brian/perl5/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="/Users/brian/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="/Users/brian/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"/Users/brian/perl5\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=/Users/brian/perl5"; export PERL_MM_OPT;
But, you don't need to use those values everywhere. You can have as many sets of those variables as you like. You can have a different set for every project, every perl, or however you want to organize it. The only trick is how you select the right set.
The synopsis of local::lib indicates that it expects you to set the appropriate value for your project:
use FindBin;
use local::lib "$FindBin::Bin/../support"; # app-local support library
Knowing that, you can have your per-version libraries by adding $^V in the path you give to local::lib (still with the binary incompatibility problems):
use FindBin;
use local::lib "/my/modules/$^V";

Do all my perl module .pm files need a `$VERSION` definition?

I just added a new file to the PDL::IO::Touchstone distribution and noticed that CPAN's indexer says version is undef since $VERSION is missing:
module : PDL::IO::MDIF
version: undef
in file: PDL-IO-Touchstone-1.009/lib/PDL/IO/MDIF.pm
status : indexed
So ::MDIF does not have $VERSION but really it is the same as the distribution version as noted in Makefile.PL:
my %WriteMakefileArgs = (
VERSION_FROM => 'lib/PDL/IO/Touchstone.pm',
...
);
Questions:
So does this module within the distribution need a version?
If so, should the new module's $VERSION be maintained separately from $VERSION provided by VERSION_FROM in Makefile.PL?
I could do $VERSION = $PDL::IO::Touchstone::VERSION but not sure if CPAN will figure that out. Will it?
I looked around and found lots of discussion of versioning practices, but nothing about versions of modules within the same Perl distribution package. Please share what the best practice here should be, I'm new to Perl modules and this is the first 2-file distribution that I've pushed out.
I'm sure I'll update the primary file when releasing a new dist, but not sure if I'll remember to bump the version of other modules in the dist when they change. It would be nice if there is a low-maintenance option here.
Update
I tried the suggestion in some answers below. Neither of these work:
$VERSION = do { use PDL::IO::Touchstone; $PDL::IO::Touchstone::VERSION };
use PDL::IO::Touchstone; our $VERSION = $PDL::IO::Touchstone::VERSION;
This is the MDIF.pm file at github: https://github.com/KJ7LNW/perl-PDL-IO-Touchstone/blob/master/lib/PDL/IO/MDIF.pm#L22
CPAN still shows version: undef:
Status: Version parsing problem
===============================
module : PDL::IO::MDIF
version: undef
...
Ok, so who gets the checkmark... any other ideas?
I like to give all of my modules versions. It's not a requirement, but consider the user experience while trying to upgrade when they don't know anything, or very little, about how CPAN works. ikegami already gave the tl;dr for this.
When you want to update a module from CPAN, the tool looks at that module's $VERSION (well VERSION sub). If that module doesn't have a version, that's undef. When CPAN.pm (or other tools maybe) looks to see if there is a more recent version, it does not find one it thinks is more recent because the latest version is also undef.
A concrete example is Mojo::UserAgent. Mojolicious only versions the main module. The other modules do not have versions. I typically do a lot of web client stuff, so my work product doesn't care about the web server side of Mojo. People using some of my stuff might not even know there is a different part of Mojo aside from the client stuff (they might even know Perl).
If I run cpan Mojo::UserAgent, it compares my currently installed version (undef) to the latest on CPAN (undef). This means that CPAN.pm guesses that I do not have something earlier than the latest available.
$ cpan -D Mojo::UserAgent
Mojo::UserAgent
-------------------------------------------------------------------------
(no description)
S/SR/SRI/Mojolicious-9.28.tar.gz
/usr/local/perls/perl-5.36.0/lib/site_perl/5.36.0/Mojo/UserAgent.pm
Installed: undef
CPAN: undef up to date
Sebastian Riedel (SRI)
kraihx#googlemail.com
Now, I have this special knowledge that this distro does this. But someone merely using something I wrote with Mojo::UserAgent probably doesn't. If they need Mojolicious 8 to get particular CSS selectors then, how do they know that?
Normally, you can specify a minimum version of a module:
use File::Glob 8.0;
I can't require a minimum version of Mojo::UserAgent:
use Mojo::Usergent 8.0;
I get a weird error (there are other import issues and design decisions going along with this):
Can't locate 8.pm in #INC (#INC contains: ...)
This isn't a big deal because Mojolicious is mostly self-contained, so I can specify its minimum version:
use Mojolicious 8.0;
But, I have to know how all of this works, how that distro works, and how Perl works to do that. For the casual user of my stuff who just wants to get work down, including modifying what I've done, they have to discover some lore to know this. They have to know how CPAN.pm or PAUSE decides what the latest version is, and so on.
Furthermore, this is a problem for modules in a distribution that don't change and have no logical reason for a version bump. Well, they do because they have an implicit dependency on the main module that does have a version, but we're not in the habit of declaring that version. Consider, for example, some module that works with an API that has changed, but the meat of the change is in another module it uses. The module you use directly has not changed its literal text, but it can't work as is because the world is different. But, since the literal text in that file hasn't changed so we typically don't bump the version.
Maybe we should declare that main module dependency, but that's a lot of work. It adds some seemingly gratuitous churn to files, which is annoying in repo history. Ah hell, now that I've thought of that, I need to fix this in some distros. Some-not all. I don't know. Something to think about.
Of course, I can document all of this in the distro, script, or whatever. But we know that I can say it and they can read it and still not appreciate the importance. And, by this time we're a far way away from "just working".
CPAN only requires a version for the distribution, and you have provided that using VERSION_FROM.
There are a variety of practices.
Only the "main module" has a version.
Each file has its own version.
Each file shares the distro's version.
CPAN evals the line that sets $VERSION, so the following should work if it's all on one line:
use PDL::IO::Touchstone; $VERSION = $PDL::IO::Touchstone::VERSION;

How to have Dist::zilla specifying min Perl version in YAML?

I’d like to specify minimum required Perl version in the yaml file. While I did put perl among requirements in my dist.ini, I think I need some module to have it written to the yaml file at build time.
My dist.ini is https://metacpan.org/source/PROCH/Proch-N50-0.032/dist.ini
The kwalitee issue is described here https://cpants.cpanauthors.org/kwalitee/meta_yml_declares_perl_version
Thanks for your help.
The convention for specifying a minimum perl version for a distribution is a runtime-requires prerequisite on 'perl'. (Note: I say convention, because there is no 'perl' module, and you can't actually cause a CPAN client to install a newer Perl if it's too old.) The appropriate plugins will read this and set other things based on it, like "use 5.XXX" in the generated Makefile.PL. Instead of passing prereqs as options to [#Basic], you need to use a plugin which can set prereqs, like [Prereqs]. Here is the documentation. You would want to use it like:
[Prereqs / RuntimeRequires]
perl = 5.018
JSON = 0
You can use the Prereqs plugin additional times for different Phase+Relationship combinations, but they each must have a unique name (the part after the /).
You can do this explicitly as stated above or you can handle it automatically via Dist::Zilla::Plugin::MinimumPerlFast
# In your dist.ini:
[MinimumPerlFast]

Check module compatibility with different Perl versions

Is there a command line switch or any other easy way that I can check the compatibility of my module with different versions of perl? I understand that if I put use 5.6.1; at the top of a script, the script will have any features later than 5.6.1 disabled. But if I have a module which uses several other modules and so on, I need a quicker way to check what minimum version of Perl to require in my Makefile.PL or Build.PL.
Run your module against Perl::MinimumVersion.
use v5.6.1 and similar will primarily raise a compile-time error if the version of Perl in use is older than that specified. In addition, the corresponding feature bundle is enabled, and if the version is v5.11 or higher, the strict pragma is enabled as well.
use v5.6.1 will not make Perl v5.8 behave like v5.6.1, and there is no way of achieving this. Even in later versions it is only features that can be controlled using the feature pragma that are adjusted to a given version.
To test your module you will have to install each different version of Perl and test it separately
You can just install different versions of perl and test against those. I install them by hand in different locations but if you want to automate that, use perlbrew.
There might be scripts or strategies that try to find the minimum version of perl needed, but everything i tried several years ago to obtain and aggregate the minimum perl version i found clunky and unreliable. Which of course doesn't mean, that it doesn't exist or can't be done, but just that i couldn't find it.

Will all perl versions support older modules?

I'm have Perl 5.8 installed on all of our servers and wanted to use the DBI and DBD::Oracle modules to access our databases. My main concern is with the newer versions of perl the DBI and DBD modules will stop working with 5.8. Then I'd have to upgrade every server to the newest perl version.
My question is as perl itself becomes later versions and modules are developed for them will they still be backwards compatible? "CPAN does not carry all ancient releases and patchlevels of Perl", if I create documentation saying run "cpan -i DBI" if the newest version of DBI will not function with 5.8?
There is no guarantee.
In general, you want to use the same versions of the modules on all your systems. If you use different versions then you will have different bugs and features available on different servers.
I'd suggest creating Debs / RPMS / etc for the ones you are going to use and then run a package repository that all your servers share.
Not absolutely, but in general perl is pretty gentle about breaking code, with not many breaking changes, and long deprecation cycles on the ones that do happen. A pretty hefty portion of the code that was uploaded to CPAN in 1999 will run without modification in perl 5.14.
Since perl 5.12, the perl release cycle has become shorter, and the deprecation periods have also become shorter, which is cause for concern, but at the same time the concept of feature versioning has gained currency. The idea is that code may declare the version of perl that it's targeting with use VERSION (e.g. use 5.16.0), and any code that doesn't declare a version is assumed to be targeting approximately 5.10. When code that targets an older perl version is run on a newer perl version, newer features that might cause compatibility issues (e.g. new keywords) are disabled, and old misfeatures may be re-enabled in the name of compatibility. This isn't an absolute guarantee, but it will be adhered to as much as practical.
More information about back-compatibility and deprecation is in perlpolicy.
In general, no. There are a lot of great new features in recent releases of Perl (smart match operator, the // operator, for two one example) that are not backwards compatible. Many authors will decide to take advantage of these features rather than keep their modules compatible with older versions of Perl.
Check the CPAN Tester's Matrix of a module, including the link about the max version that passes all of the distribution's tests, to get an idea of what versions of Perl are compatible with each version of a module.
cpan -i Some::Module will indeed attempt to install the latest version of the module Some::Module, but with a little research, it can be used to install older versions too. You need to find or guess the author of an older version and provide the path to the distribution on the CPAN mirror servers. For example,
cpan -i J/JF/JFRIEDL/Yahoo-Search-1.9.12.tar.gz
cpan -i A/AS/ASG/List-Gen-0.80.tar.gz
CPAN authors may delete their older distributions from CPAN. But even then, the distribution is available at the BackPAN if you're willing to download, unpack, and build the distribution yourself.