Github Actionsで.NET CoreのCI/CDを行う

この記事で作ったサンプルリポジトリは以下になります。
[dotnet-ci-example](https://github.com/nogic1008/dotnet-ci-example)

## TL:DR

- Windows / Linux(ubuntu) / MacOS のそれぞれで単体テストを実行
- `v0.1.0`のようなタグがあった場合はGithub Releaseを作成
- それぞれのOS向けの実行ファイルをビルド・アップロード

するGithubのWorkflowを作ります。

## プロジェクトの作成
```console
# ソリューション, コンソールプロジェクト, テストプロジェクトを作成
> dotnet new sln
> dotnet new console -o ./Sample.Core
> dotnet new xunit -o ./Sample.Test

# ソリューションに2つのプロジェクトを紐付け
> dotnet sln add ./Sample.Core/Sample.Core.csproj ./Sample.Test/Sample.Test.csproj

# テストプロジェクトにコンソールプロジェクトを紐付け
> cd ./Sample.Test/
> dotnet add reference ../Sample.Core/Sample.Core.csproj
```

## [dotnet-format](https://github.com/dotnet/format)のローカルインストール

.NET Core版ESLint。
設定は`.editorconfig`ファイルから読み込むので、別途用意してください。
(自分は別プロジェクトで[Visual Studioから作成したもの](https://docs.microsoft.com/ja-jp/visualstudio/ide/code-styles-and-code-cleanup)を引っ張ってきました)

```console
# ローカルインストールに必要なマニフェストファイルを作成
# ちなみにリストアはdotnet tool restoreでできます
> dotnet new tool-manifest

# インストール
> dotnet tool install dotnet-format
```

### (2019/12/16追記)
.NET Coreツールについては[別記事を書きました](https://qiita.com/nogic1008/items/ce2dec260aa31bf205d8)ので、そちらをご参照ください。

## ロジックとテストの実装

説明は割愛。

## ワークフローの作成

Github上で作成します。(vscodeで作ってpushしようとしたらエラーになった)
流用する際は適宜パスやバージョン等変更してください。

<details><summary>YAMLファイル</summary><div>

```yaml:dotnet.yaml
name: .NET Core CI/CD
on: [push, pull_request]

jobs:
  test:
    name: Debug Build & Test
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        dotnet-version: ['3.0.100', '3.1.100-preview3-014645']
    env:
      DOTNET_CLI_TELEMETRY_OPTOUT: 1
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
      NUGET_XMLDOC_MODE: skip
    steps:
    - uses: actions/checkout@v1
    - name: Setup .NET Core ${{ matrix.dotnet-version }}
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: ${{ matrix.dotnet-version }}
    - name: Restore Tool
      run: dotnet tool restore
    - name: Lint
      run: dotnet format --check --dry-run
    - name: Build
      run: dotnet build -c Debug
    - name: Test
      run: dotnet test -c Debug --no-build

  release:
    name: Create Github Release
    if: contains(github.ref, 'tags/v')
    needs: [test]
    runs-on: ubuntu-latest
    steps:
    - name: Create Release
      id: create_release
      uses: actions/create-release@v1.0.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: Release ${{ github.ref }}
        draft: false
        prerelease: false
    - name: Output Release URL File
      run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt
    - name: Save Release URL File for publish
      uses: actions/upload-artifact@v1
      with:
        name: release_url
        path: release_url.txt
    
  publish:
    if: contains(github.ref, 'tags/v')
    needs: [test, release]
    runs-on: ubuntu-latest
    strategy:
      matrix:
        rid: [win-x64, linux-x64, osx-x64]
    env:
      DOTNET_CLI_TELEMETRY_OPTOUT: 1
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
      NUGET_XMLDOC_MODE: skip
    steps:
    - uses: actions/checkout@v1
    - name: Load Release URL File from release job
      uses: actions/download-artifact@v1
      with:
        name: release_url
    - name: Setup .NET Core 3.0 (current)
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.100
    - name: Build
      run: |
        dotnet publish ./Sample.Core/Sample.Core.csproj  -c Release -r ${{ matrix.rid }} /p\:PublishSingleFile=true /p\:PublishTrimmed=true -o ./${{ matrix.rid }}
        zip -r ${{ matrix.rid }} ./${{ matrix.rid }}
    - name: Get Release File Name & Upload URL
      id: get_release_info
      run: |
        echo ::set-output name=file_name::${REPOSITORY_NAME##*/}-${TAG_REF_NAME##*/v}
        value=`cat release_url/release_url.txt`
        echo ::set-output name=upload_url::$value
      env:
        TAG_REF_NAME: ${{ github.ref }}
        REPOSITORY_NAME: ${{ github.repository }}
    - name: Upload Release Asset
      id: upload-release-asset 
      uses: actions/upload-release-asset@v1.0.1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.get_release_info.outputs.upload_url }}
        asset_path: ./${{ matrix.rid }}.zip
        asset_name: ${{ steps.get_release_info.outputs.file_name }}-${{ matrix.rid }}.zip
        asset_content_type: application/zip
```
</div></details>

## 実録

- [v0.1.0のタグをpushしたときのAction](https://github.com/nogic1008/dotnet-ci-example/commit/b18dad531d051b2d58c5ff0017c63a55da6aaaaa/checks?check_suite_id=317649432)
- [自動作成されたv0.1.0のリリース](https://github.com/nogic1008/dotnet-ci-example/releases/tag/v0.1.0)

ちなみにプログラムは1-20までFizzBuzzするだけのものです。

## 所感
- 並列テストの実行速度がMacOS > ubuntu >> Windowsといった感じ。
    - Windowsもうちょっとがんばって
- NuGetのキャッシュをするなどしてもっと速度改善したほうがいいかも
    - キャッシュを組み込んでみました。ハマったところもあったので[記事にしました](https://qiita.com/nogic1008/items/510480deef316df71850)