前回の(1)はこちら から。
継続的な更新
モジュールの更新は一度やったら終わりではなく、継続的に行うべきものです。新しいモジュールがリリースされるたびに繰り返し発生する作業で、管理すべきモジュールの数はアプリケーションの機能追加や規模増大とともに増えていきます。こうした繰り返し発生する作業を自動化することで、将来にわたって時間と手間を節約し、煩雑な手順を手作業で行うことによるミスを減らせることが期待できます。
Node.jsやGo、Dockerなどを使って開発している場合は、Renovate やDependabot などのツールを使うと、これらのツールのbotが自動的に依存モジュール更新のPull Requestを送ってくれるので、手間を軽減できます。Perlを使って開発している場合も、update-cpanfile という筆者が開発しているPerl製のCLI(Command Line Interface )ツールを使ってcpanfileの継続的な更新を実現できます。update-cpanfileは、cpanfileを書き換え、モジュールの要求を最新化する機能を持っています。
以降では、update-cpanfileを使った更新手順を紹介します。
update-cpanfileでcpanfileを更新する
まずはupdate-cpanfileをインストールします。執筆時点ではバージョン1.0.0がインストールされます。
$ cpanm App::UpdateCpanfile
依存関係を固定して扱いやすくする
cpanfileにバージョン指定を書いていない場合、carton install
するタイミングで最新のバージョンがインストールされるため、実行するタイミングによって結果が不定となります。このことは、バージョン管理や継続的な更新と相性が悪いため、update-cpanfileを使い始める際には、まずは依存関係を固定します。
pin
コマンドは、cpanfile.snapshotに記録されているモジュールのバージョンをcpanfileに書き戻すコマンドです。冒頭のcpanfileに対してupdate-cpanfile pin
を実行すると、3つのモジュールのバージョンが固定されます。
依存モジュールの固定
$ update-cpanfile pin
Pinned 3 modules
- JSON::XS 3.0
- List::MoreUtils 0.419
- Test::More 0.98
cpanfileを確認すると、ファイルが書き換わっていることを確認できます。3つのモジュールが==
を使った指定となり、cpanfile.snapshotに記録されていたバージョンに固定され、将来にわたってインストール結果が不変なものとなります。DateTime
はもともと1.50でバージョンが固定されているため、ここでは差分が出ません。
依存モジュールのバージョンが固定された
requires 'List::MoreUtils', '== 0.419';
requires 'DateTime', '== 1.50';
requires 'JSON::XS', '== 3.0';
on test => sub {
requires 'Test::More', '== 0.98';
};
依存関係を更新する
update
コマンドは、cpanfileでのバージョン要求指定を、CPANの最新版へと更新するコマンドです。update-cpanfile update
を実行すると、cpanfileでの4つのモジュールのバージョン指定が最新化されます。
依存モジュールの最新化
$ update-cpanfile update
Updated 4 modules
- DateTime 1.52
- JSON::XS 4.02
- List::MoreUtils 0.428
- Test::More 1.302181
cpanfileを確認すると、4つのモジュールすべてがCPANで公開されている最新のバージョンへと書き換えられています。
依存モジュールのバージョンが更新された
requires 'List::MoreUtils', '== 0.428';
requires 'DateTime', '== 1.52';
requires 'JSON::XS', '== 4.02';
on test => sub {
requires 'Test::More', '== 1.302181';
};
この時点では要求を新しくしただけで、新しいモジュールはインストールされていないことに注意してください。carton install
を実行すると、新しいモジュールがインストールされ、cpanfile.snapshotが更新されます。
CIを使った継続的な更新
ここまで、update-cpanfileを使った更新操作を見てきました。手作業でcpanfileを編集するよりは楽になりましたが、更新を実行し忘れる、コマンドを間違うなど、手間のかかる手順であることには変わりありません。そこで、更新作業を人間が手動で実行する代わりにCI環境から機械的に実行させることで、定期的に決まった手順を繰り返し、手間を軽減します。
その際、update-cpanfileのpin
は最初に一度だけ、update
は何度も行うものですので、初回のpin
は手動で行い、そのあとのupdate
はCI環境から実行するとよいでしょう。
今回は、GitHub Actionsを用いて実現する方法を紹介します。GitHub Actionsは、GitHubと統合されたCIサービスです。update-cpanfileのリポジトリにはサンプルとして、update-cpanfile自体の依存モジュールを更新するGitHub Actions用の設定ファイル を同梱しています。この設定ファイルでは、次の手順を実行してモジュールを更新します。
❶1日に一度タスクを実行する
❷actions/checkoutアクションを使ってリポジトリをcloneする
❸Docker上でupdate-cpanfileをインストールする
❹update-cpanfile update --output jsonを実行して依存モジュールの要求を更新し、結果をJSONとして出力する
❺Pull Requestの本文やタイトルを組み立てるため、実行結果のJSONをjq コマンドでMarkdownに加工する
❻peter-evans/create-pull-request アクションを使ってPull Request化する
このサンプルによって生成されたPull Request は、図1 にあるように1行1モジュールのリストとなっていて、metacpanへのリンクやChangesへのリンクを掲載しています。開発者は、CIのチェックが通っていることを確認し、Changesを読んでモジュールの変更履歴を把握したうえでマージできるようになっています。定期的に動き、Pull Requestの用意まで自動化されているため、手作業でのミスが入り込む余地のない構成となっています。
図1 自動生成されたPull Request本文
苦労なく更新するための工夫
筆者のチームでは、update-cpanfileとGitHub Actionsを使った継続的な更新のしくみを運用し、4ヵ月で74件の更新に成功しました。4ヵ月の活動を通じて、苦労なく、継続的にモジュールを更新するために工夫したことを紹介します。
テストや開発用のモジュールは事前に更新しておく
テストに使うモジュールや、アプリケーションの開発のために利用するモジュールは、動作がアプリケーションのユーザーに影響しません。これらのモジュールについては、継続的な更新を行う前処理として、手作業で一気にまとめて更新しておくと、以降の継続的な更新が楽になります。
原始的な方法ですが、update-cpanfile update
コマンドを実行したあとに、生成されたcpanfileから、on develop
(開発用のモジュールが記されるセクション)とon test
(テスト用のモジュールが記されるセクション)の中だけdiffを残す形にテキストエディタで編集することで、開発用のモジュールだけを手動で更新できます。
CIから1個ずつ更新する
update-cpanfileはlimit
オプションを実装しており、一度に更新するモジュールの個数を制限できます。筆者のチームでは、毎日のCIからの実行では--limit 1
のオプションを付けて、一度に1モジュールずつ更新するPull Requestを作っています。
開発用やテスト用のモジュールと違ってユーザーへの影響がある部分なので、1件ずつ動作確認しながらリリースしています。
更新できないモジュールはスキップする
アプリケーションの都合やモジュールの破壊的な変更などの事情でバージョンを固定したいモジュールがあったとしても、update-cpanfileは容赦なく更新してしまいます。update-cpanfileはignore-filter
オプションを持っているので、事情があって更新したくないモジュールの更新はスキップできます。
JSON::XSとDateTimeの更新をスキップする
$ update-cpanfile update --ignore-filter '^(JSON::XS|DateTime)$'
Updated 2 modules
- List::MoreUtils 0.428
- Test::More 1.302181
ただし、このままではモジュールが古びてしまうので、あくまで一時的なスキップという認識で使い、将来的には更新することが望ましいでしょう。
snapshotをCIから作る
新しいモジュールを使ってPull Requestのテストを実行するため、update-cpanfile update
の実行後に、CI上でcarton install
を実行し、cpanfile.snapshotを生成してコミットするようにしています。開発者が自由に作業できる手もとの環境ではなく、決まった手順で動くCI環境から生成することで、生成結果を固定でき、意図しないモジュールがcpanfile.snapshotに入り込んでしまうトラブルを避けやすくなります。
筆者のチームでは、GitHub Actions上でcpanfile.snapshotを構築し、Pull Requestに追加でコミットしています。本連載の第61回「GitHub ActionsとAmazon ECSを使ったDockerアプリケーションの自動デプロイ 」のgeneratecpanfile-snapshot.sh
を参考にしてください。
レビュアーをランダムにアサインする
モジュールの更新は終わりのない作業ですので、属人化せず、誰にでも運用できる体制を作ることが重要です。筆者のチームではGitHubのTeam Reviewers機能を使って、レビュアーをランダムにアサインしています。ランダムにアサインすることで、メンバー間の負担や知見の偏りを防いでいます。
まとめ
アプリケーションの依存モジュールの管理方法と、管理を楽にする手法について紹介しました。本稿が少しでも参考になり、みなさんのアプリケーションを1日でも長生きさせられることを願っています。
さて、次回の執筆者はkolukuさんで、テーマは「Perlの時間モジュール」です。お楽しみに。
特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT