$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

「Cartonの内部」について社内勉強会で発表した

Perlのバージョン管理ツールであるCartonの内部について、社内勉強会で発表したので、その時の資料をそのまま公開する。けっこう雑に作ってしまったので、正確性については保証できないけど、Cartonのソースコードリーディングの際の参考になればと思う。

アジェンダ

  • Cartonとは
  • いろいろトラブル
  • carton installの挙動を理解する

Cartonとは

  • Perlのモジュール管理ツール
  • 依存モジュールを特定のバージョンに固定して、インストールしてくれる

Cartonとは(深堀)

  • 一番重要なのはcarton install
  • 基本的にはcpanmのラッパーとしてcarton installが実現
  • Module::CPANfileなど、小さいモジュールを組み合わせている

いろいろトラブルもある

  • cpanfile.snapshotが環境によって変わる
  • carton install --deploymentでインストール出来ないものがあり失敗
  • carton installの挙動を理解すると、大体なんでそういうことが起こるかわかるので紹介

carton installの登場人物

  • cpanm
  • cpanfile
    • 依存モジュールを書くファイル
  • cpanfile.snapshot
    • cartonのバージョン固定用ファイル
  • 02packages.details.txt.gz
    • CPANにおけるインデックスファイル
    • モジュールをインストールするために、どこからダウンロードすればいいか記述
  • install.json MYMETA.json
    • あるモジュールのメタ情報が記述
    • 現在のversion, 依存モジュール
    • cpanmがモジュールインストール後に作る

carton installがやっていること

  • バージョン固定のためのファイルを生成(02packages.details.txt.gz)
  • cpanmでインストール
  • cpanfile.snapshotを生成(install.json, MYMETA.jsonを利用)

バージョン固定のためのファイルを生成

  • cpanfile.snapshotからCPANのインデックスファイルを生成
    • 02packages.details.txt
  • 02packages.details.txtには、モジュール名、version、ダウンロード元が記述
  • cpanmがあるモジュールはどのversionをどこからインストールすればいいか分かる
Apache::LogFormat::Compiler 0.32                   K/KA/KAZEBURO/Apache-LogFormat-Compiler-0.32.tar.gz
Attribute::Params::Validate 1.18                   D/DR/DROLSKY/Params-Validate-1.18.tar.gz
Class::Inspector       1.28                   A/AD/ADAMK/Class-Inspector-1.28.tar.gz
Class::Inspector::Functions 1.28                   A/AD/ADAMK/Class-Inspector-1.28.tar.gz
Class::Singleton       1.5                    S/SH/SHAY/Class-Singleton-1.5.tar.gz
DateTime               1.18                   D/DR/DROLSKY/DateTime-1.18.tar.gz
DateTime::Duration     1.18                   D/DR/DROLSKY/DateTime-1.18.tar.gz
DateTime::Helpers      1.18                   D/DR/DROLSKY/DateTime-1.18.tar.gz
DateTime::Infinite     1.18                   D/DR/DROLSKY/DateTime-1.18.tar.gz
DateTime::Infinite::Future 1.18                   D/DR/DROLSKY/DateTime-1.18.tar.gz
DateTime::Infinite::Past 1.18                   D/DR/DROLSKY/DateTime-1.18.tar.gz

cpanmでインストール

  • cpanfileを使って依存モジュールを知る
  • 02packages.details.txtを使って、どのバージョンをインストールするか知る

発行されるコマンド

  • 以下の様なコマンドが発行される
cpanm -L local --mirror http://cpan.metacpan.org/ --mirror http://backpan.perl.org/ --mirror-index local/cache/modules/02packages.details.txt --cascade-search --save-dists local/cache --cpanfile cpanfile --installdeps .
  • -L local
    • local以下にインストール
  • --mirror http://cpan.metacpan.org/ --mirror http://backpan.perl.org/ --mirror-index local/cache/modules/02packages.details.txt --cascade-search
    • 02packages.details.txtのindexでインストール、そこに存在しなかったらmetacpanやbackpanにfallback
  • --save-dists local/cache
    • モジュールのtarはlocal/cacheに保存する
  • --cpanfile cpanfile
    • cpanfileの指定

cpanm内での挙動

  • cpanfileを見ながら、依存モジュールを走査
  • あるモジュールをインストールする
    • そのモジュールのどのバージョンをインストールするかは02packages.details.txtをみて決める
    • cpanfile.snapshot -> 02packages.details.txtなので、cpanfile.snapshotに記述されたバージョンが入る
    • 02packages.details.txtに存在しない -> 他のmirrorにfallbackされ、インストールされる
  • モジュールの依存がすべて解決されるまで、これを繰り返す

cpanfile.snapshotを生成する

  • 依存モジュールはすべてlocal/以下に入ってる
  • local/以下のinstall.jsonとMYMETA.jsonをすべて取ってきて、cpanfile.snapshot作る
    • local/lib/perl5/(archname)/.meta/ 以下のディレクトリに、それぞれのdistのinstall.jsonとMYMETA.jsonが保存されている
      • 例) local/lib/perl5/darwin-2level/.meta/Algorithm-Diff-1.1902/{install|MYMETA}.json
      • この中にはモジュールのversion情報やさらなる依存情報が入ってる

なぜ問題が起こるか

  • cpanfile.snapshotが環境によって変わる
  • carton install --deploymentでインストール出来ないものがあり失敗

