アジョブジ星通信

日常系バンザイ。

TargetFrameworksからビルドできそうなフレームワークだけビルドする

これから1ヶ月以上ブログ書いてなかった。というわけで、これの続き。

この記事では PCL や Xamarin 用のアセンブリdotnet build でビルドできるようにしましたが、これだと MS の不自由アセンブリや Xamarin がインストールされていないとエラーになってしまうので、なんとかして警告レベルに落としたいので、やっていきます。

まずは対処前の状況。こんな感じのプロジェクトをビルドしていきます。

<TargetFrameworks>netstandard1.1;netstandard1.5;portable45-net45+win8+wpa81;monoandroid10;monotouch10;xamarinios10</TargetFrameworks>

https://github.com/CoreTweet/CoreTweet/blob/1d53cb7888eda55e681ed865538d95c0eb394da8/CoreTweet/CoreTweet.csproj

するとこう。

PCL がコケたところでビルドが止まってしまっていますね。というか並列ビルドしてくれよ。

では、 TargetFrameworks からビルドできなさそうなフレームワークを消していくコードを追加していきましょう。やり方を言葉で表すとこんな感じ。

  1. まとめてビルドするやつ(DispatchToInnerBuilds ターゲット)の実行前にフック
  2. 次の手順をすべてのフレームワークに実行(といっても netstandard や netcoreapp は絶対ビルドできるし、次の手順には失敗するので素通りさせる)
    1. GetReferenceAssemblyPaths タスクを叩いてみる
    2. 結果が空でないなら TargetFrameworks に追加する

以上を全フレームワークに実行します。

というわけで実際のコードはこちら。

<Target Name="CheckAvailableFrameworks" BeforeTargets="DispatchToInnerBuilds">
  <ItemGroup>
    <!-- _TargetFramework という名前は DispatchToInnerBuilds 内で使われているので、被らないようにアンダーバー増やした -->
    <__TargetFramework Include="$(TargetFrameworks)" />
    <!-- netcoreapp もあるならそれもちゃんと Condition に書きましょう -->
    <_OldTargetFramework Include="@(__TargetFramework)" Condition="!$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" />
    <_NewTargetFramework Include="@(__TargetFramework)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" />
  </ItemGroup>
  <!-- TargetFramework プロパティをセットして CheckCompilability を呼び出す -->
  <!-- TargetFramework プロパティがセットされていると、うまいこと TargetFrameworkMoniker プロパティがセットされるのを利用していきたい -->
  <MSBuild Projects="$(MSBuildProjectFile)"
           Targets="CheckCompilability"
           Properties="TargetFramework=%(_OldTargetFramework.Identity)">
    <!-- 結果をとりあえずアイテム化しておいて -->
    <Output TaskParameter="TargetOutputs" ItemName="_NewTargetFramework" />
  </MSBuild>
  <PropertyGroup>
    <!-- 強引にプロパティに変換する -->
    <TargetFrameworks>@(_NewTargetFramework)</TargetFrameworks>
  </PropertyGroup>
</Target>

<Target Name="CheckCompilability" Returns="$(CompilableFramework)">
  <!-- ContinueOnError="true" によってエラーを警告化 -->
  <!-- これで完全勝利 -->
  <GetReferenceAssemblyPaths TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
                             RootPath="$(TargetFrameworkRootPath)"
                             ContinueOnError="true">
    <Output TaskParameter="ReferenceAssemblyPaths" PropertyName="_ReferenceAssemblyPaths" />
  </GetReferenceAssemblyPaths>
  <PropertyGroup Condition="'$(_ReferenceAssemblyPaths)' != ''">
    <!-- 成功したときだけ Returns に追加されるってわけ -->
    <CompilableFramework>$(TargetFramework)</CompilableFramework>
  </PropertyGroup>
</Target>

これで警告になりました。めでたしめでたし。