$shibayu36->blog;

プログラミングの話や自分の考えを色々と書いています。特にperl、emacsや読んだ本の話が多いです。

DevLove関西で「課題をテストで解決する」という発表をしました

DevLove関西に行ったので、「課題をテストで解決する」という発表をしてきました。

内容はスライドに書いてあるとおりです。以下のことを特に話したくて、今回の発表をしました。

  • 何度も起こる課題があったときに人が気をつけようとしない
    • 最悪のケースでは根本原因を知ろうとせず、間違えた人を怒ることで解決しようとしてしまう
  • 何度も起こる課題なら、機械に自動的にやらせる
    • その例として今回はテストでやりましょうという話をした
  • あとテストいろいろあって始め方がわからないという時は、こういう課題をテストにするというところから始めるとやりやすいかもしれない

ドキュメントの場所を知らせるために、落ちるテストを作る

 今回はドキュメントの場所をどうやって気づかせるかという話を書く。

ドキュメントがあるときの問題

 以前開発のドキュメントをどこに置くか問題 - $shibayu36->blog;に書いたとおり、僕の意見としては

  • 基本は実装に一番近いところにコメントとしてドキュメント書くのが良いと思う
  • いろんなパーツが絡みあうような大きな機能の場合、導入部分だけ別の場所に書く
    • 出来るだけrepository内に入れておくと探しやすく、更新しやすいと思う

というものだった。

 基本的には一番近いコメントにすると、見つけやすさ・更新しやすさともにある程度担保することが出来る。しかし、メインの部分が明確に決まらない*1いろんなパーツが関係しあう機能の場合は、ドキュメントを書かないと全体の概要が分からないということもある。このような時、ドキュメントを書いても結局そのドキュメントに気づかれないし、そのため更新されないという問題がある。

テストでドキュメントの位置を知らせる

 最近は一つの方法として、その周辺を触るときにほぼ必ず触るような部分の変更があったら、テストが落ちるようにしておいて、そこにドキュメントへのリンクを置くのはどうか、ということをしてみている。

 特定の機能へ何かしようとしたときにある設定の追加が必要だった場合を考える。ひとまず便宜上設定の仕様をドキュメントに書くのではなく、テストにしてしまう - $shibayu36->blog;にて使ったデータを使う。

[
   {
      "blog_url" : "http://shibayu36.hatenablog.com/",
      "permission" : "public",
      "can_be_edited_by" : [
         "shiba_yu36"
      ]
   },
   {
      "blog_url" : "http://shibayu36-private.hatenablog.com/",
      "permission" : "private",
      "can_be_edited_by" : [
         "shiba_yu36",
         "shiba_yu37"
      ]
   },
   {
      "blog_url" : "http://blog.example.com/",
      "permission" : "public",
      "can_be_edited_by" : [
         "example-user"
      ]
   }
]

 この時、設定を追加したら落ちるテストを用意しておいて、そこにドキュメントへのリンクを追加する。

subtest '設定の数が正しい' => sub {
    # この部分の機能を触る時は、まずdocuments/blog_config.mdを見ましょう
    is scalar @$blogs_config, 3;
};

 こうすると、以下の順序でドキュメントに気づいてもらえるんじゃないかと思った。

  • この機能のドキュメントを見たことなしに設定を追加した人は、CIサーバでこのテストが落ちてくれる
  • テスト修正しようとして、テストのファイルを見る
  • 周辺にドキュメントのリンクがあるので、見てみる

 もちろんドキュメントはmarkdownで書いてなくてもよくて、メインに見て欲しいクラスへのリンクでも良いだろうし、なんでも良い気がする。

まとめ

 今回はドキュメントの位置をテストで知らせるという話を書いた。「メインの部分が明確に決まらない機能」というので、ブログに書いて良さそうな例が思いつかなくて、すごく分かりにくい説明になった気がする。

 実際にはこのような問題に当たったときに、ドキュメントに気づいてもらえればなんでもいいので、他にもいろいろ方法があると思う。なんかいい方法あったら教えて下さい。

*1:メインの部分が明確に決まるというのは、誰がその機能を改修しようと考えても必ず同じ所から読み始めるという意味

設定の仕様をドキュメントに書くのではなく、テストにしてしまう

 以前開発のドキュメントをどこに置くか問題 - $shibayu36->blog;という記事を書いた。まだよい方法はちゃんと考えられてないが、少しずつケースバイケースでいろいろな手法を試してみている。今回は設定項目の仕様のドキュメントという観点で考えたときに、テストを作ることで解決できないか、ということについて書く。

設定項目の仕様

 例えば以下の様な設定があったとする*1

[
   {
      "blog_url" : "http://shibayu36.hatenablog.com/",
      "permission" : "public",
      "can_be_edited_by" : [
         "shiba_yu36"
      ]
   },
   {
      "blog_url" : "http://shibayu36-private.hatenablog.com/",
      "permission" : "private",
      "can_be_edited_by" : [
         "shiba_yu36",
         "shiba_yu37"
      ]
   },
   {
      "blog_url" : "http://blog.example.com/",
      "permission" : "public",
      "can_be_edited_by" : [
         "example-user"
      ]
   }
]

 
 設定項目それぞれの意味は以下のとおり。

  • blog_url: 設定したいブログのURL
  • permission: publicなら公開、privateなら非公開
  • can_be_edited_by: 編集権限を持つユーザ

 この設定項目は特定のブログに対する情報を設定しておけるものとする。この時仕様をどのような形で引き継ぎ可能にしておくかという問題がある。

ドキュメントを書く?

 一つの方法としてドキュメントを書くという方法がある。これはよく使われるやり方だと思う。例えば今回の設定項目に関しては以下のように書いたりする。

今回のドキュメントはブログの情報の設定項目に対するものである。ブログに設定を追加する時はblogs.jsonに記入すること。

それぞれの設定項目は
- blog_url: 設定したいブログのURL
- permission: publicなら公開、privateなら非公開
- can_be_edited_by: 編集権限を持つユーザ
を表している。

また指定できる内容の仕様は以下のとおりである。
- 全体は配列になっている
- blog_urlにはURLを記述すること
  - ただし設定全体で同じblog_urlが二度出てきてはならない
- permissionにはpublicか、privateのどちらかを記述すること
- can_be_edited_byはユーザ名を配列で記述すること

 以上のドキュメントでこの設定に関する仕様は全て説明されている。ただし、以下の様な問題がある。

  • ドキュメントを読んで設定するのは人なので間違える可能性がある
  • そもそもこのドキュメントが見られない可能性がある
  • 仕様が増えていくとどんどんドキュメントが難しくなる

テストで仕様を説明する

 前述のとおりの問題があるので、ではこの問題をテストで解決出来ないかと考えた。

 それぞれの設定項目の説明は日本語のコメントで分かりやすく書いた上で、それが正しいかどうかを検証するテストを書いてみる。

use Test::More;
use Test::Deep;
use JSON::XS;
use Path::Class;
use URI;
use List::UtilsBy qw(uniq_by);

# それぞれの設定項目
# - blog_url: 設定したいブログのURL
# - permission: publicなら公開、privateなら非公開
# - can_be_edited_by: 編集権限を持つユーザ

my $blogs_config = decode_json(file('blogs.json')->slurp);

subtest 'それぞれの設定項目が正しい' => sub {
    for my $config (@$blogs_config) {
        cmp_deeply $config, {
            blog_url         => ignore(),
            permission       => ignore(),
            can_be_edited_by => ignore(),
        }, '全ての必須項目が揃っている';

        my $blog_url = URI->new($config->{blog_url});
        is $blog_url->scheme, 'http', "$blog_url はhttpのURLである";

        cmp_deeply $config->{permission}, any('public', 'private'), 'permissionはpublicかprivateのどちらかである';

        cmp_deeply $config->{can_be_edited_by}, array_each(re('[a-zA-Z0-9-]+')), 'can_be_edited_byは文字列の配列である';
    }
};

