Perl Hackers Hub

第21回Carton & cpanm―Perlモジュール管理最新事情(3)

(1)こちら⁠2)こちらから。

Carton

Cartonもcpanmと同様に、宮川達彦さんを中心に開発しているツールです。Cartonがどんなツールかについては、PODにズバリな一文があるので引用します。

carton is a command line tool to track the Perl module dependencies for your Perl application.

「Cartonとは、アプリケーションにおけるPerlモジュールの依存関係を管理するコマンドラインツール」というわけです。

また、PODのNAMEセクションには次の記述があります。

Carton - Perl module dependency manager (aka Bundler for Perl)

Rubyでお馴染みのBundlerという単語が出てきました。Rubyを使う人にはこちらのほうがピンとくるのではないでしょうか。BundlerはRubyのモジュール管理ツールとして有名で、CartonはBundlerにインスパイアされて開発されています。

どのように動くのか

それでは、Cartonとはどのように動くツールなのかを見ていきます。

そもそもPerlモジュールのインストールにはメタデータが必要です。メタデータとは、Makefile.PLや Build.PL のことで、ExtUtils::MakeMakerModule::BuildあるいはModule::Installといったツールチェイン[2]と協調して動くものです。ここにモジュール名や作者情報、そしてモジュールの依存関係などを書くようになっていて、その書き方はツールチェインごとに異なります。

Cartonはここで定義された依存関係を解決し、それらモジュールを一括で、アプリケーション単位で独立した場所にインストールしたりダウンロードしたりできます。また、ここで独立してインストールしたモジュールを使うようにPerlを実行できます。

次のセクションで解説しますが、Carton 0.9.4まではMakefile.PLやBuild.PLに書かれた依存関係を読んで実行していましたが、0.9_5以降ではcpanfileという新しいフォーマットのみをサポートするように変更されました。

なお、以降の説明では特にことわりがない場合、Carton 0.9.15を例に使い方を説明します。

cpanfile

cpanfileとは、モジュールの依存関係を定義するファイルです。RubyでいうGemfileと同じ役割を持っています。BundlerがGemfileからモジュール依存関係を解決するように、Cartonはcpanfileを使うことで同様のことを行います。

前述のとおり、以前はcpanfileは存在せずMakefile.PLなどを直接読むようになっていましたが、なぜcpanfileというフォーマットが新規に作成され、そしてCartonではそれをサポートするようになったのでしょうか。cpanfile-faqの記載を抜粋すると、次のような理由によります。

CPANにアップロードしないプロダクトの考慮
たとえばアプリケーションプロジェクトでは、依存関係の定義だけが欲しいので、Makefile.PLやBuild.PLをあえて作成する必要はない
より正確な依存関係の解析
モジュールのビルドそのものに必要となるconfigureフェーズも、正確に定義・解析できるようになる(後述)
CPAN Meta Spec v2のサブセットとなるDSLDo main Specific Languageドメイン特化言語)のサポート
Module::InstallライクなDSLに加えて、CPAN Meta Spec v2の機能であるバージョンの範囲指定などもサポートされる

既存のMakefile.PLなどをそのまま利用するのでは、CPAN Meta Spec v2の完全なサポートが難しいなど制約が残ります。既存のツールチェインを無理矢理ハックして利用するのは筋が悪いですし、そのせいでCartonの新機能開発にも制約が出てしまったりもするので、cpanfileという新規フォーマットを使ってCartonの利用シーンに合ったやるべきことを実現するというアプローチは、お互いにとって良いことだと言えるでしょう。

cpanfileの書き方

cpanfileは前述のとおりModule::Installによく似たDSLで依存関係を定義します。PODからcpanfileの例を引用します。

requires 'Catalyst', '5.8000'; # 5.8000 or newer
requires 'Catalyst::View::JSON', '>= 0.30, < 0.40';

recommends 'JSON::XS', '2.0';
conflicts 'JSON', '< 1.0';

