« 2013年1月 | メイン | 2013年3月 »

2013年2月15日

今週のおーぷんそーす活動と、Dist::MakerのModule::Build対応とか

深夜のテンションで「おりゃっ」とpullreqを送りつける今日この頃、皆様いかがお過ごしでしょうか。

そんな雑なpullreqに対応してくださってる皆様、本当にありがとうございます。

てことで、今週のpullreq活動。

Dist::Maker

愛用させてもらっているモジュール雛形作成ツール。

Module::Build対応とModule::Buildを使った雛形DefaultMBをpullreqして0.07でshipit してもらいました。

以下のようにすればM::B形式で簡単にモジュールが作成できます。簡単ですね!

% cpanm Dist::Maker

# 設定。設定ファイルの位置は ~/.dim/config
% dim config --import-from-gitconfig
% dim config template.default DefaultMB

# 雛形作成
% dim init My::Module
% cd My-Module

# モジュール書いた後のCPAN Upload処理
% perl Build.pl
% ./Build distmeta
% ./Build manifest
% shipit

これでアナタもシーパン王サー!

これだと最低限ですが、Dist::Makerの雛形書くのは簡単なんで、 Dist::Maker::Template::* を見ながら、自分用にカスタマイズしたの用意すると良いかと。

M::Bの説明書くのめんどくなってきたんで、これでM::B使えますよってことで一つ。

Time::Piece::Plus

Time::Pieceを継承してDateTimeライクなインターフェースを追加した日付モジュール。

結構便利なんじゃないかと思うんですが、あまり使われている気配がなくて不思議な感じがしています。皆様日付処理どうしているのでしょうか。わたし、気になります。

これのadd/subtractメソッドにDateTime的なインターフェースを加えてpullreqして取り込んでもらいました。以下の様な感じ。

my $time_pp = Time::Piece::Plus->localtime;
$time_pp = $time_pp->add(days => 7);

DateTimeと違うのは破壊的じゃないところですね。

これでDateTimeで僕が使ってる機能は大体Time::Piece::Plusに揃ったので、大手を振って以降予定。


両方共俺得でしか無いフィーチャーですが、勇気を出してエイヤッとpullreqした次第。

gfxさん、takuji31さん、ありがとうございました。

DBIx::Schema::DSLも「俺得でしか無いんだよなー」とか言っていたら@typesterに「俺得でも全然良いから出しなよ」とか言われてかなり勇気をもらったりしました。

そういや、Parse::Crontabも俺得でしかないと思っていたけど、機能要望が届いていたりしてありがたい話だなーとか思いました。俺得は誰かの得にもなる、…かも知れない。

おれとくでもいいじゃない、にんげんだもの。

02:32

2013年2月13日

CPANモジュールのパッケージングの歴史

最近同僚が次々とCPAN Authorになってて良い流れだなーとか思っています。

ただ、CPANへのモジュールの上げ方がわからないとか、M::Iを使えばいいのか M::Bを使えばいいのか、それらがそもそも何やってるのか分からないという話も 聞くので、僕自身もその辺の知識を整理してアップデートしました。

とりあえず、今はModule::Buildを使っておけば良いんじゃないかと 思っていますが、そこに至る歴史的経緯をまとめてみます。

大体、以下に書いてあることに加えて、最近の動きを書いています。

Module::Build:MakeMakerの後継者を目指して

PerlでCPAN形式のモジュールを配布する場合は、Makefile.PLなりBuild.PLなりを モジュール作者が用意して、それがインストールに必要なファイル類を自動生成 するという流れになっています。

既存の雛形を使うと色々ファイルができてぎょっとしますが、最低限必要なのは lib/以下のモジュール本体と、(Makefile|Build).PLだけです。

ExtUtils::MakeMaker(EUMM)

Unix系OSではビルドツールに昔からmakeが多く使われているのは御存知の通り。 Perlモジュールもmakeでビルドしてインストールするのが楽だろうってことで、 make用のMakefileをPerlで書き出す仕組みが作られた。

