Perl Hackers Hub

第50回 Minillaを使ったモダンなCPANモジュール開発(2)

前回の(1)こちらから。

CPANモジュールの作成

それでは、実際にCPANモジュールを作成していきましょう。

モジュールの命名規則

まずはモジュールの名前を決めましょう。CPANの世界では、階層化された説明的な名前が使われる傾向にあります。たとえば、MarkdownのモジュールであればText::Markdownというように「Text」が付くといった具合です。そこまで強い強制力はありませんが、ガイドラインもあります。

Perlの世界はご存じのとおりTMTOWTDIThere's more than one way to do it.注1ですし、ほかの言語の影響も受け、最近は個性的な名前のCPANモジュールも増えてきました。それ自体は悪いことではありませんが、慣れないうちはガイドラインや、既存のモジュールをお手本にしてモジュール名を考えるとよいでしょう。

CPANモジュールにはAcme::という階層があります。ここはジョークモジュール用の階層で、比較的気軽にモジュールをアップロードできます。そこで、以降はAcme::Songmuというモジュールを作りながら、Minillaの使い方を説明していきます。

モジュールのひな型を作成する

minil newサブコマンドを使って、モジュールのひな型を生成します。

% minil new Acme::Songmu
Writing lib/Acme/Songmu.pm
(省略)
[Acme-Songmu] $ git add .
Finished to create Acme::Songmu

いろいろ出力されましたが、プロジェクトディレクトリのAcme-Songmuが作られ、ひな型が配置されるとともに、自動的にgit addまで実行されました。

MinillaではGitによる管理が必須

Minillaを使ったモジュール開発では、プロジェクトをGitで管理することが必須となっています。CPANモジュールに含めたいファイルはすべてGitで管理されている必要があり、リポジトリには必ずリモートリポジトリが割り当てられていないといけません。

リモートリポジトリはGitHubを利用するのがお勧めです。あとで説明するREADME.mdへのステータスバッジの表示設定など、Minilla自体がGitHubに最適化されているからです。Minillaで作成されたプロジェクトディレクトリと同名のリポジトリをGitHub上に作成し、それをリモートリポジトリとして割り当ててください。今回のAcme-Songmuであれば、git remote add origin git@github.com:Songmu/Acme-Songmu.gitといった具合です。

試しにテストを実行する

minil newで作られたプロジェクトディレクトリに移動して、まずは試しにminil testを実行してみましょう。minil testはモジュールのテストスイートを実行するコマンドです。

$ minil test
(省略)
t/00_compile.t .. ok
All tests successful.
Files=1, Tests=1, 0 wallclock secs ( 0.02 usr 0.01 sys +
0.07 cusr 0.02 csys = 0.12 CPU)
Result: PASS

モジュールのビルドとテストが実行され、成功しました。このように、Minillaを使うとひな型の時点でPerlモジュールとしての体裁がすでに整っています。

モジュールのディレクトリ構成を理解する

さっそく開発を進めたいところですが、その前に、minil newが生成したファイルの役割やディレクトリ構成の作法を理解しておきましょう。

minil newが生成したファイルをもとにCPANモジュールのディレクトリ構成を理解していきましょう。初期ファイルは次のように分類できます。

  • ライセンスファイル
    • LICENSE(ライセンスファイル)
  • 設定ファイル
    • minil.toml(Minillaの設定ファイル)
  • 開発時に編集するファイル
    • lib/Acme/Songmu.pm(メインのモジュールファイル)
    • t/00_compile.t(最低限のテストファイル)
    • cpanfile(依存モジュールを記述するファイル)
    • Changes(変更履歴を記述するファイル)
  • Minillaが自動的に更新するファイル
    • Build.PL(モジュールビルドのためのファイル)
    • META.json(モジュールのメタ情報が記述されたファイル)
    • README.md(ドキュメントファイル)

順に見ていきましょう。初期生成はされない各種配置用ディレクトリについても説明します。

ライセンスファイル

LICENSEファイルには、CPANモジュールで標準的な、GPLv1とArtistic License 1.0のデュアルライセンスが記述されています。ライセンスにこだわりのない限りはこのままで問題ありません。

設定ファイル

minil.tomlはMinillaの設定ファイルです。その名のとおり、TOMLTom's Obvious, Minimal Language形式で記述します。ただ、細かいカスタマイズが必要にならない限りは、ほとんど触ることはありません。

開発時に編集するファイル

lib/Acme/Songmu.pmがメインのモジュールファイルです。ほかにモジュールファイルを追加したい場合は、libディレクトリ以下に追加できます。

t/00_compile.tは最低限のテストファイルです。今後テストファイルはtディレクトリ以下に配置します。

cpanfileは依存モジュールを記述するためのファイルです。のちほど詳しく説明します。

Changesはモジュールの更新履歴を記述するためのファイルです。

Minillaが自動的に更新するファイル

Build.PLMETA.jsonREADME.mdは、今後もMinillaが自動的に更新していくものです。開発時にこれらのファイルを意識する必要はありません。Gitリポジトリに含める必要はありますが、手で編集してはいけません。

README.mdを編集できないことは奇妙に思えるかもしれませんが、Minillaでは、モジュールファイル内に書かれたPodPlain Old Documentationドキュメントをもとに、README.mdを自動生成するためです。こちらに関しても後述します。

コマンドラインツール配置用ディレクトリ

Perlはコマンドラインツールを簡単に作れる言語でもあるため、CPANモジュールにコマンドラインツールを同梱したい場合があります。Minillaにおけるminiコマンドがまさしくそれです。そういった場合、プロジェクトディレクトリ直下にscriptディレクトリを作成し、そこにスクリプトファイルを配置すれば、それらのファイルをMinillaが自動的に検出し、CPANモジュールに含めてくれます。

逆に、開発時に作者だけが使うスクリプト類をscriptディレクトリに配置するのは危険です。そのような作者のためのファイルはauthorディレクトリに配置することが規約となっています。

サンプルコード配置用ディレクトリ

サンプルコードを同梱したい場合は、examplesegのどちらかに配置することが推奨されています。使い分けは好みの問題ですが、筆者はegを使うことが多いです。

コード以外のファイルを配置するディレクトリ

ソースコード以外のファイルをモジュールに同梱したい場合があります。たとえば、テンプレートファイルや辞書ファイルなどです。そういったファイルはshareディレクトリに配置します。

その場合、CPANにアップロードしてからも正しく動作させるために、コード内からそれらのファイルに直接アクセスするのではなく、File::Shareなどのモジュールを通してアクセスして開発する必要があります。

モジュールを開発する

それでは、CPANにアップロードするモジュールを開発していきましょう。とはいえ、モジュール自体は一般的なPerlモジュールやプロジェクトのディレクトリ構成に従って開発していけば問題ありません。libディレクトリ以下にモジュールファイルを、tディレクトリ以下にテストファイルを配置して開発していきます。

CPANモジュール開発ならではの要件として、モジュール作者だけが実行したいテストを書きたくなる場合があります。そういったときは、xtディレクトリにテストファイルを配置することが慣例となっています。

ドキュメントを書く

ドキュメントは、メインのモジュールファイル内にPod形式で記述します。Acme::Songmuの場合はlib/Acme/Songmu.pmがメインのモジュールファイルです。

このPodが、CPANにアップロードした際に、CPANやMetaCPANのサイト上で整形して表示されます。また、MinillaはPodをもとにREADME.mdを自動生成するしくみとなっているので、GitHubのサイト上でもドキュメントが見やすく表示されます。

Podの書き方は、perldoc perlpodで調べられます。最初は書き慣れないかもしれませんが、CPANにアップロードするのであればしっかり書きましょう。最低限、Minillaのひな型が出力してくれるNAME、SYNOPSIS、DESCRIPTIONの項目は埋めておくとよいでしょう。

NAMEとDESCRIPTIONは単語のとおりモジュールの名前と説明です。特徴的なのはSYNOPSISで、これはモジュールの簡単な使い方を示すサンプルコードを記述するセクションです。CPANモジュールではSYNOPSISの記述が慣例的に推奨されています。モジュールの典型的な使い方や、作者がどう使ってほしいのかを端的に示せるため非常に良い文化です。ほかの言語でも同様の慣習が欲しくなることがあります。

スペルチェックに対応する

Minillaは、ドキュメントのスペルチェックをリリース時に自動的に行ってくれます。スペルミスが検知されると、リリースは失敗します。これは便利ですが、スペルチェッカが過敏に反応してリリースが失敗して困ることもあります。

