このドキュメントでは、GitHub Actions(GHA)を使ってExercismの言語トラックの継続的インテグレーション(CI)ワークフローを設定する方法を説明します。速く、信頼でき、堅牢なCIワークフローを自分で作るために活用できるベストプラクティスと例を紹介します。このフォルダーにあるGHAワークフローは、基本構造が同じままなので、どんなCIにも合わせて応用できます。
内容は次のとおりです。
これらのワークフローファイルの実装例はexercism/javascriptで見つけられます。
このドキュメントの残りは、ワークフローがどのように動作するかを説明するために書かれています。急いでいて、PRスクリプトを最適化せずにTravisやCircleからGHAに切り替えたいだけなら、Travisからの移行の約10分ガイドをご覧ください。
リポジトリの内容が整合しているかを確認するために推奨されるアクションは次のとおりです。
config.jsonを確認するためのconfigletリンティング
v3では新しいファイルが必要です。これはconfigletに移るかもしれません)トラック固有のアクションを追加することもできます。たとえば、次のようなものです。
さらに、次のように、あると便利なチェックを追加したくなるかもしれません。
各アクションについて、どれくらいの頻度で実行すべきかを考えましょう。
configletのリンティングは非常に重要です(config.jsonが壊れるとトラックが壊れてしまうため)。そのため、おそらく常に実行すべきですが、ただしコミットごとに1回実行すれば十分です。実行すべきアクションをローカルでも利用できるようにしておくと、とても役立ちます。つまり、実際の処理を行うスクリプトを手動でも実行できるようにするということです。そのためには、ワークフローファイルの中にアクションをインラインで書かず、独立したスクリプトを作成します。たとえば、スタブの確認はワークフローファイルの中にシェルで書ききることもできますが、ここでは代わりに新しい実行可能スクリプトscripts/ci-checkを作成することをおすすめします。
「でも、コマンドはとても短いですよ。たとえば
eslint . --ext ts --ext tsxのように」。このコマンドを更新する必要が出たとき、ドキュメント、ワークフローファイル、そして_メンテナーの頭の中_という、あらゆる場所で更新しなければならなくなります。これをスクリプトに切り出せば、そうした問題はすべて解決します。また、ワークフローファイルを読むのはとても大変に感じられることもあります。
scripts/prとscripts/pr-checkスクリプト(テンプレートを参照)は、このPRで変更または追加されたファイルごとに1つずつ、複数の引数を付けて実行されます。たとえば、two-ferが更新された場合、呼び出しは次のようになります。
scripts/pr exercises/two-fer/README.md exercises/two-fer/.meta/example.ext
アクションは、変更された_ファイル_ではなく、変更された_演習_に対して実行することをおすすめします。ファイルを変更すると、演習全体に影響が及ぶ可能性が高いからです(構成やパッケージなどを考えてみてください)。
まだ準備ができていない? / 複雑すぎる?
この最適化を実装する前なら、無視しても問題ありません! 移行ガイドでも、後の段階で追加することをほのめかしています。入力引数を無視すれば、すべてのチェックがすべての演習で実行されます。これはまったく問題ありません。単に時間がかかるだけです。
トラックに単一の「トップレベル」の依存関係ファイルやその他の構成ファイルがある場合は、整合性のステップを追加します(これはscripts/syncやbin/syncと並んで存在し、すべての構成ファイルをすべての演習にコピーするものです)。このステップは、トップレベル/ベースのファイルが演習ディレクトリにコピーされたものと同じであることを確認します。これで依存関係を更新し、リポジトリ全体に同期でき、すべての演習が同じ構成を持つことを確認できます。
これを実現する一般的な方法は、チェックサムを使うことです。Ubuntu(およびその他さまざまなLinuxディストリビューション)にはsha1sumというツールが付属していますが、構成ファイルをハッシュ化または圧縮してチェックサム値にする(md5、sha1、crc32)_どのような_方法でも機能します。
$ sha1sum README.md
cd58091c5043bf21f00d39ff1740d8b2976deeff *README.md
トラックがGitHubトークンやその他のシークレットへのアクセスを必要とする追加のワークフローを使っている場合、そのワークフローで使うすべてのアクションを特定のコミットに固定するのがベストプラクティスです。詳しくはGitHubのセキュリティ強化ガイドを参照してください。
たとえば、次のとおりです。
- uses: julia-actions/setup-julia@v1
+ uses: julia-actions/setup-julia@d26d1111976eae5f00db04f0515ab744ec9cd79e # 1.3.1
ツールに依存関係管理用のロックファイルがある場合は、それをリポジトリにコミットし、ワークフローファイル内で「フローズンロックファイル」を使うことを検討しましょう。たとえば、npm ci、yarn install --frozen-lockfile、bundle install --frozenです。これにより、依存関係を変更するときにロックファイルが最新であることが保証され、悪意のあるパッケージの混入を防げます。
このディレクトリには、少なくとも次のテンプレートがあります。
configlet.yml: このワークフローは最新のconfigletバイナリを取得して、このリポジトリをリントします。コミットごとに実行されます。PRでは、実際のコミットと「マージ後」のツリーに対して実行されます。ci.yml: このワークフローはmainブランチでのみ、コミットごとに1回実行されます。
pr.ci.yml: このワークフローはPRでのみ、コミットごとに1回実行されます。
PR以外のワークフローは、workflow_dispatchから手動でトリガーすることもできます。
各ファイルの先頭には、どの「スクリプト」を用意すべきかが記載されています。これらをバイナリにしたい場合は、scripts/xxxをbin/xxxに置き換えます。ツールによっては、バイナリがbinフォルダー内にあることを_必須としている_ものもあります。
scripts/ci: 例の解答を使ってすべての演習をテストに対してビルドおよびテストするスクリプトscripts/ci-check: すべての演習をリントし、オプションでスタブや構成の整合性などを確認するスクリプトscripts/pr: scripts/ciと同じですが、入力として渡されたパスから解決された演習だけを実行しますscripts/pr-check: scripts/ci-checkと同じですが、入力として渡されたパスから解決されたファイルまたは演習だけを実行します問題が発生した場合や、ワークフローを誰かにレビューしてほしい場合は、@exercism/github-actionsチームにメンションしてください。
すべての演習でCIを実行すべきトップレベルのファイルを変更した
これを書いている時点では、
pr.ci.ymlは「拡張子」によるテストしか許可していません。理想的には、特定のファイル(たとえばテストを実行するバイナリ)が変更されたときに常にトリガーされるように更新すべきです。ただし、こうした変更はめったになく、しかもメンテナーによって行われるため、ci.ymlがmainで常にすべてに対して実行されるという事実でおそらく十分安全です。
Windowsでscripts/xxxファイルを作成したら、{他のOS}で動作しなくなった
既定では、Windowsで作成されたファイルには、実行可能かどうかに関するメタデータがgit-indexに埋め込まれません。Windowsの権限のモデルが異なるためです。Gitは既定で、git-indexのメタデータを使って、POSIXベースのシステムでそのファイルを実行可能にすべきかどうかを判断するため、結果として
scripts/xxxファイルは実行可能になりません。
git update-index --chmod=+x scripts/xxx
git commit -m "Make scripts/xxx executable"