それが ExtUtils::MakeMaker 。20世紀末くらいの話。

Makefile.PLの中でuse ExtUtils::MakeMaker;WriteMakefile(…);とかやって使う。

Perl5当初からコアモジュールなので、Makefile.PLの中でuseしてもCan’t locateとか 怒られない。

これで、以下の様な今のPerlモジュールインストールの形の基礎が作られました。

% perl Makefile.PL
% make && make test && make install

実際のところcpanm?コマンドもtarball落としてきて、こういうインストールコマンド を自動的に実行してくれています。

makeフェイズで実際に何をやっているかとかは以下が参考になります。

Perlのmakeフェーズの挙動

Module::Build(M::B)

make installでやってることってそんな大したことなくて、特にPurePerlの場合は ファイルの配置くらいだし、それってPerlの方が得意じゃね? ってことで、makeに頼らないPerlモジュールビルドツールってことで作られたのが、 Module::Build 。初リリースは2001年。

Build.PLというファイルを作ると、perl製のビルドスクリプトをBuildという名前で作 成する。

モジュールインストール手順は以下の通り。外部ツールに頼らずにPerlだけで完結して いる。

% perl Build.PL # Build ファイル作成
% ./Build test
% ./Build install

単にビルドスクリプトを作るだけではなく、モジュールのメタデータの自動生成と その仕様を定義し始めたのが功績で、 META.yml なんかはExtUtils::MakMaker 側にも入ることになります。これ後々の重要ポイントです。テストに出ますよ。

ただ、

  • 当然ながらModule::Buildがインストールされていない環境だと動かない*
  • CPAN Shellが上記のインストール方法に対応してくれるのが遅かった
  • XSモジュール作成用には弱かった

等の問題があり、また次に述べるModule::Installに多くの人が移行してしまったため 一旦あまり使われなくなります。

* 一応、Perl5.10からはコアモジュール入りしています。
また、ExtUtils::MakeMaker形式のMakefile.PLを自動生成する機能も 後から追加されたので、それを使って、モジュール作者側でパッケージに Makefile.PLも同梱するという回避策もあったりはします。

Module::Install(M::I)

そこに現れたのがM::I。正しくパッケージングすればサラの環境でも モジュールインストールが可能で、他にも良いことづくめなので、一世を風靡します。 今でもかなり使われていますね。

多くの人が使っているので、見よう見まねで他の人が書いているM::I形式の Makefile.PLを作ったことがある人も多いんじゃないでしょうか。僕がそうでした。

最初に作られたのは2003年くらい。特徴としては以下。

  • Makefile.PLをDSLでかっこ良く記述できる
  • インストール方法は従来のmake
  • プラグインで拡張可能 
  • inc/ の活用

前項でも述べましたが、EUMMにはコアモジュールならではの強みがあり、 サラの環境でいきなりperl Makefile.PLしてもちゃんと動きます。

ところがコアモジュールじゃないモジュールだと、エンドユーザーが事前にその モジュールをインストールしないといけないという問題が発生します。

じゃあどうするか。そこで、考えられたのが、

インストールモジュールをパッケージに同梱してしまう

というエポックメイキングな力技です。perl Makefile.PLすると、Makefileや メタファイルが作られるとともに、inc/が作られてそこにM::I自身や付随する プラグインが格納されます。

configure_requires登場によるinc/のオワコン化とM::B再び

inc/ は確かに現実的な解決策ですが、さすがにやりすぎ感があります。

インストール時にいきなり(Makefile|Build).PLを走らすから事前にインストールモジュールが 必要になるからこうなったわけですが、それを動かす前に必要なモジュールをインストールする 仕組みがあればよさそうです。

