Azure Pipelines は Job から標準出力に特殊なテキストを書き出すと、それをコマンドとして扱う機能があります。Logging Commands と呼ばれているみたいですが、ログ以外にも機能があります。
この形式がとにかく分かりにくいし、毎回 echo
や Write-Host
を書くのも面倒なので、C# での CLI 実装の勉強を兼ねて作ってみました。
CommandLine 用の Parser はいろんな種類がありますが、今回は System.CommandLine
を使ってみました。
クロスプラットフォーム向けの CLI には Go を使うのが一般的な流れだと思いますが、C# で書きたかったので .NET Core を使っています。
GitHub Release から適当なプラットフォーム向けのバイナリをダウンロードして叩けば動きます。ヘルプは System.CommandLine
が自動的に用意してくれています。
サブコマンド毎にヘルプが出るので、手書きよりは分かりやすいと思います。
ちなみにスクリプトから変数を定義する場合は、以下のような引数で叩きます。やっていることは引数を解析し、Logging Commands の形にフォーマットして標準出力に書き出しているだけです。
> pipeline task setvariable testvar kazuakix ##vso[task.setvariable issecret=False;isoutput=False;variable=testvar]kazuakix
パイプライン内で実行すれば、後続の Job で変数を使った処理が行えるようになります。
とまあ、機能自体は非常に大したことがないですが、実装のためにいろんなライブラリやサービスを組み合わせて使ったので、むしろそっちの方を紹介したいので書きます。
利用しているライブラリ・サービス
System.CommandLine
リポジトリ名から .NET Core での標準になりたい雰囲気を感じたので使ってみました。
正直なところ使い勝手はイマイチかなと思います。今回は Command
を継承したクラスをコマンド毎に用意して、コレクション初期化子を使って RootCommand
に追加する方法を選びました。
ドキュメントとサンプルが全く足りていないという点が割と致命的な感じでした。
Warp
.NET Core 3.0 からは単体の実行ファイルとしてパッケージング出来る予定ですが、まだ Go Live も付いていないので .NET Core 2.2 +Warp という構成で実行ファイルとしてパッケージングしました。
.NET Global Tool としてインストール出来るので非常に簡単です。
Scott Hanselman もブログで紹介しているので、非常に参考になりました。仕組みとしては初回起動時に一時ディレクトリへ展開してから起動しているので、初回は少し遅いです。
Linker Option を設定すると必要ないアセンブリを削ってくれるので、最終的な実行ファイルのサイズを抑えることが出来ます。一応 crossgen とかも使っているようです。
通常の Self-contained App なら 66MB 近くになりますが、Warp 後は 17MB ぐらいです。
Azure Pipelines
ビルドとリリースは Azure Pipelines の Multi-stage pipelines を使って行いました。
今回は対応プラットフォームとして Windows / Linux / macOS の 3 つを用意したかったので、それぞれの VM Image を使ってビルドするようにパイプラインを書きました。
.NET Core では RID を指定すれば別プラットフォーム向けのバイナリを生成できますが、Azure Pipelines の各 Hosted Agent を使ってみたかったので。
プロジェクトが Public の場合は Windows / Linux / macOS 向け Job が並列に実行されるので、全体としたビルド時間を短縮できます。Warp を使ったビルドは Linker Option によっては時間がかかるので好都合です。
例によってリリースはタグを打った時に行うようにしていますが、今回は GitHub Release でビルドしたファイルを公開したかったので、GitHub Release Task を使ってアップロードしています。
それぞれのプラットフォーム毎に zip がアップロードされているのが確認できるはずです。
各プラットフォーム向けのビルドは strategy
/ matrix
を使って書きましたが、プラットフォームでの差異を吸収するのが少し面倒でした。*1
もちろん YAML で書いたパイプライン定義を公開しているので、良ければ参考にしてください。
*1:特に実行ファイルの .exe の有無とか script での cmd / bash の差など