on 'test' => sub {
  requires 'Test::More', '>= 0.96, < 2.0';
  recommends 'Test::TCP', '1.12';
};

on 'develop' => sub {
  recommends 'Devel::NYTProf';
};

requiresrecommendsはModule::Installでも使われており、それぞれ必須モジュールおよび推奨モジュールを書きます。ほかにconflictssuggestsもありますので、詳しくはCPAN::Meta::Specを参照してください。ちなみにCarton 0.9.15でサポートされているのはrequiresのみです。

これらの引数は以下のように指定でき、モジュール名は必須ですが、バージョン定義部分は省略すると取得できる最新バージョンとして解釈されます。バージョン部分を指定する場合は、最低バージョン、範囲指定、あるいは特定バージョンというようにさまざまな書き方ができます。

requires 'Module::Name';
requires 'Module::Name', 'MINIMUM VERSION';
requires 'Module::Name', '>= MINIMUM, < MAXIMUM';
requires 'Module::Name', '== SPECIFIC VERSION';

フェーズ

on 'PHASE'という記法で、モジュール定義の動作フェーズを限定できます。フェーズにはconfigurebuildtestruntimedevelopの5つがあります。上記例のようにフェーズ指定がない場合は、runtimeフェーズとして扱われます。

テストのみで必要なモジュールはtestフェーズで、モジュール開発者のみ必要なモジュールはdevelopフェーズで、ビルド実行前にツールチェインレベルで必要なモジュールはconfigureフェーズで、というように使い分けることができます。

on 'confgure' => sub {
  requires 'Module::Build', '0.40';
  requires 'Module::CPANfile', '0.9031';
};

on 'test' => sub {
  requires 'Test::More', '>= 0.96, < 2.0';
};

on 'develop' => sub {
  recommends 'Devel::NYTProf';
};

既存ツールチェインとの連携

ところで、cpanfileを使うとMakefile.PLやBuild.PLが使えなくなるわけではありません。それら用のcpanfileブリッジモジュールが存在するので、あわせて使うことで、CPANモジュールのプロジェクトであっても、依存関係はcpanfileに定義して、そのほかのメタデータはツールチェインのものをそのまま使うことができます。

Module::Installの場合

Module::Install の場合は、Module::Install::CPAN fileを使うことで実現できます。次のようにcpanfileとMakefile.PLを書くだけで連携できます。依存関係以外の情報は、今までと同様にMakefile.PLに定義します。

cpanfile
on 'configure' => sub {
  requires 'Module::Install';
  requires 'Module::Install::CPANfile';
};

requires 'Some::Module';
Makefile.PL
use inc::Module::Install;
(省略)
cpanfile;
WriteAll;

Module::Buildの場合

Module::Buildの場合は、Module::Build::Pluggable::CPANfileを使います。こちらも作者などの情報は従来どおりにBuild.PLに書くだけで大丈夫です。

cpanfile
on 'configure' => sub {
  requires 'Module::Build', '0.40';
  requires 'Module::Build::Pluggable::CPANfle', '0.04';
};

requires 'Some::Module';
Build.PL
use Module::Build::Pluggable qw(
    CPANfile
);

my $builder = Module::Build::Pluggable->new(
  (省略)
);
$builder->create_build_script();

Cartonの導入

ここまでCartonを動かすためのしくみを見てきたので、いよいよCarton自体をインストールしていきます。CartonはCPANにモジュールとして登録されているので、それこそcpanmを使ってインストールしましょう。

$ cpanm Carton

インストールされたことを確認します。

$ carton -v
carton v0.9.15

旧バージョンの導入

前述のように、Carton 0.9_5以降ではMakefile.PLおよびBuild.PLの利用がサポート外になったので、事情によりこれらビルドファイルのままCartonを使いたい場合は、0.9.4を指定してインストールするとよいでしょう。先に紹介したcpanmの機能を使うと、バージョン固定のインストールも簡単です。

