kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

カテゴリーアーカイブ: MsBuild

.NET Core 1.1 で msbuild はどうなったのか


一度は nuget統合がらみで project.json とかに移行するという形でdiscon言われた msbuild ですが、結局息を吹き返し、Visual Studio 2017 RC で .NET Core App (ASP.NET vNext 改め ASP.NET Core でのWebアプリを含む)のビルドランナーとして復活を果たしました。

この msbuild について、ビルドのカスタム手法について諸々調べてみましたので blog にまとめてみます。

デフォルトの csproj からの targets / props の読み込み

実際にプロジェクトを作って csproj を開いてもらうと解りますが、msbuild の Import による targets の取り込みがありません。過去の知識だけを頼りに調べようとすると一体何が起こってるのやらで即詰みしますね。

いつ増えたんだ!って感じですが、 msbuild に /pp:出力ファイル名 で import によるファイルインクルードの解決結果を出力するオプションがあるのでこれを使って標準ターゲットを取り込んだ XML を生成すると、その冒頭で回答が出てました。

<?xml version=”1.0″ encoding=”utf-8″?>
<!–
==========<snip>===============================================
C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2\WebApplication2.csproj
==========<snip>===============================================
–>
<Project ToolsVersion=”15.0″ DefaultTargets=”Build”>
  <!–
==========<snip>===============================================
  <Import Project=”Sdk.props” Sdk=”Microsoft.NET.Sdk.Web”>
  This import was added implicitly because of the Project element’s Sdk attribute specified “Microsoft.NET.Sdk.Web”.

C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web\Sdk\Sdk.props

==========<snip>===============================================
–>

csproj ファイルの冒頭、Sdk アトリビュートによって、Sdk.props の読み込みが行われていて、Sdk.props は C:\Program Files\dotnet\sdk\… にあるという事でした。

実際の csproj での Project 要素のSdkアトリビュートは以下のようになっています。

<Project ToolsVersion=”15.0″ Sdk=”Microsoft.NET.Sdk.Web”>

この Sdk 属性により関連 props が読み取られます。

この Sdk.props は以下のように終わります。

  <!–

==========<snip>===============================================

  </Import>

C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web\Sdk\Sdk.props

==========<snip>===============================================

==========<snip>===============================================
  </Import>

C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2\WebApplication2.csproj
==========<snip>===============================================
–>

続いて csproj での定義内容が現れ、

  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
  <PropertyGroup>
    <PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
  </PropertyGroup>
  <PropertyGroup>
    <UserSecretsId>aspnet-WebApplication2-898cd849-2e88-466a-81b4-e1d20363bffb</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include=”Microsoft.ApplicationInsights.AspNetCore” Version=”2.0.0-beta1″ />
    <PackageReference Include=”Microsoft.AspNetCore” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.AspNetCore.Authentication.Cookies” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.AspNetCore.Identity.EntityFrameworkCore” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.AspNetCore.Mvc” Version=”1.1.1″ />
    <PackageReference Include=”Microsoft.AspNetCore.StaticFiles” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.EntityFrameworkCore.Design” Version=”1.1.0″ PrivateAssets=”All” />
    <PackageReference Include=”Microsoft.EntityFrameworkCore.SqlServer” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.EntityFrameworkCore.SqlServer.Design” Version=”1.1.0″ PrivateAssets=”All” />
    <PackageReference Include=”Microsoft.EntityFrameworkCore.Tools” Version=”1.1.0-msbuild3-final” PrivateAssets=”All” />
    <PackageReference Include=”Microsoft.Extensions.Configuration.UserSecrets” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.Extensions.Logging.Debug” Version=”1.1.0″ />
    <PackageReference Include=”Microsoft.VisualStudio.Web.CodeGeneration.Design” Version=”1.1.0-msbuild3-final” PrivateAssets=”All” />
    <PackageReference Include=”Microsoft.VisualStudio.Web.BrowserLink” Version=”1.1.0″ />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include=”Microsoft.EntityFrameworkCore.Tools.DotNet” Version=”1.0.0-msbuild3-final” />
    <DotNetCliToolReference Include=”Microsoft.Extensions.SecretManager.Tools” Version=”1.0.0-msbuild3-final” />
    <DotNetCliToolReference Include=”Microsoft.VisualStudio.Web.CodeGeneration.Tools” Version=”1.0.0-msbuild3-final” />
  </ItemGroup>

csproj の Project 要素内が展開された続きが以下です。

  <!–
==========<snip>===============================================
  <Import Project=”Sdk.targets” Sdk=”Microsoft.NET.Sdk.Web”>
  This import was added implicitly because of the Project element’s Sdk attribute specified “Microsoft.NET.Sdk.Web”.

C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web\Sdk\Sdk.targets
==========<snip>===============================================
–>
  <!–
***********************************************************************************************
Sdk.targets

WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
          created a backup copy.  Incorrect changes to this file will make it
          impossible to load or build your projects from the command-line or the IDE.

Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
–>

csproj の Project 要素の末尾に Sdk.targets が暗黙に展開されるようになっています、これが Sdk アトリビュートの仕組みという事ですね。 Project 要素に Sdk アトリビュートを付けると先頭に props 、末尾にtargets が挿入される、そういう仕組みです。

SDK として配布展開される props / targets の参照方法としては非常にシンプルで良いと思います。

csproj.user はどうなった

プリプロセス結果から Import Project を拾ってみた結果は以下です。

長いですけど、結果的に csproj.user の読み込みは無くなってます。

途中赤字にしてるのがプロジェクト配下の props / targets の読み込みでビルドのカスタム化という点で言えば有望株です。

<Import Project=”Sdk.props” Sdk=”Microsoft.NET.Sdk.Web”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web\Sdk\Sdk.props