そこで利用されたのがMETA.ymlです。META.ymlはもともとCPANサイト掲載に必要なメタ情報 なんかをまとめてあるファイルですが、そこに事前にインストールされている必要があ るモジュールの情報を含めてしまえばよさそうです。

それがconfigure_requiresセクションです。

今はEUMMもM::IもM::Bも適切にPLファイルを書けば、META.ymlにconfigure_requiresを出力 してくれるので、それをパッケージに同梱します。

また主だったモジュールインストーラー(cpan(m|p)?)も現在はconfigurerequiresに対応 していて、Makefile.PLを走らせる間に、META.ymlのconfigurerequiresセクションを見て、 そのモジュールをインストールしてくれます。

このようにconfigure_requiresを使えば、inc/に色々同梱する必要はなくなりました。

configure_requiresに関しては以下にまとまっていました。

https://www.socialtext.net/perl5/configure_requires

また、M::Iの利点であるプラグイン機構も、昨年tokuhiromによって開発された Module::Build::Pluggableを使えばM::Bで実現出来ますし、M::B自体も継承すれば なにげに簡単に拡張できるようになっています。

そうなるとM::Iの利点はDSLと既存のプラグイン資産くらいです。それにDSLにも良し 悪しがあるし、M::I自体のが動作が不安定だった時期もありました。

だったら、M::BはPerl5.10からコアモジュールだしそれ使えばよくね?となってきてるのが 最近の流れ。

githubの存在も無縁じゃなくて、Module::Build使ったほうが色々楽だったりします。 具体的には以下の様な感じです。

  • Travis連携が超絶楽
  • cpanmでgithubからモジュールを簡単に直接インストールしてもらえる

まとまらないまとめ

とりあえず、今日はここまで。次はファイル構成の話なんかを気が向いたら書きます。

なんかざっくり書く予定が、途中から丁寧になって長文化してしまうのが悪い癖。

12:50

2013年2月11日

DBIx::Schema::DSLってのを書いている話

https://github.com/Songmu/p5-DBIx-Schema-DSL

Teng使って開発しててDDLを直接書くのに大分疲れてきたので、RDBのスキーマを定義、 管理するDSLモジュールを書いています。

Podに書いてあるのとほぼ同じですが、こんな風にスキーマ定義しておいて、

package My::Schema;
use DBIx::Schema::DSL;

database 'MySQL';
create_database 'my_database';
default_unsigned;

create_table 'book' => columns {
    integer 'id',   primary_key, auto_increment;
    varchar 'name', null;
    integer 'author_id', not_null;
    decimal 'price', 'size' => [4,2];

    add_index 'author_id_idx' => ['author_id'];

    belongs_to 'author';
};

create_table 'author' => columns {
    primary_key 'id';
    varchar     'name';
    tinyint     'age';

    add_index 'age_idx' => ['age'];

    has_many 'book';
};
1;

こんな感じで、DDLを出力できます。

use 5.014;
use My::Schema;
say My::Schema->output;       # output DDL
say My::Schema->no_fk_output; # without fk constraints

そんな違和感ないんじゃないかなーと思います。

内部的にはSQL::Translatorを使っていて、SQL::Translatorオブジェクトを 組み立てるDSLを提供しているだけとも言えます。

案の定、@fujiwaraには「直でSQL書けばいいじゃん」とか言われてしまったのですが、 @typesterには「ちゃんと完成したら使うよ」と評価を頂いております。

作っている動機としては

  • DDL直接書くのはクオートとかカンマとか気をつけないといけないし、冗長な記述も多いのでたるい
  • DDL直書きスタイルだと、複数人開発だと微妙に書き方がバラバラになったりするのがイヤン
  • ER図描画等のため、MySQL workbenchには外部キー制約つけたDDL食わせたいけど、本 番ではある程度インデックスを間引きたいので、外部キー制約つけてないDDLも出力 したいとかそういうニーズがある
  • ゆくゆくはORMと連携させて、クラス定義とか、リレーションメソッド生やすとかそういうことできると良さそう

