The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Module::FromPerlVer - install modules compatible with the running perl.

SYNOPSIS

    # Aside: unless anyone can find a glaring omission in 
    # the mechanism or utility sub selection this will
    # become version v1.0.

    # ./version directory has sub-dirs with basenames of 
    # parsable perl version strings.
    # 
    # when this module is used the highest numbered version
    # directory compatile with the running perl is copied 
    # into the execution directory of Makefile.PL.
    #
    # source_paths() is useful for describing what gets
    # copied, cleanup() is handy for iterating tests or
    # prior to making a bundle.

    # Makefile.PL

    use Module::FromPerlVer;

    # relative paths to files copied for this version of 
    # perl -- different versions of perl may have different
    # collections of files copied.

    my $copied_files    = Module::FromPerlVer->source_paths;

    # remove the files copied and any empty direcories they
    # were copied into (dir's with pre-copy files left in
    # them are untouched).

    my $removed_count   = Module::FromPerlVer->cleanup;

    # at this point lib, t, and friends are populated with 
    # modules compatible with the running perl. because 
    # the destination directory is $Bin this can include 
    # README, MANIFEST, or Changes files. note that 
    # overwriting  Makefile.PL will *not* work since it 
    # has already been compiled.

    # override the perl version:

    # this can be useful for regression testing earlier 
    # versions of modules against the running version 
    # (e.g., validating experimental features, benchmarking
    # older code against newer perl executables).
    #
    # arguments can be passed to use or set in the environment
    # with a variable of the option uppercased. For example:

    $ PERL_VERSION='5.024002' perl Makefile.PL;

    use Module::FromPerlVer qw( perl_version v5.24.1 );

    # use a file to determine the perl version.
    # this takes the use-ed version from the file
    # to set the expected perl version (e.g., to 
    # validate if other modules are suitable).

    use Module::FromPerlVer   qw( version_from ./lib/foobar.pm );

    VERSION_FROM='./lib/foobar.pm' use Module::FromPerlVer;

    # or a one-line datafile.
    # "use vX.Y.Z" will find up to that version, "no vX.Y.Z" will 
    # use up to that version minus '0.000001'.
    #
    # perl versions with use or no support any version
    # string avaiable (see "version" module).
    # 
    # say that v5.24.2 breaks your module:

    echo 'no v5.24.2' > ./perl-version;

    use Module::FromPerlVer   qw( version_from perl-version );

    # override the source directory:

    # maybe you don't like the name 'version', you
    # prefer "history" instead.

    use Module::FromPerlVer   qw( source_prefix history );

    # maybe you don't like storing your older versions in 
    # a directory, you prefer to use git with tags to snag
    # the working directory with a default tag prefix of 
    # "perl/":

    use Module::FromPerlVer qw( use_git 1 );

    # use ThingyDotNet/<perl version> as the git tag:

    use Module::FromPerlVer qw( source_prefix ThingyDotNet/ );

    # set up everything but skip making the copy.
    #
    # this reqires callig "get_files" to get the 
    # versioned files in their proper place.
    #
    # none of the utility subs take any arguments and
    # can all be called using module or object notation.

    use Module::FromPerlVer   qw( no_copy 1 );

    my ( $filz, $dirz ) = Module::FromPerlVer::source_files;

    Module::FromPerlVer->get_files;

    my $madness = 'Module::FromPerlVer';
    my $method  = 'cleanup';

    $madness->$method;

DESCRIPTION

Basic idea: Divide up the source space for a module distro by supporting Perl version. At that point when you want to start using features in a new version of Perl just start a new directory and work with it. When you release the module distro the module's version compatible with the running perl will be selected at install time. No tests in the module for $^V are necessary.

Using the module