単純なスペルミスは修正すればよいですが、それ以外に関しては次の対応をすれば、無用なチェックを防げます。

  • 除外単語をPod冒頭のstopwordsリージョンに記述する
  • Pod上のコード片はcode text記法のC<>やC<<>>できちんと囲む

以下は、それぞれの対応をAcme::Songmuのソースコード内から抜粋したものです。

除外単語をstopwordsリージョンに記述する
=encoding utf-8

=for stopwords sandboxing

=head1 NAME
Pod上のコード片はC<>で囲む
=item C<< $songmu->gmu >>

README.mdにステータスバッジを表示する

README.mdには、外部サービスを用いたCIContinuous Integration継続的インテグレーション)やカバレッジ計測などのステータスを表すバッジ画像を表示したい場合があります。GitHubでホストされているOSSプロジェクトでは、これらの表示をしているものがたくさんあります。

MinillaはREADME.mdをPodから自動生成するため、README.mdを直接編集してバッジ画像表示のリンクを書き足すのは好ましくありません。その代わり、Minillaでは簡単にバッジを表示させる設定を記述できます。

たとえばTravis CIであれば、設定ファイルのminil.tomlに次の記述を追記することで実現できます。ほかにも多くのサービスに対応しています。詳しくは、Minillaのドキュメントをご覧ください。

badges = ["travis"]

Perlのプロジェクトを各種CIサービスと連携させる方法については、本連載第49回CPANモジュールの品質を支えるCI技術を参照してください。

モジュールのバージョンを記述する

メインモジュールファイル上のグローバル変数$VERSIONでバージョンを記述します。これがCPANモジュール自体のバージョンになります。Minillaの標準のひな型では、次のようなクラシックな小数文字列形式のバージョンが出力されています。

our $VERSION = "0.01";

モジュールをまだ一部環境でサポートが残っている古いPerl 5.8にも対応させたい場合は、このままの形式を利用するのが無難ですが、それ以降のバージョンのみを対象とする場合は、近年馴染み深いセマンティックバージョンのような形式で宣言することもできます。

use version 0.77; our $VERSION = version->declare("v0.0.1");

これは必ず1行で書かないといけません。少し冗長ですが、一度書いてしまえば、あとはMinillaがリリース時にソースコード内のバージョン番号(上記のv.0.0.1の部分)をリリースバージョンに自動的に書き換えてファイルを更新してくれるため、それほど気にする必要はありません。

依存モジュールの記述と動作確認

CPANモジュールのインストールが失敗する主要な原因の一つに、依存モジュールの記述漏れが挙げられます。CPANモジュールと言えばインストールに失敗しやすいというイメージを持っている人もいるでしょう。

しかし、昔に比べ、今は依存を正しく記述することは簡単になりました。依存を記述する方法としてcpanfileがデファクトスタンダードになり、それを機械的に自動生成させるためのツールチェインも整ってきているからです。

cpanfileについて詳しくは、perldoc cpanfileもしくは、WEB+DB PRESS Vol.75の本連載第21回Carton & cpanm─⁠─ Perlモジュール管理最新事情を参照してください。

scan-prereqs-cpanfileで簡単に依存を抽出する

cpanfileは手で記述するのではなくソースコードから機械的に自動生成し、それをそのまま利用できるのが理想です。今なら、scan-prereqs-cpanfileコマンドに自動生成させるのが簡単です。このコマンドは、CPANから次のようにインストールできます。

% cpanm -n App::scan_prereqs_cpanfile

scan-prereqs-cpanfileコマンドを次のように実行します。ソースコード内のuse文などを静的に解析して依存モジュールを自動的に抽出し、cpanfileに書き出されます。

% scan-prereqs-cpanfile --scan-test-requires > cpanfile

書き出された内容を確認し、必要に応じて調整をしてからリポジトリにコミットしてください。基本的には調整する必要はないでしょう。場合によっては標準モジュールや依存モジュールの内部モジュールも記述されますが、気にする必要はありません。標準モジュールは将来的に削除されるかもしれませんし、機械的に抽出した内容をそのまま利用できる利点は大きいからです。

use文に最低依存バージョンを記述する

