lib::vswitch - select another version of 'dist' into @INC
# In the calling code
use lib::vswitch BioPerl => '1.2.3';
use Bio::AlignIO;
# XXX: describe how it gets there
This module adds to @INC
the necessary directories for a specific version of a distribution (dist), so the modules inside may be loaded.
It also takes steps to ensure scripts loading multiple modules do not inadvertently attempt to use
two different versions of a dist simultaneously.
The files for the specified version of the dist must have been installed already, in a particular way. XXX: This module does not yet help with that, but it should.
Modules from one version of a dist may rightfully assume that other modules from the same dist are of the same version.
Maintaining the validity of this assumption is something lib::vswitch should do, and ad-hoc version switching may not.
When the dist is not already present on @INC
, we simply add a dist no more than once. This is mediated by "%VSW" and "uselib".
Below are cases where the dist Foo
has version 1 already available on @INC
, and the caller requests to uselib(Foo =
2) >
- A.
-
Module
Foo::A
was loaded fromFoo v1
. Version switch to distFoo v2
makes a different version ofFoo::A
available. - B.
-
Module
Foo::B1
was loaded fromFoo v1
. Version switch to distFoo v2
makes available aFoo::B2
which expectsFoo::B1
to load fromFoo v2
also. - C.
-
Version switch to dist
Foo v2
- no modules loaded fromFoo
yet.Module
Foo::C3
appears in both versions, but there is no problem because the newer one is found and the old one is shadowed. This is otherwise likeFoo::A
above.If module
Foo::C1
appears only in v1 andFoo::C2
appears only in v2 (modules added to or removed from the dist), it becomes possible to load either or both. Each could reasonably expect a version match withFoo:::C3
, and the absence of the module from the other version.
For A and B, it was too late to vswitch, so we should refuse to try. For C, we must prevent the loading of modules from the shadowed dist.
Case A is simple to detect, though it needs some I/O.
Cases B and C requires the knowledge that Foo
dist contains, in various versions, all of those modules; but without assuming that it includes the entire Foo::
namespace. This may be found by reading the .packlist
if that is available, or for a vswitch-installed dist by scanning the tree.
Mechanisms to prevent accidental mixing of modules from different versions may also make it harder to deliberately replace portions of the dist.
This is undesirable, because the maintenance of this code was already complicated before we got involved. Better would be for the code doing this to make it clear what is happening.
A likely solution is that the dist version being switched in has local edits applied: use lib::vswitch BioPerl => '1.2.3-patched'
.
The "do not vswitch a dist twice" rule can be prevented by deleting from "%VSW". A neater way might be nice.
The "do not vswitch when the new tree contains an already-loaded module" rule may need an override. Possibly of the form use lib::vswitch Foo =
2, -force_loaded => qr{^Foo::A$} >.
This is yet-another module messing with @INC
. There should be a good reason.
Nearby I find several applications which depend on a specific version of some distributions of modules. These dependencies are maintained in several ways.
Write the app to assume the latest version of a module, within some range. Make this explicit in the
use
when there is a known bug.With this, version requirements increase gently upwards. Much of CPAN operatees in this way.
Problems start when the new version of a module removes some feature and you lack the time to work around, re-implement or test for the new version; or you use a locally hacked version and the changes do not make it upstream.
Once you are stuck on an old version, there might be no incentive to upgrade and it could require an increasing amount of work.
Write the app to use whichever version it finds itself presented with, in a backwards- or forwards-compatible way, using the documented replacements for deprecated calls etc..
I mention this option for completeness. If your code was doing that, you wouldn't be reading this.
You can "roll your own" during app installation: install the version you need and make sure nobody upgrades it.
We often do this, in various ways. It is quick and easy.
When the application with the version requirement is a collection of modules (a "dist"), it may have callers which also need and provide the same module. Will they use the correct version? Would your code notice if it didn't?
The
use
function doesn't provide for a "maximum version" so you are on your own at this point.There may be a perfectly usable copy of the modules you want already on
@INC
, but while you need to force the use of a known-version copy it will be difficult to take advantage of that.Build something over Module::PortablePath and use tags of the form
ensembl62
.These tags must be maintained (in our case, by the central authority via a helpdesk ticket). You need a new one for each version.
What happens if a script brings in ensembl62 and then later adds ensembl63?
How do you know when ensembl47 is no longer used?
Distributions which are well known to have dependent applications using specific versions could provide explicit support to do this.
This could reduce the repetition down to approximately the number of dists used this way, but only where and when the dist authors care to add support.
In short, the problem can be managed but it takes some effort.
From the list above, there are some common problems on the application side
Prevent the simultaneous import of two versions of the same dist.
Allow safe sharing of known-version dist install trees.
and more in the managing of installations of dists
It should be as easy to install another version as to install any other dist.
Installed versions should be enumerable.
It would be very useful to enumerate dependent applications. Preferably without waiting for them to be run, in order to hook the version switching call.
XXX: Here the Module::PortablePath tag solution may be superior because it uses one symbol-shaped string to declare the dependency.
Bonus points for making the often-used set of known-version dists easy to install via CPAN.
The freezing of module version requirements in otherwise-living code is itself a problem.
Will making it easier to manage the knock-on problems make that worse?
This hash is used to prevent the use of different versions of the same dist.
It is offered as part of the public API for this module to improve transparency. Keys are dist names, values are the versions set by "uselib".
Writing to it is likely to cause problems.
These are not particularly intended for normal use by client code, but might form some useful building blocks.
Return the path for the specified version of the named dist.
This is based on "class_file" in File::ShareDir and so should respect subclassing of lib::vswitch (XXX: untested).
Discover the new path using "find", then ask lib to add it to @INC
.
This checks and updates "%VSW" to prevent multiple uselib
of the same dist.
This does the work when you write something like
use lib::vswitch 0.73 BioPerl => '1.2.3';
# 0.73 would be a minimum version for lib::vswitch itself.
use lib::vswitch 'Foo-Bar' => '0.05-hacked';
Currently it takes one ($dist => $vsn) pair and calls "uselib" with them.
Extra flags or the ability to uselib
multiple pairs may be added later.
There is no unimport via no
. It is not clear that this could be done safely.