cpanfile.snapshotが環境によって変わる

  • local/以下にインストールされているモジュールをすべて見るため
    • local/以下に手動でモジュール入れたらおかしくなる
    • 一回cpanfileに依存を増やした後、消してもlocalからは消えないので残る
    • rm -rf local/ して再インストールしないとおかしくなる...
  • MYMETA.jsonを参考にするが、動的に依存を変えるモジュールによって、環境によって違うMYMETA.jsonが作成される
    • Macの場合とLinuxの場合で依存モジュールを変えるモジュールは、MYMETA.jsonが環境によって変わる
    • cpanfile.snapshotはそれを参考にするので、変わる

carton install --deploymentでインストール出来ないものがあり失敗

  • carton install --deploymentは02packages.details.txtだけを見て、metacpanにfallbackしないモード
  • 環境によってミスったcpanfile.snapshotが作られていると死ぬ
    • 02packages.details.txtに必要なモジュールが含まれていないため

結論

  • モジュールのインストールはcpanmがやっている
    • うまく02packages.details.txtを作って、cpanmをだますことで、バージョン固定
  • cartonはcpanfile.snapshotからCPANのインデックスを作るのと、cpanfile.snapshotを作るだけ

他のパッケージ管理ツールは?

  • bundler
  • npm
  • etc

bundler

  • 基本はglobalに全部モジュール入れる、bundle exec時にPATHの通し方を工夫する
    • 同じモジュール入っていたら無視できるのでインストール速い
  • Gemfile.lockは毎回Gemfileから依存をたどって作り直す(local/から全部ではない)
  • gemに依存を全部取ってくるAPIがあって、そこから環境依存のものも全部取ってきてるっぽい(?)
    • cpanfile.snapshot, Gemfile.lockには依存が多く記述されるのは問題ないため

carmel

  • bundlerっぽい動き方を目指している
  • 5分くらい使ってみたけどなんかうまく動かなかった

参考資料

perlの変数のデータ量を測定する

perlの変数のデータ量を調べたいということがあって、何を使ったらいいか調べたんだけど、Devel::Sizeというのが便利そうだった。

こんなかんじで使える。

use Devel::Size qw(size total_size);
 
my $size = size([1, 2, 3, 4, 5]);
warn $size;

my $total_size = total_size({
    a => [1, 2, 3],
    b => {
        c => [1, 3, 5],
    },
});
warn $total_size;


sizeというのは構造のために使われている容量を出してくれるやつ。

The size function returns the amount of memory the variable returns. If the variable is a hash or an array, it only reports the amount used by the structure, not the contents.


ちゃんと容量を知りたかったらtotal_sizeというのを使わないといけない。

The total_size function will traverse the variable and look at the sizes of contents. Any references contained in the variable will also be followed, so this function can be used to get the total size of a multidimensional data structure. At the moment there is no way to get the size of an array or a hash and its elements without using this function.


ひとまずバイト単位で簡単に出してくれるので便利。ただやってみると意外とPerlメモリ使うなあという気持ちになる。

cpanmとインストールするモジュールの走査

 今回はcpanmがモジュールの依存の何をインストールしようとするのか分からなくて、それを調べたのでメモ。現状の仕様のみ書いてるのと、全然読めてないので勘違いがあるかもしれない。

どのへんを見ると分かるか

 基本的にwant_phasesとか、install_typeとかいう文字列を追っかけてくと良い。具体的には

 あと関係しそうなオプションは

  • installdeps
  • notest
  • with-develop
  • with-feature, with-all-features
  • with-recommends, with-suggests

あたり。

フェーズとインストールタイプ

http://perldoc.jp/docs/modules/CPAN-Meta-2.132140/lib/CPAN/Meta/Spec.pod#Phases を見ると分かりやすい。

あと実際の実装は https://github.com/Perl-Toolchain-Gang/CPAN-Meta/blob/5f96f40032e21e10d7e4adfaf2b3b3b476c278b4/lib/CPAN/Meta/Prereqs.pm とか、そのあたりっぽい。

フェーズとインストールタイプの組み合わせで入れるものが決まるっぽい。特定のフェーズ(configure, build, testとか)で、どのような要求がされているか(requires, recommends, suggests)というので、入れるモジュールが決まる。

例) installdeps + notestのとき

 cartonがインストールするときのデフォルトはinstalldeps + notestでcpanmに渡される。この時

  • cpanfileに記録されたモジュールはbuild, runtime, testがインストールされる
  • cpanfileに書かれた依存のさらに依存はbuild, runtimeがインストールされる

という挙動になりそう。

 それは https://github.com/miyagawa/cpanminus/blob/452beb8583340780fe547e00501bf3d7619c5d71/lib/App/cpanminus/script.pm#L2135-L2137 を見ると分かる。

 cpanmはインストールするときに、依存を探して更にその依存を見つけたら再帰的にインストールして...という処理を繰り返している。その時$depthというのは今どの深さを辿ってるかを表していて、installdepsでcpanfileの依存を入れる時は0になるけど、依存の依存を見る時は1、依存の依存の依存を見る時は2とかになりそう。

 上のことからintalldepsでcpanfileに書かれている物を見ている時はdepthが0、installdepsがtrueとなるので、testまでインストールされる。こうしている理由は https://twitter.com/hide_o_55/statuses/315073204966277122 に書かれている。

 依存の依存はdepthが1以上になるので、buildとruntimeのみがインストールされる。

 あとはデフォルトはrequiresのインストールタイプを入れようとするように見える。https://github.com/miyagawa/cpanminus/blob/452beb8583340780fe547e00501bf3d7619c5d71/lib/App/cpanminus/script.pm#L97

まとめ

もうだめだ。