こんにちは、MLBお兄さんこと松村です。
MLB はトレードデッドライン (TDL) も過ぎ、プレーオフに向けて本気出すチームによる補強が目まぐるしく行われました。
みなさんソースコードのフォーマットはちゃんとやっていますか?
ここでいうフォーマットとは「整形」のことで、一定のルールに基づいてファイル内のインデントやスペース等を整えることを指します。
dotnet format
コマンド
.NET アプリケーションのコードフォーマットを行うために、 dotnet format
というコマンドラインがあります。
このコマンドでは .editorconfig
ファイルに沿ってフォーマットが行われます。( .editorconfig
ファイルが無ければ既定の設定でフォーマットされます)
例えばフォーマットがガタガタの C# ファイルに対して dotnet format
コマンドを実行すると、Visual Studio で行うようなフォーマットが行われることが分かります。
$ dotnet format consoleapp/consoleapp.csproj --no-restore --verbosity diag The dotnet runtime version is '8.0.0-preview.6.23329.7'. dotnet CLI バージョンは '8.0.100-preview.6.23330.14' です。 'C:\Program Files\dotnet\sdk\8.0.100-preview.6.23330.14\' にある MSBuild.exe を使用しています。 ワークスペース 'C:\Users\yuta\source\github\github-actions-samples\dotnet\net8.0\consoleapp\consoleapp.csproj' でコード ファイルを書式設定します。 ワークスペースを読み込んでいます。 Project consoleapp is using configuration from 'C:\Users\yuta\source\github\github-actions-samples\.editorconfig'. Project consoleapp is using configuration from 'C:\Users\yuta\source\github\github-actions-samples\dotnet\net8.0\consoleapp\obj\Debug\net8.0\consoleapp.GeneratedMSBuildEditorConfig.editorconfig'. Project consoleapp is using configuration from 'C:\Program Files\dotnet\sdk\8.0.100-preview.6.23330.14\Sdks\Microsoft.NET.Sdk\analyzers\build\config\analysislevel_8_default.globalconfig'. 1860 ミリ秒で完了します。 書式設定可能なファイルを判定しています。 178 ミリ秒で完了します。 フォーマッタを実行しています。 コード スタイル 分析を実行しています。 診断を決定しています... Running 3 analyzers on consoleapp. 1478 ミリ秒で完了します。 診断を修正しています... 2 ミリ秒で完了します。 1480 ミリ秒で分析が完了します。 アナライザー参照 分析を実行しています。 診断を決定しています... Running 144 analyzers on consoleapp. 224 ミリ秒で完了します。 診断を修正しています... 0 ミリ秒で完了します。 225 ミリ秒で分析が完了します。 2289 ミリ秒で完了します。 コード ファイル 'C:\Users\yuta\source\github\github-actions-samples\dotnet\net8.0\consoleapp\Class1.cs' が書式設定さ れました。 コード ファイル 'C:\Users\yuta\source\github\github-actions-samples\dotnet\net8.0\consoleapp\Program.cs' が書式設定されました。 2 個中 5 個のファイルが書式設定されました。 4351 ミリ秒で書式設定が完了します。 $ git diff diff --git a/dotnet/net8.0/consoleapp/Class1.cs b/dotnet/net8.0/consoleapp/Class1.cs index 1ef4b1a..1c7620c 100644 --- a/dotnet/net8.0/consoleapp/Class1.cs +++ b/dotnet/net8.0/consoleapp/Class1.cs @@ -1,13 +1,14 @@ - namespace consoleapp; +namespace consoleapp; internal class Class1 - { - private string field1 = "hoge"; +{ + private string field1 = "hoge"; - public string Property1 - => field1 ; + public string Property1 + => field1; - public string Greet () { - return $"Hello, World { Property1 }!" ; // comment - } + public string Greet() + { + return $"Hello, World {Property1}!"; // comment } +} diff --git a/dotnet/net8.0/consoleapp/Program.cs b/dotnet/net8.0/consoleapp/Program.cs index 55b1765..13b1e2c 100644 --- a/dotnet/net8.0/consoleapp/Program.cs +++ b/dotnet/net8.0/consoleapp/Program.cs @@ -1,6 +1,6 @@ - // See https://aka.ms/new-console-template for more information - using consoleapp; +// See https://aka.ms/new-console-template for more information
開発中にこまめに Visual Studio でフォーマットを行えばよいのですが、忘れたりすることもあるので自動化できるといいなと思っていました。
そこで GitHub Actions で実施する CI の一作業としてフォーマットを行うワークフローを作ってみました。
ワークフローの流れ
流れをざっと図にするとこちらのようになります。
プルリクエストが作成された際にそのプルリクエストのベースブランチに対して dotnet format
コマンドを実行し、差分があれば(=整形されれば)自動的にコミットしてベースブランチに対してプルリクエストを作成するという構成です。
dotnet format
コマンドを実行した結果、差分がなければ整形された箇所がないということになるので、プルリクエストを作成しません。
ではここから GitHub Actions のワークフローの YAML ファイルを解説します。
なお YAML ファイル全体はこちらを参照してください。
ジョブ1 : フォーマットしてブランチにコミット&プッシュ
作業ブランチに対して dotnet format
を実行します。
フォーマットするだけなので C# のリストアやビルドは行う必要はありません。(--no-restore
)
run-dotnet-format: runs-on: ubuntu-latest outputs: base-branch-name: ${{ steps.commit.outputs.base-branch-name }} head-branch-name: ${{ steps.commit.outputs.head-branch-name }} changed: ${{ steps.verify-diff.outputs.changed }} steps: - uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0.x' dotnet-quality: 'preview' - name: Format run: | dotnet format consoleapp/consoleapp.csproj --no-restore --verbosity $DOTNET_FORMAT_VERBOSITY - name: Check if there are any changes id: verify-diff run: | git diff --quiet . || echo "changed=true" >> $GITHUB_OUTPUT - name: Commit if: steps.verify-diff.outputs.changed == 'true' id: commit run: | git checkout -b $HEAD_BRANCH git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com git add . git commit -m "[bot] Auto formatted" git push --set-upstream origin $HEAD_BRANCH echo "base-branch-name: $BASE_BRANCH" echo "base-branch-name=$BASE_BRANCH" >> $GITHUB_OUTPUT echo "head-branch-name: $HEAD_BRANCH" echo "head-branch-name=$HEAD_BRANCH" >> $GITHUB_OUTPUT env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
dotnet format
コマンドによる差分の有無を判別する処理は以下のコマンドです。
git diff --quiet . || echo "changed=true" >> $GITHUB_OUTPUT
そして差分がある場合は、独自のブランチを作成しフォーマット後のコードをコミット&プッシュしています。
このときの Git 認証は perssions
かつ secrets.GITHUB_TOKEN
で成立します。
ジョブ2 : プルリクエストを作成
フォーマット後のコードを含んでいるブランチは、マージまで行わないと意味がないですよね。
そのため、フォーマット後ブランチから作業ブランチへのプルリクエストを作成します。
call-pull-request-workflow: name: Call pull request workflow needs: run-dotnet-format if: needs.run-dotnet-format.outputs.changed == 'true' uses: ./.github/workflows/github-pull-request-creation.yml with: base-branch-name: ${{ needs.run-dotnet-format.outputs.base-branch-name }} head-branch-name: ${{ needs.run-dotnet-format.outputs.head-branch-name }}
処理かいてないじゃん?と思われるかもしれませんが、「プルリクエストを作成するワークフロー」は別途作成し再利用しています。(=Reusable workflow)
Reusable workflow については、過去に弊社ブログに登場しています。
プルリクエストを作成するワークフローを抜粋すると、このような構成になります。私は GitHub CLI を利用しています。
jobs: create-pull-request: runs-on: ubuntu-latest outputs: pull-request-url: ${{ steps.pr.outputs.url }} steps: - name: Checkout uses: actions/checkout@v3 - name: Create a pull request id: pr run: | url=$(gh pr create \ --base $BASE_BRANCH \ --head $HEAD_BRANCH \ --title "$TITLE" \ --body "") echo "url=$url" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ジョブ3 : プルリクエストURLの出力
最後はおまけですが、自動作成されたプルリクエストの URL を出力します。
URL はジョブ2で出力(output)された URL を使用します。
show-pull-request-url: name: Show pull request url needs: call-pull-request-workflow runs-on: ubuntu-latest steps: - run: echo ${{ needs.call-pull-request-workflow.outputs.pull-request-url }}
このワークフロー全体をデモした動画も載せておきますので、流れをイメージしていただければと思います。
※動画は 1.4 倍速にしています(Xのアップロードサイズに収まらなかった!)
twitter.comdotnet-format-gitops-x1.4 #dotnet #github pic.twitter.com/Q6WkTfV1h4
— Yuta Matsumura⚾MLBお兄さん (@tsubakimoto_s) 2023年8月5日
長らく実現したかったワークフローができたので、個人的にとても重宝しています。
このワークフローを作る過程で Reusable workflow や、ワークフロー内での Git 操作なども改めて学ぶことができたので良かったです。
似たようなことをやってみたい方はぜひ参考にしてください。