「local::libを切り替える」を試してみる

前回までのあらすじ

ようやくlocal::lib童貞を捨てた - すぎゃーんメモから1.5ヶ月ほど経過。
やはりプロジェクト毎にモジュールのバージョンを揃えておきたくて、各プロジェクト用にモジュールをインストールしている。
アカウント作成して切り替え、という方法は早々に見切りをつけ、最近は

#!/bin/sh
export MODULEBUILDRC="/Users/sugyan/local/.modulebuildrc"
export PERL_MM_OPT="INSTALL_BASE=/Users/sugyan/local"
export PERL5LIB="/Users/sugyan/local/lib/perl5:/Users/sugyan/local/lib/perl5/darwin-thread-multi-2level:$PERL5LIB"
export PATH="/Users/sugyan/local/bin:$PATH"

$@

のようにパスを追加するシェルスクリプトに引数をそのまま渡して、

$ local.sh perl hoge.pl

という具合に実行させるようにしていた。

本題

けどスマートな方法じゃないよなぁ、と思っていたところに、こんなやり方を教えていただいた!ありがとうございます!
local::lib を切り替える - unknownplace.org
早速ほぼ丸パクリで試してみた。


"$HOME/extlib/local"にプロジェクト関係なく共通で最新のモジュール群を、"$HOME/extlib/MyProject"にそのプロジェクト専用のモジュールをインストールしていくことにする。
最近話題のcpanminusを使ってちょっと古めのモジュールとか入れてみたりもしてみよう。
GitHub - miyagawa/cpanminus: cpanminus - get, unpack, build and install modules from CPAN
cpanminusを使うとメモリ制限のきついレンタルサーバーでもCPANモジュールが楽にインストールできる - otsune's SnakeOil - subtech

$ cpanm -l $HOME/extlib/local AnyEvent
$ cpanm -l $HOME/extlib/MyProject http://search.cpan.org/CPAN/authors/id/M/ML/MLEHMANN/AnyEvent-5.11.tar.gz


"$HOME/.zshrc"にて切り替え用の関数とデフォルトのextlibなどを指定

export PATH=$PATH:$HOME/local/bin
function locallib () {
    INSTALL_BASE=$1
    if [ -d $INSTALL_BASE ]; then
        eval $(~/local/bin/use-locallib $INSTALL_BASE)
    fi
}

locallib $HOME/extlib/local

で、"$HOME/local/bin/use-locallib"コマンドは丸パクリ。

#!/usr/bin/env perl

use strict;
use warnings;
use Pod::Usage;

use Config;
use File::Spec;

my $install_base = $ARGV[0]
    or pod2usage(-1);

$install_base = File::Spec->rel2abs($install_base);

my $path     = $ENV{PATH};
my $perl5lib = $ENV{PERL5LIB};

push @INC, File::Spec->catdir($install_base, 'lib', 'perl5');

require local::lib;
my %env = local::lib->build_environment_vars_for($install_base, 1);

# remove $PERL5LIB set by old local::lib if it exists.
if (my $old_base = $ENV{PERL_MM_OPT}) {
    my %mmopt;
    for my $opt (split /:+/, $old_base) {
        my ($k, $v) = split /=/, $opt;
        $mmopt{$k} = $v;
    }

    if (my $old_installbase = $mmopt{INSTALL_BASE}) {
        if ($old_installbase eq $install_base) {
            # do nothing if install_base is equal to old one
            exit;
        }

        my @old_perl5lib = (
            File::Spec->catdir($old_installbase, 'lib', 'perl5'),
            File::Spec->catdir($old_installbase, 'lib', 'perl5', $Config{archname}),
        );

        $env{PERL5LIB} = do {
            my @env;
            ENV: for my $e (grep { $_ } split $Config{path_sep}, $env{PERL5LIB}) {
                for my $old (@old_perl5lib) {
                    next ENV if $old eq $e;
                }
                push @env, $e;
            }
            join $Config{path_sep}, @env;
        };

        my $old_path = File::Spec->catdir($old_installbase, 'bin');
        $env{PATH} = do {
            my @p;
            for my $p (grep {$_} split $Config{path_sep}, $env{PATH}) {
                next if $p eq $old_path;
                push @p, $p;
            }
            join $Config{path_sep}, @p;
        };
    }
}

while (my ($k, $v) = each %env) {
    print qq[export $k="$v"\n];
}

=head1 NAME

use-locallib - set/switch local::lib environment

=head1 SYNOPSIS

use-locallib (MODULE INSTALL BASE)


これで、

$ locallib $HOME/extlib/MyProject
$ locallib $HOME/extlib/local

といったカンジで環境を切り替えられるようになる。
あとは、どの環境を使っているか間違わないようにPROMPTに表示させる。自分の場合は以下のような。

precmd() {
    LANG=en_US.UTF-8 vcs_info
    LOADAVG=$(sysctl -n vm.loadavg | perl -anpe '$_=$F[1]')
    if [ "$INSTALL_BASE" = "" ]; then
        CURRENT_LOCALLIB=""
    else
        CURRENT_LOCALLIB="%{${fg[magenta]}%}$(echo $INSTALL_BASE | sed -e "s|$HOME/||; s|extlib/||") %{$reset_color%}"
    fi
    PROMPT='${CURRENT_LOCALLIB}${vcs_info_msg_0_}%{${fg[yellow]}%}%* ($LOADAVG) %%%{$reset_color%} '
    RPROMPT='%{${fg[green]}%}%/%{$reset_color%}'
}


切り替えた先で入れたモジュールは他の環境と切り離されるので、各プロジェクト毎にモジュールのバージョンを調整できるようになる。

こんなカンジで使い分けられる!


結構イケてそうなので、しばらくこれでやっていってみます。