Using this module requires two things: The module and a source for perl-version-speific files. With "use_dir" the files are found in a specific local directory; with "use_git" the are found via (guess... if you said "cvs checkout from ./attic" you'd be wrong).

Version Directory

The default directory for module versions is "version". This has sub-directories that are parseable as Perl versions:

    ./version/5.005_003  
    ./version/5.006001
    ./version/5.16
    ./version/v5.24

in each case the basename of the directory is processed by version::parse.

The filesystem under each version is whatever dir's and files are suitable for that version of perl. Common examples are README, MANIFEST, Changes, ./lib, ./t, ./bin.

Version Tag

With git the current repository is scanned via "git tags" with the source prefix. The versions need to look path-ish:

    perl/v5.6.1
    Perly/5.005003
    MyPerlStuff/v5.24

will all do nicely: they have a '/' with a "basename" of a parsable perly version.

Git in the MANIFEST

The git checkout requires a working git repository to work in. This means that your tarball must be a working clone of the repository. That in turn menas that the MANIFEST include everything required to copy a working repository.

Basically:

    find .git -type >> MANIFEST;

will do the trick. You might not want to copy all of your hook files, etc, in which case feel free to copy whatever minimal repository you wish.

Aside:

    The alternative is that noone can install the module without a 
    working network connection (not simply test it, the repository 
    must be accessible for "perl Makefile.PL" to extract the 
    appropriate version tag).

Anyone with a strong opinion one way or the other should feel free t contact me about this.

Basic Use

Using the module in setup code (e.g., Makefile.PL):

    use Module::FromPerlVer;
    use Module::FromPerlVer qw( use_dir 1 );

will look in the ./version directory, sort the subdir's in numerically decreasing order by version (see version) and finds the highest version directory that is less than or equal to the running perl's version. The contents of this directory are copied to the same directory as the running code.

Versions derived from "use" will be less than equal to that version, versions derived from "no" will be less than or equal to the version minus 0.000001.

Supplying the Perl Version

It may be useful in testing to choose a specific subdirectory (e.g., regression testing older code with newer perl executables, or testing the module itself).

Each of these will be passed to version::parse for final validation. If version cannot parse the value then the code will die with a "Bogus perl_version:..." error showing the version string being parsed.

In order of priority the Perl version used to select the module's compatible directory are:

$ENV{ PERL_VERSION }

Any true value of will be used. If this is supplied with the "version_from" argument a warning will be issued and the environment variable will be used. Note that "Cat" "Dog" and "I don't know" are all true, but useless and will cause the code to croak.

One use of this is testing the current module with multple versions of perl:

    #!/bin/bash

    for perl in /opt/perl5/5*/bin/perl
    do
        for i in version/*
        do
            # pick the appropriate module 
            # version from whatever is running.

            perl Makefile.PL    && 
            make all test       ;
        done
    done

or testing all the available module versions with a specific version of perl:

    #!/bin/bash

    perl='/opt/bin/perl-5.24.1';

    for i in ./version/*
    do
        COMPATIBLE_VERSION="$(basename $i)" \
        $perl Makefile.PL                   &&
        make all test                       ;
    done
use Module::FromPerlVer ( version_from => $path );

The path will be scanned for "use <version string>" and the first one located will be processed. The file can be a module, executable, or flat file with one line in it, so long as "use" followed by a parsable version string is found Life is Good.

The main use of this is validating multiple versions of perl with a specific release of the module.

Overriding "version".

If you hate the name "version", use "version_dir" to set it:

    use Module::FromPerlVer qw( version_dir history );
Skipping the Copy

If you prefer to call get_files yourself (e.g., after cleanup or pre-configuring some other part of the environment) use:

    use Module::FromPerlVer qw( no_copy 1 );

This will install all of the utility subs (see "Utility Subs" below) but will not execute get_files().

Use Messages

The module supplies most of the information necessary to debug environment and configuration issues when import() is called:

    # Def: default_use = 'dir'
    # Def: version_dir = 'version'
    # Def: git_prefix = 'perl/'
    # Extract with: 'Dir' (Module::FromPerlVer::Dir)
    # Perl version: 5.026001
    # Source prefix: ./t/version
    # Module version: 5.005003 <= 5.026001
    # Module source: ./t/version/5.005003
    # Source files:
    #   ./lib/Foo.pm
    #   ./pod/Foo.pod
    #   ./etc/version.dat
    # Install cleanup.
    # Get files with: 'Dir'
Configuration
    # Def: default_use = 'dir'
    # Def: version_dir = 'version'
    # Def: git_prefix = 'perl/'

There are three variants: 'Def', 'Env', 'Arg' which show where the values came from.

Handler

This shows which of use_* or the default_use was chosen for the copy handler. In this case the default_use value of 'dir' provided the source:

    # Extract with: 'Dir' (Module::FromPerlVer::Dir)
Utility Installation

When the utility subs are installed they provide basic information about the values selected from the arguments, perl version, and workspace.

See "Utility Subs" (below) for more information on what these mean.

Utility Subs

A few utility sub's are installed into Module::FromPerlVer when it is used. These are used internally to drive the file copy process and can be used to supply the values used to build the module filesytem.

None of these take any arguments: they cannot be used to re-set the values determined at import time.

None of them are exported, they can be called usig either package or object notation:

    Module::FromPerlVer::get_files;   # package::subname

    Module::FromPerlVer->cleanup;           # class->method
    $module_name->cleanup;

None of them exist until import() is called: requiring Module::FromPerlVer will not install any of them.

The only one that is likely useful outside of the module itself are copy_files() and cleanup() which can be called from Perl code for repeated testing (e.g., bash that iterates perl versions).

Calling convention.

All of these ignore any arguments, they can be called as class methods or via fully-qualified paths with the identical effect:

    Module::FromPerlVer::foobar();
    Module::FromPerlVer->foobar();
perl_version()

Returns the parsed, numified Perl version value (see version module).

version_source()

Returns the source of the version directory (e.g., dir "version" or tag "perl/").

version_source()

The subdir or tag from which the files are sourced. This will be a relative path from $FindBin::Bin, including the version directory:

    ./version/5.006001
    ./version/v5.24.2

    perl/v5.24
    perl5.005_003
source_files()

For directory source only (i.e., where the files can be looked up easily beforehand):

Used in a scalar context this returns an arrayref of relative paths to files copied from the source_dir into the working directory by copy_files().

Used in a list context it returns two arrayrefs: one of the files one of the directories. The former is used with unlink in cleanup to remove only the files that were copied; the latter is used to rmdir empty directories.

get_files()

This executes the copy/checkout of source files.

Note: If import is called with "no_copy" and a true value then this will have to be called, see examples for calling convention, above.

cleanup()

For Dir this removes the files copied, empty dir's left behind. This approach allows for some files to be re-used across multiple releases without getting clobbered by cleanup. Likely examples are common tests (i.e., t/*.t) or a MANIFEST for modules which have all of the same files in each version.

For Git this checks out whatever branch was in use when copy_files() was called. That's the best guess I have for what was in use at the time a verion-specific tag was checked out.

NOTES

Q: Why tags, not branches?

A given brach may have un-tested updates on it. At that point it is not safe to assume HEAD will always work; at *that* point there isn't any good way to automatically checkout a given branch.

Tags are static, can be assigned by the developer for specific commits, and can be re-assigned as necessary if code is updated.

This also saves a whole lot of separate branches for version- specific generations of code. Just leave them in master -- or wherever you work -- with the tags behind.

SEE ALSO

version

This does the parsing of version numbers from code and dirs. The POD incudes examples of both parsing and sorting Perl versions.

File::Copy::Recursive

Describes how the files are copied by Dir.pm.

LICENSE

This code is licensed under the same terms as Perl-5.26 or any later released version of Perl.

COPYRIGHT

Copyright 2018, Steven Lembark, all rights reserved.

AUTHOR

Steven Lembark <lembark@wrkhors.com>