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 にトドメを刺しそう (good bye

リポジトリ

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/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