という感じですかね。

本番で外部キー制約つけないとか「SQLアンチパターン」に見事にアンチパターンとし て書かれていましたね。仕方ないね。

あと、SQL::TranslatorがMoo依存になってたので、Moo使っております。

今やってるプロジェクトで使い始めてるんで、もうちょっとしたらCPAN上げる予定。

00:31

2013年2月10日

真夜中のテンションでDSLを作り始めるとポエム的に危ない

普段押さえ込んでいる黒魔術の力を開放することに悦びを覚え、その威力に陶酔し、次第にその力を行使することそのものに快感を覚え初め、遂にはダークサイドに堕ちてしまうのである。

23:43

2013年2月 2日

2013年のGetopt::Long

完璧な引数処理モジュールなどといったものは存在しない。完璧なGetopt::Longが存在しないようにね。

バッドノウハウの宝庫として有名なGetopt::Longですが、なんだかんだでデファクトで、gnu parallel等、名の知れたコマンドラインツールで使われていたりします。標準モジュール縛りでサクッとコマンドラインツールを書くこともあるでしょうし、そうではなくても、Getopt::Longで片付くことも多いので、個人的なベタープラクティスとかtipsとかを書きます。

Perlでコマンドラインオプションの解析に Getopt::Long を使う時、絶対に忘れてはいけない引数

大事なことは上の記事に書いてあるので、まずはこれを読んでください。

サンプルコード

僕がスクリプトを書くときのの雛形は大体以下の様な感じ。

#!/usr/bin/env perl

=head1 DESCRIPTION

サンプルです

=head1 SYNOPSIS

    % sample.pl

=cut

use strict;
use warnings;
use utf8;

use Getopt::Long qw/:config posix_default no_ignore_case bundling auto_help/;
use Pod::Usage qw/pod2usage/;

# ハッシュで受ける
GetOptions(
    \my %opt, qw/
    foo=i
    var=s
    all
/) or pod2usage(1);

# 必須オプションのチェックとか
my @required_options = qw/foo/;
pod2usage(2) if grep {!exists $opt{$_}} @required_options;

# 処理
my $app = MyApp->new(%opt);
...

podとpod2usage

pod書いておくと、pod2usageで出てくるし便利。上の方にpodを書いているのは、仕事で使うツールだと、先に説明と使い方を書いておいたほうがすぐ使えるし分かりやすいという判断。

posix_default no_ignore_case

上のtagomorisさんの記事を熟読のこと

gnu_compatは指定不要

configにgnu_compatは指定していません。上記記事では、「—option=value といった、これはこれで馴染みがある形式の指定がデフォルトでは無効のため、」と書いてありますが、これは勘違いで、—option=valueの形はデフォルトで有効です。

じゃあ、gnu_compatは何なのかというと、—option=valueでvalueに空文字列を指定し たいときに—option= とかやると、エラーになるのを回避してくれるというオプションです。

bundling

引数をまとめられるのは嬉しい。

それよりも個人的に嬉しいのは、このオプションを指定すると、ロングオプションを 必ずハイフン2つで始めなくてはいけなくなるということ。

例えば、上で allというオプションがあるが、デフォルトだと-allでも有効に なってしまうが、bundlingを指定すると—allじゃないと有効にならない。

auth_help

—helpとかした場合に、pod2usage出力を呼んで処理を抜けてくれる。

pod2usage(0) if $opt{help};とか書かなくて良くなるのが微妙に便利。

同様にauto_versionてのもあって、—versionのハンドリングを自動でやってくれる。 $main::VERSIONが指定してあったらそれがバージョン番号として使われる。

これもちょっと便利だけど、普段は必要ないなーという感じ。

GetOptionsはハッシュで受ける

変数ではなくハッシュで受けてるのは、上記のように必須オプションのチェックがし やすいし、上記のようにそのまま引数として使えるのでスッキリして良いんじゃないかと思っている。

