この記事は Perl Advent Calendar 2017 の7日目です。
6日は songmuさんによる、 えるしっているか、Lは便利 でした。
今日は、新しいCPANのモジュールマネージャー周りのお話をしたいと思います。

Perlでプロジェクト/ツールの開発をしよう! という時に、モジュールマネージャー Carton を使われる場合が多いのではないでしょうか。

しかし、Perlでプログラムを書いていくと、依存するCPANモジュールが数百個...というのはよくある事で、プロジェクト作成毎に、数百のモジュールインストールを待つには人生は短すぎる、、、そう思われている方も多い事でしょう。

そんな時は、もう一つのモジュールマネージャー Carmel の出番です!

Carmelとは

Cartonと同じ作者である、Tatsuhiko Miyagawaさんが開発されている新しいモジュールマネージャーです。

Carmel - CPAN Artifact Repository Manager

Carmelの特徴は、一度インストールしたバージョンのモジュールは、ローカル内の中央のリポジトリ(~/.carmel)に保存されます。
同じモジュールの違うバージョンも、別々に保存され切り替える事などが可能です。
その為、違うプロジェクトを開始する時でも、一度モジュールをcarmel経由でインストールしていれば、中央のリポジトリから使い回されるので、「モジュールをダウンロードしてインストール」という工程を省く事ができ、とても素早くプロジェクトを作成できます。

まずは、cpanmなどからインストールします。バージョンが表示されればインストールされています。

$ cpanm Carmel --notest
$ carmel version

例えば、プロジェクトのcpanfileがこんな感じで依存が多くあったとします。

cpanfile
# catalyst application runtime
requires 'Catalyst::Runtime'            => '== 5.90115';
requires 'Catalyst::View::JSON'         => '== 0.36';
requires 'Catalyst::View::TT'           => '== 0.44';
requires 'Catalyst::Helper::View::JSON';
requires 'Catalyst::Helper::View::TT';
requires 'Catalyst::ControllerRole::CatchErrors';
requires 'Catalyst::Plugin::File::RotateLogs' => '== 0.06';
requires 'Catalyst::Plugin::ConfigLoader';
requires 'Catalyst::Action::RenderView';
requires 'Catalyst::ActionRole::JSV';

# json schema validator
requires 'JSV' => '== 0.08';

# databases
requires 'DBI';
requires 'DBD::mysql';
requires 'SQL::Format';
requires 'DBIx::TransactionManager';
requires 'Scope::Container';
requires 'Plack::Middleware::Scope::Container';

# datetime
requires 'Time::Moment';

# error handler
requires 'Try::Tiny';

# access token generator
requires "Crypt::JWT" => "== 0.018";

# FastCGI on H2O
requires "FCGI::ProcManager";



on 'develop' => sub {
    requires 'Catalyst::Devel' => '== 1.39';
    requires 'Term::Size::Any';
    requires 'Devel::REPL';
    requires 'Text::API::Blueprint';
};

on 'test' => sub {
    requires "Plack::Test";
    requires "Plack::Util";
    requires "Path::Tiny";
    requires "Test::More";
    requires "Test::JSON";
}

おもむろに、インストールコマンドを打ちます。

$ carmel install

初回はまだどのモジュールもインストールされていないので、ダウンロードandインストールする時間は、Carton同様にかかります。が、これらのいずれかのモジュールを使うcpanfileを、違うプロジェクトなどで用意し、carmel installを実行してみると、かつてダウンロードしたモジュールはスキップされるので、インストールが高速化される事がわかると思います。一旦reinstallしたい、などの場合も効果的です。

