tech.guitarrapc.cóm

Technical updates

VisualStudio の .csproj でビルド構成に依存せずApp.config のConfiguration Transformを行おう

Visual Studio で C#など を書いていると、データベースの接続設定などアプリケーションで利用する値を外部ファイルに外出ししたくなることが多々あります。この時利用するのが アプリケーション構成ファイル App.config です。

さてこの App.config ですが、コンソール/WPF アプリでもビルド構成によって変えたりできることは良く知られています。そう、ASP.NET MVC などWebアプリケーションで行う、web.config の Configuration Transform と同様のイメージです。

本記事では、WebアプリケーションでなくともConfiguration Transform を利用する方法について見ていきましょう。

目次

Configuration Transform を行うパターン

紹介するのは、2つの方法です。

  1. ビルド構成に応じた Configuration Transform
  2. ビルド構成に依存しない Configuration Transform

1は有名な方法なのでサクッと行きます。

実は、2をやりたいと思ったのですが見つけられなかったのです。そこで .csproj をいじってできるようにしてみました、というのがこの記事の本題。

両方とも、App.config の Configuration Transform をやりたいという目的は変わらず、やるためのトリガーが違います。それぞれ使い所を適切に選択すると便利だと思います。

ビルド構成に応じたConfiguration Transform

ググると .csproj を編集することが多いのです。が、これをGUIでさくっとやるための素晴らしいVS拡張があります。

  • SlowCheetah
  • Configuration Transform

visualstudiogallery.msdn.microsoft.com

visualstudiogallery.msdn.microsoft.com

両方とも同じことができるので、今回は Configuration Transformを VS2015 で使ってみます。

VS2015 で利用するためには

どちらのVS拡張も2013 までしか現在対応していません。

このため、ダウンロードしてインストールしようとしても VS2013 までしか入らないように見えます。

vsixmanifest をいじることで VS2015 にムリヤリインストールも可能です。が、あくまでもライセンスが問題なければという条件下です。

今回は、両方のプロジェクトともに Apache2.0 だったので問題ないと判断して紹介します。が、十分ライセンスに留意しないといけないと思います。*1

.vsixmanifestの変更

.vsixnupkg同様 Zip で固めただけなので解凍すれば中身が見えます。あとは、.vsixmanifestの対象プラットフォームをちょちょいといじるだけです。この方法は、@neuecc に教えてもらいました。ありがとうございますありがとうございます!

解凍したら、extension.vsixmanifest を開いて

SupportProduct に VS2015 を足すだけです。

gist.github.com

VS2015 は、 VisualStudio Version が 14.0 なので以下を差し込むことになります。

<VisualStudio Version="14.0">
    <Edition>Ultimate</Edition>
    <Edition>Premium</Edition>
    <Edition>Pro</Edition>
</VisualStudio>

manifestを更新したら、再び ファイルをZip で固めて拡張子を .vsix にします。

これでVisual Studio 2015 にインストールできるようになりました。

VS を再起動すれば、インストールしたVS拡張が利用できるようになります。

PowerShell で自動化してみる

PowerShell 5.0 で追加された Compress-Archive の秘孔をついてしまうので 7zip を使うか自分で Zipを書いてください。

gist.github.com

使い方

詳しい説明は、id:tanaka733 さんの素敵な記事を見るといいでしょう。ここでは記事の内容がイメージしやすいように簡単に触れます。

tech.tanaka733.net

Slow Cheetah も Configuration Transform もほぼ変わりません。ビルド構成ごとにTransform したい プロジェクトの App.config を 右クリックして、Add Transform とすれば実行時点のビルド構成に応じた App.<ビルド構成>.Config が生成されます。

SlowCheetah の例

Config Transform の実施

てきとーにコンソールアプリケーションを作って試してみます。

Configuration Transform の VS拡張をいれた後に、App.config を右クリックするとAdd Config Transform が追加されています。

ビルド構成は、標準の Debug と Release のみです。

この状態で、Add Config Tranform すると、ビルド構成に応じた App.<ビルド構成>.config が生成されました。

Config Transform の記述

早速、Configuration Transform の変換構文に従って変換を書いてみましょう。

App.config と App.Debug.config と App.Release.config はこんな感じで、hoge というキーの値をビルド構成ごとに変更してみます。

gist.github.com

変換結果のプレビュー

変換結果はビルドせずともPreview Config Transform を実行することでプレビューできます。

たとえば、App.Debug.config の変換がうまくいったかみてみると?うまく変換されましたね。

ビルドで変換を確認

早速Debug と Release ビルドを行って

結果を見てみましょう。

gist.github.com

まずはDebugビルドです。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
  <appSettings>
    <add key="hoge" value="Debug"/>
  </appSettings>
</configuration>

つぎに Releaseビルドです。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
  <appSettings>
    <add key="hoge" value="Release"/>
  </appSettings>
</configuration>

いかがでしょうか?ビルド構成に応じて app.config の内容が変わるのでテスト環境、本番環境など切り替えが容易になりますね。

ビルド構成を追加したら?

Hoge というビルド構成を追加してみます。

先ほどと同様にAdd Config Transform をすれば、App.Hoge.config が追加されます。

あとは、Config Transform を App.Hoge.config に書くだけです。

gist.github.com

.csproj にはどのような変更が加えられているのか

VS拡張がやっているのは、.csproj への変更とApp.<ビルド構成>.config のテンプレート生成だけです。

重要な.csprojの差分をみてみましょう。

これが Configuration Transform が追加した内容です。

gist.github.com

ポイントは、4行目の $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll"の参照追加です。Web.config の変換でも利用しているこれを使って、Transform構文での変換を行っています。

