Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Why eval $version?

by Aldebaran (Deacon)
on Jul 08, 2020 at 22:22 UTC ( #11119051=perlquestion: print w/replies, xml ) Need Help??

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

I'm still processing what I experienced at TPC. I've had time to catch up on some of the youtube videos from the ones I missed. Sometime in all the zooming, I thought I heard one of the presenters say

that's why we eval version

I thought it was Sawyer X, who says a lot of things parenthetically, but I went over his talk for a third time and didn't hear it again. I didn't understand what he meant, so I could well have misheard it or just dreamed up the recollection wholesale.

A couple days later, I'm going through the guts of a test script for local::lib and I see this, beginning line 16 of xt/bootstrap.t

sub check_version { my ($perl, $module) = @_; my @inc = `$perl -le "print for \@INC"`; chomp @inc; (my $file = "$module.pm") =~ s{::}{/}g; ($file) = grep -e, map { "$_/$file" } @inc; return undef unless $file; my $version = MM->parse_version($file); eval $version; }

Q1) What purpose does this line serve?

 eval $version;

Q2) Is this a "string eval?"

Q3) Do dangers lurk in its use, cf. Uri Guttman's 2019 TPC talk on 'eval'?

Q4) Does anyone else remember the "that's why we eval version" comment, or is it just me?

Thanks for your comment,

Replies are listed 'Best First'.
Re: Why eval $version?
by davido (Cardinal) on Jul 09, 2020 at 15:00 UTC

    There are a number of sources of information on why this has crept into the preamble of many modules, but the authoritative source is probably perlmodstyle, where it says this:

    A correct CPAN version number is a floating point number with at least 2 digits after the decimal. You can test whether it conforms to CPAN by using

    perl -MExtUtils::MakeMaker -le 'print MM->parse_version(shift)' \ 'Foo.pm'

    If you want to release a 'beta' or 'alpha' version of a module but don't want CPAN.pm to list it as most recent use an '_' after the regular version number followed by at least 2 digits, eg. 1.20_01. If you do this, the following idiom is recommended:

    our $VERSION = "1.12_01"; # so CPAN distribution will have # right filename our $XS_VERSION = $VERSION; # only needed if you have XS code $VERSION = eval $VERSION; # so "use Module 0.002" won't warn on # underscore

    With that trick MakeMaker will only read the first line and thus read the underscore, while the perl interpreter will evaluate the $VERSION and convert the string into a number. Later operations that treat $VERSION as a number will then be able to do so without provoking a warning about $VERSION not being a number.

    Never release anything (even a one-word documentation patch) without incrementing the number. Even a one-word documentation patch should result in a change in version at the sub-minor level.

    So it's a trick to allow ExtUtils::MakeMaker to be able to deal with underscores in development-level release version numbers, while still letting numeric checks of version numbers work. The reason it works is because Perl allows underscores in numeric literals, but to get that treatment, the numeric literal needs to be evaluated by the interpreter. We need to preserve the underscore in the string for EU::MM (so can't make it a literal at compiletime), but we need it to get that special treatment eventually. That requirement is solved by doing the string eval.

    For a subset of possible numeric representations a substitution regular expression such as this would be equally adequate: $VERSION =~ tr/_//d;. But I imagine that is inadequate for some of the edge cases of what Perl is able to convert to a number. There's a purity to shrugging and saying "Rather than trying to solve it through string manipulation, let Perl solve it the way Perl already knows how to."

    This is another unfortunate bit of "tribal knowledge" discussed in one of SawyerX's TPCiC talks. Wouldn't it be great if EU::MM could look elsewhere to decide if this is a development release, or if use Foo 0.02 already dismissed underscores from $VERSION. But then this might break some esoteric machinations performed by modules already on CPAN, so in an effort to avoid breakage we inflict confusion through another piece of knowledge that raises the barrier of entry a little higher.

    Update: There are, of course, other authoritative sources of information on how the versioning works, and these can provide additional context as to why the eval $VERSION code is common:

    These don't explicitly mention the underscore significance, but do discuss how EU::MM finds version numbers, and what constitute legal version numbers.

    Additionally, there's the oracle itself: The venerable CPAN documentation: The Perl programming Authors Upload Server (PAUSE): Developer Releases. This oft-missed document needs to be required reading for CPAN contributors.

    What is not immediately clear when reading that section of the document is that make dist incorporates the version number into the distribution tarball's filename. So if there's an underscore in the version number, there will be the requisite underscore in the filename, hinting the CPAN / PAUSE indexer that this is a development release, not to show up in the mainline index. It also informs that there are "other ways to do it." Putting -TRIAL in the distribution tarball's filename immediately before the file extension would also cause it to be marked as a development release. This can be accomplished in the Makefile.PL by including a DISTVNAME field with the version, including that -TRIAL bit, but this doesn't seem to be a very common approach. It could also be gamed with the DISTNAME field, but then the author has to be really careful to meet proper semantic requirements.


    Dave

      Wouldn't it be great if ... use Foo 0.02 already dismissed underscores from $VERSION.

      Doesn't it?

      $ cat Foo.pm package Foo; use strict; use warnings; our $VERSION = '1.01_01'; print "Hello!\n"; 1; $ perl -I. -we 'use Foo 1.00; 1;' Hello! $

      I must have missed something but there are no warnings here (on perl 5.20.3) and there's no eval in the module. Is this a solved problem?

        The problem is that $Foo::VERSION is still 1.01_01, the underscore included.
        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        This works for the same reason, why eval $VERSION works, when numifying a string Perl accepts all digits till it encounters a non-digit.

        So when comparing numerically the strings won't pose a problem.

        That behavior is inherited from a C function a can't recall something like atonumber().

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

Re: Why eval $version?
by LanX (Cardinal) on Jul 08, 2020 at 23:47 UTC
    > Q1) What purpose does this line serve?

    the long read: https://xdg.me/blog/version-numbers-should-be-boring/

    in short: there are different notations for version numbers which only become comparable after eval.

    > Q2) Is this a "string eval?"

    yes

    > Q4) Does anyone else remember the "that's why we eval version" comment, or is it just me?

    Sawyer gave two talks, probably you are confusing them?

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

      there are different notations for version numbers which only become comparable after eval

      Yes, there are some notations that require the eval.
      However, I believe the eval is not needed for comparison when the version string notations are valid floating point numbers (eg "2.31" or "3.20200729").

      Cheers,
      Rob
        One can use _ as delimiter in long literal numbers but not when numifying strings.

        "3.202_007_29"

        Eval treats the string as a literal number.

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

Re: Why eval $version?
by Haarg (Curate) on Jul 10, 2020 at 10:20 UTC

    Other comments have covered various reasons why some code does eval $VERSION and other version number complications, but the local::lib test has some specific concerns. The test needs to be able to run with no non-core modules on perl versions going back to 5.6. This means tools like version.pm are not available to make the version comparisons easier. Additionally, the test needs to check the version of ExtUtils::MakeMaker, and several releases of perl shipped with versions of it that included underscores in the version.

    So the intent is to convert a value like "6.57_05" into the number 6.5705 so that it can be compared numerically. This could be done in other ways to avoid using stringy eval, but given the use case there isn't really any danger from using an eval.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (3)
As of 2020-09-19 03:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    If at first I donít succeed, I Ö










    Results (114 votes). Check out past polls.

    Notices?