subtest '同じblog_urlが全体で二つあってはならない' => sub {
    # blog_urlでユニークな配列を作り直す
    # blog_urlが全てユニークであればもともとの個数と同じになるはず
    my $blogs_config_uniq_by_blog_url = [ uniq_by { $_->{blog_url} } @$blogs_config ];

    # 個数をチェック
    is scalar @$blogs_config_uniq_by_blog_url, scalar @$blogs_config, "blog_urlが全体でユニークである";
};

done_testing();

 このテストはドキュメントに書こうとしていた以下の仕様を正しくテスト出来ていて、

- 全体は配列になっている
- blog_urlにはURLを記述すること
  - ただし設定全体で同じblog_urlが二度出てきてはならない
- permissionにはpublicか、privateのどちらかを記述すること
- can_be_edited_byはユーザ名を配列で記述すること

さらに

  • 人が間違えてもテストが落ちる
  • テストが落ちるので、ドキュメントが見られなくても気づく
  • 仕様が増えてきた時もテストを増やしていけば、誰かが正しいかチェックする必要が省ける

というようにドキュメントの問題も解決している。

 なのでこういう項目はドキュメントを作るより、テストを書くほうがよいと思った。

まとめ

 上に書いたように今回は設定の仕様をドキュメントに書くという話と、そうではなくてテストにしたほうが良いかもしれないということについて書いた。実際にはケースバイケースでこの方向では解決できないものも非常にたくさんあるので、何か考えたことがあったらまた書いていこうと思う。

*1:解説用にでっち上げた設定項目なので、こういうのはデータベースに入れるべきとかはなしで

社内Cartonコードリーディング会第二回のメモ

社内Cartonコードリーディング会第一回のメモ - $shibayu36->blog; に引き続き、第二回を開催したので、その時の内容をメモしておきます。そのままメモを公開しただけなのでかなり雑です。間違ってることも多いと思います。

今回の趣旨

今回は、cpanfile.snapshotがどのように作られるのか、という点に絞ってコードリーディングをしていきました。

cpanfile.snapshotとMYMETA.json, install.jsonの関係

  • 各モジュールのMYMETA.jsonやinstall.jsonを使ってcpanfile.snapshotを作成している
  • MYMETA.json にはそのモジュールの cpanfile とか pod とかから得られそうな情報が載ってる
    • モジュール名、作者、build requires、runtime requires、test requires、etc.
  • install.json にはそのモジュールが提供するパッケージの情報が載ってる。
    • 例えば DateTime モジュールなら DateTime、DateTime::Duration、DateTime::Helpers、etc.
  • これらのファイルは誰が作っている?
    • おそらくcpanm?
  • cpanfile.snapshot の provides には install.json から得られる情報が、requirements には MYMETA.json から得られる情報が載っているっぽい?

cartonがcpanfile.snapshotに必要なモジュールを集める手順

  1. local/以下にモジュールがインストールされているはずなので、ディレクトリをrecursiveにたどり、入っているモジュールのメタデータを集める
  2. そのなかでCPAN::Meta::Requirementsなどを利用し、snapshotに利用するモジュールを抽出する

snapshotに利用するモジュールを抽出する

https://github.com/miyagawa/carton/blob/master/lib/Carton/Snapshot.pm#L134..L146 このあたりの処理がやっている

accepts_module

my $bool = $req->accepts_modules($module => $version);

...
For modules that do not appear in the requirements, this method will return true.
  • また複数versionに対する依存があった時は、条件を満たす一番最新のモジュールを選択する


