local::lib を切り替える

レガシーなアプリをメンテするのにレガシーな Perl モジュールが必要になることがあり、そのために古いアプリ用には専用の local::lib ディレクトリを切っているわけですが、普段から日常的に local::lib を使用しているため shell が上がったタイミングではデフォルトの local::lib 環境変数がセットされていて切り替えが非常に面倒だった。

これまではそれほど頻繁に使用しなかったので放置していたのだが、ここのところ頻繁に必要になるためいい加減うざくなってきて簡単に local::lib を切り替えられるよう設定をしてみた。

zsh に次のような関数をつくり、それで local::lib を切り替えるようにする。

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

これは

$ locallib ~/perl5

などのように INSTALL_BASE を指定して使う。指定されたパスが存在したら use-locallib というコマンドを使用して環境変数を切り替えるという内容。

この use-locallib コマンドは以下のようなソースになっていて、古い local::lib の環境変数をクリアするとともに、新しい local::lib の環境変数を print するというものになっている。

#!/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)

これで local::lib を簡単に切り替えられるようになるけれど、複数の local::lib 環境を同時に使用しているとどのシェルがどの local::lib を使用しているかわからなくなってしまう。

そのためシェルのプロンプトに INSTALL_BASE を表示されるようにしてしのいだ。

show INSTALL_BASE in terminal prompt

これは単純に以下のような PERL_MM_OPTINSTALL_BASE をプリントするスクリプトを PROMPT 設定から読んでいるだけである。

#!/usr/bin/env perl

use strict;
use warnings;

my %mm_opt;
for my $opt (split /:+/, $ENV{PERL_MM_OPT} || '') {
    my ($k, $v) = split /=/, $opt;
    $mm_opt{$k} = $v;
}

my $install_base = $mm_opt{INSTALL_BASE};
if ($ENV{HOME}) {
    $install_base =~ s/^$ENV{HOME}/~/;
}

print $install_base || 'none';
by typester / at 2010-03-01T17:17:00 / perl · zsh / Comments(0)