kazuk は null に触れてしまった

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

.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 してログ眺めれば不思議な事なんてない(震え声

Vivado HLS で Windows 版バイナリは出ているが


 

C Simulation が使えないので、割と即詰むようだ。

パイプライン化を試そうと #pragma HLS PIPELINE を一行入れたら WARNING: [RTGEN 206-101] がワラワラと

Please use C simulation to confirm this function argument can be read from or written to.

って言われたので C Simulation 動かしたら、WARNING: [SIM 51] HLS only supports CLANG compiler in Linux.

なので、開発環境として Windows は使えないようです。

 

ま、ありがちな事ではありますが、 HLS に続くBitstream生成で割と大量なメモリを要求する事は目に見えているので仮想マシンで用意するのもホストOS分つらくなるし幸いに使用頻度の低いマシンがあるので Linux を物理インスタンスに導入しようそうしようといった感じ。

Vivado HLS での高位合成を業務系バッチに適用できるか試してみる


 

Amazon の FPGA F1 インスタンスでは、Xilinx の Vivado および Vivado HLS で設計を行う事になる。

Vivado が Velilog 等 RTL でのハードウェア記述で FPGA へ投入するBitstreamを生成するのに対して、Vivado HLS が C / C++ でのロジック記述から高位合成を行うという事でソフトウェアエンジニアとしては多少記述の抽象度が低いとは言え C /C++ で記述できるのはうれしいことだ。

Vivado HLS についての記述例やサンプルを色々眺めてみても bitmap を扱う例だったり業務データの扱いによくあるレコード的な物が無かったりしたので、とりあえず構造体使えるの?程度に試してみたのが以下。

#include <hls_stream.h>

struct OrderLine
{
    int itemId;
    int amount;
    int unitPrice;
};

long foo( hls::stream<OrderLine> indata, int count)
{
    long totalPrice =0;

    for( int i=0;i<count;i+=4 )
    {
        OrderLine line0;
        indata.read(line0);
        totalPrice += line0.unitPrice * line0.amount;
    }
    return totalPrice;
}

よくあるっちゃよくある、注文明細行なめて、単価x個数を集計するようなバッチ処理。

image

3クロックごとに1行を処理するロジックに落ちたみたい。

hls の stream を使ったので、struct の各要素の各ビットを入れる為の pin があるハードウェアが作られた!(ちゃんと構造体がパラメータとして使えてる!)

image

メモリにマップしてそれを読み込んで処理して回るロジックにしてみたりとかは、また今度ですが一応とっかかりとしては普通にCで構造体とか作ってレコード定義してそれ処理して回るハードウェアアクセラレータは普通に作れるようだってところ。

AWS から FPGA 搭載 IaaS


 

Amazon EC2 F1 Instances (Preview)

https://aws.amazon.com/jp/ec2/instance-types/f1/

いやぁ、Azure が先行するかと思ってたのですが、Amazon の方が先に使える事になるんですかね。

という訳で、自分の中で想定してた諸々と想定して無かった諸々を書いてみたいと思います。

ネットワークトラフィックの前さばきには使えない

https://aws.amazon.com/jp/blogs/aws/developer-preview-ec2-instances-f1-with-programmable-hardware/

The New F1 Instance の構成要素を見ると PICe x16 で CPU と接続されている以外は 288bit-wide な 64GiB DDR4 ECC メモリが接続されているとなっていて、それ以外のハードウェア接続は無いようです。

なので GPGPU ソリューションで問題になりがちだった NIC が遠いという問題は依然として存在します。ネットワーク入力に対してのレイテンシ問題に対して CPU で応答処理するのに比べて PCIe で FPGA 側へ転送、FPGA側での処理後 PCIe で CPU 側に転送するわけでオーバーヘッドは大きいです。

メモリの実効帯域幅はこの説明だけでは解りませんが一般的なPCにおけるCPUとの帯域幅の数倍あるように思えます。DDR4のクロックは解らないですが 288bit-wide という事は DDR4 SDRAM DIMM が概ね 64bit-wide(ECC 72bit-wide) なのを 4 並列ですのでちゃんと使い切れば GPU ボード内でのメモリ帯域幅に匹敵する帯域があるように見えます。容量的にも64GiB あるので広帯域+大容量で、すげぇってレベルです。

ハイエンドFPGA UltraScale+ VU9P を搭載

スペックは以下参照

http://www.xilinx.com/support/documentation/selection-guides/ultrascale-plus-fpga-product-selection-guide.pdf

その気になれば 100G Eth 9ch とか Interlaken 150G 9ch とか繋がる化け物(前述のとおり、NIC に FPGA 繋がってないのでもったいない)、DSP ユニット 6840個とか Peak INT8 DSP が 21.3 テラ ops とかDSP側を見ても余裕石です。

開発環境の AMI 提供

これがぶっ飛びで、FPGA の開発環境って、ソフトのライセンスで 3000ドルだ4000ドルだとかするんですが、インストール済みの AMI が提供されるのでインスタンスを起動するだけ、インスタンスのお値段次第ですが 30万円~50万円の開発環境出費は覚悟せずに始められるという事になります。(Azure – Altera – Intel が来るだろうと、Altera に40万円、開発ボードに40万ぐらい、この夏に貢いだ俺まじ涙目です)

いい土俵だ

前述のとおり、NIC に繋がってない FPGA なので、条件はさほど GPU と変わりません、広帯域+大容量のメモリがあるという点でも変わりません。ガッチガチにFPGAとGPUで真っ向勝負するなら「いい土俵だ」ってぐらいに似た条件です。乗せれるアプリの傾向も割と似た傾向になるかもしれません。整数演算が多いなら FPGA が向く、半精度ないし単精度の浮動小数点演算が多いならば GPU が向くとか、GPUでは if が弱いが FPGA は余り影響を受けないとかその辺も出てくるかもしれません。

GPU でもFPGAでもロジックの実装に OpenCL を使っているならコンパイル環境の違いだけでしかないかもしれません。結果早かった方使えばいいじゃないかっていう乱暴な選択ができるかもしれませんね。

今出てる断片的な情報を集めた結果ですがFPGAならではの高速トランシーバー活かしたネットワークパケット処理とか小回りが利きそうな所は割と使えないので残念、逆に GPU並みに簡単に始められる、懐痛まずに始められるのはすごく良い、どっちが良いかは解らない程度に条件はイーブンに見えます。

FPGA 再入門してみた(1)


Intel が Xeon に FPGA を統合する話ってのが去年の今頃出てたのを受けて、「データーセンター向けだ」と言ってるIntelの言い分は「聞いてないふり」して、市場に出てきたら相手できるようにという事でFPGAでハードウェアロジックを作る方に「再」入門してみています。

広義のプログラミングロジックは PLD/PAL とか触ったことがあったような気もするレベルの過去なので完全な初心者のような気もします。

 

開発環境

OpenCL でさっくり高位合成とか行きたかったですが、予算的に手の出る開発ボードだと「やっすい」ので「しょっぱい」具体的には FPGA の LE 数やIOの為のトランシーバー数が少ないので絶対的にできる事が少ない。LE数やトランシーバー数考えて何ができるものって線から手が出る物選ぶとちょっと世代が前の物なので OpenCL とかが使えない。

結局安いけど OpenCL で高位合成でさっくりかける物と、OpenCL サポートされてないけどそこそこ機能性はある物を組み合わせて勉強って感じです。

完全な初心者としては割と贅沢な環境ですが以下。

  • PC: Windows 10 Pro (Core i7 / RMA 32GB / SSD / HDD )
  • FPGA 開発ボード Terasic DE4-230 (OpenCL非対応) / Terasic DE1-SoC Board (OpenCL 対応)
  • 開発ソフト Quartus Prime 16.0

FPGA ボードの DE4-230 が約30万円、Quartus Prime が年間15万円ぐらいという事で普通に PC のソフト開発を始めるのに比べればコストはまぁかかります。

Visual Studio 2015 の Community Edition がタダとかに比べればハードルたけぇなオイです。

 

なんでFPGA? GPGPU でいいんじゃないの?

GPGPUでいける事やるんならそれでいいんです。単純に言えば、GPU ボードに NIC ポートが乗ってて、LANケーブル刺せるならGPGPUでも良いかもしれません。あと SATA コネクタついてて SSD を GPU に直結できるとうれしいですね。そんなもん無いけど。

GPU を使った SSL アクセラレーションとかなんで流行らなかったのかの話と一緒なのですが、NIC側から何か要求が入ってきて、この要求をGPUに処理させようとすると NIC から OSに割り込みかかって OS がカーネルモードでNICからの入力データをユーザー空間にコピー、それをGPU に渡すと OS に組み込まれたドライバが PCI-e 経由で GPU 側のメモリに DMA、GPUでそれを処理、戻りも似たり寄ったりの面倒くささで NIC まで応答を流すわけです。結局 GPU とNICの間の転送に数マイクロ秒~数ミリ秒は普通にかかりGPU側での処理がCPUの数十倍早くても転送に食われる時間考えるとGPUでの処理がデメリットになる事はあります。

対する FPGA は NIC や DRAM に対して直接のインターフェースをもっていますので、NIC から入ってきたデータを OS やCPUを介さずに直接処理して応答を直接NICに投げ返す事ができる。NICを通して流れるパケットそのままが見えるので、受信が完了する前にパケットに書かれてる事で処理して応答を送り始めるとかできちゃうので普通にソフト処理してる時の計測基準をそのまま使うと応答時間がマイナスになる事もある。(要求転送の終わり時刻と応答転送の開始時刻の差を応答時間としてますよね普通だと)

要するにGPGPU はスループット志向で大量データの一括処理、FPGAはレイテンシ志向で入ってきたデータの即時処理、それぞれ向き不向きがあって使い分けるべき素材なのです。

もう2年前の記事だったりしますけど、10GbE 来たら普通のNIC に普通のOSではパケット小さいと死ねます。 http://codezine.jp/article/detail/7631

そこから2年、CPU や NIC 、OS 何が進化したかというと「何も変わってねぇ」感はあります。 CPU のシングルスレッド性能はすでに5年前から頭打ちしてます、NICからの割り込み処理はシングルスレッド性能の影響下にありますので最新鋭のメニーコア Xeon を投入したところで効き目は限定的です。NICは10GbE 対応NICは安くなりましたがオフロードできるものの選択肢は増えてません。OSは Windows だと 2012 R2 がまだ現役で当時と変わりません。

んで、オフロードNICのエンジンチップがですね、 FPGA だったり FPGA ベースで開発された ASIC だったりして10GbE にまともに対応できているプログラマブルな何かというと FPGA ぐらいしか現状では選択肢が無いので FPGA に行ってみたって形ですね。

GPGPUも自分はやってないわけじゃないけど、GPGPUでのスループット志向とは違うレイテンシ重視な方が元々通信系のエンジニアとしてはやりたい領域って事で。

 

日本人すくねぇ

FPGA-NICで10GbEとか割とまだ誰も動いてない感あって、日本人少ないし日本語情報とか割と無い感じなので、だれかやってるー?お友達なろうぜー?的な blog でした。

 

async/await はTaskには依存してないです


(追記)mataliloさんに await は依存してないけど async はっ?てツイッターで指摘されて気づきました。async は依存してます。メソッドの内容を Task に包みます。async はそういう意味で現状存在するTaskという選択肢にデフォルトで依存しています。単純に試した範囲内で同一名前空間、同名のクラスに+いくらかのメソッドというレベルの実装では置き換えできませんでした。すいません。(/追記)

 

F#の立ち位置 – 猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 – haskell

を読んで、色々とちょっと待ってよ感があったので書いてみます。

C#は色々詰め込みすぎってのはまぁそう。色々詰め込みすぎた結果として直行性を失ってる部分もある事はある。

ラムダの中で yield できないとかそういうのは確かに直行性を失ってる。catch ブロック内で async 使えないとかもその通り。

んでもね、async/await が Task にべったりくっついてて剥がしようがないってのは間違った話なんでそれを元に話が膨らんでいくのはちょっと待てなわけです。

 

さて、async/await を適用する場合の要件とかは以下の記事で書いてある通りです。自分が読む限りで間違いはありません。

連載:C# 5.0&VB 11.0新機能「async/await非同期メソッド」入門(最終回):第3回 非同期メソッドの内部実装とAwaitableパターンの独自実装 (2/2) – @IT

bleis さんは 並列/並行基礎勉強会でasync/awaitをDisってきた – ぐるぐる~ で「現状の async/await が Task と結びついているのは、Awaitable パターンが要求するシグネチャに戻り値の型に対する規定がないことから来ています。 つまり、Awaitable パターンを実装する Awaitable/Awaiter に「Task」が出てこないのに、裏で勝手に「Task」にラップされてしまうのです。」と書いてますが、そんな事は起こりません、やられていません。

順を追って確認しましょう。

メソッドの戻り値型であるTaskは Awaitable として返された物で Awaitable の利用方法である Awaiter の取得に答えます。要するにメソッドの戻り値として素直に戻り何もラップ等はされません。TaskはAwaitableとして利用するのに必要十分なメソッド(GetAwaiter)を実装していますのでそのようなラップ操作も必要ありません。 (Task<TResult>.GetAwaiter Method)

Task を返しているのはメソッドその物で、C#は GetAwaiter およびその後のAwaiterに言語仕様でのawaiterを満たすメンバが存在する為 await が適用可能であると判定しているだけです。

さて、await が適用されるとC#は Task の GetAwaiter を呼びだすコードを生成します。TaskのGetAwaiter はランタイムライブラリの TaskAwaiter<T> (System.Runtime.CompilerServices namespace)を返します、TaskAwaiter は Task を Awaiter としてラップする為のメソッドを実装しています。 .NET Framework ランタイムライブラリが Task を Awaiter としてTaskAwaiter でラップしているのであり Task でラップされているのではありません。そしてC#は TaskAwaiter を言語仕様で決めた通りに Awaiter として利用しているだけです。

C#言語はもう限界かもといってる文脈で出てくる事柄が何を何がラップするのかのされるの係り受けが違うし、C#言語でなくランタイムが主体だし、勝手にと言ってる事がメソッドシグネチャ通りにTaskAwaiter<T>を返したりって事で、問題の一文はぶっちゃけマルっと違います。

 

そして、TPL が C# での async/await に便宜を計ったのは Task のGetAwaiter ただ一点これだけで async/await の使い方でよく出てくる ContinueWith その他は async/await 登場前の C# 4.0 / .NET 4.0 から存在します。(Task クラス (System.Threading.Tasks) – .NET Framework 4.0) 、async/await の為にランタイムライブラリに追加されたのは TaskAwaiter であり Task を Awaiter として扱う為にラップします。

このように TPL とランタイムライブラリの間も過度に依存しないように実装されており、TPLの寿命という問題でC#言語の言語仕様そのものは揺るがないでしょう(ユーザーコードはいっぺんにTPLと共に寿命を迎えてしまう事になりますが)。C#の言語仕様そのものは TPL には全く依存性は無く、TPLとランタイムの間は C# 言語仕様での async/await のサポートの為に必要最小限に近い良く設計された依存性を持っていると思います。

 

主語がC#言語なのかランタイムなのか、TaskでラップするのかTaskがAwaiterにラップされるのか裏で勝手になのかシグネチャ通りやんなのか、数点の違いですが違うものは違いますという事で、盛り上がってる人達には落ち着け落ち着けと。んで、話題上で関連するランタイムとTPLがべったりくっついてて剥がしようがないとかもなく、良く設計された依存性でつながっている様に自分には見えますって事で、妄想をマジ受けし過ぎなんじゃないでしょうか。

んでC#は直行性とか一貫性を失ってる部分も無いわけじゃないけど、C#がダメだの限界だとかの話は根っこの理解違ってての話が広まるにも程があるだろ程度には看過しがたい状態かなと

.NET 基礎勉強会で IL の話をしてきました


資料現物とかは以下

PowerPoint のファイル http://sdrv.ms/12Gh11k

ソースとかはこちら https://github.com/kazuk/SimpleILer/

 

比較的簡単(当社比)なコードで、 MSIL を逆アセンブル、制御フロー解析、データフロー解析をやっています。

コアロジックは Msil クラス に実装されていて、上から順に ILInstructions で命令の切り出し、GetRuns で基本ブロックに分解、PopulateControlFlowPath で制御フローパスの列挙、 SimulateEvalStack で評価スタックのシミュレーションをやり、結果として各 IL 命令のデータ依存グラフが得られます。

基本ブロック

簡単に言えば、制御構造単位での命令列です。基本ブロックでは末尾を除いて制御フロー命令が無いようになっています。また、基本ブロックの途中には飛び込まない(制御フローが飛び込む場所は基本ブロックの先頭になる)ように切り出しされます。

[制御フロー命令ではない一連の命令] optional [制御フロー命令]

制御フロー命令が無い基本ブロックの末尾には、次のブロックへの無条件分岐が暗黙に存在すると考えても一緒です。

基本ブロックを導入する事で、制御フローの列挙が非常に簡単になります。各基本ブロック内には制御フロー命令は存在しませんから基本ブロック内の命令を完全取り去って nop にしてしまっても、トポロジー的な意味でのメソッド内での制御構造は変化しません。逆にMSIL を吐き出したく、その中である程度複雑な制御トポロジーを扱いたい(=コンパイラを書きたい)なら複数の基本ブロックを作り上げてその基本ブロックを出力ILバイト列にどういう順序で出力するか、基本ブロックを接続する制御フロー命令は何を使えば良いかという順序で考える事になります。

動作フローのパス列挙

基本ブロックに分解された結果の基本ブロックの末尾命令が示すブロック間の接続を Queue を使った幅優先探索をやっています。

例外とかで飛び出す、飛び込む物を認識したい場合にはもうちょっと細工が要りますが要点は一緒です。

単純に無条件分岐なら分岐先基本ブロックが次の探索対象、条件分岐であれば条件成立時と非成立時の両方を探索するようにキューすることになります。当然に多分岐なら多分岐のすべての分岐先を探索します。

評価スタックのシミュレーションとデータフロー解析

今回の実装では評価スタックの要素型は意識していません。何個のデータを Push するかによって命令オフセットをスタックに積み、何個のデータを Pop するかにしたがって、スタックに積まれた命令オフセットを取り込んで、どの命令が積んだデータをどの命令が受け取るかという事だけを求めています。命令オフセットだけあれば再度 IL バイト列を見てあげれば実際の操作もわかるし、そのスタック要素の型も求まりますのでそこのコストが低ければ解析結果に入れる必要とか無いんじゃないのって事です。

デコンパイルやクロスプラットフォーム変換

この関係性を元に “データを受け取る IL 命令 ( データを出したIL命令 , … )” の形式で出力された構造がデモの最後に表示された変な式構造です。 IL 命令が add だったら + を二項演算子の中置記法でだすとかの判定と変換をしつこくやっていけば普通の言語での式になるでしょう。表示せずに式の木構造を何かに出せばもっとなんかできるかもしれません。(.NETの式木のノードを出力すれば式木になるでしょう) LLVM Instruction で出せばLLVMバックエンドでコンパイルできるはずでしょう。

この辺はやりたきゃどうぞの世界です。

まとめ

こんな基礎の上に .NET が乗っかってるんだって話で MSIL 読もうぜ、コードで!って話でした。

自分的には制約ソルバーとか証明って方に進んでいきたいんだけど、それをするには僕の人生ではまだ時間が足りていない感じで妄想段階を出ていませんです。

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 とかラベル作ってあるんでラベル付けて入れてくれれば、「あぁ、それ俺も使う」とかならさくっと作るかもしれんし、そうでなければ放置するかもしれん。

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

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

Doc of code 販売開始のお知らせ


 

滅多にやらない宣伝記事が増えまして申し訳ありませぬ。

というわけで Doc<Code> については最後の宣伝記事になるでしょう。

販売サイトがオープンしましたので、「買ってくれ!」以上のこの記事には意味はございません。

http://kazuk.azurewebsites.net/

 

んで、今月中はキャンペーンを設定します。

日本時間での今月中にライセンスを購入いただくと、ライセンスの付与期間が倍でライセンスキーが発行されます。

1年ライセンスを買っていただくと 2年後に無効になるライセンスキーが送られるという事になります。

販売初期限定という事で、どうかよろしくお願いいたします。

 

会社等での購入をお考えの方は kazuk.dll@kazuk.jp にご連絡ください。見積書、納品書による対応のほか、銀行振り込みでの入金その他は現状メールでの対応とさせて頂きます。

普通の人は全く知らないでもいい MSIL の基礎知識


 

.NET基礎勉強会 http://connpass.com/event/2441/ で ILについてお話させていただく事になったんですが、まぁ 30分枠ぐらいだと、だいぶ話せる事が限られるんで、あらかじめ Blog に記事乗せといた方が良いかなー的に書いておきます。

 

「手元にございますMSIL命令表をご覧ください」 「えっ、どこよ?」

.NET Framework がインストールされている環境であれば、MSIL命令表は入っています。mscorlibアセンブリ、 System.Refrection.Emit 名前空間配下の OpCodes クラスのOpCode型フィールドをリフレクションで舐めてください。

命令表としての活用の仕方にもよりますが、プレフィックスバイト等も命令表には入っています (Prefix1 etc)、自分の用途でプレフィックスとか要らない場合には、そういう物をフィルタしてあげましょう。 OpCode の OpCodeType を見ればそれがプレフィックスなのか判別する事ができます。

MSIL バイト列の見かた

さて、MSILバイト列の見かたです。バイト列そのものは実行時にリフレクションで取るならば、 MethodInfo から MethodBody を引き、MethodBody から GetILAsByteArray メソッドで取得する事ができます。

このバイト列に各 IL 命令がどのように入っているかです。

オペコード

まず、IL オペコードには 1バイトと 2バイトがあります。

命令のサイズは OpCodes を舐めているならば、OpCode の Size プロパティから取得できます。 これが 1の物は 1バイト命令で、2の物は2バイト命令です。

オペコードの値そのものは OpCode 構造体の Value プロパティに入っています。

2バイトのオペコードはMSILバイト列にビッグエンディアンのバイト順で入ります。要するに 0xDEAD の Value を持つ命令のオペコードが 0xDE 0xAD の順でILバイト列に入ります。(1バイトのプレフィックス命令と、1バイト命令がつながって入っているともいえます。) ※ MSILバイト列で唯一のビッグエンディアンです、他はすべてリトルエンディアンになっています。

1バイトオペコード命令

OpCode オペランド(無い場合もある)

2バイトオペコード命令

Prefix
OpCode
OpCode
(LowByte)
オペランド(無い場合もある)

オペランド

オペランドはオペコードによりますが、 OpCode 構造体の OperandType で取得できます。

OperandType の switch だけが特殊ですが、それ以外は単純にオペランドのサイズは固定されています。そのバイト数分をリトルエンディアンとして読みだせばオペランド値として使える値を普通に取得する事ができます。

OperandType が switch の場合、switch のオペランドにはラベル数が入ります、ラベル数分 InlineBrTarget がバイト列に入ります。

ブランチオフセットの起点

ブランチオフセットの起点はブランチ命令の次の命令です。

たとえば IL Offset n に ShortInlineBrTarget で 3 を指定する1バイトのブランチ命令があった場合、 n +1 (opcode size ) +1 (operand size) がブランチオフセットの起点となり、n +1 (opcode size ) +1 (operand size) + 3(operand value) がブランチ先のIL Offset になります。

ILの動作フローを変える物はブランチ命令と、ret、throwとそれに関連する例外処理ぐらいしかありません。ブランチ命令のオフセットを解釈できるようになった今あなたはILのコントロールフロー解析ができるようになったという事です。

まとめ

7/20 日に話す事はここから後の話ですって事で、MSILの基礎の基礎でした。

7/20 日にはMSILの評価スタックと例外フレームについて話したいと思っているのですが、時間枠的に評価スタックの話で一杯かもしれません。