Visual Studio ビルド処理を拡張する

Visual Studio のビルド処理は、プロジェクト ファイルにインポートされる一連の MSBuild .targets ファイルによって定義されます。 通常、Visual Studio プロジェクトとして SDK を使用する場合、これらは暗黙的にインポートされます。 このインポートされるファイルの 1 つである Microsoft.Common.targets を拡張することで、ビルド処理の複数のポイントでカスタム タスクを実行できます。 この記事では、Visual Studio のビルド処理を拡張するために使用できる 3 つの方法について説明します。

  • カスタム ターゲットを作成し、BeforeTargetsAfterTargets 属性を使用して、実行するタイミングを指定する。

  • 共通のターゲットで定義されている DependsOn プロパティをオーバーライドする。

  • 共通のターゲット (Microsoft.Common.targets またはインポートされるファイル) で定義されている特定の事前定義済みターゲットをオーバーライドする。

AfterTargets と BeforeTargets

カスタム ターゲットで AfterTargetsBeforeTargets 属性を使用して、実行するタイミングを指定できます。

次の例からは、出力ファイルで何らかの作業を行うカスタム ターゲットを追加する目的で AfterTargets 属性を使用する方法がわかります。 この場合、CustomOutput という新しいフォルダーにカスタム ファイルがコピーされます。 この例からはまた、カスタムのビルド操作によって作成されたファイルを消去する方法も確認できます (ターゲットは CustomClean)。具体的には、BeforeTargets 属性を使用する、CoreClean ターゲットの前にカスタムの消去作業を実行するように指定するという方法が採られています。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild" AfterTargets="Build">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Text="_FilesToCopy: @(_FilesToCopy)" Importance="high"/>

    <Message Text="DestFiles:
        @(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles=
          "@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean" BeforeTargets="CoreClean">
    <Message Text="Inside Custom Clean" Importance="high"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files='@(_CustomFilesToDelete)'/>
  </Target>
</Project>

警告

事前定義済みのターゲットとは異なる名前を必ず使用してください (例えば、ここではカスタムのビルド ターゲットに AfterBuild ではなく CustomAfterBuild と名前を付けました)。これは、そのような事前定義済みターゲットは、それらの定義もする SDK インポートによってオーバーライドされるためです。 定義済みターゲットのリストについては、この記事の最後にあるを参照してください。

DependsOn プロパティを拡張する

ビルド プロセスを拡張するもう 1 つの方法は、DependsOn プロパティ (例えば、BuildDependsOn) を使用して、標準ターゲットの前に実行する必要があるターゲットを指定することです。

このメソッドは、事前定義済みターゲットをオーバーライドする場合に適しています。これについては、次のセクションで説明します。 事前定義済みターゲットのオーバーライドはまだサポートされていますが古い方法です。しかし、MSBuild ではターゲットの定義が順番に評価されるため、プロジェクトをインポートする別のプロジェクトが、既にオーバーライドしているターゲットをオーバーライドすることを防ぐことはできません。 そのため、たとえば、プロジェクト ファイルに定義されている最後の AfterBuild ターゲットが、その他すべてのプロジェクトがインポートされた後に、ビルド中に使用されるターゲットになります。

共通のターゲット全体で DependsOnTargets 属性で使用される DependsOn プロパティをオーバーライドすることで、ターゲットの意図しないオーバーライドを防ぐことができます。 たとえば、Build ターゲットには、DependsOnTargets 属性値 "$(BuildDependsOn)" が含まれています。 以下を検討してください。

<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>

XML のこの部分は、Build ターゲットを実行するには、BuildDependsOn プロパティに指定されているすべてのターゲットを先に実行する必要があります。 BuildDependsOn プロパティは次のように定義されています。

<PropertyGroup>
    <BuildDependsOn>
        $(BuildDependsOn);
        BeforeBuild;
        CoreBuild;
        AfterBuild
    </BuildDependsOn>
</PropertyGroup>

プロジェクト ファイルの終わりで BuildDependsOn という名前の別のプロパティを宣言することでこのプロパティ値をオーバーライドできます。 SDK スタイルのプロジェクトでは、明示的なインポートを使用する必要があることを意味します。 最後のインポートの後に DependsOn プロパティを挿入できるように、「暗黙的および明示的なインポート」を参照してください。 新しいプロパティに前の BuildDependsOn プロパティを含めることで、ターゲット一覧の最初と最後に新しいターゲットを追加できます。 次に例を示します。

<PropertyGroup>
    <BuildDependsOn>
        MyCustomTarget1;
        $(BuildDependsOn);
        MyCustomTarget2
    </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTarget1">
    <Message Text="Running MyCustomTarget1..."/>
</Target>
<Target Name="MyCustomTarget2">
    <Message Text="Running MyCustomTarget2..."/>
</Target>

プロジェクト ファイルをインポートするプロジェクトは、既に行っているカスタマイズを上書きすることなく、これらのプロパティを拡張できます。

DependsOn プロパティをオーバーライドするには

  1. オーバーライドする共通のターゲットで事前定義済み DependsOn プロパティを特定します。 以下の表をご覧ください。一般的にオーバーライドされる DependsOn プロパティの一覧です。

  2. プロパティ ファイルの終わりにプロパティの別のインスタンスを定義します。 新しいプロパティに元のプロパティ (たとえば、$(BuildDependsOn)) を含めます。

  3. プロパティ定義の前または後にカスタム ターゲットを定義します。

  4. プロジェクト ファイルをビルドします。

一般的にオーバーライドされる DependsOn プロパティ

プロパティ名 追加されたターゲットは、この時点より前に実行されます:
BuildDependsOn メイン ビルドのエントリ ポイント。 ビルド処理全体の前または後にカスタム ターゲットを挿入する場合に、プロパティをオーバーライドします。
RebuildDependsOn Rebuild
RunDependsOn 最終的なビルド出力の実行 (.EXEの場合)
CompileDependsOn コンパイル (Compile ターゲット)。 コンパイル手順の前または後にカスタム プロセスを挿入する場合に、プロパティをオーバーライドします。
CreateSatelliteAssembliesDependsOn サテライト アセンブリの作成
CleanDependsOn Clean ターゲット (すべての中間および最終的なビルド出力を削除)。 カスタム ビルド処理からの出力をクリーンアップする場合に、プロパティをオーバーライドします。
PostBuildEventDependsOn PostBuildEvent ターゲット
PublishBuildDependsOn ビルドの公開
ResolveAssemblyReferencesDependsOn ResolveAssemblyReferences ターゲット (特定の依存関係に対して依存関係の推移閉包を見つける)。 以下を参照してください。ResolveAssemblyReference

例: BuildDependsOn と CleanDependsOn

次の例は、BeforeTargetsAfterTargets の例に似ていますが、同様の機能を達成する方法を示しています。 ビルド後に出力ファイルをコピーし、さらにそれに対応する CustomClean タスクを CleanDependsOn を利用して追加する独自のタスク CustomAfterBuildBuildDependsOn で追加するという手法でビルドを拡張します。

この例では、これは SDK スタイルのプロジェクトです。 この記事の SDK スタイルのプロジェクトに関する注記で前述したように、Visual Studio でプロジェクト ファイルの生成時に使用される Sdk 属性の代わりに、手動のインポート手法を使用する必要があります。

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <BuildDependsOn>
      $(BuildDependsOn);CustomAfterBuild
    </BuildDependsOn>

    <CleanDependsOn>
      $(CleanDependsOn);CustomClean
    </CleanDependsOn>

    <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Importance="high" Text="_FilesToCopy: @(_FilesToCopy)"/>

    <Message Text="DestFiles:
      @(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles="@(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean">
    <Message Importance="high" Text="Inside Custom Clean"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files="@(_CustomFilesToDelete)"/>
  </Target>
</Project>

要素の順序は重要です。 BuildDependsOn 要素と CleanDependsOn 要素は、標準 SDK ターゲット ファイルをインポートした後に表示される必要があります。

事前定義済みターゲットをオーバーライドする

共通の .targets ファイルには、事前定義されている空のターゲットのセットが含まれています。これは、ビルド処理の一部の主要ターゲットの前後で呼び出されます。 たとえば、MSBuild では、メインの CoreBuild ターゲットの前に BeforeBuild ターゲットが呼び出され、CoreBuild ターゲットの後に AfterBuild ターゲットが呼び出されます。 既定では、共通のターゲットの空のターゲットでは何も行われませんが、プロジェクト ファイルで任意のターゲットを定義することで、それらのデフォルト ビヘイビアーをオーバーライドできます。 この記事で前述したメソッドをお勧めしますが、このメソッドを使用する古いコードを目にする可能性があります。

プロジェクトで SDK を使用する場合 (例えば Microsoft.Net.Sdk)、「明示的および暗黙的インポート」で説明されているように、暗黙的インポートから明示的インポートに変更を加える必要があります。

事前定義済みターゲットをオーバーライドするには

  1. プロジェクトで Sdk 属性を使用する場合は、明示的なインポート構文に変更します。 「明示的および暗黙的インポート」を参照してください。

  2. オーバーライドする共通のターゲットで事前定義済みターゲットを特定します。 次の表をご覧ください。これは、安全にオーバーライドできるターゲットの入力候補一覧です。

  3. プロジェクト ファイルの最後に、</Project> タグの直前と明示的 SDK インポートの後で、ターゲットを定義します。 次に例を示します。

    <Project>
        <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
        ...
        <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
        <Target Name="BeforeBuild">
            <!-- Insert tasks to run before build here -->
        </Target>
        <Target Name="AfterBuild">
            <!-- Insert tasks to run after build here -->
        </Target>
    </Project>
    

    最上位の Project 要素で Sdk 属性が削除されていることに注意してください。

  4. プロジェクト ファイルをビルドします。

事前定義済みターゲットの表

次の表では、共通のターゲットでオーバーライドできるすべてのターゲットを示しています。

ターゲット名 説明
BeforeCompileAfterCompile これらのターゲットのいずれかに挿入されているタスクは、コア コンパイル完了の前または後に実行されます。 ほとんどのカスタマイズはこれら 2 つのターゲットのいずれかで行われます。
BeforeBuildAfterBuild これらのターゲットのいずれかに挿入されているタスクは、ビルド内の他のすべての前または後に実行されます。 注: BeforeBuild ターゲットと AfterBuild ターゲットは、ほとんどのプロジェクト ファイルの終わりにあるコメントで既に定義されており、ビルド前イベントとビルド後イベントをプロジェクト ファイルに簡単に追加できます。
BeforeRebuildAfterRebuild これらのターゲットのいずれかに挿入されているタスクは、コア再ビルド機能の呼び出しの前または後に実行されます。 Microsoft.Common.targets のターゲット実行順序は BeforeRebuildCleanBuildAfterRebuild です。
BeforeCleanAfterClean これらのターゲットのいずれかに挿入されているタスクは、コア クリーン機能の呼び出しの前または後に実行されます。
BeforePublishAfterPublish これらのターゲットのいずれかに挿入されているタスクは、コア公開機能の呼び出しの前または後に実行されます。
BeforeResolveReferencesAfterResolveReferences これらのターゲットのいずれかに挿入されているタスクは、アセンブリ参照解決の前または後に実行されます。
BeforeResGenAfterResGen これらのターゲットのいずれかに挿入されているタスクは、リソース生成の前または後に実行されます。

ビルド システムと .NET SDK には、他にも多くのターゲットがあります。「MSBuild ターゲット - SDK と既定のビルド ターゲット」を参照してください。

カスタム ターゲットの成功事例

プロパティ DependsOnTargetsBeforeTargets 両方とも、ターゲットを別のターゲットの前に実行する必要があることを指定できますが、両方とも異なるシナリオで必要です。 依存関係の要件を指定するターゲットが異なります。 独自のターゲットのみを制御でき、システム ターゲットまたはその他のインポートされたターゲットは安全に変更できないため、方法の選択肢が制限されます。

カスタム ターゲットを作成するときは、次の一般的なガイドラインに従って、ターゲットが意図した順序で実行されるようにします。

  1. DependsOnTargets 属性を使用して、ターゲットを実行する前に完了する必要があるターゲットを指定します。 制御するのがターゲットのチェーンの場合、各ターゲットは DependsOnTargets でチェーンの前のメンバーを指定できます。

  2. 前に実行する必要がある制御しないターゲット (BeforeTargets="PrepareForBuild" などのビルドの早い段階で実行する必要があるターゲット) には BeforeTargets を使用します。

  3. 必要な出力が使用可能であることを保証する制御しないターゲットには、AfterTargets を使用します。 例えば、参照リストを変更するものには AfterTargets="ResolveReferences" を指定します。

  4. これらは、任意の組み合わせを使用できます。 たとえば、DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile" のようにします。

明示的および暗黙的インポート

Visual Studio によって生成されたプロジェクトでは、通常、プロジェクト要素に Sdk 属性を使用します。 このような種類のプロジェクトは、SDK スタイルのプロジェクトと呼ばれます。 「MSBuild プロジェクト SDK の使用」を参照してください。 次に例を示します。

<Project Sdk="Microsoft.Net.Sdk">

Sdk 属性を使用するプロジェクトの場合、2 つのインポートが暗黙的に追加され、1 つはプロジェクト ファイルの先頭に、もう 1 つは最後に追加されます。

暗黙的なインポートは、プロジェクト ファイルの最初の行の Project 要素の後に、次のような インポート ステートメントを含めたものと同じです。

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

次のインポート ステートメントをプロジェクト ファイルの最終行として使用します。

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

この構文は、明示的な SDK インポートと呼ばれます。 この明示的な構文を使用する場合は、プロジェクト要素で Sdk 属性を省略する必要があります。

暗黙的な SDK インポートは、古いプロジェクト ファイル内の一般的なコンストラクトである、特定の「共通した」 .props または .targets ファイルをインポートすることと同様です。次のような例が挙げられます。

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

and

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

このような古いリファレンスは、このセクションで先に示した明示的な SDK 構文に置き換える必要があります。

明示的な SDK 構文を使用すると、最初のインポートの前または最終的な SDK インポートの後に独自のコードを追加できます。 つまり、インポートされた .props ファイルで有効となる最初のインポートの前に、プロパティを設定することで動作を変更できます。また、最後のインポートの後にいずれかの SDK .targets ファイルで定義されているターゲットをオーバーライドすることもできます。 次に説明するように、このメソッドを使用して、BeforeBuild または AfterBuild をオーバーライドできます。

次のステップ

他にも MSBuild を使用してビルドをカスタマイズする方法はたくさんあります。 「ビルドのカスタマイズ」を参照してください。