tech.guitarrapc.cóm

Technical updates

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

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

https://msdn.microsoft.com/ja-jp/library/ms184658.aspx

https://msdn.microsoft.com/ja-jp/library/ms243192.aspx

さてこの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

https://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5

https://visualstudiogallery.msdn.microsoft.com/579d3a78-3bdd-497c-bc21-aa6e6abbc859

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

VS2015 で利用するためには

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

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

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

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

.vsixmanifestの変更

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

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

SupportProductにVS2015を足すだけです。

https://gist.github.com/guitarrapc/be22423ba42a969c2dbb

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を書いてください。

https://gist.github.com/guitarrapc/b57e907205d29899ead5

使い方

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

https://tech.tanaka733.net/entry/configuration-transform-visual-studio-extension

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すると、ビルド構成に応じたAdd Config Tranformが生成されました。

Config Transform の記述

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

https://msdn.microsoft.com/ja-jp/library/vstudio/dd465326.aspx

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

https://gist.github.com/guitarrapc/4867f1ac5b2e52e512d4

変換結果のプレビュー

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

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

ビルドで変換を確認

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

結果を見てみましょう。

https://gist.github.com/guitarrapc/1b5da3dbe5f1a26f8c85

まずは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に書くだけです。

https://gist.github.com/guitarrapc/9b9b4de78417a1e5ad88

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

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

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

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

https://gist.github.com/guitarrapc/38de1ad255cfce02a3d0

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

これらの変換はビルド成果物パスであるbinで直接行わうことはなく、中間出力パスbinつまりbinで実施されます。 binでapp.configをTransform構文に応じて変換して、最後にMSBuildがbinから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はHogeビルドなどHogeとは異なるビルド構成を使うか選択できるためです。

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

https://gist.github.com/guitarrapc/0f93ceb58fb43c4736d8

エラーは次の通りです。

https://gist.github.com/guitarrapc/edc607706e40eea15317

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

MSBuildで.csprojを指定してビルドすると、ProjectAが.csprojビルド構成の時、ProjectB/Cは.csprojビルドを使うという判断がつきません。これが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です。

https://gist.github.com/guitarrapc/4d905512734374864112

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

/p:Configuration=Release,BuildTarget=Hoge

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

https://gist.github.com/guitarrapc/ff3dd112642f12795b8a

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

https://gist.github.com/guitarrapc/9b9b4de78417a1e5ad88

ビルド時変換で、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拡張でできるようにしてくれました! やったー!

https://tech.tanaka733.net/entry/release-CSProjUtil

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

まとめ

MSBuild勉強会してほしいお。

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

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