$ cpanm Carton@v0.9.4
# or
$ cpanm MIYAGAWA/carton-v0.9.4.tar.gz
(省略)
$ carton -v
carton v0.9.4

一括インストール

Cartonで現在できることは、cpanfileを使った依存モジュールのダウンロード、インストール、そしてインストール済みのモジュールを使った実行などがあります。ここからそれぞれ説明していきます。

Cartonのメイン機能である一括インストールを行うには、installサブコマンドを使います。ここではサンプルとして次の内容のcpanfileを使います。

requires 'Plack';

この状態でCartonを実行すると、依存モジュールが一括でインストールされます。

$ carton install
Installing modules using cpanfile
Successfully installed File-ShareDir-Install-0.04
Successfully installed Try-Tiny-0.12
(省略)
Successfully installed Plack-1.0024
34 distributions installed
Complete! Modules were installed into local

このとき、モジュールはカレントディレクトリのlocalディレクトリ以下にインストールされます。

$ tree local
local
├─ bin
│ ├─ instmodsh
(省略)
└─ lib
     └─ perl5
           ├─ Apache
           │    └─ LogFormat
           │          └─ Compiler.pm

          (省略)

125 directories, 301 files

carton installを実行すると、carton.lockというファイルが生成されます。このファイルにはインストールされたモジュールのメタデータが入っており、後述するほかのサブコマンド実行時に利用されます。

インストールの挙動を変更するオプションが3つあるので、以降で紹介します。

インストール先の変更─⁠─ --path、PERL_CARTON_PATH

インストール先を変更したい場合は、--pathオプション、あるいはPERL_CARTON_PATH環境変数で指定できます。

$ carton install --path=vendor/modules
# or
$ PERL_CARTON_PATH=vendor/modules carton install
Installing modules using cpanfile
(省略)
34 distributions installed
Complete! Modules were installed into vendor/modules

場所が変わっただけで、インストールされたモジュールに違いはありません。

$ tree vendor/modules
(省略)
125 directories, 301 files

デプロイモード ─⁠─ --deployment

--deploymentオプションを指定すると「deployment mode」として動作します。具体的には、インストール後にcarton.lockの更新を行わなくなります。利用シーンとしては、アプリケーションのデプロイ先で依存モジュールを展開したい場合などに利用します。

$ carton install --deployment
Installing modules using cpanfile (deployment mode)
(省略)
34 distributions installed
Complete! Modules were installed into local

bundleしたモジュールをインストール─⁠─ --cached

後述するbundleサブコマンドを使うと、依存モジュールをローカルディレクトリに一括ダウンロードできます[3]⁠。--cachedオプションは、このダウンロード済みのディレクトリをCPANミラーとして使用するオプションです[4]⁠。

$ carton install --cached
(省略)
34 distributions installed
Complete! Modules were installed into local

local環境での実行

execサブコマンドを使うと、前述したlocalディレクトリにインストールしたモジュールを使ってプログラムを起動できます。

$ carton exec -- plackup -v
Plack 1.0024

execのあとに--を挟んで実行したいコマンドラインを指定しないと、たとえば上記の-vオプションがplackupではなくcartonのオプションとして解釈されてしまうので、注意が必要です。

$ carton exec plackup -v
carton v0.9.15

local/lib以外のディレクトリを独自に@INCに差し込みたい場合は、perlと同様に-Iオプションで指定できます。

$ carton exec -Iextlib -- perl -le 'print join "\n", @INC'
extlib
local/lib/perl5/darwin-2level
local/lib/perl5
. 
(省略)

また、local以外の場所にモジュールをインストールしている場合は、PERL_CARTON_PATH環境変数でディレクトリを指定して実行します[5]⁠。

$ PERL_CARTON_PATH=vendor/modules carton exec -- plackup -v
Plack 1.0024

依存モジュールのダウンロード

bundleサブコマンドで依存モジュールのtarballを一括ダウンロードできます。