インストールが完了すると、Carton互換の cpanfile.snapshot が出来ているのが確認できます。
同時に、 .carmel/MySetup.pm も作成されます。
これは、carmelが管理している、実行時にモジュールを参照するパスを繋げる為のモジュールです。(Carmelが管理しているファイルなのでいじりません。

実行する時は cartonとほぼ同じ使い方なので、スムーズに覚える事ができます。

$ carmel exec -- perl hoge.pl
$ carmel exec -- plackup -a myapp.psgi

などなど。以上で、モジュールを中央リポジトリからロードして使う事は、このままで可能なのですが、 cartonの様に、local/にモジュールを集めてパスを通して使う方が、本番環境などの場合など利便性があるでしょう。

$ carmel rollout

すると、中央リポジトリから参照している依存モジュール全てを、local/ にインストール(コピー)します。cartonと同じ状態ですね。
一旦 rolloutすると、中央リポジトリではなく、local/からのみモジュールをロードするように挙動が変わります。

そのため、一旦rollout後、新しく依存モジュールをcpanfileに記載して、carmel installだけでは local/にはまだ保存されていないため、度に rolloutが必要になります。

補足ですが、起動方法のついては別の起動方法もあり、carmelコマンドを使用せず、下記のように、perlコマンドから依存性を解決した状態で起動する事も可能です。

$ perl -MCarmel::Setup test.pl  

Carmel::Setupモジュールは、.carmel/MySetup.pmを読もうとしますので、パスを繋げるなどの必要はあります。

これで、サクサクPerlプロジェクトを作成できるようになりましたね!

App::cpm

cpmは、Shoichi Kajiさんが開発されている、並列CPANモジュールインストーラーです。

App::cpm - a fast CPAN module installer

本番環境でも、cartonやcarmelを使って、cpanfile.snapshot から deployment install... というのが定番かと思いますが、やはり本番環境でもインストールを高速化したいところです。

cpmは、cpanfile.snapshotから並列にモジュールをインストールできるばかりか、数々の豊富なオプションが揃っております。
使い方もhelpで表示されますが、ここではオプションを中心に見てみましょう。

$ cpm --help     
.....
....
...                                                                                                                          
Options:
      -w, --workers=N
            number of workers, default: 5
      -L, --local-lib-contained=DIR
            directory to install modules into, default: local/
      -g, --global
            install modules into current @INC instead of local/
      -v, --verbose
            verbose mode; you can see what is going on
          --prebuilt, --no-prebuilt
            save builds for CPAN distributions; and later, install the prebuilts if available
            you can also set $ENV{PERL_CPM_PREBUILT} true to enable this option
          --target-perl=VERSION  (EXPERIMENTAL)
            install modules as if verison is your perl is VERSION
          --mirror=URL
            base url for the CPAN mirror to use, you can use --mirror multiple times
            default: https://cpan.metacpan.org
      -r, --resolver=class,args (EXPERIMENTAL, will be removed or renamed)
            specify resolvers, you can use --resolver multiple times
            available classes: metadb/metacpan/02packages/snapshot
          --reinstall
            reinstall the distribution even if you already have the latest version installed
          --dev (EXPERIMENTAL)
            resolve TRIAL distributions too
          --color, --no-color
            turn on/off color output, default: on
          --test, --no-test
            run test cases, default: no
          --man-pages
            generate man pages
          --retry, --no-retry
            retry configure/build/test/install if fails, default: retry
          --configure-timeout=sec, --build-timeout=sec, --test-timeout=sec
            specify configure/build/test timeout second, default: 60sec, 3600sec, 1800sec
          --show-progress, --no-show-progress
            show progress, default: on
      -V, --version
            show version
      -h, --help
            show this help
          --with-requires,   --without-requires   (default: with)
          --with-recommends, --without-recommends (default: without)
          --with-suggests,   --without-suggests   (default: without)
          --with-configure,  --without-configure  (default: without)
          --with-build,      --without-build      (default: with)
          --with-test,       --without-test       (default: with)
          --with-runtime,    --without-runtime    (default: with)
          --with-develop,    --without-develop    (default: without)
            specify types/phases of dependencies in cpanfile to be installed

素晴らしいオプションの数々!!

本番環境では、cpanfile.snapshotから developとtestの依存モジュールを抜いたruntimeのモジュールだけ、並列コア数を適切に増やして並列インストールするのが良い感じです。

$ cpm install --without-develop --without-test --workers=8

これで、本番環境へのデプロイも速くなりましたね!

運用するサーバについて余談

Nginx+PSGIサーバや、Apache+mod_perlなど用途に合わせた多種多様の起動方法がありますが、僕は最近、カジュアルにPerlアプリを動かすために、H2O を使っています。

H2O - the optimized HTTP/1, HTTP/2 server

PSGIサーバStarletなど数々のPerlモジュールを開発している Oku Kazuhoさんが開発されている高速なWebサーバです。

h2o.conf
"/":
  fastcgi.spawn: 'exec /var/www/apps/hogeapi/p5env plackup -s FCGI --nproc 16 -a /var/www/apps/hogeapi/app.psgi'

こんな感じに h2oから、FastCGIプロセスを起動して管理してもらいます。

p5env
#!/bin/sh
set -e
export CATALYST_HOME="/var/www/apps/hogeapi"
export PLACK_ENV="production"
export PERL5LIB="/var/www/apps/hogeapi/local/lib/perl5:/var/www/apps/hogeapi/lib"
exec "$@"

上記は cpm でinstallしたモジュールにパスを繋げる程度のファイルです。

PSGIサーバを前段のWebサーバからリバースプロキシさせる方法と違って、h2oプロセスだけ死活監視、スタートアップさせればいいというカジュアルな運用が出来ます。

それでは今日の話は以上です。

明日は mackee_wさんで 「Test2と一緒に暮らしていく」 です!