以上の条件から

  • cpanfileに記述されたモジュールはsnapshotに含まれる
  • 依存の依存などの関係から複数バージョンのメタデータが見つかった場合は、cpanfileを満たすversion rangeの中で最新の物が選択される
  • ただしlocalにこれまでインストールされてしまっていて、cpanfileに書かれていないものや、cpanfileに書かれたもののさらなる依存などは、CPAN::Meta::Requirementsに登録されていないので、snapshotに含まれてしまう
    • そのため依存を消した時などはlocalを一度消してinstallし直さないと厳密なsnapshotが作れない
    • 環境によっては少しずつ違うsnapshotが出来てしまう

さらなる疑問点

  • snapshotの前段階で、carton installがどういうロジックでlocal以下にcpanモジュールを入れていくか
  • carton installのときにglobalに入っているモジュールによって、インストールされるモジュールが変わったりするのか
    • globalというのはlocal/libではなく、使っているperlのsite_perl的なとこに入っているモジュール

次回見ようと思っているところ

 次回はcarton installとcpanmの関係で、どのようなロジックでモジュールを入れていくかについて見ようと思っています。

nginxのproxy設定ファイルも自動テストしよう

 最近nginxでリバースプロキシの設定を書いているんだけど、設定のたびに本番に緊張しながら反映していた。さらにその副作用として、nginxのファイルはリファクタリングされないという問題があった。

 そこで反映する前にバグ等が見つかるようにnginx設定のテストを書きたいと考えた。今回はperlでテストを書いた。

どういうテストをしたいか

 やり方によってnginxの設定ファイルの分割の方法は違うのだけど、例えば以下の様なnginxの設定があり、それが別のファイルのhttpコンテキストの中にincludeされているという分割で考える。この時、この設定ファイルに書かれた内容が正しく動いているかテストを書きたい。

upstream app1 {
    server app1.host;
}
upstream app2 {
    server app2.host;
}

server {
    listen 8080;
    location / {
        proxy_set_header Host 'dummy.host';
        proxy_pass http://app1;
    }

    location /app2 {
        proxy_pass http://app2;
    }

    location ~ ^/(html|css|images|js)/ {
        access_log off;
        root /path/to/repository/static;

        if ($arg_version) {
            expires max;
            add_header Last-Modified "Thu, 01 Jan 1970 00:00:00 GMT";
        }
    }
}

 この時

  • /にアクセスしたときにHostヘッダがリクエストヘッダにつくか
  • /app2にアクセスしたときに、app2 upstreamにリクエストが投げられるか
  • /html/sample.htmlなどの静的ファイルにアクセスしたときに、うまくキャッシュ用ヘッダが出力されるか

などのことをテストしたい。

テストの基本的な考え方

 基本的にはTest::TCPを利用して、テスト用に手元でnginxとそのupstreamとなるappサーバを立て、リクエストを投げ、そのリクエストやレスポンスをテストする。

 上に書いたテストしたい内容をチェックするためには、以下の図に書いてあるように、

  • nginx -> upstreamに投げられるリクエスト
  • nginx -> UserAgentに戻るレスポンス

の二つを検証すれば良い。

 そのため以下のようにテストできないかと考えた。

そこで

  1. Test::TCPを利用して、upstreamとなるappサーバを立ち上げる
    • この時nginxのリクエストの中でテストしたいものをresponseに入れる
  2. テストしたいファイルを読み込んだnginxをTest::TCPで起動
  3. 普通にLWP::UserAgentとかで立ち上がったnginxにリクエストして、返ってきた内容をテスト

という手順でテストしてみようと考えた。

upstreamとなるappサーバを立ち上げる

 これは以前Test::TCPを使ってテスト用にmemcached, app, nginxサーバを立てる - $shibayu36->blog;にも書いた。

 appサーバを立ち上げるときに、テストしたいリクエストをレスポンスに詰めておく。2台のapp serverを立てるサンプルコードは以下。