<Import Project=”$(MSBuildSdksPath)\Microsoft.NET.Sdk\Sdk\Sdk.props” Condition=”Exists(‘$(MSBuildSdksPath)\Microsoft.NET.Sdk\Sdk\Sdk.props’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.props

<Import Project=”$(MSBuildThisFileDirectory)..\build\Microsoft.NET.Sdk.props”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.props

<Import Project=”Microsoft.NET.Sdk.DefaultItems.props”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.DefaultItems.props

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.Sdk.CSharp.props” Condition=”‘$(MSBuildProjectExtension)’ == ‘.csproj'”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.CSharp.props

<Import Project=”$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\15.0\Microsoft.Common.props

<Import Project=”$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props” Condition=”‘$(ImportProjectExtensionProps)’ == ‘true’ and exists(‘$(MSBuildProjectExtensionsPath)’)”>
C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2\obj\WebApplication2.csproj.nuget.g.props

<Import Project=”$(NuGetPackageRoot)microsoft.diasymreader.native\1.4.0\build\Microsoft.DiaSymReader.Native.props” Condition=”Exists(‘$(NuGetPackageRoot)microsoft.diasymreader.native\1.4.0\build\Microsoft.DiaSymReader.Native.props’)”>
C:\Users\kazuk\.nuget\packages\microsoft.diasymreader.native\1.4.0\build\Microsoft.DiaSymReader.Native.props

<Import Project=”$(MSBuildSdksPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.props” Condition=”Exists(‘$(MSBuildSdksPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.props’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.props

<Import Project=”$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Web.ProjectSystem.props” Condition=”Exists(‘$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Web.ProjectSystem.props’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web.ProjectSystem\build\netstandard1.0\Microsoft.NET.Sdk.Web.ProjectSystem.props

<Import Project=”$(MSBuildSdksPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.props” Condition=”Exists(‘$(MSBuildSdksPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.props’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\Sdk\Sdk.props

<Import Project=”$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Publish.props” Condition=”Exists(‘$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Publish.props’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\Microsoft.NET.Sdk.Publish.props

<Import Project=”Sdk.targets” Sdk=”Microsoft.NET.Sdk.Web”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web\Sdk\Sdk.targets

<Import Project=”$(MSBuildSdksPath)\Microsoft.NET.Sdk\Sdk\Sdk.targets” Condition=”Exists(‘$(MSBuildSdksPath)\Microsoft.NET.Sdk\Sdk\Sdk.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.targets

<Import Project=”$(MSBuildThisFileDirectory)..\build\Microsoft.NET.Sdk.BeforeCommon.targets” Condition=”‘$(IsCrossTargetingBuild)’ != ‘true'”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.BeforeCommon.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.DefaultOutputPaths.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.DefaultOutputPaths.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.TargetFrameworkInference.targets” Condition=”‘$(TargetFramework)’ != ””>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.TargetFrameworkInference.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.RuntimeIdentifierInference.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.RuntimeIdentifierInference.targets

<Import Project=”$(LanguageTargets)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Microsoft.CSharp.targets

<Import Project=”$(CSharpTargetsPath)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Microsoft.CSharp.CurrentVersion.targets

<Import Project=”$(CSharpCoreTargetsPath)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Roslyn\Microsoft.CSharp.Core.targets

<Import Project=”Microsoft.Common.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Microsoft.Common.targets

<Import Project=”$(CommonTargetsPath)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Microsoft.Common.CurrentVersion.targets

<Import Project=”$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.targets\ImportAfter\*” Condition=”‘$(ImportByWildcardAfterMicrosoftCommonTargets)’ == ‘true’ and exists(‘$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.targets\ImportAfter’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\15.0\Microsoft.Common.targets\ImportAfter\Microsoft.NuGet.ImportAfter.targets

<Import Project=”$(NuGetRestoreTargets)” Condition=”Exists(‘$(NuGetRestoreTargets)’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\NuGet.targets

<Import Project=”$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.targets\ImportAfter\*” Condition=”‘$(ImportByWildcardAfterMicrosoftCommonTargets)’ == ‘true’ and exists(‘$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.targets\ImportAfter’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\15.0\Microsoft.Common.targets\ImportAfter\Microsoft.TestPlatform.ImportAfter.targets

<Import Project=”$(VSTestTargets)” Condition=”Exists(‘$(VSTestTargets)’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Microsoft.TestPlatform.targets

<Import Project=”$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.targets” Condition=”‘$(ImportProjectExtensionTargets)’ == ‘true’ and exists(‘$(MSBuildProjectExtensionsPath)’)”>
C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2\obj\WebApplication2.csproj.nuget.g.targets

<Import Project=”$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\1.1.0\build\netstandard1.0\Microsoft.Extensions.Configuration.UserSecrets.targets” Condition=”Exists(‘$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\1.1.0\build\netstandard1.0\Microsoft.Extensions.Configuration.UserSecrets.targets’)”>
C:\Users\kazuk\.nuget\packages\microsoft.extensions.configuration.usersecrets\1.1.0\build\netstandard1.0\Microsoft.Extensions.Configuration.UserSecrets.targets

<Import Project=”$(MSBuildThisFileDirectory)..\build\Microsoft.NET.Sdk.targets” Condition=”‘$(IsCrossTargetingBuild)’ != ‘true'”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.targets

<Import Project=”Microsoft.NET.Sdk.Common.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.Common.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.PackageDependencyResolution.targets” Condition=”Exists(‘$(MSBuildThisFileDirectory)Microsoft.PackageDependencyResolution.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets

<Import Project=”Microsoft.NET.Sdk.DefaultItems.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.DefaultItems.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.DisableStandardFrameworkResolution.targets” Condition=”‘$(DisableStandardFrameworkResolution)’ == ‘true'”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.DisableStandardFrameworkResolution.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.GenerateAssemblyInfo.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.GenerateAssemblyInfo.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.Publish.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Publish.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.PreserveCompilationContext.targets”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.PreserveCompilationContext.targets

<Import Project=”$(MSBuildThisFileDirectory)Microsoft.NET.Sdk.CSharp.targets” Condition=”‘$(Language)’ == ‘C#'”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.CSharp.targets

<Import Project=”$(NuGetBuildTasksPackTargets)” Condition=”Exists(‘$(NuGetBuildTasksPackTargets)’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\NuGet.Build.Tasks.Pack\build\NuGet.Build.Tasks.Pack.targets

<Import Project=”$(MSBuildSdksPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.targets” Condition=”Exists(‘$(MSBuildSdksPath)\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web.ProjectSystem\Sdk\Sdk.targets

<Import Project=”$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Web.ProjectSystem.targets” Condition=”Exists(‘$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Web.ProjectSystem.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Web.ProjectSystem\build\netstandard1.0\Microsoft.NET.Sdk.Web.ProjectSystem.targets

<Import Project=”$(MSBuildSdksPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.targets” Condition=”Exists(‘$(MSBuildSdksPath)\Microsoft.NET.Sdk.Publish\Sdk\Sdk.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\Sdk\Sdk.targets

<Import Project=”$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Publish.targets” Condition=”Exists(‘$(MSBuildThisFileDirectory)..\build\netstandard1.0\Microsoft.NET.Sdk.Publish.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\Microsoft.NET.Sdk.Publish.targets

<Import Project=”$(WebPublishProfileFile)” Condition=”Exists(‘$(WebPublishProfileFile)’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\PublishProfiles\DefaultProfile.pubxml

<Import Project=”$(_ComputeTargetsDir)Microsoft.NET.Sdk.Publish.ComputeFiles.targets” Condition=”Exists(‘$(_ComputeTargetsDir)Microsoft.NET.Sdk.Publish.ComputeFiles.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\ComputeTargets\Microsoft.NET.Sdk.Publish.ComputeFiles.targets

<Import Project=”$(_CopyTargetsDir)Microsoft.NET.Sdk.Publish.CopyFiles.targets” Condition=”Exists(‘$(_CopyTargetsDir)Microsoft.NET.Sdk.Publish.CopyFiles.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\CopyTargets\Microsoft.NET.Sdk.Publish.CopyFiles.targets

<Import Project=”$(_TransformTargetsDir)Microsoft.NET.Sdk.Publish.TransformFiles.targets” Condition=”Exists(‘$(_TransformTargetsDir)Microsoft.NET.Sdk.Publish.TransformFiles.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\TransformTargets\Microsoft.NET.Sdk.Publish.TransformFiles.targets

<Import Project=”$(_PublishTargetsDir)Microsoft.NET.Sdk.Publish.$(PublishProtocol).targets” Condition=”Exists(‘$(_PublishTargetsDir)Microsoft.NET.Sdk.Publish.$(PublishProtocol).targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\PublishTargets\Microsoft.NET.Sdk.Publish.FileSystem.targets

<Import Project=”$(_DotNetCLIToolTargetsDir)Microsoft.NET.Sdk.DotNetCLITool.targets” Condition=”Exists(‘$(_DotNetCLIToolTargetsDir)Microsoft.NET.Sdk.DotNetCLITool.targets’)”>
C:\Program Files\dotnet\sdk\1.0.0-rc3-004530\Sdks\Microsoft.NET.Sdk.Publish\build\netstandard1.0\DotNetCLIToolTargets\Microsoft.NET.Sdk.DotNetCLITool.targets

さて、有望株と言える二つに注目しましょう。

$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props と $(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.targets を読み込んでいます。

dotnet build /v:diag

での出力から、MSBuildProjectExtensionsPath は Project の obj 配下となります。

MSBuildProjectExtensionsPath = C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2\obj\

よりによって obj かよ!って事でこれは残念。NuGet から展開された props / targets を実行するのに使ってるみたいです。

ですが、それの近くに Condition がマッチせずに Import されなかった

<Import Project=”$(DirectoryBuildPropsPath)” Condition=”‘$(ImportDirectoryBuildProps)’ == ‘true’ and exists(‘$(DirectoryBuildPropsPath)’)”>

<Import Project=”$(DirectoryBuildTargetsPath)” Condition=”‘$(ImportDirectoryBuildTargets)’ == ‘true’ and exists(‘$(DirectoryBuildTargetsPath)’)” />

な Import があります。

DirectoryBuildPropsPath はプロジェクトディレクトリの Directory.Build.props、DirectoryBuildTargetsPathはプロジェクトディレクトリの Directory.Build.targets があれば読み込むようになっています。

やってみた

Directory.build.targets をプロジェクト配下に作成します。

<?xml version=”1.0″ encoding=”utf-8″ ?>
<Project>
  <Target Name=”mySampleTarget” AfterTargets=”Build” >
    <Message Text=”mySampleTarget running” Importance=”High”/>
  </Target>
</Project>

dotnet build でビルド実行した結果、定義したターゲットが実行されました。

C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2> dotnet build
Microsoft (R) Build Engine version 15.1.523.56541
Copyright (C) Microsoft Corporation. All rights reserved.   WebApplication2 -> C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2\bin\Debug\netcoreapp1.1\WebApplication2.dll   mySampleTarget running C:\Users\kazuk\Documents\Visual Studio 2017\Projects\WebApplication4\WebApplication2>

Visual Studio でビルドしてもいけるよね?

いけるみたいです。上記でのメッセージはビルドの出力ウインドウに出ました。

まとめ

遠回りはした気もしないでもないが、 Directory.build.props / Directory.build.targets をプロジェクトディレクトリに置くだけで msbuild でのビルドプロセスはカスタマイズできます。

その昔のビルドカスタマイズポイントだった csproj.user とかはどうやら無くなってます。

なんかカスタマイズとかしたのに読まれてなくない?とか一体どこのファイルがコレやってるのとかその辺を調べるのには /pp でプリプロセス結果吐かせると便利ですね。(このファイル、Import の解決した結果を完全に吐いてくれるので別環境でビルドするには便利かもしれません。)

あとは /v:diag してログ眺めれば不思議な事なんてない(震え声

NuGet で MsBuild ターゲットが展開できるのが便利すぎて全俺が(ry


というわけで、 NuGet & MsBuild なネタ。

NuGet 2.5 から MsBuild の targets と props の展開ができるようになりました。

NuGet 2.5 のリリースノート

んで何が便利かというと、ビルド時処理のカスタマイズを部品化できて、 csproj を人がいじらないでも、その部品の付け外しが NuGet パッケージのインストール/アンインストールで簡単にできるという事で、これまでは一部のビルドヲタとかビルドをカスタムすることができる人に覆いかぶさっていた作業が NuGet パッケージ入れろよとか、外せよの一言で済んでしまうという事である。

取りあえず、便利すぎて濡れたので作った物

そういうカスタムビルド処理用の NuGet パッケージを書くためのプロジェクトテンプレートを作ってみたのが NuGetCustomBuilder になる。

んで、このプロジェクトテンプレートを元に、一般的によく使いそうなビルド拡張を NuGet パッケージとしてどんどんこさえちゃうよ!ってのが OnBuild だ。

これで何がどう変わるのか

NuGet を絡めてちょっと大きな規模の開発とかをどう変える事ができるのか書いてみたい。

その昔は Visual Studio がいっぱいいっぱいになって落ちるか落ちないかまで、1つのソリューションにライブラリプロジェクトとかどんどん放り込んでいったはずだ。なんでそんなことをしたのかというと、ビルドの依存性とかのつながりがソリューションの単位で閉じていて、ビルド順序の制御とか依存関係を追いかけてやる事ってのはソリューションで閉じた単位でしか、開発環境としては面倒が見れなかったからだ。

これを複数のソリューションに簡単に分割でき、そしてさらにパッケージ間で依存関係が定義できるようになったのが NuGet のいいところでおいしい所で、それ以前はビルドイベントを使ってビルド後にアセンブリをどっかにまとめて出して、それを参照している別のソリューションでビルド前にそれを取り込むような事をやってきたはずだ。

NuGet でそれが可能になったはずでも、旧来通りにソリューション間で依存アセンブリを受渡ししていたりすることは非常に多かった。

まず、最初の要因として底辺揃えが横行する現場では新機能なんてそうそう使えるもんじゃない。ましてや、外部からのモジュールを NuGet で勝手に取り込んでしまっては困るという都合があったりするかもしれない。

その第一の障壁を乗り越えたとしても、取り込めるけど、自分たちで NuGet パッケージを作るのはあまりやられてこなかった。

NuGet パッケージを作るうえでのパッケージングや、パッケージをリポジトリに出すところの問題だ、ビルドイベントを使うと Visual Studio にとっては依存関係のトラッキングの利かないタスクが増えた=何がどう変わるか予想ができないから常にそのタスクを動かさざるを得ない=ビルド時間が延びる。ビルド時間を延ばさずにできる方法といえば、MsBuild の知識を身に着けて csproj を直接編集せざるを得なかった。

結果として、そこまでやるのは必要知識の増大でしかなく(底辺揃えの横行している現場では必要知識の増大は最大の導入障壁である)、64bit版OSにメモリ乗っければ現行路線を進んでも扱えるソリューションもメモリに大きくなるし問題ないでしょうっていうムーアの法則頼りの解決法も 64bit への移行期であることも幸いして機能しているのでパッケージシステムに頼らないでも回る所は回っているからよっぽどでない限り問題無いのだ。

さて NuGet パッケージを作ってみよう、それをローカルリポジトリに配置してみよう

簡単にクラスライブラリを NuGet パッケージにしてみよう。時代にしたがってポータブルクラスライブラリをベースに作ろう。

imageimage

パッケージマネージャーコンソールで、 Install-Package OnBuild.PackageAssemblyForNuget とするとパッケージングの準備がされる。

imageimage

ビルドすると nuspec が作られるので、プロジェクトに追加する。

image

ビルドすると Properties\AssemblyInfo.cs の記述不足が指摘されるので直す。

imageimage

Warning だけになればパッケージは作成されている。(直す直さないは任せる)

image

ご覧のとおりで csproj には直接には手を入れてないので、 MsBuild の知識はなくともこれは可能になったと言える。

続いて、 OnBuild.PublishNugetPackage を入れる。

image

Readme が表示されるので、それに従って設定する。プロジェクト名.csproj.nupublish ファイルをサンプルからコピペで作る。

imageimage

リリースモードでだけリポジトリにコピーが走るようになっているので Release に切り替えてビルドすると、NuGet リポジトリへコピーが走る。

image

ローカル NuGet リポジトリへの発行も、csproj を直接変更する必要はなく実現されたという事になる。

これでローカルなNuGet リポジトリを使って、パッケージを管理する事で共通モジュールの実装等を行う上でのネックだったビルドカスタマイズに関する手間は追放できたし、csproj を直接編集するとか素で言ったら km 単位で引かれるような事をやらせないでOK になったわけだ。

ビルドサーバクラスターという夢

「C#はビルドが早い!」って言われるけどさ、C++等と比べれば確かにそうなんだけど、規模が大きくなればそこそこ遅い。

企業向けの一連のプログラム大き目なシステムで exe を数百個ビルドするとか、共通のアセンブリに手が入ると当然に数百個の exe をビルドしなおさなければならない。

話を戻して1ソリューションに exe を一杯おかれると、ビルドの並列化とかしたくても詰む、なんで詰むかってそりゃ依存関係を追いながらビルドする単位がデカいから詰む。頑張っても Debug 版はこっちのビルドサーバ、Release 版はあっちと分けれて二つとかになって、いまどきの安いマシンを大量投入とか頑張りたくても頑張れない。

んで、ソースはソース管理に、細かいパッケージはパッケージリポジトリにって恰好になると、パッケージの依存関係を追いながら、ビルドをすることができる。んで、小分けパッケージ間での依存関係でビルドしていくなら、ソリューションの単位が小さくなって、数が増えるので並行ビルドができるようになる。

いまどきのビルドサーバーさんは TFS にしても TeamCity にしても Jenkins にしても、ビルドエージェントは必要なら複数持てて並行ビルドが可能なら並行ビルドをやってのけるので、並行度を上げれば上げただけビルドが早くなる構成に持っていくには、小分けパッケージの依存関係で管理するのがおすすめって事。

規模のデカいシステムをメンテしていて、ビルドの遅さに泣いている人はこの辺取り組んでおくんなまし。ってか取り組むような相談受けて作ってみた感なので、頑張ってくださいませ(to 該当者)。

まとめ

OnBuild にあったほうがいいんじゃない?というネタとか、 pull request くれればどんどんマージするし、自分で必要だなーって思ったネタとか入れていくつもりだったりする。プロジェクトテンプレートを元に勝手に作って放流してくれても別に構わない。

んで、csproj のカスタムで悩んでるとかあれば Issues · kazuk/OnBuild に request とかラベル作ってあるんでラベル付けて入れてくれれば、「あぁ、それ俺も使う」とかならさくっと作るかもしれんし、そうでなければ放置するかもしれん。

ちゅうわけで、ソーシャルコーディングしたい。

(まとまっているのかはよくわからない。)

Visual Studio で kindle 本をビルドする


ネタ的には msbuild と Visual Studio のカスタマイズネタでございます。

Kindle 本のソース

Kindle 本を書く上でのソースは、html と pdf, word 文書等が選べるようです。

細かい事はこっちを参照 Amazon.com: Kindle Direct Publishing: HelpType of Formats

んで、今回は Visual Studio の ASP.NET Web Application の csproj を魔改造して html から mobi 形式で kindle 本をビルドするという形にします。プロジェクトのドキュメントとか、ソリューション内に kindle 本のプロジェクト置けば電子書籍化できるという誰得な物ですね。

というわけで ASP.NET Web Application のプロジェクトを作ります。 MsBuildBook とか付けてますけど、こんな本が Amazon の Kindle ショップに並んでるはずがないよね!

目次を index.html として作ります。

imageimage

この目次はあくまでもコンテンツです、本の中身の目次だと思っても良いでしょう。そういう意味で好き勝手な事を書いて良いです。

目次情報ファイル ncx と opf の作成

とりあえず将来的には T4 とか msbuild カスタムタスクで吐かせるつもりでも一端手書きで書いておきます。

opf も書いておきましょう。

imageimage

ビルドツールの入手

Download KindleGen 2.8 から kindlegen を入手します。プロジェクト内に buildTools フォルダを作り exe を配置しました。

とりあえず opf を叩くとビルドされます。おめでとう。

ビルドされた mobi を kindle の doucments 以下に USB 接続で送り込むと実際に表示を確認する事ができます。

image  IMG_0040IMG_0041

表紙と目次をそれぞれ表示した所、ちゃんと表示できてますね。

目的は自動化だ!(いや、それ手段ですし

さて、手順は解ったという事で自動化していきましょう。

まず、 build そのものをプロジェクトのビルドでできるようにしましょう。

おもむろにプロジェクトをアンロードして 編集に入ります。とりあえず実行してたコマンドを Exec で叩きます。

  <Target Name="MobiBuild" AfterTargets="Build">
    <Exec Command="buildTools\kindlegen MSBuildBook.opf" 
          StdOutEncoding ="utf-8" 
          StdErrEncoding="utf-8"  />
  </Target>

AfterTargets で Build を指定しているので標準の build が動いた後でこのターゲットが実行されますね。

コマンドプロンプトで build したらちゃんと実行結果を得られました。プロジェクトを再読み込みし、リビルドすると、はいできたー。

imageimage

opf 書くのだるいよね

うん、だるいね。本体のHTMLをどんどん書いていけば出来上がるのが理想だね。

csproj には html の一覧あるし、これを元に出したいね。というわけで opf 作成の msbuild のカスタムタスクを書く。

using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Build.Utilities { public class GenerateOpf : Task { [Required] public ITaskItem Metadata { get; set; } [Required] public ITaskItem[] Contents { get; set; } [Required] public string OutputFile { get; set; } public override bool Execute() { var metadata = XDocument.Load(Metadata.ItemSpec); var doc = new XDocument( new XElement( "package", new XAttribute("unique-identifier", "uid"), CreateMetadataElement(metadata), CreateManifestElement(Contents), new XElement("spine", new XAttribute("toc", "toc")), new XElement("guide") )); doc.Save(OutputFile, SaveOptions.OmitDuplicateNamespaces); return true; } private static XElement CreateManifestElement(IEnumerable<ITaskItem> contents) { var manifestElement = new XElement("manifest"); manifestElement.Add( new XElement( "item", new XAttribute("id", "toc"), new XAttribute("media-type", "application/x-dtbncx+xml"), new XAttribute("href", "toc.ncx"))); foreach (var item in contents.Where( i => Path.GetExtension(i.ItemSpec) == ".html")) { var itemSpec = item.ItemSpec; Debug.Assert(itemSpec != null, "itemSpec != null"); var idValue = Path.GetFileNameWithoutExtension(itemSpec); manifestElement.Add( new XElement( "item", new XAttribute("id", idValue), new XAttribute("media-type", "text/x-oeb1-document"), new XAttribute("href", itemSpec))); } return manifestElement; } private static XElement CreateMetadataElement( XDocument metadata) { return new XElement( "metadata", CreateDcMetadataElement(metadata), CreateXMetadataElement() ); } private static XElement CreateXMetadataElement() { return new XElement( "x-metadata", new XElement( "output", new XAttribute("encoding", "utf-8"), new XAttribute("content-type", "text/x-oeb1-document")), new XElement( "EmbeddedCover", new XText("images/Cover.jpg")) ); } private static XElement CreateDcMetadataElement( XDocument metadata) { XNamespace dc = "http://purl.org/metadata/dublin_core"; XNamespace obe = "http://openebook.org/namespaces/oeb-package/1.0/"; var dcMetadataElement = new XElement( "dc-metadata", new XAttribute(XNamespace.Xmlns + "dc", dc.NamespaceName), new XAttribute(XNamespace.Xmlns + "oebpackage", obe.NamespaceName) ); foreach (var element in metadata.Element("dc-metadata").Elements()) { dcMetadataElement.Add(element); } return dcMetadataElement; } } }

これを csproj に組み込むには以下のように。

  <UsingTask 
    TaskName="GenerateOpf" 
    TaskFactory="CodeTaskFactory" 
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <Metadata ParameterType="Microsoft.Build.Framework.ITaskItem"
            Required="true" />
      <Contents ParameterType="Microsoft.Build.Framework.ITaskItem[]" 
            Required="true" />
      <OutputFile ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Xml" />
      <Reference Include="System.Xml.Linq" />
      <Code Type="Class" Language="cs"
          Source="..\Build.Utilities\GenerateOpf.cs" />
    </Task>
  </UsingTask>
  <Target Name="OpfGenerate" BeforeTargets="Build">
    <GenerateOpf 
         Metadata="Metadata.xml" 
         Contents="@(Content)" 
         OutputFile="MSBuildBook.opf" />
  </Target>

dublin core なメタデータだけ Metadata.xml に書けば、こんな opf が吐かれる。

<?xml version="1.0" encoding="utf-8"?>
<package unique-identifier="uid">
  <metadata>
    <dc-metadata xmlns:dc="http://purl.org/metadata/dublin_core" xmlns:oebpackage="http://openebook.org/namespaces/oeb-package/1.0/">
      <dc:Title>msbuild をこねる本</dc:Title>
      <dc:Language>en-us</dc:Language>
      <dc:Creator>kazuk</dc:Creator>
      <dc:Description>msbuildをこねてビルドの自動化とかを色々カスタマイズする本です</dc:Description>
      <dc:Date>26/03/2013</dc:Date>
    </dc-metadata>
    <x-metadata>
      <output encoding="utf-8" content-type="text/x-oeb1-document" />
      <EmbeddedCover>images/Cover.jpg</EmbeddedCover>
    </x-metadata>
  </metadata>
  <manifest>
    <item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" />
    <item id="index" media-type="text/x-oeb1-document" href="index.html" />
    <item id="MSBuildProjects" media-type="text/x-oeb1-document" href="MSBuildProjects.html" />
    <item id="VSAndMSBuild" media-type="text/x-oeb1-document" href="VSAndMSBuild.html" />
  </manifest>
  <spine toc="toc" />
  <guide />
</package>

html の列挙とかは csproj にある Content のリストからやってるだけだから Web アプリケーションプロジェクトに HTML ページを突っ込んで行けば自動的に opf に組み込まれるので手間がかからない。

ncx も吐いてやろうか!

似たような方法を取れば ncx も吐けます。目次の順序とかは各HTMLからキー要素をなんか抽出してあげるとかすれば良いので手間は似たようなもんでしょう。

まとめ

今日のところはこんな感じ、普通に VS でHTMLを書いてビルドすれば kindle で読める mobi を生成するプロジェクトを作る事ができました。

多分 VS Express でも同じ事はできるはずなんで、無料のExpress でHTMLシコシコ書けば普通に kindle 本を書くのはできそうです。画像とかそういうのを使ったときにどうなるとか、その辺解ったら続編を書くかもしれません。

こんな事はだれもやらねーよかもしれませんけど、VS に markdown から HTML への変換のカスタムツールとか仕込めば、markdown とかでジャカジャカ書いたものを電子書籍にするのも簡単かもしれませんね。

まとめではないこと

自分が本書いて amazon の direct publishing 出したら誰か買いますか?>買うわけないよね!

NuGet パッケージの作成とローカルリポジトリへの発行


Gistしましたって事でお知らせ。

https://gist.github.com/kazuk/5089069

何をするものですか?

ビルドすると NuGet パッケージを作り、ローカルリポジトリに発行します。

使い方

  1. ソリューションを右クリックし、「NuGetパッケージの復元の有効化」を行います。(又は NuGet.exe をソリューション配下、 .nuget ディレクトリに置いてください)
  2. ソリューションにGistから取ったファイルを突っ込んでください。
  3. csproj の末尾で Import してください
  4. 必要に応じて NuGetRepository を PropertyGroup 定義してください(定義しないと \\nugetrepos.local\NuGetRepos にパッケージを発行します)
  5. プロジェクトに nuspec を加え、ビルドアクションに NuSpec を指定します。

nuspec 作るってどうやって?

csproj のある場所で NuGet.exe spec すればいいんじゃないかな?

そのままパッケージしようとすると author とか色々設定されてないよって言われるはずだけど、文句言われた所直せばいいと思うよ。

csproj の末尾でImportって?

プロジェクトを右クリックして「プロジェクトのアンロード」、もう一回右クリックで csproj の編集すればいいよ。

ファイル末尾の </Project> の前でローカルリポジトリの場所と targets ファイル置いた場所によって設定してね。

  <PropertyGroup>
    <NuGetRepository>\\FileServer.local\NuGetRepos</NuGetRepository>
  </PropertyGroup>
  <Import Project="..\Build.Utilities\CustomBuild.targets" />

発行って Copy してるだけに見えるけど

ファイルシステムフォルダをローカルリポジトリとして使ってるならコピーだけでいいんだよ。

NuGet.org とかに出す前に色々確認して欲しいからローカルにしか出さないよ。

Metro Style Application 開発の為の Visual Studio Project Template(1)


Visual Studio 2012 RC には Metro Style Application のためのプロジェクトテンプレートが幾つか含まれています。

これらのプロジェクトテンプレートからプロジェクトを起こしてみての解説を2回ぐらい書きましたが、今回はこのプロジェクトテンプレートの実体がどのように定義されているか、その内容を確認するとともに今後の開発で使うプロジェクトテンプレートの準備などに焦点を当ててみたいと思います。

標準のプロジェクトテンプレートのインストール場所

Visual Studio 2010 RC においてプロジェクトテンプレートの標準のインストールパスは以下の通りです。(Windows 8 RP x64にインストールした場合)

C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\ProjectTemplates\CSharp\Windows Metro style

このフォルダを開くと1041フォルダ配下に実際のプロジェクトテンプレート毎にフォルダがあり、プロジェクトテンプレートの実体が格納されています。

imageimage

このファイル群をベースに、プロジェクトテンプレートを修正してもいいですし、プロジェクトテンプレートを新規に作成してもいいでしょう。

Visual Studio SDK によるプロジェクトテンプレートの作成

プロジェクトテンプレートを作成するには簡単な方法としてはある程度作りこんだプロジェクトを元に Visual Studio から [ファイル]-[テンプレートのエクスポート]があるのですが。根がマゾいので簡単な方法は使わずにプロジェクトテンプレートを一から作成する事にしましょう。

この作業を行うには Visual Studio SDK が必要になります。

Download: Visual Studio 2012 RC SDK – Microsoft Download Center – Download Details

をインストールしてください。インストールを完了すると Visual Studio からプロジェクトの作成時に機能拡張配下からプロジェクトテンプレートが作成できるようになります。

image

今回はMetro Style Application の為のクラスライブラリを単体テストプロジェクトと共に生成し、ビルドのカスタマイズ等も(比較的)簡単にできるようビルドのカスタマイズプロジェクトも設定するという形でちょっと規模の大きいプロジェクトテンプレートを作成してみます。

Metro Style Application の為のプロジェクト要件の確認

ここで既存のプロジェクトテンプレートの内容を確認しましょう。プロジェクトテンプレートの中核を担うのは vstemplate という拡張子がついたファイルで、このファイルをもとに関連ファイルが取り込まれてプロジェクトが生成されますので Metro Style Application としての基本的な設定事項を把握しなければ話が始まりませんが、歴代の vstemplate はその詳細まで解説された事は殆ど皆無じゃないかな程度に情報量が少ないので、自分達の手元にあるVSにインストールされた vstemplateを見るのを最初にするのをおすすめです。

標準のプロジェクトテンプレートの ClassLibrary の vstemplate の内容とプロジェクトテンプレートを作成する場合の vstemplate には以下の要素に違いがある事がわかります。

<TemplateGroupID>WinRT-Managed</TemplateGroupID>
<RequiredFrameworkVersion>4.5</RequiredFrameworkVersion>
<TargetPlatformName>Windows</TargetPlatformName>
<RequiredPlatformVersion>8</RequiredPlatformVersion>
<CreateInPlace>true</CreateInPlace>

実際には当然に TemplateID などは違うのですが、そりゃテンプレートが違えばちがうわなで今回はスルーします。

TargetPlatformName や RequiredPlatformVersion 等が明らかに増えていますので、これを持つ vstemplate を作ります。

Metro Style Application の為の csproj 設定

いやー増えてますねー、Any CPU, x86, x64, ARM それぞれに Debug/Release でビルドするので当然ですが。全部ビルドしようとかすると単純に時間は20~30%増しって計算にちょっとgkbr

さて、大きな変更がありますね、vs2010までの csproj では Microsoft.CSharp.targets を見ていた物が $(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets を見ています。

XAMLのコンパイルタスクが必要なのはわかるのですが、このターゲットの解決に Visual Studio Version が入るってことは本気に Visual Studio インストールしないと(SDKだけだと)ビルドもできないのかも。ビルドサーバを用意する上でこれはちょっと難点ですね。最低限のExpressでも入れとけばビルドできるといえばそうなのかもしれませんけど。(ビルドしかできないかもしれませんけどね)

細かい詳細は基本的には丸コピー以外の選択肢は殆ど無い気がします、各 Configuration 毎に最低限のビルドオプションを設定したうえでソースはCompile ItemGroupにというのが基本的なクラスライブラリプロジェクトの構造です。

次の段階として、関連するテストを収容するための単体テスト用プロジェクトもこのテンプレートに取り込むのですが、順番にやるのが良いでしょう、ここで一旦 (1) として区切りをつけようと思います。

(1) までのまとめ

とりあえず github に push しときました。

リポジトリはこちら https://github.com/kazuk/MetroAppBase

ここまでの内容のコミットはこちら https://github.com/kazuk/MetroAppBase/commit/28e89e2623d54991cf9bcb57ea69dc5d270f0a29

予習したい人の為に (2) で何をするかという事で MSDN ドキュメントの参照リンクなど

方法 : 複数プロジェクトのテンプレートを作成する

(2)ではMetroスタイルアプリケーションのテストプロジェクトについて調べた上で、このプロジェクトテンプレートに組み込む事でクラスライブラリを作成と同時に単体テストを活用した開発ができる様にしようと思います。

まとめでは無いこと

MS的には Visual Studio 2012から TFS Express を使ってねとか色々あるみたいなんですが、少なくともこれまで経験から 「TFS は肌に馴染みませんでした。」(いいなーと思うところも無いわけではないけど、VSが落ちるとか落ちるとか落ちるとかで寧ろ足を引っ張られる印象の方が強く)というわけでVS2012での自分の開発環境での採用は無い感じです。

VS2012世代で自分の目論む開発環境ですが、基本的には git / github をソース管理のメインに据えてこれをビルドするビルドサーバは TeamCity か Jenkins か検討中、現状は VS2012のUltimateを90日試用期間で使ってますが(OSがCP/RP/RTMと行く流れで再インストールになりながらそれぞれ最大90日使えればまぁお得!)、最終的には Premium 辺りに落ち着くんじゃないかと思っています。

ソース管理の基本が TFS から git/github になる事で自分の開発環境整備(包丁を研ぐ段階)の物が表に出せるし出てしまうので晒しでやっていこうかなとか。git/github に出してる物は MIT ライセンスのつもり(どこに書くんだろう)なのでお好きにパクッテって頂戴。

githubでの pull request とか貰った事ないので、なんか楽しい変更があれば pull request も遠慮なく。

個人事業主になりまして昼間は他のお仕事しながら夜のお勉強タイム確保+お子ちゃま台風上陸という私事もありまして一日一時間も勉強に取れないので遅々として進まずの可能性もなきにしもですが今後うん十年仕事する上での準備なので着実に書いていければなーという感じです。

ASP.NET MVC プロジェクトのサブシステム分割とマージ


なんとなく出来ちゃったという物のおすそわけ blog.

 

何を解決する物ですか?

ある意味 ASP.NET MVC による大規模アプリケーション開発をサポートします。

大規模というのは、サブシステム分割が必要となるレベルであり、例えば Controller が数十からそれ以上に達する可能性の高いシステムです。

このような大規模アプリケーションを作る場合、ASP.NET MVC の Area 等を利用してアプリケーション内で分割するのは一つのアプリケーションの規模の増大を招くだけであり、規模の暴力に対してのアプローチとはなりえません。

根本的にはアプリケーションをサブシステムに分割し、各個サブシステムを統合するという手順が望ましいわけです。

単純に msbuild プロジェクトファイル一個でこの分割によって分かれたアプリケーションの統合が出来ましたよって事です。

ソースと動作原理

Build.Utilities\HugeWebApplication.targets

<?xml version="1.0" encoding="utf-8" ?>
<Project 
  ToolsVersion="4.0" 
  DefaultTargets="Build" 
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <_copyContentSourceContent Include="@(Content)" Exclude="**/*.config" />
    <_copyContentsOutputFiles
      Include="@(_copyContentSourceContent->'$(CopyContentOutputPath)\%(identity)')" />
  </ItemGroup>

  <Target Name="CopyProjectContents">
    <Message Text="$(MSBuildProjectName) からコンテンツをコピーします 出力先 $(CopyContentOutputPath)" />
    <Copy SourceFiles="@(_copyContentSourceContent)"
          DestinationFiles="@(_copyContentsOutputFiles)" SkipUnchangedFiles="true" />
  </Target>

  <Target Name="MargeProjectContents" AfterTargets="BeforeBuild" >
    <MSBuild 
      Targets="CopyProjectContents" 
      Projects="%(ProjectReference.Identity)" 
      Properties="CopyContentOutputPath=$(MSBuildProjectDirectory)" />
  </Target>
</Project>

動作原理としては MargeProjectContents ターゲットが BeforeBuildの前に実行され ProjectReference で参照しているプロジェクトに対して CopyProjectContents ターゲットを実行します。

CopyProjectContents ターゲットは呼び出されたプロジェクト上で定義された Content を呼び出し元プロジェクトのディレクトリに Copy します。

結果として空のASP.NETプロジェクトで MVC プロジェクトをプロジェクト参照すれば MVC プロジェクトの Contents は空のASP.NETプロジェクトに転送されます。bin に MVC プロジェクトの出力が配置されるのはプロジェクト参照のデフォルトの挙動で、そこに対しては何も変更していませんので bin にMVCアプリケーションのアセンブリは配置されます。

使い方

ソリューション上に ASP.NET MVC アプリケーションを複数作成し、統合先となる空の ASP.NET アプリケーションを作成します。統合先プロジェクトから各 ASP.NET MVC アプリケーションをプロジェクト参照します。

各 ASP.NET MVC アプリケーション、および 統合先 ASP.NET アプリケーションの csproj の末尾に以下の一行を追加します。

  <Import Project="..\Build.Utilities\HugeWebApplication.targets" />

後はビルドするだけです。

image image

スクリーンショットではWebApplication1 が MvcApplication1 / MvcApplication4 を参照しています。

ビルド結果では WebApplication1 の配下に Views その他の Content がコピーされている事が解ります。

コピー対象としてかち合う事が見え見えの web.config 等 config 類は含ませていません。

Global.asax/.cs どうするかなーとか解決すべき課題が無いわけではありませんが、ここまでの労力が msbuild のtargetsファイル 20行ってのは上々の戦果といっても悪くないかなーと。

統合先として今回は空のASP.NET アプリケーションで紹介しましたが、ASP.NET MVC アプリケーションでも構わない=依存する基盤部の取り込みを各部でする為に応用も出来るという事も備考まで。

やってみたらコレ困るわな事があればコメントにどーぞです。

という訳で、ハッピーなクリスマスを!

BUILD ネタという事で – C# Advent Calender 12/9


//BUILD/ と思わせておいて msbuild と Visual Studio のネタ。

という訳で、「Hay U! 思い通りにビルドをコントロールできてるかい?」

うちの Blog はたびたび msbuild のネタを取り上げるという事で、生粋のビルドヲタクである事は読者の皆様にはバレバレだと思いますが、C# Advent Calender に参加して、ビルドヲタたるものこれは知っとくべきとか、こんぐらい当然なのよっていう物を世間に知らしめておきたいかなと。

msbuild の基礎の基礎(1) 処理対象の項目一覧 ItemGroup とアイテム

msbuild は処理対象の項目の一覧を ItemGroup として定義します。シンプルに csproj の中でファイルやアセンブリ参照が ItemGroup として定義されています。

VIsual Studio でクラスライブラリのプロジェクトを作り、デフォルトである Class1 に加えてClass2,Class3を作った状態の csproj には以下の2つのItemGroupが定義されています。

<ItemGroup>
<
Reference Include=System/>
<
Reference Include=System.Core/>
<
Reference Include=System.Xml.Linq/>
<
Reference Include=System.Data.DataSetExtensions/>
<
Reference Include=Microsoft.CSharp/>
<
Reference Include=System.Data/>
<
Reference Include=System.Xml/>
</
ItemGroup>
<
ItemGroup>
<
Compile Include=Class1.cs/>
<
Compile Include=Class2.cs/>
<
Compile Include=Class3.cs/>
<
Compile Include=Properties\AssemblyInfo.cs/>
</
ItemGroup>

ソースファイルは Compile Itemが作成されていて、参照設定はReference になっていますね。

この Compile/Reference は最終的に Import されている Microsoft.CSharp.Targets を介して csc タスク(C# Compiler Task)に引き渡されてビルドされるというわけ。

単純にコンパイルするソースであれば Compile で単純に足せばよいという事ですが、コンパイル出来ればビルドとか素人の考え、色々な処理の仕方に応じた分類はいくらでも増やしたいものです。

Web やってれば画像をなんらか処理したり、cssやjsをminify したり色々やる事があるでしょう、その他やっている事に応じて色々やりたいことなんて幾らでもあるでしょう。

やる事を増やすには Target / Task を新たに定義してって事になるのですが、処理対象としてはこの ItemGroup を種類で抽出して受け取る事になるので結局は種類を増やす以外は有りません。

単純に Visual Studio でテキストファイルを追加すると種別は Content として入ります。

<ItemGroup>
<
Reference Include=System/>
<
Reference Include=System.Core/>
<
Reference Include=System.Xml.Linq/>
<
Reference Include=System.Data.DataSetExtensions/>
<
Reference Include=Microsoft.CSharp/>
<
Reference Include=System.Data/>
<
Reference Include=System.Xml/>
</
ItemGroup>
<
ItemGroup>
<
Compile Include=Class1.cs/>
<
Compile Include=Class2.cs/>
<
Compile Include=Class3.cs/>
<
Compile Include=Properties\AssemblyInfo.cs/>
</
ItemGroup>
<
ItemGroup>
<
Content Include=TextFile1.txt/>
</
ItemGroup>


この状態のソリューションエクスプローラーとプロパティは以下の通り。

image

このプロパティでのビルドアクションを「なし」に変えると ItemGroup 定義は以下の様になります。

<ItemGroup>
<
None Include=TextFile1.txt/>
</
ItemGroup>

これで ItemGroup の種別が ビルドアクションで選択される事はみなさんの知識になりました。

要するに Visual Studio のファイルプロパティでビルドアクションのリストに必要な ItemGroup 種別を足すことができればテキストなりXMLなりを足してそれに必要な ItemGroup 種別を割り当てる事ができ、それに応じた処理をさせる事ができます。

ここで特殊な ItemGroup を csproj に追加します。

<ItemGroup>
<
AvailableItemName Include=TextToBitmap>
<
Visible>false</Visible>
</
AvailableItemName>
</
ItemGroup>
<
ItemGroup>
<
TextToBitmap Include=TextFile1.txt/>
</
ItemGroup>

AvailableItemName という ItemGroup にTextToBitmap をVisibleメタデータをfalse で追加しました、ここで追加したTextToBitmap を TextFile1.txt に割り当てています。このファイルのプロパティと、ビルドアクションのドロップダウン内容が以下になります。

imageimage

AvailableItemName で指定した項目がビルドアクションのドロップダウンに追加されています、他のクラスとかにも TextToBitmap が選択できるようになったのが確認できるでしょう。

これでビルドの細かい事とか理解していない非ビルドヲタの人にはビルドアクションを指定してねだけで済ませる事ができるわけです。

ItemGroup の操作についてはターゲット内での操作とか含めてこの先もチョロチョロ出てきますが基本中の基本としてはビルド作業の細かい処理に送りつけるファイルを分類する事ができるという事です。

C# Advent Calender なのに C# がここまでで一行も出てこないとか、気にしないで下さい、先の方でちょっとだけ出てきますので。

msbuild の基礎の基礎(2) ターゲット実行順序の制御

ターゲットの構造は Target の属性による入出力ファイルの指定や依存関係によるビルド処理の順序指定と、Target内に記述されたタスクでの処理の定義に分かれます。

標準で Import されている Microsoft.CSharp.Target やその中から Import される Microsoft.Common.Targets の内容を熟知すると立派なビルドヲタなわけですが必要知識が極端に上がるだけで説明の邪魔なので一端 Microsoft.CSharp.Targets の Import をコメントアウトして Build ターゲットを自作してみます。

<ItemGroup>
<
Reference Include=System/>
<
Reference Include=System.Core/>
<
Reference Include=System.Xml.Linq/>
<
Reference Include=System.Data.DataSetExtensions/>
<
Reference Include=Microsoft.CSharp/>
<
Reference Include=System.Data/>
<
Reference Include=System.Xml/>
</
ItemGroup>
<
ItemGroup>
<
Compile Include=Class1.cs/>
<
Compile Include=Class2.cs/>
<
Compile Include=Class3.cs/>
<
Compile Include=Properties\AssemblyInfo.cs/>
</
ItemGroup>
<
ItemGroup>
<
AvailableItemName Include=TextToBitmap>
<
Visible>false</Visible>
</
AvailableItemName>
</
ItemGroup>
<
ItemGroup>
<
TextToBitmap Include=TextFile1.txt/>
</
ItemGroup>
<!–
<Import Project=”$(MSBuildToolsPath)\Microsoft.CSharp.targets” /> —>
<
Target Name=Build>
<
Error Text=まだ作ってないよ/>
</
Target>

Error タスクを使ってメッセージを出して単純に失敗する Build ターゲットを作りました、結果はこの通り。

image

意図通り動いている事が確認できました。まず依存関係での処理順序の制御をしてみましょう。

<!– <Import Project=”$(MSBuildToolsPath)\Microsoft.CSharp.targets” /> —>
<
Target Name=BeforeBuild>
<
Warning Text=BeforeBuild is running/>
</
Target>
<
Target Name=BuildCore>
<
Warning Text=BuildCore is running/>
</
Target>
<
Target Name=AfterBuild>
<
Warning Text=AfterBuild is running/>
</
Target>
<
Target Name=BuildDependsOnTargets=BeforeBuild; BuildCore; AfterBuild>
<
Error Text=まだ作ってないよ/>
</
Target>

DependsOnTargets で各タスクを作成しました、実行結果は以下の通り、今回は出力ウインドウも付けています。出力ウインドウの表示は本当に実行順序設定どおりなのですが、エラー一覧は色々な並べ替えがあるので注意が必要です。エラー一覧の2番目のカラムの数値で並べ替えすると出力順序に一致します。

結果としては DependsOnTargets で指定したターゲットが指定の順番で実行され、各ターゲットの実行が完了した後にDependsOnTargets を指定したターゲットが実行される事が解ります。これでターゲットの実行順序は設定できる事が解ります。

image

msbuild 4より前ではこれが唯一のビルドターゲットの実行順序の制御方法でした、msbuild 4では AfterTargets, BeforeTargets 属性によって既存のターゲットの実行順序の指定の位置でターゲットを実行する事を指定する事ができます。

After – Before という順序で書いたのはアルファベット順などという事でなく、実行順序の関係がそうだという事です。 AfterTargets で指定したターゲットの後、BeforeTargets で指定したターゲットの前で指定を行ったターゲットが実行されます。

以下のターゲット定義を設定する事で BeforeBuild, BuildCore の間で Transform が実行されます。

<Target Name=Transform
AfterTargets=BeforeBuild
BeforeTargets=BuildCore>
<
Warning Text=Transformが割り込んでみた/>
</
Target>

image

この関係を利用すると Microsoft.CSharp.Targets や Microsoft.Common.Targets での定義済みターゲットの実行が行われる間に割り込みが可能である事が解ります。

msbuild の基礎の基礎 (3) ItemGroup の処理、一括処理と順次処理

一度 Build のみの簡単な構成に戻して Task の呼び出しの例を紹介しましょう。

<Target Name=Build>
<
Warning Text=Example1: @(Compile)/>
<
Warning Text=Example2: @(Compile->’%(filename).generated%(extension)’)/>
<
Warning Text=Example3: %(Compile.filename)/>
</
Target>

若干ややこしそうな記述になりましたがこれをビルドすると以下の出力が出ます。

warning : Example1: Class1.cs;Class2.cs;Class3.cs;Properties\AssemblyInfo.cs
warning : Example2: Class1.generated.cs;Class2.generated.cs;Class3.generated.cs;AssemblyInfo.generated.cs
warning : Example3: Class1
warning : Example3: Class2
warning : Example3: Class3
warning : Example3: AssemblyInfo

Example3 のWarning が1行に関わらず4回、各 cs について実行されている事が解ります。

このようにパラメータの与え方によってmsbuild は自動的にタスクの呼び出し方を区別し繰り返し制御を実行します。

@(項目種別) で指定した場合、項目の集合がそのまま渡されます。

@(項目種別->’射影定義’) で指定した場合、項目の集合が射影されて項目集合として渡されます。(集合の変換)

%(項目種別 .参照するメタデータ) と記述すると参照するメタデータを参照する項目定義を要素に分割して繰り返しタスクないしはターゲットに渡します。

これによりにファイルを個別に処理する方法と複数のファイルを一括してタスク処理する方法を理解した事になります。

しかし標準タスクでできる事なんてたかが知れてるぜっていうビルドヲタの人は次のステップへ進みましょう。

msbuild ビルドヲタの第一歩 msbuild inline タスク

やっと C# Advent Calender らしい内容になりますよー、C#コードで msbuild のビルド処理で使われるタスクを記述する事にします。

まずは普通の inline タスクです。

<UsingTask TaskName=SampleTask
TaskFactory=CodeTaskFactory
AssemblyFile=$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll>
<
ParameterGroup />
<
Task>
<Code Type=FragmentLanguage=cs>
<![CDATA[
Log.LogWarning(“this is inline task”);
]]>
</
Code>
</
Task>
</
UsingTask>
<
Target Name=Build>
<
SampleTask />
</
Target>

Type属性で Fragment を指定した場合 Taskクラスの派生クラスの Execute メソッドの内部を記述する事になります。上記例では TaskクラスのLogプロパティを介して Warning メッセージを出力しています。

image

簡単に言えば Execute メソッド内のコードとして有効なコードであれば基本的には何でも書けます。 メソッドの分割が必要なレベルの複雑なコードでも Func<>, Action<> デリゲートとラムダで上手くやれば何とでもなるのが C# の素晴らしさ、記述規模さえ気にしなければどうにでもなります。

タスクにパラメータが必要であれば ParameterGroup で指定すれば良く、外部アセンブリを参照する事も using による名前空間参照も指定できますので必要であれば LINQ を使う事もできますし、XML処理をXLINQでも簡単です。

ゆとる msbuild ビルドヲタ msbuild inline タスクのソースファイルオプション

ドキュメントには Source 指定ができるとしか書いてなく、例も何にもなくという酷い扱いのSource 指定でのカスタムタスク定義を紹介しましょう。

Source オプションを使う場合にはソースを収容するクラスライブラリプロジェクトを用意すると便利です、自分は半分お約束的に Build.Utilities という名前のクラスライブラリをソリューションのトップに用意しています。

最も簡単な例として単にWarningを吐くタスクから。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Utilities;

namespace Build.Utilities
{
    public class SourceBasedCustomTask : Task {
        public override bool Execute()
        {
            Log.LogWarning("this is Source based custom task");
            return true;
        }
    }
}

これを読み込む csproj 側の記述は以下の通り。

<UsingTask TaskName=SourceBasedCustomTask
TaskFactory=CodeTaskFactory
AssemblyFile=$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll>
<
ParameterGroup />
<
Task>
<
Code
Type=Class
Language=cs
Source=..\Build.Utilities\SourceBasedCustomTask.cs/>
</
Task>
</
UsingTask>

なにがゆとれているか、そうです、インテリセンスです。作成したBuild.Utilitiesプロジェクトで、Microsoft.Build.FrameworkとMicrosoft.Build.Utilities.v4.0を参照する事で普通のコードを書いているのと同様の完全な Visual Studio 支援下でカスタムタスクを書く事が出来ています。

image

Source の位置指定は該当プロジェクトファイルからの相対となりますのでソース管理にそのまま突っ込んであげれば、ビルドサーバだったり同僚のマシンでも普通に動きます。

msbuild のインラインタスクが出る前はビルドを細かく制御したいというビルドヲタクをするには一つ厄介な問題がありました、カスタムタスクアセンブリの配布です、カスタムタスクアセンブリは csproj と共に共同して開発を行う同僚やCI ビルドサーバへ配布する必要がありました。ソリューションの中にバイナリを入れてしまえば良い、それは確かにそうなのですが、そのバイナリを更新する方法や手段は手動?ビルドヲタクに手動でなんかやれって言って素直にやると思って?

これらの問題は単純にソースでタスクを渡せる事で一発解消です、ソース管理にソースを入れるという単純な事で何とでもなってしまいます。

さて、ここまでの内容をもってすれば、ビルド時に少なくともC#で書ける事であれば何でもでき、それをVisual Studioの支援下で記述できるというのは納得してもらえるでしょうか。

出来るのは解ったけど性能は?:インクリメンタルビルドのサポート

はい、ビルドでは性能重要です。ちょっと変な事するとあっというまに毎回コンパイルが走り、依存しているプロジェクトすべてが毎回ビルドされるようになってしまいます。

そうなると開発作業の大半がビルド待ちにもっていかれるわけで、ビルド時にやってくれる事の価値によってはぶっちゃけ邪魔に感じる事でしょう。

出来るのは良い、本当に必要な時だけ実行されるならもっと良いという訳で、ファイルの更新状況に応じてそれをやるかやらないか制御されなければなりません。

msbuildはもちろんインクリメンタルビルドをサポートしていて、ターゲットの Inputs / Outputs 属性でこれが制御されます。

制御ルールとしては単純で、Inputs に記述されたファイルより Outputs に記述されたファイルが新しければターゲットの実行は省略されます。

Inputs / Outputs それぞれは@(項目グループ)で ItemGroup を受ける事が出来ますので、複数のファイルの更新関係を元にターゲットのそれぞれを実行するかを制御できるという事になります。

まとめ

csproj を開いたことないとか言う人は悔い改めてちょうだい。

Multi Project な Project Template を作成する


元ネタは

How to: Create Multi-Project Templates
http://msdn.microsoft.com/en-us/library/ms185308.aspx

なんだけど、一回作ってる最中にうまくビルドできなくなったので、メモとりながら「やってみる」という blog

要 Visual Studio SDK です。

プロジェクトテンプレートのプロジェクトを作成。ソリューションエクスプローラー上での構造があうように作る。

imageimage

<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" 
            Type="ProjectGroup"
            xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>LibWithUnitTest</Name>
    <Description>&lt;No description available&gt;</Description>
    <Icon>LibWithUnitTest.ico</Icon>
    <ProjectType>CSharp</ProjectType>
    <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion>
    <SortOrder>1000</SortOrder>
    <TemplateID>526730eb-c930-4614-8398-64a21c9f008a</TemplateID>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>LibWithUnitTest</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="Implementation">
        ClassLibrary\ClassLibrary.vstemplate
      </ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="UnitTest">
        UnitTest\UnitTest.vstemplate
      </ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
</VSTemplate>

あれ、ビルドできた。(ぉぃ

んでプロジェクトの追加したら普通にできた。(ぉぃ

 

企画倒れ!

…となぜ最初のは失敗したのかなと。 .cs のファイルプロパティで、ビルドアクションは「なし」にしてないと駄目。子プロジェクト側 vstemplate のビルドアクションは「なし」にした方が良いみたい。(VSTemplate ビルドアクションになってるとその分 Project Template の zipが吐かれますね)って事で、ファイルプロパティの設定間違ってるだけでした。

imageimage

っていう感じですね。

ってここまで書いてからこんなの見つけちゃった。

Multi-Project Templates with Wizard: Visual Studio 2010 Sample

http://vsix.codeplex.com/

プロジェクト作成時の Wizard まで実装してるね!コッチでも子プロジェクトの vstemplateのビルドアクションは無しになってますね。

http://vsix.codeplex.com/SourceControl/changeset/view/61761#1354286

<ItemGroup> <None Include="Children\Windows Library\ProjectTemplate.vstemplate" /> </ItemGroup>

ほら、無し(None)でしょ。

MsBuild 4.0の新機能でビルド時コード生成とか


MsBuild 4.0 には結構良い新機能が入っております。

ここんとこツイッターでモニョモニョ言ってる MsBuild インラインタスクとか。

MsBuild ターゲット

  • RunBeforeTargets および RunAfterTargets (MSBuild 4.0)

(原文ママ、スキーマ見た感じでは BeforeTargets / AfterTargets が正しい)

これを使って TFS ビルド中にバージョンを埋めるタスクを作りましたのでおすそ分け

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build"
         xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask
      TaskName="CreateAssemblyVersionSource"
      TaskFactory="CodeTaskFactory"
      AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup>
      <Path ParameterType="System.String" Required="true" />
      <VersionAndRevision ParameterType="System.String" Required="true" />
      <BuildUri ParameterType="System.String" Required="true"  />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs">
<![CDATA[
        string buildNumber = new Uri( BuildUri ).Segments.Last();

        Log.LogMessage( "CreateAssemblyVersionSource Path: {0}",Path );
        StringBuilder sb = new StringBuilder();
        sb.AppendLine( "using System;" );
        sb.AppendLine( "using System.Reflection;" );
        sb.AppendLine( "using System.Runtime.InteropServices;" );
        sb.AppendLine( "using System.Resources;" );
        sb.AppendFormat( "[assembly: AssemblyVersion(\"{0}.{1}\")]"            ,VersionAndRevision, buildNumber ).AppendLine();
        sb.AppendFormat( "[assembly: AssemblyFileVersion(\"{0}.{1}\")]"            ,VersionAndRevision,buildNumber ).AppendLine();
        File.WriteAllText( Path, sb.ToString() );
]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="LabelAssembly" Condition="'$(BuildUri)'!=''"
          AfterTargets="PreBuildEvent" BeforeTargets="CoreCompile"  >
    <Message Text="LabelAssembly target running" />

    <CreateAssemblyVersionSource
        Path="AssemblyVersion.cs"
        VersionAndRevision="$(VersionAndRevision)"
        BuildUri="$(BuildUri)" />

    <ItemGroup>
      <Compile Include="AssemblyVersion.cs" />
    </ItemGroup>

  </Target>
</Project>

やっている事としては結構単純で、 File.WriteAllText で cs ファイルを吐きだす事の AssemblyVersion / AssemblyFileVersion アセンブリ属性を。

それを Compile ItemGroup にInclueしてるだけです。

TFSビルド時には BuildUri にビルドの識別Uriがきますので、これがある時だけターゲットを定義するように Condition しており。

AfterTargets/BeforeTargets を使って CoreCompile ターゲットの前に実行される様に設定しています。

AfterTargets/BeforeTargetsによって関連するターゲットには何も変更を入れる必要がなくターゲットを差し込む事ができるというのが素晴らしい所でございます。

<PropertyGroup>

<VersionAndRevision>1.0.0</VersionAndRevision>

</PropertyGroup>

<Import Project=”ターゲットを保存した場所”/>

をcsprojの末尾でやるとTFSでのビルド番号がバージョンに反映されます。

このインラインタスクでやってる StringBuilder での編集をT4でPreprocessorしたテンプレートによって ITask実装しておくと、ビルドシーケンス中でのコード生成はもはやお手の物って訳で、組み合わせ次第で夢が広がりますねーなお話。

MSの中の人には MsBuild 4.0 関連の情報を MSDN Library のメニューにマージして頂きたいとか…。