tech.guitarrapc.cóm

Technical updates

GitHub Actions で .NET Framework プロジェクトをビルドする

Visual Studio拡張のプロジェクトは、今でも .NET Frameworkです。 .NET Frameworkということは、基本的にWindowsでのビルドになります。

今回、AppVeyorでやっていたビルドをGitHub Actionsに移行したのでメモ。

最終的に、次のように .NET FrameworkのビルドがGitHub Actionsで完結します。

.NET Framework のビルドがGitHub Actions で成功した図

TL;DR

  • .NET FrameworkのアプリもGitHubホストランナーでWindows OSを選べば問題ない
  • Visual Studio拡張のような変なビルドでも問題ないので安心してビルドできる
  • GitHub ActionsはAppVeyorにトドメを刺しそう

リポジトリ

VS拡張で提供しているOpenUserSecretsをVS2019対応するついでにAppVeyorからGitHub Actionsに移植します。

github.com

事前知識

GitHub Actionsでの書き方など一通りの注意は公式Docみるかまとめたので参照してください。

tech.guitarrapc.com

CIサービスの変化

  • Before: AppVeyor
  • After: GitHub Actions

前提として、AppVeyor / GitHub ActionsのいずれにおいてもWindows依存のビルドはDocker Imageでのビルド実行ではなくホストマシンでの実行となります。

それぞれの定義を見ていきます。

AppVeyor の定義

OpenUserSecrets/appveyor.yml at 362cc778821bf9724176ed9439c8c4f6c84e5e8a · guitarrapc/OpenUserSecrets · GitHub

image: Visual Studio 2017
version: '1.0.{build}'
shallow_clone: false
pull_requests:
  do_not_increment_build_number: false

configuration: Release
platform: Any CPU

before_build:
 - nuget restore src/OpenUserSecrets.sln

build:
  project: src/OpenUserSecrets.sln

artifacts:
- path: '**\*.vsix'

AppVeyorは、イメージにVisual Studioなどのツールが入っているのでVisual Studio 2017Visual Studio 2019を選んでおきます。

www.appveyor.com

GitHub Actions

OpenUserSecrets/.github/workflows/build.yml at master · guitarrapc/OpenUserSecrets · GitHub

name: build

on: [push]

jobs:
  build:

    runs-on: windows-2019

    steps:
    - uses: actions/checkout@v1
    - uses: warrenbuckley/Setup-Nuget@v1
    - run: nuget restore $Env:GITHUB_WORKSPACE\src\OpenUserSecrets\OpenUserSecrets.csproj
    - uses: warrenbuckley/Setup-MSBuild@v1
    - run: MSBuild.exe $Env:GITHUB_WORKSPACE\src\OpenUserSecrets\OpenUserSecrets.csproj -p:Configuration=Release
      timeout-minutes: 5
    - uses: actions/upload-artifact@v1.0.0
      with:
          name: artifacts
          path: src\OpenUserSecrets\bin\Release\OpenUserSecrets.vsix

GitHub Actionsは、hostによってインストールされているツールが変わります。

help.github.com

help.github.com

Windows-2019はVisual StudioおよびMicrosoft.VisualStudio.Workload.VisualStudioExtensionが入っているので、このホストイメージで問題ありません。

パスを解決する

こういったCIでビルドするときに妙なはまり方をしやすいのが「PATH」です。 特にツールのパスは、「どこにインストールされたのか興味がない」のに、パスがとっていないと気にする必要があります。 そのため、こういったツールを利用するときはパスを通すのが定石です。

.NET Frameworkのビルドは、「パッケージをNuGetで復元する」「msbuildでビルド」というよくある2段階を踏んで実行されます。 この2つで使うツールを、GitHub Actionsでパス解決しつつ実行する方法を考えましょう。

nuget.exe のパス解決

NuGetのパッケージリストアはnuget.exeを使って行います。 nuget.exeのパス解決は、uses: warrenbuckley/Setup-Nuget@v1で行えるのでぜひ利用しましょう。

github.com

nuget restore csprojのパスでNuGetパッケージリストアが行えるようになりました。

MSBuild.exe のパス解決

