Carmelとcpm

この記事は [Perl Advent Calendar 2017](https://qiita.com/advent-calendar/2017/perl) の7日目です。
6日は songmuさんによる、 [えるしっているか、Lは便利](http://www.songmu.jp/riji/entry/2017-12-07-perl5-advent-calendar-day6-l.html) でした。
今日は、新しいCPANのモジュールマネージャー周りのお話をしたいと思います。

Perlでプロジェクト/ツールの開発をしよう！　という時に、モジュールマネージャー [Carton](https://metacpan.org/pod/Carton) を使われる場合が多いのではないでしょうか。

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

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

Carmelとは
---------

Cartonと同じ作者である、[Tatsuhiko Miyagawa](https://metacpan.org/author/MIYAGAWA)さんが開発されている新しいモジュールマネージャーです。

[Carmel - CPAN Artifact Repository Manager](https://github.com/miyagawa/Carmel)

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

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

```bash
$ cpanm Carmel --notest
$ carmel version
```

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

```perl: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";
}
```

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

~~~bash
$ carmel install
~~~

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

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

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

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

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

~~~bash
$ carmel rollout
~~~

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

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


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

~~~bash
$ perl -MCarmel::Setup test.pl  
~~~

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


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


App::cpm
---------
cpmは、[Shoichi Kaji](https://metacpan.org/author/SKAJI)さんが開発されている、並列CPANモジュールインストーラーです。

[App::cpm - a fast CPAN module installer](https://metacpan.org/pod/App::cpm)


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

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

~~~bash
$ 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のモジュールだけ、並列コア数を適切に増やして並列インストールするのが良い感じです。

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

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


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

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

[H2O - the optimized HTTP/1, HTTP/2 server](https://h2o.examp1e.net/)

PSGIサーバStarletなど数々のPerlモジュールを開発している [Oku Kazuho](https://metacpan.org/author/KAZUHO)さんが開発されている高速なWebサーバです。
 

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

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

~~~bash: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](https://qiita.com/mackee_w)さんで 「Test2と一緒に暮らしていく」 です！