Upgrade all modules installed by local::lib - perl

I've been using local::lib to handle the installation of Perl modules on a server so I can get the right versions for some development work without polluting the system installation.
However, the system administrator has recently upgraded Perl from 5.16 to 5.18 and I'm now getting errors relating to binary modules, e.g.
perl -e 'use Scalar::Util'
Perl API version v5.16.0 of List::Util does not match v5.18.0 at /usr/lib64/perl5/5.18.2/XSLoader.pm line 92.
Compilation failed in require at /home/paul/perl5/lib/perl5/x86_64-linux/Scalar/Util.pm line 11.
Compilation failed in require at -e line 1.
BEGIN failed--compilation aborted at -e line 1.
My understanding is that I can fix the problem by forcing local::lib to rebuild all of its modules, but I can't find anything in the documentation which tells me how to do this, or even how to get a list of all the modules that have been installed using local::lib (if I had that in a 'one module per line' text file, I could easily write a Bash script to process it).
Is this possible, or do I have to remove the ~/perl5 directory and reinstall all the modules from scratch (possibly missing some as I can't remember them all)?

Update: For some time now, INSTALL_BASE has been producing a better directory structure that avoids this problem for new installs.
And that's why the perl Makefile.PL INSTALL_BASE=... convention (and the corresponding one for Build.PL) used by install::lib sucks.
Removing (or renaming the directory so you have backup) is the easiest solution. You can find out what you had installed by looking for .pm files.
cd ~
mv perl5{,16}
cd perl516/lib/perl5
find -name '*.pm' | xargs perl -MConfig -E'
for (#ARGV) {
s!^\./!!;
s!^5\.\d+\.\d+/!!;
s!^x86_64-linux/!!;
s!^auto/!!;
s!\.pm\z!!;
s!/!::!g;
say;
}
' | xargs cpan
(Do a dry run — one without the trailing | xargs cpan — first.)
Note that if you don't want to be at the mercy of your admin's upgrades, you can use perlbrew to easily install a whole build of Perl in your home dir.

If you are using cpanm you can force it to rebuild modules in your local::lib location by using the -L and --reinstall switches:
list_modules | cpanm -L ~/perl5 --reinstall
where list_modules is a script that feeds the names of modules to cpanm (there's also an -f option to cpanm). This script could be like #ikegami's above or something like cpan-outdated (which only lists outdated modules however). Here is a
hackish attempt that mostly worked for me recently (note: ikegami's is probably better) - when it fails or the script gives cpanm a module name it doesn't recognize, cpanm keeps going and doesn't seem to break anything (but make backups):
cd $PERL_LOCAL_LIB_ROOT
perl -MFile::Find -MConfig -E'
find {
wanted => sub {
$mod = $_ if /\.pm\z/;
$mod =~ s/lib\/perl5\/auto\/.*//g;
$mod =~ s/lib\/perl5\/\Q$Config{archname}\E\/.*//g;
$mod =~ s/lib\/perl5\///g;
push #mods, $mod unless $mod =~ /^$/;
},no_chdir=>1
},"lib/perl5"; #modhash{#mods}=(); say for sort keys %modhash '
By changing where and what File::Find finds with wanted() you can feed a different list of modules to cpanm. It would be nice if cpan or cpanm had an internal _method or -switch that allowed you to force the rebuilding of local::lib installed modules that use XS. Is there such a thing?
It was cpanm, carton (and seeing the node.js tool npm in action) that inspired me to do a lot more local::lib based installs. Now the CORE bundled "CPAN client" that ships with perl (cpan) seems to be getting more automagical and easier to use as well. I really like local::lib since it allows you to use the system perl but manage your own module stack without system level privileges. However, it can be easier overall to manage changes and upgrades if you use perlbrew to run a "non system" ("local") perl. Of course you can do this and still have local::lib, carton etc. manage a directory or application specific stack of modules.
1). Another way to do an upgrade is to use perllocal to generate a list of your currently installed modules (NB I'm not entirely sure if perllocal.pod is kept in a reliable/useful state). [Edit: In fact perllocal keeps a history of your installed modules rather than a list of those currently installed. You'll want to filter this or you will end up reinstalling the entire series of module versions one after the other!). This perlmonks node shows how to clean up your perllocal.pod: http://www.perlmonks.org/?node_id=483020. I prefer to keep the history.]
To parse perllocal.pod for input to cpanm, search through the file saving the matches in an array, then split them by twos to create a hash from the array elements (key,value,key,value). More recent installations and versions numbers are lower in the file, so you can create a hash with module names as keys and have the values updated by later entries:
perl -ne 'push #arr, grep {defined}
(/\A=head2.*:\s+C<Module>\s+L<(.*)\||.*C<VERSION:\s(.*)>\Z/msx); }{
%h = map{ split/,/,$_,2 } #arr; print "$_\#$h{$_}\n" for keys %h' perllocal.pod
(NB: this doesn't error check - entries in perllocal.pod occasionally lack VERSION data and other oddities so beware.)
2). But this method pales in comparison to the shell script mentioned by ilmari in #perl-help on IRC. It uses
jq - a commandline utility you really need to have (you'll want it more after this). If you have used cpanm to install your modules it will have created install.json files. You can leverage that to make list to feed to cpanm for reinstalling your current set of modules:
find ~/perl5/ -name install.json -exec jq '.name + "#" + .version' {} +
Very fast, very simple and you can combine it with the cpanm method of using curl to self-install in order to rebuild your modules.
3). If you use perlbrew to manage your perl installations you can easily copy and reinstall all your modules from one perl version to another.
HTH!

Related

Why do official perl docker images have two version of perl?

I'm working on a legacy product which uses the Docker perl:5.10-threaded image and ran into an issue trying to debug things when I discovered there are two version of perl - one in /usr/local/bin/perl and one in /usr/bin/perl. In this particular image, they are actually different versions
/usr/local/bin/perl -> 5.10.1
/usr/bin/perl -> 5.20.2
The issue it was causing is that each has a different #INC path.
$ /usr/local/bin/perl -V
[snip]
/usr/local/lib/perl5/5.10.1/x86_64-linux-thread-multi
/usr/local/lib/perl5/5.10.1
/usr/local/lib/perl5/site_perl/5.10.1/x86_64-linux-thread-multi
/usr/local/lib/perl5/site_perl/5.10.1
$ /usr/bin/perl -V
[snip]
/etc/perl
/usr/local/lib/x86_64-linux-gnu/perl/5.20.2
/usr/local/share/perl/5.20.2
/usr/lib/x86_64-linux-gnu/perl5/5.20
/usr/share/perl5
/usr/lib/x86_64-linux-gnu/perl/5.20
/usr/share/perl/5.20
/usr/local/lib/site_perl
.
The latest versions, like perl:5.30-threaded also have two versions and different #INC paths as well.
/usr/local/bin/perl -> 5.30.0
/usr/bin/perl -> 5.28.1
It turns out my legacy app uses #!/usr/bin/perl, which was very confusing to me when perl foo.pl didn't work the same as foo.pl (it complained about missing libraries).
To add a bit more color, this legacy app also installs a bunch of perl libraries via apt-get. E.g.,
libcpanel-json-xs-perl libxml-libxml-perl libcgi-pm-perl
libtie-ixhash-perl
libswitch-perl libmime-lite-perl liblist-moreutils-perl
libdate-calc-perl libnet-sftp-foreign-perl
libxml-libxslt-perl liburi-escape-xs-perl
libdatetime-perl
These seem to install things accessible by /usr/bin/perl, which is perhaps why the app uses it in the shebang lines.
The other thing this app does is install some cpanm items and then copy them over, so they are accessible to /usr/bin/perl
RUN cpanm XML::XML2JSON
RUN cpanm JSON
RUN cp /usr/local/lib/perl5/site_perl/5.10.1/XML/XML2JSON.pm /usr/lib/x86_64-linux-gnu/perl5/5.20/XML
RUN cp /usr/local/lib/perl5/site_perl/5.10.1/JSON.pm /usr/lib/x86_64-linux-gnu/perl5/5.20
This seems like a hack to me, but I'm new to perl development, so who knows?
All this leads up to my questions:
why are there two version of perl in the Docker images?
are apt-get and cpanm incompatible with each other?
is there a better way to install these perl libs?
Perl is an essential part of many Linux distributions, and has to come pre-installed. The system perl that is used by the operating system is usually installed as /usr/bin/perl. Modules for it are managed through the package manager (e.g. apt) and not via cpan/cpanm. If you were to install modules for the system perl yourself, this might conflict with modules expected by the operating system. Worse, installing the wrong module version could break parts of the OS. Similarly, replacing the system perl is a bad idea. That's why those Docker images install the different perl alongside.
For your apps, you should avoid the system perl. If you want to install extra modules for use with the system perl, consider using local::lib. In some cases you might install dependencies such as C libraries or external tools via apt, but you wouldn't use apt-provided Perl modules.
Unless you are targeting a specific operating system, do not hardcode the #!/usr/bin/perl shebang. Instead, prefer #!/usr/bin/env perl so that the script will use the perl that is first in the PATH. Alternatively, use wrapper scripts to explicitly invoke the correct perl installation. For example:
#!/bin/sh
exec /usr/local/bin/perl -I/path/to/extra/modules /path/to/my/script "$#"
Note that you cannot share modules between different versions of Perl. During installation of XS modules they are compiled for a specific version, and will fail to load with a different Perl version. For your local perl, just install dependencies via cpanm and ignore modules that were installed for the system perl.

Using local Perl module instead of one installed by CPAN

I have found it necessary to expand upon a CPAN module. (Unicode::CharName goes up to Unicode 4.1; I need some characters from Unicode 5.0 & 5.1).
I've made the changes needed and have my own CharName.pm module.
I now would like to use it with my various Perls. I currently use:
Strawberry Perl for Windows
git for Windows MINGW64; My .bashrc sets
$PATH to Strawberry perl and $PERL5LIB=/c/Strawberry/perl/vendor/lib:/c/Strawberry/perl/site/lib
WSL Ubuntu
Where should I put my version of Unicode::CharName, so that it over-rides the ones installed by CPAN?
I don't want to have to change any scripts that currently
use Unicode::CharName;
Using cpanm you could download the module, patch it, and install it as normal:
$ cpanm --look Unicode::CharName
# new shell opens
$ patch lib/Unicode/CharName.pm custom.patch # or whatever process
$ perl Makefile.PL
$ make install
$ exit
You can also install it to a local::lib to avoid overwriting it globally, by adding the -l local/ option to the cpanm command. Then you can add the absolute path of this local::lib to your PERL5LIB or via -I or use lib. If you specified /path/to/local for the -l option, it would be /path/to/local/lib/perl5.
Manually copying files rather than going through the normal installation process is likely to lead to problems. Many distributions depend on the installation process to build the modules correctly. Also, you will need to install the module separately for each Perl you want to use it for; installed Perl modules are not generally cross-compatible between Perl versions or architectures. (A strictly simple pure-Perl module can be an exception to these rules, but the only module I feel comfortable abusing this way is App::cpanminus, because it was designed to do this.)

Moving / cloning a perlbrew installed perl plus all the additional cpan modules

I am using perlbrew. I have installed lots of cpan modules under perlbrew perl-5.20.2.
Is there a way (or what is the best way) to do a tarball installation of my perl-5.20.2 plus all the CPAN modules that I have installed under perlbrew so that I can just clone it unto another machine?
I am aware of perlbrew download perl-5.20.2 but that only seems to tarball perl-5.20.2 and not all the CPAN modules that I have installed.
With perlbrew you can use the lib command to create a local::lib to go with your perlbrew perl.
perlbrew lib create perl-5.20.2#app_reqs
Then, if all goes well, when you install modules you will find them in:
$HOME/.perlbrew/libs/perl-5.20.2#app_reqs
If you don't use the perbrew lib create approach to managing your modules, then they are installed to $HOME/perl5/perlbrew/perls/perl-5.20.1/lib/site_perl/5.20.2. Cloning either of those directories might work, but you are likely better off reinstalling all the modules using the techniques from the perlbrew.pl website since XS modules should be rebuilt etc.
If you want to reuse and track local sources the most reliable approach is to create a local CPAN mirror to work from using App::lcpan or minicpan. If you have already downloaded the source using cpanm a quick hackish approach is to find the source files (under $HOME/.cpanm/) and do something like this in your shell:
% mkdir ~/cpansourcefiles
% for source in ~/.cpanm/work/*/* ; do cp $source ~/cpansourcefiles ;done
Then you can get cpanm to install using those sources by passing the filename as the argument instead of the module name:
% cpanm ~/cpansourcefiles/List-MoreUtils-0.406.tar.gz
or even:
% cpanm ~/cpansourcefiles/*
NB: YMMV since this is prone to breakage and might skip some of the version and dependency checking you normally get with cpanm but it is simpler than setting up a mirror when it works.
A few other high powered tools that make deploying Perl applications robust and reliable:
Carton
Pinto
EDIT:
Tools like perlbrew, pinto, carton, and cpanm are a vast improvement over a motley personal collection of scripts for doing similar things. Thanks to all the developers and contributors to these tools!
I'm not aware of any features in cpanm or perlbrew that allow you to reliably produce a list of installed files and their versions. Something like:
cpanm --list_installed
perlbrew list_installed_versions
or:
cpanm --export-cpanfile
perlbrew list_installed_as_cpanfile
might be a welcome feature.
As I noted in a comment to the OP above, you can garner useful information about your module installation from the install.json files that are installed by cpanm. Modules like CPAN::Meta, Module::Metadata and Distribution::Metadata can be helpful too.
The suggestion to use find and jq (which was from #Ilmari Karonen see Upgrade all modules installed by local::lib in this answer) led to the quick unfinished hack below. One challenge/issue is there's sometimes install.json files left lying around in multiple locations:
lib/perl5/$Config{archname}/.meta/Whatever-Mod-1.0050000/install.json
lib/perl5/$Config{archname}/.meta/Whatever-Mod-1.0090000/install.json
... etc.
This is likely because these files are not always cleanly removed when upgrading, reinstalling or other mucking about PEBKAC errors. To work properly, the code below should be changed so that it notices when there are multiple name-version combinations of a module's install.json and then does a more thorough check that the module is installed and gets its version. The script should have options: $dir could come from #ARGV. TIMTOWTDI, "well volunteered", etc.
#!perl
# list_installed_mods.pl
# DOES NOT THOROUGHLY VERIFY CURRENT VERSION
use File::Find;
use JSON;
use v5.16;
my $dir = "$ENV{HOME}/perl5/lib/perl5";
for my $installed ( find_installed($dir) ) {
say parse_install_json( $installed );
}
sub find_installed {
my $libdir = shift;
my #files;
File::Find::find ({ wanted =>
sub { push #files, $File::Find::name if /install\.json/i} },
$libdir );
return #files;
}
sub parse_install_json {
my $filename = shift;
my $json_text = do {
open(my $json_fh, "<:encoding(UTF-8)", $filename)
or die("Can't open \$filename\": $!\n");
local $/;
<$json_fh>
};
my $install = decode_json($json_text) ;
return ( $install->{name} ,"\#", $install->{version} ) ;
}
Possibly not the best way, but here's what I did recently.
I committed my Perlbrewed Perl version to a git repo, so I could use git archive to create a tar for me. Likewise with my Local::Lib modules. Then I wrote a little bit of scripting so that I could tag master, build archives from a tag, copy the archive to the remote server, unpack and chmod/chown the files.
I did this because at the time it was a quick and dirty solution to not having the time to set up Pinto or Carton.
It's only part of the solution but want mentioned so far: Perl's configure script has a -D relocateableinc parameter since some versions.
When building/brewing a Perl with that option the lib paths won't be hardcoded absolute path but relative to the perl binary which allows you to move the entire directory around or just rename it.
I'm building all my Perl's with that option since years and it hasn't created any problems so far.
The clone-modules command might be what you're seeking.
Example output for the below command follows:
perlbrew clone-modules perl-5.36.0 perl-5.37.6
Installing 398 modules from perl-5.36.0 to perl-5.37.6 ...
! Couldn't find module or a distribution AWS-Signature4
Successfully installed Algorithm-Diff-1.201
Successfully installed Algorithm-Loops-1.032
Successfully installed Module-Build-0.4231
Successfully installed ExtUtils-PkgConfig-1.16
Successfully installed Alien-VideoLAN-LibVLC-0.04
Successfully installed File-Slurp-9999.32
You can run this command after building a new perl with perlbrew to migrate (the latest versions of) CPAN modules from one version to another by reinstalling them for the new version that was just built.

Finding Perl modules already packaged for Debian and Redhat

I'm investigating making a Perl application that uses many modules into either a Debian and/or Redhat package. Currently, I believe the 'cleanest' way to do this is to cite, where possible, the modules that are packaged already for the given distribution.
The alternative would be to use CPAN and probably have some duplications, problems with #INC etc.
However, I can find or interrogate a list of Debian packages here: http://pkg-perl.alioth.debian.org/cpan2deb/ but I can't currently find an equivalent for Redhat/Fedora. Also I don't really know whether cpan2deb is authoritative and up to date.
If there's another clean way to do this, I'd welcome any other ideas too.
The Debian Perl Group is your best bet on the Debian world. Not only do they intercept all spread modules packages for Debian but they also try to keep them up to date.
See this page:
http://qa.debian.org/developer.php?login=pkg-perl-maintainers#lists.alioth.debian.org
There is cpanspec but it's not been touched in a few years. I seem to remember cpan2rpm but I don't have much experience with that one.
The alternative would be to use CPAN and probably have some duplications, problems with #INC etc.
I've got a perl program packaged for debian with a large number of dependencies. For expediency, I've chosen a grubby hybrid approach with some packaged modules as dependencies, plus a cheat backdoor CPAN install, which runs from my post-installation script. I hive a copy of my application's META.yml, then recheck my dependencies.
1.debian/rules file makes a copy of META.yml:
override_dh_auto_install:
dh_auto_install; \
cp META.yml etc/;\
2.debian/libmyapp-perl.install then installs META.yml:
etc/META.yml /usr/share/myapp/etc/
3.debian/libmyapp-perl.postinstall then cross-checks dependencies:
echo "Cross checking with cpan";
for m in `perl -Mstrict -MYAML::Syck -e'my $r = YAML::Syck::LoadFile("/usr/share/myapp/etc/META.yml")->{requires}; for (grep {$_ ne "perl"} (sort keys %$r)) {eval "use $_ $r->{$_}"; print "$_\n" if $#}'`; do
# would prefer App::cpanminus, but that's not packaged for debian either?
PERL_MM_USE_DEFAULT=1 perl -MCPAN -e "install $m";
done
Not exactly clean, but a quick approach to installing a mix of dependent debian packages plus a few CPAN modules.

Uninstall all perl modules installed by cpan

Yesterday I wanted to test some software and in the documentation it said, to install I just needed to type
cpan -i Software
I never used cpan, I just know that it is the perl package manager. (Is it..?) However, it turned out that I needed loads of dependencies, and stupid as I am, I just installed all of them. (First, I had to set up cpan which asked me lots of questions) Long story short, I just want to remove all of it again. I googled a bit, and it seems like cpan does not have an uninstall routine, especially for all the packages at once.
Can I just remove some directory or will I run into troubles?
the cpan command isn't really a package manager like apt-get is. It is more a tool that downloads and installs from CPAN (the site, or one of its mirrors). After it has finished this task it doesn't remember much about what was done before, at least not enough to remove previously installed modules, at least not reliably, cleanly or dependency-safely. (Update: After looking at App::pmuninstall, it can be used to handle dependencies, but it does so by connecting to outside (read: web) sources, which compute these separately, which is fine, but I stand by the previous statement that CPAN.pm doesn't do this.)
I used to worry about removing modules, but now I realize that most Perl modules take up so little room that I just don't worry about having a few extra modules installed that you will never use. So unless you are on a computer with a REALLY small disc, I would just let it be.
On Windows or if you are using a non-system Perl on Linux/Mac you could just remove Perl and reinstall it. I would not recommend this if you are using the system installed Perl on Linux/Mac however as you could break your OS doing this (you might be ok if you were careful, but not worth it to save a few Mb!).
In the future, you can easily install a local version of Perl using perlbrew, there are tutorials all over the web if the docs aren't sufficient (they should be). This also has the bonus of letting you play with the newest and greatest Perl versions, which your system likely doesn't come with yet. Then if you install a mountain of junk, or even break it doing something crazy, remove that version and reinstall/install a different version.
Another nice tool is cpanminus (or cpanm for short) which is a newer, more user friendly cpan tool. All the cool kids are using it.
You can uninstall individual modules with cpanplus (ships with Perl) like this:
cpanp uninstall SQL::Abstract
You can view all modules installed with the cpan script like this:
perldoc perllocal
Putting the two together:
for module in $(perldoc -u perllocal | grep -F 'C<Module> L<' | sed 's/^.*L<\(.*\)|.*>$/\1/') ; do
cpanp uninstall "$module"
done
I'm not sure about removing "all of it". But to remove a single module you can use App::pmuninstall with it's sole script pm-uninstall to uninstall modules. You might then be able to write some kind of script to recursively remove the deps.
If you can't use cpan any more because there are incompatible modules in you path, you can remove all installed modules by hand. For example, I upgraded from Fedora 22 to Fedora 23 and the Perl version changed. All modules installed previously via cpanm into /usr/local/lib64/perl5 did not work any more and prevented me from using cpanm.
$ cpanm --uninstall Apache::DBI
Attempt to reload Scalar/Util.pm aborted.
Compilation failed in require at /usr/share/perl5/vendor_perl/File/Temp.pm line 18.
...
I could solve this by moving that directory:
$ mv /usr/local/lib64/perl5 /root/usr-local-lib64-perl5
The name of that directory may vary on your system.
Carefull: If a module installed files outside of that directory, for example system library files, these files will remain there.
I will change Flimm's answer to use cpanm and optionally uninstall cpanm itself in the end of the script:
#!/usr/bin/env bash
for module in $(perldoc -u perllocal | grep -F 'C<Module> L<' | sed 's/^.*L<\(.*\)|.*>$/\1/' | sort | uniq) ; do
if [[ "$module" =~ "App::cpanminus" ]]; then
continue
fi
echo "Uninstalling $module..."
yes | cpanm --uninstall "$module"
done
cpanm --uninstall App::cpanminus
I think the best option is uninstall Perl and install it again.