MSBuildは通常Visual Studioを一緒に入っています。 これを解決するツールとしてvswhereがあるのですが、そんなものを使わずwarrenbuckley/Setup-MSBuildを使いましょう。パスに入れてくれます。

github.com

MSBuild.exe csprojやslnのパスでビルドが実行できるようになりました。

NuGet のパッケージリストアを行う

基本的に、現在のリポジトリのチェックアウトパスに興味ありません。 環境変数GITHUB_WORKSPACEを使うといい感じにcheckoutしたときのベースパスが解決されます。

これで、csprojのパスが$Env:GITHUB_WORKSPACE\src\OpenUserSecrets\OpenUserSecrets.csprojとわかりました。

キャッシュを考えそうですが、今回のような即座にパッケージリストアが完了する場合は考えなくてもいいでしょう。

いい感じでNuGetのパッケージリストアが実行できました。

ビルドを行う

ビルド時のcsprojパスもNuGetと同じでGITHUB_WORKSPACEを使えばokです。

また、今回はVisual Studio拡張をビルドしたら、そのビルドパッケージを配布するのでリリースビルドを行います。

MSBuild実行時時にConfigurationプロパティをReleaseに切り替えればokです。

いい感じでMSBuildが実行されました。

デフォルトのcsprojは、CIでビルドするときに次のセクションで紹介するdevenv初期化が走って限界である6 hourまでタスクがタイムアウトしません。 timeout-minute: 5は、ビルドが5分以上かかること自体、異常とみなしてタイムアウトを仕掛けています。

Visual Studio 拡張ビルド時のdevenv 初期化をスキップする

Visual Stduio拡張は、クラスライブラリなどと違って「ビルド時にVisual Stduio )devenv.exe) の初期化を行う」動きをします。 CI的にはdevenvの初期化なんてされてほしくないわけで、実際永遠に終わりません、厄介! (6 hour timeoutでビルド失敗する悲劇が起こる)

6時間タイムアウトでビルドが失敗した図

対策はいくつか考えられます。

github.com

私のオススメは、PropertyGroupでDeployExtensionをfalseにして初期化を行わないことです。 csprojに<DeployExtension Condition="'$(GITHUB_ACTIONS)' != ''">False</DeployExtension>を設定しておけばGitHub Actionsでのみ無効化されます。

DebugとReleaseビルドで無効化したいので次のようになるでしょう。

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <!-- Stop Initialize Visual Studio Experimental Instance on CI -->
    <DeployExtension Condition="'$(GITHUB_ACTIONS)' != ''">False</DeployExtension>
    <!-- 省略 -->
  </PropertyGroup>

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <!-- Stop Initialize Visual Studio Experimental Instance on CI -->
    <DeployExtension Condition="'$(GITHUB_ACTIONS)' != ''">False</DeployExtension>
    <!-- 省略 -->
  </PropertyGroup>

これはオススメしません。失敗するはずです。このPRも最終的に取り消して↑の手法に切り替わっています。

https://github.com/microsoft/azure-pipelines-image-generation/pull/828github.com

パッケージをアップロードして GitHub Webから取得できるようにする

GitHub Releaseに置くことも考えそうですが、Visual Studio拡張はMarket Placeからの配布が基本なので今回はReleaseページには載せないことにします。 となると、ビルドしたAction毎にビルド成果物である .vsixファイルがWeb上から取得できればokです。

こういった時に使えるのが、Artifactsです。 ビルドでvsixが生成されるパスは、csprojのあるパスからみてbin\Release\OpenUserSecrets.vsix とわかっているので、指定すればokです。

upload-artifactアクションは、環境変数GITHUB_WORKSPACEのパスで実行されるので、Artifactsのpathはリポジトリルートからみた指定でokです、親切!

期待通りアップロードされています。

いい感じで取得できることがわかります。

おまけ

今回のOpenUserSecretsのアップデート (1.1.0) は、この記事にあったNuGetのパッケージをインストールしてくださいメッセージへの対処やVS2019対応です。

shuhelohelo.hatenablog.com

REF

基本はこれ。パス解決の考慮がないのでCI的には取り回しが悪いので注意。

github.community