インストールされたCPANディストリビューションのバージョンを特定できない問題について

Cartonを使っているとこの問題があって今までだましだましやっていたのだけどそれも限界になってきたので問題の解決を図ってみたんですが一筋縄じゃいかなそうだったので一旦問題の整理と周知のためにエントリを書いてみる次第。

用語の整理

CPAN author諸氏におかれましてはあたりまえのことだとは思いますが一応整理。

ディストリビューション(distribution)
CPANにおいて実際に配布されるファイルのこと。tar.gzかzipファイル。複数のモジュールやMETAファイル等が含まれている。リリース(release)とも。
モジュール(module)
ディストリビューションに含まれる *.pm ファイルのこと。
パッケージ(package)
モジュールに含まれるpackage宣言されたperlにおける名前空間のこと。http://www.perl.com/CPAN/modules/02packages.details.txt の一列目はこれに該当。ちなみに二列目はそのバージョンで、三列目はディストリビューションを指している。

階層としては下記のような関係

ディストリビューション <= モジュール <= パッケージ

cpanmの引数に渡すのはパッケージ名。cpanm Gearman が通らなかったりするのはこのあたりの事情だったりする。

問題の再現

cpanm と cartonの最新Dev版がインストールされているものとます。

# 作業ディレクトリの用意
mkdir /path/to/tmp/multipsgi
cd /path/to/tmp/multipsgi

# 過去のPSGIディストリビューションを大量にインストールしてみる
cpanm -L local http://search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.101.tar.gz
cpanm -L local http://search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.10.tar.gz
cpanm -L local http://search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.03.tar.gz
cpanm -L local http://search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.02.tar.gz
cpanm -L local http://search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.01.tar.gz
# 最後にインストールしたversionは1.01

# cpanfileの用意
echo "requires 'PSGI';" > cpanfile

carton install
carton list

最後のcarton listでインストールされたバージョンの1.01に関する情報が表示されて欲しいが実際には"PSGI-1.03"等と違うものが表示される。このcarton.lockをdeployするとPSGI-1.03がインストールされてしまう。

問題の原因

cartonではインストールされているディストリビューションの特定に cpanmで作成される lib/perl5/$Config{archname}/.meta/*/install.json を使っている。先程の例だと ls local/lib/perl5/i686-linux/.meta/等とすると

PSGI-1.01  PSGI-1.02  PSGI-1.03  PSGI-1.10  PSGI-1.101

となっており、過去インストールされたすべてのバージョンの情報が残っておりそのすべての情報を取得してしまい、その後のHash化等の影響で過去インストールされたいずれかのバージョンが非決定的に特定されるということになってしまっている。

これは一見するとcartonの問題のようにも見えるが、そう言い切れるほど問題は単純ではない。

解決策?

  1. 最新バージョンをインストールバージョンとする

    問題の99%はこれで解決するだろうけど、ダウングレードをサポートできなくなってしまう。

  2. perllocal.podを解析する

    perllocal.podというのはディストリ ビューションのイ ンストール履歴のようなものでlib/perl5/{archname}/perllocal.podにある。現状の情報でできることなのでなかなかよい 気もするのだけども上書きによるダウングレードには対応できてもpm-uninstall等が使われると難しいか。(pm-uninstallでperllocalも編集してしまうという可能性もある)

  3. インストールされているパッケージのバージョンからディストリビューションのバージョンを特定する

    install.jsonは

    {"provides":{"PSGI":{"version":"1.02","file":"PSGI.pm"}},"target":"http://search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.02.tar.gz","version":"1.02","name":"PSGI","dist":"PSGI-1.02","pathname":"http:/search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.02.tar.gz"}

    のような情報を提供しており一見providesに指定されているものとインストールされているパッケージのバージョンを比較すればディストリビューションを特定できそうであるが、ディストリビューションはモジュール以外のファイルの変更によるアップグレードが行われる可能性もあるし、またPSGI-1.01等においては

    {"provides":{"PSGI":{}},"target":"http://search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.01.tar.gz","version":"1.01","name":"PSGI","dist":"PSGI-1.01","pathname":"http:/search.cpan.org/CPAN/authors/id/M/MI/MIYAGAWA/PSGI-1.01.tar.gz"}

    のように一切パッケージのバージョンを提供しない場合もあるので特定を汎用的に行うのは不可能。

  4. ディストリビューションのインストール時に既存のmetaディレクトリを消してしまう

    これが現実的なのかなーとか。ちなみに今までのだましだましの解決はこれを手動でやっていました。

  5. インストールされたディストリビューションのリストを別途管理する

    大掛かりすぎるか…

 

rubygemsはどうしているか

そもそも複数バージョンを並行してインストールできて利用するときにもバージョンを指定できるので、現在インストールされている(ただひとつの)バージョンという特定は必要無い。ただしバージョンを指定されない時は最新バージョンを利用する模様。