Perl Hackers Hub

最終回 Carmelによる依存モジュール管理
CPANモジュールの更新を高速⁠安全に(2)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはcpanm、Plackの作者としても知られる宮川達彦さんで、テーマは「Carmelによる依存モジュール管理(2⁠⁠」です。

<前回(1)こちら。>

CarmelとCartonの違い

この節では、CartonとCarmelの動作の違いや、Carmelを利用するメリットなどを紹介します。

Cartonの動作原理

Cartonの動作原理を簡単におさらいします。

Cartonは内部的にcpanmコマンドの--local-lib-containedオプションを利用し、cpanfileで指定されたモジュールをlocalディレクトリ以下にすべてインストールします。そのあと、インストールされたバージョンをlocalディレクトリ内からスキャンしてcpanfile.snapshotファイルに書き出します。

localディレクトリはcpanmコマンドのインストール対象としてのみ指定されており、Cartonはこのディレクトリを直接管理することはありません。このため、複数ユーザーが異なるマシンでCartonを利用した場合、マシン間で同じ状態が維持されている保証はありません。

Carmelの動作原理

Carmelは内部的にcpanmコマンドのオプションなどは利用せず、cpanfileを読み込み、必要となるモジュールを再帰的に取得し、そのモジュールをCarmelリポジトリ[1]以下にディレクトリを作成して保持します。

これらのモジュールはCartonのようにlocalディレクトリ以下にインストールされることはありません。

$HOME/.carmel/5.20.1-darwin-2level/builds
  Plack-1.0048/
    blib/
      arch/
      lib/
  Plack-1.0050/
    blib/
      arch/
      lib/

installlistexecなどのサブコマンドを実行すると、Carmelはこのリポジトリをスキャンして、指定された条件にマッチする、あるいは固定されたバージョンのディレクトリを探し、モジュール名とディレクトリのマッピングを作成します。実際にPerlがモジュールをロードするとき(例:use Plack;⁠、ここではマッチするディレクトリ<Carmelリポジトリ>/Plack-1.0050/blib以下の/lib/Plack.pmがロードされます。これには、Perlのインクルードパスをオーバーライドして、必要なファイルが固定されたディレクトリからロードされるようにしています。

ユーザーから見た違い

はじめに紹介したとおり、CartonとCarmelのコマンドには互換性や同等の別名コマンドが用意されているため、すでにCartonを利用しているみなさんは、ほとんどワークフローを変更することなく、Carmelを利用し始めることができます。また、Carmelを使ってみて何か問題があれば、Cartonに戻ることも簡単です。すでにCartonを使っていて特に問題がない場合でも、Carmelを試してみる価値はあります。この項では、Cartonを利用して問題になりやすいケースを紹介します。

たとえば、Mojoliciousのみに依存したアプリケーションを作ったとします。cpanfileを作成して、Cartonを利用して依存モジュールを管理してみましょう。

cpanfile
requires 'Mojolicious';
> carton install
Installing modules using /path/to/carton-demo/cpanfile 
Successfully installed Mojolicious-9.33
1 distribution installed
Complete! Modules were installed into /path/to/carton-demo/local

のちに、Mojoliciousを利用することはやめて、Plackに変更したとしましょう。cpanfileを書き換え、carton installを再度実行します。

cpanfile
requires 'Plack';
> carton install
Installing modules using /path/to/carton-demo/cpanfile
(省略)
Successfully installed Plack-1.0050
31 distributions installed
Complete! Modules were installed into /path/to/carton-demo/local

このとき、Cartonはlocalの管理をcpanmに丸投げしているため、Mojoliciousがcpanfileから削除されていることを伝える手段がありません。結果として、Mojoliciousモジュールはlocalの中に残っています。このため、carton listなどを実行すると、Mojoliciousがスナップショットに残ったままになります。

> carton list | grep Mojo
Mojolicious-9.33

> grep Mojo cpanfile.snapshot
  Mojolicious-9.33
    pathname: S/SR/SRI/Mojolicious-9.33.tar.gz
(省略)

この問題を回避するためには、localを全削除してからcarton installをやりなおす必要があります。しかし、たびたびこうしたクリーンアップが必要になるのは手間です。また、そもそもcarton installを再度実行すると、すべてのモジュールをはじめからインストールすることになり、依存モジュールの数が多い大きなプロジェクトでは相当な時間がかかります。

Carmelではこのような問題は起こりません。依存するモジュールはlocalディレクトリにインストールせず、Carmelリポジトリに保持しています。このため、

  • インストールやアップデートでローカルの環境を汚すことがない
  • 必要なバージョンがすでにリポジトリにある場合、インストールが高速に行われる
  • アップデートやロールバックが高速かつ正確に行える

というメリットがあります。carmel installコマンドは、すでに対象となるバージョンがCarmelリポジトリ内にある場合、そのマッピングを記録するだけですので、非常に高速に実行されます。

プロダクション環境での利用

前節で紹介したように、carmel installコマンドは使用されるモジュールとCarmelリポジトリ内のディレクトリとのマッピングを作成し、carmel execコマンドで@INCをオーバーライドすることで特定のバージョンをロードすることを実現しています。これは開発環境ではうまく動作しますし、プロダクション環境でも、Carmelリポジトリを特定のパスに指定すれば問題なく動作します。ただ、コンテナなどでの利用を前提とした場合、こうした方法よりもシンプルなやり方が用意されています。

CI環境のみでの利用

プロダクション環境ではcarmel execを利用したくない場合、carmel rolloutコマンドが便利です。

前述のとおり、CarmelはCartonと異なり、インストールしたモジュールを管理のためにカレントディレクトリのlocalに保持しないようになっています。ただ、モジュールがlocalにインストールされていると、実行時にperl -Ilocal/lib/perl5 ...とすることで、carton execなしにスクリプトを起動できるというメリットがありました。

これをCarmelでも実現するのがcarmel rolloutコマンドです。carmel rolloutで、Carmelリポジトリ内のモジュールをlocalディレクトリにエクスポートできます。

> carmel rollout
Installing libwww-perl-6.70 to /path/to/demo/local
Installing Encode-Locale-1.05 to /path/to/demo/local
Installing File-Listing-6.15 to /path/to/demo/local
Installing HTTP-Date-6.05 to /path/to/demo/local
(省略)
> find local/lib
local/lib
local/lib/perl5
local/lib/perl5/Devel
local/lib/perl5/Devel/StackTrace.pm
local/lib/perl5/Devel/StackTrace
local/lib/perl5/Devel/StackTrace/AsHTML.pm
local/lib/perl5/Devel/StackTrace/Frame.pm
local/lib/perl5/POSIX
local/lib/perl5/POSIX/strftime
local/lib/perl5/POSIX/strftime/Compiler.pm
(省略)

cpanfile.snapshotに指定されているバージョンを維持したまま、すべてのモジュールがlocal/lib/perl5以下に展開されています。

たとえば、CI環境などでこのcarmel rolloutコマンドを実行し、localディレクトリ以下をコンテナのイメージに追加してしまう方法などが考えられます。筆者が運営しているサービスではこの方法でプロダクション用のコンテナイメージを作成しています。プロダクション環境(コンテナ内)ではcarmel execを使う必要はなく、直接Perlのインクルードパスとして指定すれば、

> perl -I$PWD/local/lib/perl5 ./myapp.pl

のようにしてモジュールを読み込むことができます。もちろん、環境変数PERL5LIBや、use libなどでこのディレクトリを指定してもかまいません。

ローカル環境のみでの利用

Dockerなどのコンテナは利用せず、またプロダクション環境でCarmelをインストールできない、したくないというユースケースも考えられます。そうした場合にも、carmel packageコマンドを使うと、ローカル環境のみでCarmelを使い、CIやプロダクション環境では通常どおりモジュールをインストールできます。

carmel packageは、モジュールのアーカイブファイルやモジュールインデックスなど、CPANミラーとして利用できる一式のディレクトリをvendor/cache以下に作成します。

> carmel package
Copying K/KA/KAZEBURO/Apache-LogFormat-Compiler-0.36.tar.gz
Copying P/PL/PLICEASE/Class-Inspector-1.36.tar.gz
(省略)
Writing vendor/cache/modules/02packages.details.txt.gz
---> Complete! 45 distributions are packaged in vendor/cache

このvendor/cacheディレクトリをGitリポジトリに追加しておけば、Carmelがない環境(CI、プロダクション環境など)でも、次のようにcpanmコマンドの--fromオプションにディレクトリを指定することで、cpanfilecpanfile.snapshotで指定したモジュールを完全に再現できます。

> cpanm --from $PWD/vendor/cache --installdeps .

依存しているモジュールやcpanfile.snapshotに変更があるたびに、carmel packageを実行してvendor/cache以下を最新に保っていく必要があります。

まとめ

Carmelを利用することで、プロジェクトが依存しているモジュールとそのバージョンを管理できます。現在開発中のバージョンv0.9以降では、依存モジュールの更新時に、変更内容やChangelogを表示するコマンドなどが追加され、さらに便利になっていますので、こちらも使ってみてください。

本連載「Perl Hackers Hub」も本誌の休刊と合わせて最終回となりました。前回寄稿したのは本連載の第1回でしたので約13年ぶりということになります。ずいぶんと長寿連載となったようで、その最初と最後に書かせていただいて光栄に感じています。また、それ以前も含めると約22年間の間、筆者、読者としてお世話になりました。読者の皆様、編集部の方々、ありがとうございました。

おすすめ記事

記事・ニュース一覧