MSBuild の DRY

ビルドの自動化に MSBuild を使っているけど、ビルド手順がよく似た複数のプロジェクト*1があるとき、プロジェクト1つ1つにビルドファイルを用意していた。でも、タスクの内容はほとんど同じで、違うのは作業ディレクトリや Subversionリポジトリ URL くらい。

内容がほとんど同じとはいえ、プロジェクトの数は10個くらいあるので、ビルドファイルを保守するのはちょっと大変。タスクを使い回せないか考えていたら、Import を使う方法が思いついた。

まず、プロジェクトごとに異なる値を PropertyGroup で定義。共通のビルドファイルもインポートしておく。

環境定義用ビルドファイル
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="BuildAll"
         xmlns = "http://schemas.microsoft.com/developer/msbuild/2003">
    <!--プロジェクトごとに異なる環境を記述-->
    <PropertyGroup>
        <WorkingDirectoryPath>作業フォルダのパス</WorkingDirectoryPath>
        <BinDirectoryPath>アセンブリの出力先</BinDirectoryPath>
        <SolutionFilePath>VisualStudioソリューションファイルのパス</SolutionFilePath>
        <SourceRepositoryUrl>ソースコードリポジトリ</SourceRepositoryUrl>
        <BinRepositoryUrl>アセンブリのリポジトリ</BinRepositoryUrl>
        <CommonBuildFilePath>共通のビルドファイルのパス</CommonBuildFilePath>
    </PropertyGroup>
    <!--共通のビルドファイルをインポート-->
    <Import Project="$(CommonBuildFilePath)"/>
</Project>

次にインポートされるビルドファイルを作る。PropertyGroup で定義したプロパティを使ってタスクを記述していく。

共通タスク用ビルドファイル
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="BuildAll"
         xmlns = "http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <MSBuildCommunityTasksTargetFilePath>MSBuildCommunityTasks の Target ファイルの場所</MSBuildCommunityTasksTargetFilePath>
    </PropertyGroup>

    <ItemGroup>
        <OutputFiles Include="$(BinDirectoryPath)\*.dll"/>
        <OutputFiles Include="$(BinDirectoryPath)\*.xml"/>
        <OutputFiles Include="$(BinDirectoryPath)\*.config"/>
        <OutputFiles Include="$(BinDirectoryPath)\*.manifest"/>
        <OutputFiles Include="$(BinDirectoryPath)\*.exe"/>
        <OutputFiles Include="$(BinDirectoryPath)\config\*"/>
        <OutputFiles Include="$(BinDirectoryPath)\*.tt"/>
        <OutputFiles Include="$(BinDirectoryPath)\*.tpl"/>
    </ItemGroup>

    <Import Project="$(MSBuildCommunityTasksTargetFilePath)"/>

    <Target Name="BuildAll">
        <!--作業フォルダ削除-->
        <RemoveDir Directories="$(WorkingDirectoryPath)" />

        <!--リポジトリからコードとアセンブリをチェックアウト-->
        <SvnCheckout RepositoryPath="$(SourceRepositoryUrl)"
                     LocalPath="$(WorkingDirectoryPath)"/>
        <SvnCheckout RepositoryPath="$(BinRepositoryUrl)"
                     LocalPath="$(BinDirectoryPath)"/>

        <!--古いアセンブリを削除-->
        <SvnClient Command="delete"
                   Targets="@(OutputFiles)"/>
        <SvnCommit Targets="$(BinDirectoryPath)"
                   Message="古いアセンブリを削除。"/>

        <!--ビルド実行-->
        <MSBuild Projects="$(SolutionFilePath)"
                 Properties="Configuration=Release;Platform=Any CPU;"/>

        <!--アセンブリをコミット-->
        <SvnClient Command="add"
                   Targets="@(OutputFiles)"/>
        <SvnCommit Targets="$(BinDirectoryPath)"
                   Message="新しいアセンブリを追加。"/>
    </Target>

</Project>

ビルドするプロジェクトを増やすときは、既にある環境定義用ビルドファイルをコピーして、作業ディレクトリやリポジトリURLを書き変えればいい。

これでビルドファイルの保守がだいぶ楽になるな。もっと早くやればよかった。

*1:Visual Studio のプロジェクトのことじゃないよ!