また、%optをさらにData::Validatorに掛けると良いんじゃないかと、typesterが言っていました。そういうこともやりやすくなります。

以前は変数で受ける派でしたが、typesterの影響でハッシュで受ける派になりました。

GetOptionsでエラーが有った際に処理を中断するように or pod2usage(2); とやるのは定石。

pass_through

上記では指定していないし、普段は指定しないが、GetOptionsを複数回指定したい時などに使うのがpass_throughオプション。

通常@ARGVに余計な引数があれば警告を出してGetOptionsも偽値を返すが、それを無効にするオプション。

foo=var --all -- hoge=fugaのように引数を--で区切れば、順番に処理をすることも可能ですが、必要な引数がごちゃまぜになっている場合はうまくいかないので、pass_throughを指定します。

ままあるのが、Plackアプリを作って起動スクリプトを作った時に、port指定やらサーバー指定やらも混在してしまう場合。

まあ、書いててもなんのことやらだと思うので、拙作Plack::App::Directory::Markdownに同梱してあるpad-markdown.plのソースを一部抜粋して説明に変えます。

use Getopt::Long qw/GetOptions :config auto_help pass_through/;
...
GetOptions(\my %options, qw/
    root=s
    encoding=s
    title=s
    tx_path=s
    markdown_class=s
    markdown_ext=s
/) or pod2usage(2);

my $app = Plack::App::Directory::Markdown->new(%options)->to_app;

push @ARGV, '--port=9119' unless grep {/^(?:--port|-p)\b/} @ARGV;
my $runner = Plack::Runner->new;
$runner->parse_options(@ARGV);
$runner->run($app);

ここでは、GetOptionsを2回読んでいるわけではありませんが、$runner->parse_options(@ARGV) がGetOptions同様の処理を行なっています。

configが上の説明とだいぶ違いますが、これを書いた頃はベタープラクティスが固まってなかったので、ご容赦下さい。

16:45

Module::Advisor便利ですね

こういう時こそ、Lの出番です。

% perl -ML -E 'Module::Advisor->new->check'

14:47

Plack::Middlewareで$envの中身を見るテストを書く方法

Plack::Middlewareの中で $env->{'psgix.hoge'} 的な何かを突っ込むことはあるん じゃないかと思うが、そのテストをどう書くかという話。

Plack::Testだとレスポンスは見られるが、$envはもう見られない。

結論を言ってしまうと、$envをシリアライズして、response bodyに突っ込んでしまう $appを作るのが乱暴かつお手軽かと思う。

以下サンプルコード。

package Plack::Middleware::Hoge;
use strict;
use warnings;
use parent 'Plack::Middleware';

sub call {
    my ($self, $env) = @_;
    $env->{'psgix.hoge'} = 'fuga';
    $self->app->($env);
}

このMiddlewareをテストする。

use strict;
use warnings;
use Test::More;
use Plack::Test;
use Data::Dumper;
local $Data::Dumper::Terse  = 1;
local $Data::Dumper::Purity = 1;

use Plack::Middleware::Hoge;

my $app = sub {
    my $env = shift;
    [200, ['Content-Type' => 'text/plain'], [Dumper $env]];
};
$app = Plack::Middlware::Hoge->wrap($app);

test_psgi $app, sub {
    my $cb = shift;
    my $res = $cb->(GET '/');

    my $env = eval $res->content;
    is $env->{'psgix.hoge'}, 'fuga';
};

JSONでシリアライズすることも考えたけど、Data::DumperはPerl5.8でも標準添付 なのでそれでよいかな、と。

以前は$appのなかにokとか書いてたんだけど、そうすると、リクエストに応じて、$app 作り変えないといけなかったりしてめんどくさいのでこういう形に落ち着いた次第。

Plack::Middeware::Outh::OAuthもこの形式でテストがりっと書き換えました。

14:35