$ carton bundle
Bundling modules using cpanfile
File-ShareDir-Install-0.04
Plack-1.0024
\_ Try-Tiny-0.12
(省略)
Complete! Modules were bundled into /Users/ikasam_a/myapp/
local/cache

デフォルトでlocal/cacheディレクトリ以下にCPANミラーと同様の構造で保存します。

$ tree local/cache
local/cache
 └─ authors
      └─ id
           ├─ A
           │   └─ AD
           │        └─ ADAMK
           │             ├─ Class-Inspector-1.28.tar.gz
          (省略)

41 directories, 34 files

ただし、modulesディレクトリと02packagesファイルが存在しない[6]ため、そのまま独立したCPANミラーとして利用することはできません。

ダウンロード先の変更─⁠─ --path、PERL_CARTON_PATH

インストールと同様に、--pathオプションあるいはPERL_CARTON_PATH環境変数で保存先を指定できます。ただし、保存先として指定したパスに/cacheが自動で付与されることに注意が必要です。

$ carton bundle --path=vendor/bundle
Bundling modules using cpanfile
(省略)
Complete! Modules were bundled into /Users/ikasam_a/myapp/
vendor/bundle/cache

$ tree vendor/bundle/cache
(省略)
41 directories, 34 files

bundleであらかじめモジュールを取得しておくと、install --cachedを利用してローカルでインストールを完結させることができるので、いちいちリモートにモジュールを取得しにいくと都合が悪いケースで活用できます。

依存モジュールの確認

モジュールの表示をするサブコマンドにはlisttreeshowがあります。名前から予想できるように、主に一覧表示か個別表示かの違いです。

listサブコマンドを実行すると次のように、依存モジュールのディストリビューション一覧がリスト表示されます[7]⁠。

$ carton list
Test-NoWarnings-1.04
IO-HTML-1.00
(省略)

list --treeとすると整形して簡易ツリー表示になります。treeサブコマンドもまったく同じで、単純にlist --treeのショートカットなだけです。

$ carton list --tree
# or
$ carton tree
Plack-1.0024
 Test-Requires-0.06
  ExtUtils-MakeMaker-6.66
(省略)

show Module::Nameで指定したモジュールの情報がJSONでダンプされます。省略していますが、mymetaセクションにはCPAN Meta Spec v2のメタデータが記載されます。

$ carton show File::ShareDir::Install
{
   "dist" : "File-ShareDir-Install-0.04",
   (省略)
   "provides" : {
      "File::ShareDir::Install" : {
         "file" : "File/ShareDir/Install.pm",
         "version" : "0.04"
      }
   },
   "target" : "File::ShareDir::Install",
   "version" : "0.04"
}

なお、これらのサブコマンドはcarton.lockファイルが存在しないとエラーになるので、先にcarton installあるいはcarton bundleを実行しておく必要があります。

Carton今後の課題

Cartonは1.0リリースに向けて鋭意開発中のプロダクトですので、未実装の部分や挙動がおかしい部分もあります。

たとえばサブコマンドによってオプションの一貫性が取れていない部分もありますし、bundleinstall時のcarton.lockファイルの扱いがあまり考慮されていない、といったことが挙げられます。また、Bundlerで使える基本サブコマンドのいくつか[8]には未対応です。

今回紹介した機能で基本的なモジュール管理はできるようになりますが、使い込んでいくと、もう一歩細かいところを制御したいケースもあるかと思います。幸いなことにCartonも、そしてcpanmもGitHubで開発されているので、気になった点があれば、パッチを書いてpull requestを送れば取り込んでもらえるかもしれません :p

なお、本誌発売時にはCarton 1.0がリリースされているかもしれませんが、基本的な使い方に変更はない予定です。

まとめ

今回は、Perlモジュール管理の最新事情として、Cartonとcpanmについて簡単に解説しました。まだまだ開発中のツールではありますが、基本機能はそろってきているので、本稿が導入の参考になればと思います。

さて、次回の執筆者はmalaさんで、テーマは「クローラの作り方」です。

おすすめ記事

記事・ニュース一覧