これらの変換はビルド成果物パスであるbinで直接行わうことはなく、中間出力パス $(IntermediateOutputPath) つまり /obj で実施されます。 /obj でapp.config を Transform構文に応じて変換して、最後に MSBuild が /obj から /bin にコピーするので変換されるので変な副作用がないのですね。なるほど。

また、Web.config はビルド時変換とデプロイ時変換の2段構えです。が、App.config の変換はビルド時変換のみなのも違いです。

単純ですが .csproj に仕込むことで意識せず自動化されるので非常に強力だと思います。

ビルド構成に依存しないConfiguration Transform

ビルド構成に応じた App.config の変換はわかりました。VS拡張もあるので簡単で便利です。

しかし、ビルド構成が複数あるプロジェクトだと困ることになるケースがあるので見てみましょう。

実際どんなシーンが困るの?

自分だけのプロジェクトで、ビルド構成を沢山作っていても問題になることはまずないでしょう。

困るのは「ビルド構成を独自に増やしているプロジェクトに外部プロジェクトをプロジェクト参照した時」です。そのため、GitHub での Submodule によるプロジェクト参照などはまさに困るケースになりかねません。

ASP.NET MVC の Web.config の場合は、変換を容易に組めるので大した問題はないのですが、コンソールアプリケーションだと 標準では Configuration Transform がないので困るのですね。

.csproj指定でのMSBuildビルド時に怒られる

私が困ったのは、ProjectA にプロジェクト参照で ProjectB/C を追加しているケースでした。

SolutionA
┣ ProjectA
┗Submodule
  ┣ ProjectB
  ┗ ProjectC

ProjectA が ビルド構成をDebug/Relase/Hoge と3つ持っています。ProjectB/ProjectC はビルド構成を Debug/Release の2つしか持っていない状況で考えてください。

Project A を Hogeビルド構成でビルドすると

  • VSでのビルドは通ります

これは、VSはソリューション単位でのビルドが可能で、ProjectAが Hoge ビルド構成の時に、ProjectB/C は Releaseビルドなど Hoge とは異なるビルド構成を使うか選択できるためです。

  • 一方で、MSbuildで .csproj を指定したHogeビルドは失敗します

gist.github.com

エラーは次の通りです。

gist.github.com

原因は The OutputPath property is not set for project 'ClassLibrary1.csproj'.です。理由は明確で、今回はClassLibrary1,2 共に、Hogeビルド構成がいため 「Hogeビルド構成にOutputPath はない」のです。

MSBuild で.csprojを指定してビルドすると、ProjectA が Hogeビルド構成の時、ProjectB/C は Release ビルドを使うという判断がつきません。これが ProjectA を Hogeビルドした時に、Project B/C で怒られる原因です。

回避方法は単純で、ProjectA に Hoge ビルド構成がある場合、ProjectB/C にも同様に Hogeビルド構成を追加して、ビルド構成を一致させれば問題ありません。

もちろん、MSBuild でもソリューションファイル.sln を指定してビルド実行すればビルドできます。しかし.sln指定では、ビルドしなくてもいいプロジェクトを含む全プロジェクトがビルドされてしまい、無駄にビルド時間が伸びることがあります。*2

そこで MSBuildでは、指定したプロジェクトだけビルドされるように「対象プロジェクトの .csprojを指定」したい、この時にビルド構成が一致しないと怒られるのです。はい。

ビルド構成を追加しなくない

ProjectB/C を他のアプリケーションでも参照している場合、ProjectA のためだけにビルド構成を追加するのは嫌ではないですか?私は嫌だったので、ビルド構成を使わずにMSBuild で App.config できる方法をさがしました。

それが、MSBuildでビルド時に独自のパラメータを渡して App.config を変換する方法です。

独自のビルド時パラメータでApp.configを変換する

VS拡張Configuration Transform がどのように App.config を変換しているかは学びました。であれば、これを応用するだけの簡単なお仕事です。

以下を、ビルド元の .csproj (サンプルなら ConsoleApplication2.csproj)に追加すればok です。

gist.github.com

.csproj を編集後、MSBuild 実施時に「Releaseビルド構成、BuildTargetにHogeを指定」を意味する、以下のパラメータでビルドします。

/p:Configuration=Release,BuildTarget=Hoge

するとビルド構成はRelaseビルドを使いつつ、 App.config は App.Hoge.config の内容に応じてビルド時コンフィグ変換されています。

gist.github.com

App.Hoge.config の内容に応じて

gist.github.com

ビルド時変換で、ConsoleApplication2.exe.config も変換されていますね。

対応できるビルドパターン

先ほどの Configuration Transform と合わせることで、次の3つのビルドパターンに対応しています。

ビルド構成 内容
VS での通常のビルド ビルド構成で App.configが App.$(Configuration).config と切り替わる
MSBuild で Configurationを指定してビルド 1 の VS と同じ挙動 (全プロジェクトに ビルド構成を用意する必要がある)
MSBuild でトリガーとなる BuildTarget を指定してビルド Release ビルドの状態で BuildTarget を指定すると、App.config が App.$(BuildTarget).config と切り替わる

この対応をいれることで、ASP.NET MVC なプロジェクト同様、MSBuild からの .csproj 指定ビルドでもビルド構成を Release のままでビルド時の App.config の Configuration Transform が可能になります。

VS 拡張

この .csproj の変更、面倒です。

嘆いていたら、id:tanaka733 さんが、上記 .csproj の変更を VS拡張でできるようにしてくれました!やったー!

tech.tanaka733.net

ぜひ利用していただけると!

まとめ

MSBuild 勉強会してほしいお。

*1:この後触れますが .csproj をいじればできることなので、VS拡張必須ではありません。

*2:MSBuild は CI での自動化処理に利用されることが多くビルド時間が短い方が望ましいでしょう。