# テスト用app serverを立てるutility
sub start_http_server {
    my ($app) = @_;
    return Test::TCP->new(
        code => sub {
            my $port = shift;

            my $server = HTTP::Server::PSGI->new(
                host    => "127.0.0.1",
                port    => $port,
            );

            $server->run($app);
        },
    );
}

my $app1 = start_http_server(sub {
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $headers = { map { $_ => $req->header($_) } $req->headers->header_field_names };

    # リクエストheaderをテストするために、responseに入れておく
    [ 200, [ 'Content-Type' => 'text/plain' ], [ encode_json $headers ] ];
});

my $app2 = start_http_server(sub {
    [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Hello App2' ] ];
});

 これでnginxのテストに必要なupstreamのdummy serverが出来た。

テスト用nginxを起動

 これもTest::TCPを使ってテスト用にmemcached, app, nginxサーバを立てる - $shibayu36->blog;に、Test::TCPを使ってnginxを起動するという話だけ書いた。

 上の内容に追加して、テストするためにはいくつかの問題を解決する必要がある。

  • テストしたいファイルを指定して読み込める
  • テスト用に、設定内のportやrootのディレクトリを書き換える

 そのためのコードが以下。

# テスト用nginxを立てるutility
sub start_nginx_server {
    my %opts = @_;
    my $app1_port = $opts{app1_port};
    my $app2_port = $opts{app2_port};
    my $conf_file = $opts{conf_file};

    return Test::TCP->new(
        code => sub {
            my $port = shift;

            my $temp_dir = File::Temp::tempdir;

            my $nginx_conf = file($conf_file)->slurp;

            # ---- 設定ファイルの書き換えを行う ----
            # listenの番号書き換え
            $nginx_conf =~ s{listen 8080}{listen $port}g;

            # upstreamをdummy serverに書き換え
            $nginx_conf =~ s!upstream app1 {.*?}!upstream app1 { server localhost:$app1_port; }!s;
            $nginx_conf =~ s!upstream app2 {.*?}!upstream app2 { server localhost:$app2_port; }!s;

            # ファイルパスを現在のディレクトリに書き換え
            my $current_directory = Cwd::getcwd();
            $nginx_conf =~ s{/path/to/repository}{$current_directory}g;

            # 設定ファイルを書き換えた内容をテストしやすいwrapperにくるんで
            # nginxにそのまま渡せる形に
            my $conf = <<"EOS";
daemon off;

error_log $temp_dir/error_log crit;
lock_file $temp_dir/lock_file;
pid $temp_dir/nginx.pid;

events {
    worker_connections  1024;
}

http {
    client_body_temp_path $temp_dir/client_body_temp;
    proxy_temp_path $temp_dir/proxy_temp;

    $nginx_conf
}
EOS

            my $fh = Path::Class::file("$temp_dir/nginx.conf")->openw;
            print { $fh } $conf;
            close $fh;

            # 起動
            exec "nginx -c $temp_dir/nginx.conf -p $temp_dir";
        },
    );
}

# 立ちあげたapp1, app2を利用して、nginxを立てる
# proxy.nginx.confのテストをしたい
my $nginx = start_nginx_server(
    app1_port  => $app1->port,
    app2_port  => $app2->port,
    conf_file  => 'proxy.nginx.conf',
);

これでテスト用nginxの起動まで出来た。

立ち上がったテスト用サーバにリクエストを送ってテスト

 あとは立ち上がったnginxサーバにリクエストを送ることでテストが出来る状態になっている。テストしたい内容を振り返ると

  • /にアクセスしたときにHostヘッダがリクエストヘッダにつくか
  • /app2にアクセスしたときに、app2 upstreamにリクエストが投げられるか
  • /html/sample.htmlなどの静的ファイルにアクセスしたときに、うまくキャッシュ用ヘッダが出力されるか

だったので、それに対応するようなテストを書いたのが以下。

my $ua = LWP::UserAgent->new;
subtest '/へのリクエスト' => sub {
    my $data = decode_json($ua->get('http://localhost:' . $nginx->port)->content);
    # app1のリクエストヘッダがレスポンスに含まれるはずなのでそれを検証
    is $data->{Host}, 'dummy.host', 'Hostヘッダがリクエストに付く';
};

subtest '/app2へのリクエスト' => sub {
    my $content = $ua->get('http://localhost:' . $nginx->port . '/app2')->content;
    is $content, 'Hello App2', 'app2にリクエストがいく';
};

subtest '/html/sample.htmlへのリクエスト' => sub {
    # responseヘッダは普通に取得できるはずなのでそれを検証
    subtest 'versionというクエリがついた時、キャッシュ用ヘッダが出力' => sub {
        my $res = $ua->get('http://localhost:' . $nginx->port . '/html/sample.html?version=123');
        ok $res->header('Expires'), 'ヘッダが出力される';
    };

    subtest 'クエリがつかない時、Expiresのヘッダが出ない' => sub {
        my $res = $ua->get('http://localhost:' . $nginx->port . '/html/sample.html');
        ok ! $res->header('Expires'), 'ヘッダが出力されない';
    };
};

done_testing();

まとめ

 今回はnginxの設定もテストしたいということで、perlによるテストの実装方法について書いた。今回のサンプルコードの全体は下に貼り付けておく。

 社内ではこういうコードをベースにさらにユーティリティを作っていて、もう少し簡単にいろんなテストを出来るようにしている。このへんももし公開できるならうまく公開していきたい。またnginxをテスト用に立ち上げる部分は汎用化出来るような気もするので、可能だったらCPANに上げたい気もする。

 最近は基本的に何か問題があったら、テストで解決できないかと考えるようにしているのだけれど、やはり今回nginxのテストを書いてみると、一気にnginxの設定ファイル群をリファクタリング出来たし、結構成功した気がする。ぜひお試しください。

サンプルコード全体

Carp::Replyを使って、モジュールの挙動を追いかける

 最近Cartonコードリーディング会をやっているのですが、そのときにCarp::Replyを使ってみたのでメモ。

 例えばCartonとかの中身を追いかけるときに、この時点でこの変数はどうなっているのかとか調べたくなることがあります。そういうときにRubyのPryみたいな機能を持つ、Carp::Replyというのを使ってみました。

 ひとまずやりやすいようにCartonのrepositoryをclone。

$ cd ~/development/
$ git clone https://github.com/miyagawa/carton.git

 ここに変更加えたのが使われるようにPERL5LIBに追加します。

$ export PERL5LIB=~/development/carton/lib

 あとは止めたい場所に以下の文を追加するだけです。

use Carp::Reply qw(repl);Carp::Reply::repl();

 例えばcarton installのsnapshot周りの作成で止めたかったので、以下の場所に追加します。

         }
     };