新しい機能を使いたい場合など、依存モジュールに最低依存バージョンがある場合、cpanfile内で明示的にそのバージョンを指定する必要があります。それも、コード内のuse文に最低依存バージョンを記述することで、scan_prereqs_cpanfileに自動的に出力させられます。

たとえば、Class::Accessor::Lite::Lazyのバージョン0.03以降にある機能を使いたい場合は、次のように記述します。

use Class::Accessor::Lite::Lazy 0.03;

このように記述すると、scan-prereqs-cpanfileは次のように最低依存バージョンを含めて依存を書き出してくれます。このように積極的に最低依存バージョンを記述するのはグッドプラクティスです。

requires 'Class::Accessor::Lite::Lazy', '0.03';

これはあくまで「最低」バージョンの指定であって、バージョン「固定」ではありません。cpanfileにはバージョン固定のための記法はありますが、依存のコンフリクトを避けるために、CPANに公開するモジュールでは使わないことを強く推奨します。

scan-prereqs-cpanfileで抽出できない依存

Module::Loadなどのクラスローダや、evalなどで動的に読み込まれている依存モジュールは、scanprereqs-cpanfileでは抽出できません。これらの依存は、自分でcpanfileに追記する必要があります。

なお、動的なモジュールロードはそもそもトリッキーでコードがわかりづらくなる場合も多いです。そもそも動的ローディングが本当に必要かどうかを考え、不要な場合は削除することも大事でしょう。

必要最小限のテスト実行のためにTest::Requiresを活用する

ユーザーがCPANモジュールをインストールする場合、デフォルトではそのモジュールのテストコードが実行され、テストを通った場合にインストールが実施されます。そのため、ユーザー環境によっては必要のない一部のテストをスキップする処理を記述する場合があります。

たとえば、特定のモジュールが入っているときのみテストを実行させたいことがあります。典型的には、データベースを扱うモジュールで、DBD::mysqlが入っているときのみMySQL関連のテストを実行するといったケースです。そういった場合はTest::Requiresというモジュールを使うのがセオリーで、テストコード上に次のように記述します。

use Test::Requires 'DBD::mysql';

これで、この記述がされたテストファイルはDBD::mysqlがテスト実行環境に入っているときのみ実行されます。

開発者側ではTest::Requiresのテストをスキップしない

前項のTest::Requiresの振る舞いは、モジュールをインストールする利用者にとってはうれしい挙動です。ただ、開発者の手もとやCI環境ではテストスイートを回したいため、スキップされるのはうれしくありません。Test::Requiresで指定されている依存モジュールを抽出し、それをモジュール作者のための依存としてcpanfileに記載できると良さそうです。

そのためのscan-prereqs-cpanfileのオプションが、先ほども指定した--scan-test-requiresです。たとえば先ほどのuse Test::Requires 'DBD::mysql'が記述されたテストファイルが存在するプロジェクトに対して、scan_prereqs_cpanfile --scan-test-requiresを実行すると、次のセクションが出力されます。

on develop => sub {
requires 'DBD::mysql';
};

cpanfiledevelopセクションは、モジュール作者のための依存モジュールを羅列する箇所です。ここに記述されたモジュールをcpanmでインストールするためには、次のように依存のインストール時に--withdevelopを指定します。

% cpanm --installdeps --with-develop .

モジュール開発者やCI環境では上記のコマンドで依存のインストールを行い、テストスイートを実施するとよいでしょう。

リリース前確認を行う

これで一通りモジュールの開発が終わりました。リリース前にテストスイートを実行してみましょう。minil test --allを実行すると、モジュールのテストのほかに、リリース前のテストも一通り実行してくれます。

% minil test --all
(省略)
t/00_compile.t ................ ok
t/01_singleton.t .............. ok
t/02_gmu.t .................... ok
xt/minilla/cpan_meta.t ........ ok
xt/minilla/minimum_version.t .. ok
xt/minilla/permissions.t ...... ok
xt/minilla/pod.t .............. ok
xt/minilla/spelling.t ......... ok
All tests successful.
Files=8, Tests=14, 14 wallclock secs ( 0.05 usr 0.03 sys
+ 2.83 cusr 0.53 csys = 3.44 CPU)
Result: PASS

標準のテストのほかにxt/minilla/*.tというMinillaが生成したモジュール検証用のテストがいくつか実行されています。これらが通れば、問題なくCPANへのリリースが行えるということになります。

<続きの(3)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