+    # install.jsonを見つけたディレクトリのMYMETA.jsonのリストが来る
     for my $file (@installs) {
         my $module = Carton::Util::load_json($file->[0]);
         my $prereqs = -f $file->[1] ? CPAN::Meta->load_file($file->[1])->effective_prereqs : CPAN::Meta::Prereqs->new;
@@ -164,6 +168,9 @@ sub find_installs {
         }
     }

+    use Carp::Reply qw(repl);Carp::Reply::repl();
+
     my @new_dists;
     for my $module (sort keys %installs) {
         push @new_dists, $installs{$module};

 それで追記した場所にPERL5LIBを通しつつ、carton installを実行すると以下のように止めていろいろ出来ます。
f:id:shiba_yu36:20140405134239g:plain

 便利ですね。ほかにもいろいろ機能があるみたいなので、また機会があったら調べてみてブログに書こうと思います。

社内Cartonコードリーディング会第一回のメモ

 最近社内でCartonを使う機会が増えてきているのですが、そもそもCarton自体が何やってるかちゃんと把握できてなくてハマったりするので、社内有志でコードリーディング会を開催しています。今回は第一回のときに出てきに作ったメモを公開します。そのままメモを公開しただけなのでかなり雑です。間違ってることも多いと思います。

今回の目的

  • carton install周りで何が起こっているかざっと把握する

疑問とか

  • snapshotが環境によってずれることがあるけどどうして?
  • --deployment, --cachedとかがついた時に何が変わるか
  • cachedしてもcpanにfallbackしてたけどなぜ
  • インストール先の切替はどうするのか
  • そもそもversion固定どうやってるか
    • 順序は関係なくversionが固定されるのか
  • cpanfileはperlスクリプトとして認識されるのか
  • local以下に別のモジュールを入れるとsnapshotに記録されてしまう問題
  • on test, on developなどはいみあるのか

いろいろわかったこと

carton概要

  • cartonはcpanfileとcpanfile.snapshotの取り持ちを行っているだけで、いろいろなもののwrapperになっているだけ
    • インストール系はcpanm
    • メタ情報の取得系はCPAN::Meta系
    • cpanfileのことはModule::CPANfileとか
  • snapshotを作る部分が肝だけど、まだ読み込めてない

--deployment, --cached

  • cpanmに渡すオプションが変わってる
  • --deploymentだとcpanmに渡す--cascade-searchオプションがoffになる
  • --cachedだとmirrorにvendor/cacheが追加される
  • --cascade-searchがoffだと別mirrorにfallbackされない?

cachedつけてもfallbackする件

  • local/vendor/cacheがmirrorに追加されるけど、fallbackはされる
  • cachedとdeploymentが両方してるとfallbackされないかもしれない
    • deploymentだとcascade-searchがoffになるため

インストール先の切替

インストールパスの見つけ方
  1. --pathの指定があったらそれを使う
  2. PERL_CARTON_PATHが指定されていたらそれを使う
  3. そうでなかったらcpanfileと同じディレクトリのlocal以下
cpanfileの見つけ方
  1. cpanfileオプションを見る
  2. PERL_CARTON_CPANFILEオプションを見る
  3. なにもしていなかったら現在のディレクトリを上に遡っていって、/までたどる

versionの固定をどうやっているか

  1. snapshotからcpanのindexファイルを作る
  2. cpanmにそのファイルを--mirror-indexオプションで渡す
  3. cpanmはそれに従ってインストールする

この時

  • --deploymentが付いていると他のmirrorにfallbackされないため、snapshotに書いていないものはインストールできずにエラーが起こる
  • deploymentで無ければ、通常のmirrorにfallbackされるので、cpanfileにしか書いてないモジュールもインストールされる、もしくはアップデートされる

その他

  • 同じモジュールで複数のversionだったら常に新しい方になりそう

cpanfileはperlスクリプトとして認識されるのか

  • 実装としてはperlスクリプトとして一応評価される
  • Module::CPANfileの方に実装がある

snapshotの作り方

まだ色々わかってない。わかったことだけ。

  • 基本的にlocal/lib/perl5/(archname)/.meta/以下のメタ情報を使って作成する
  • この時cpanfileを見ていなくて(???)、local以下にあるもの全て使う
  • snapshotに入れるかどうかの条件があって、いろいろやってる
    • まだ全部わかってない
    • install.jsonがあったら、そのディレクトリのMYMETA.jsonを利用してる
  • なので適当にlocal以下に別モジュール入れるとsnapshotに入っちゃいそう
  • 逆にそのinstall.jsonを消すとsnapshotに入らなくなる

on test, on develop

  • withoutでdevelopとかfeatureとか消せる
  • このへんの扱いはModule::CPANfileでグルーピングされたあと、cpanmがいい感じにあつかう?

第二回の話

 昨日第二回をやって、その時のテーマはcpanfile.snapshotをどうやって作っていっているかという観点でコードリーディングをしました。またまとめ次第公開しようと思います。

twitter
hatena
rss
Social Icon ブログパーツ