次の方法で共有


フォルダー別にビルドをカスタマイズする

MSBuild によってインポートされる特定のファイルを追加して、既定のプロパティ設定をオーバーライドし、カスタム ターゲットを追加できます。 これらのカスタマイズのスコープは、これらのファイルを配置する場所によってフォルダー レベルで制御できます。

この記事では、次のシナリオに適用できるカスタマイズについて説明します。

  • ソリューション内の多くのプロジェクトのビルド設定をカスタマイズする
  • 共通のファイル ディレクトリで多くのソリューションのビルド設定をカスタマイズする
  • フォルダーの複雑な構造のサブフォルダーに対して異なるビルド設定をカスタマイズする
  • 既定の設定、既定のビルド フォルダー、SDK によって設定されたその他の動作 (Microsoft.Net.Sdk など) をオーバーライドする
  • 任意の数のプロジェクトまたはソリューションに適用されるビルド ターゲットを追加またはカスタマイズする

C++ プロジェクトを使用している場合は、「C++ ビルドのカスタマイズ で説明されているメソッドを使用することもできます。

Directory.Build.props と Directory.Build.targets

すべてのプロジェクトに新しいプロパティを追加するには、ソースを含むルート フォルダーに Directory.Build.props という名前の 1 つのファイルで定義します。

MSBuild を実行すると、Microsoft.Common.props がディレクトリ構造内で Directory.Build.props ファイルを検索します。 それを見つけると、ファイルをインポートし、その中で定義されているプロパティを読み取ります。 Directory.Build.props は、ディレクトリの下にあるプロジェクトにカスタマイズを提供するユーザー定義ファイルです。

同様に、Microsoft.Common.targets は、Directory.Build.targetsを検索します。

Directory.Build.props は、インポートされたファイルのシーケンスの早い段階でインポートされます。これは、インポートで使用されるプロパティ(特に、ほとんどの .NET プロジェクト ファイルで .NET SDK を使用する場合など)、Sdk 属性を使用して暗黙的にインポートされるプロパティを設定する必要がある場合に重要です。

手記

Linux ベースのファイル システムは、大文字と小文字を区別します。 Directory.Build.props ファイル名の大文字と小文字が正確に一致していることを確認してください。一致していないと、ビルド プロセス中に検出されません。

詳細については、この GitHub の問題 を参照してください。

Directory.Build.props の例

たとえば、Visual Studio ソリューション内のすべてのプロジェクトの出力ディレクトリを設定する Directory.Build.props ファイルを次に示します。 各プロジェクトの出力は、独自のプロジェクト名の下に配置されます。 この例では、Directory.Build.props ファイルはソリューション フォルダー内にあり、その下にはサブフォルダーに多数のプロジェクトがあります。 $(MSBuildProjectName) プロパティは、各プロジェクトの名前を指定します。 Directory.Build.props ファイルは、独自のビルド中に各プロジェクトにインポートされるため、ソリューション内の個々のプロジェクトごとに適切な値に評価されます。

  1. ソリューションをクリーンアップして、古い出力ファイルを削除します。

    msbuild /t:Clean SolutionName.sln

  2. Directory.Build.propsという名前の新しいファイルをリポジトリのルートに作成します。

  3. 次の XML をファイルに追加します。

    <Project>
       <PropertyGroup>
          <OutDir>C:\output\$(MSBuildProjectName)</OutDir>
       </PropertyGroup>
    </Project>
    

    手記

    $(OutDir) プロパティは出力への絶対パスであり、これを使用すると、.NET プロジェクトで通常使用される構成、ターゲット フレームワーク、またはランタイムのサブフォルダーの作成がバイパスされます。 通常のサブフォルダーをカスタム出力パスの下に作成する場合は、代わりにプロパティ BaseOutputPath を使用してみてください。

  4. MSBuild を実行します。 プロジェクトの既存の Microsoft.Common.propsMicrosoft.Common.targets のインポートでは、Directory.Build.props ファイルを見つけてそれをインポートし、新しい出力フォルダーがそのフォルダーの下にあるすべてのプロジェクトに対して使用されます。

検索範囲

Directory.Build.props ファイルを検索すると、MSBuild はプロジェクトの場所 $(MSBuildProjectFullPath)からディレクトリ構造を上方向に移動し、Directory.Build.props ファイルを見つけた後に停止します。 たとえば、$(MSBuildProjectFullPath) が c:\users\username\code\test\case1 した場合、MSBuild はそこで検索を開始し、次のディレクトリ構造のように、Directory.Build.props ファイルを見つけるまでディレクトリ構造を上方向に検索します。

c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\

ソリューション ファイルの場所は、Directory.Build.props とは無関係です。

インポート順序

Directory.Build.props は、Microsoft.Common.props 早い段階でインポートされ、後で定義されたプロパティは使用できません。 そのため、まだ定義されていない (および空と評価される) プロパティを参照することは避けてください。

Directory.Build.props で設定されたプロパティは、プロジェクト ファイル内またはインポートされたファイル内の別の場所でオーバーライドできるため、Directory.Build.props の設定は、プロジェクトの既定値を指定 と考える必要があります。

Directory.Build.targets は、NuGet パッケージから .targets ファイルをインポートした後、Microsoft.Common.targets からインポートされます。 そのため、ほとんどのビルド ロジックで定義されているプロパティとターゲットをオーバーライドしたり、個々のプロジェクトが設定した内容に関係なく、すべてのプロジェクトのプロパティを設定したりできます。

プロパティを設定する必要がある場合、または以前の設定をオーバーライドする個々のプロジェクトのターゲットを定義する必要がある場合は、最終的なインポートの後に、そのロジックをプロジェクト ファイルに配置します。 SDK スタイルのプロジェクトでこれを行うには、最初に SDK スタイルの属性を同等のインポートに置き換える必要があります。 「MSBuild プロジェクト SDKを使用する方法」を参照してください。

手記

MSBuild エンジンは、プロジェクト (PreBuildEventを含む) のビルドの実行を開始する前に、評価中にインポートされたすべてのファイルを読み取ります。そのため、これらのファイルは、PreBuildEvent やビルド プロセスの他の部分によって変更される予定はありません。 変更は、MSBuild.exe の次回の呼び出しまたは次の Visual Studio ビルドまで有効になりません。 また、ビルド プロセスに多数のプロジェクト ビルド (マルチターゲットプロジェクトやビルド依存プロジェクトと同様) が含まれている場合、Directory.build.propsを含むインポートされたファイルは、個々のプロジェクト ビルドごとに評価が行われるときに読み取られます。

ユース ケース: 複数レベルのマージ

この標準的なソリューション構造があるとします。

\
  MySolution.sln
  Directory.Build.props     (1)
  \src
    Directory.Build.props   (2-src)
    \Project1
    \Project2
  \test
    Directory.Build.props   (2-test)
    \Project1Tests
    \Project2Tests

すべてのプロジェクト (1)の共通プロパティ、src プロジェクト の共通プロパティ (2-src)、および テスト プロジェクトの共通プロパティ (2 テスト)が望ましい場合があります。

MSBuild で "内部" ファイル (2 src2 テスト) を "outer" ファイル (1) と正しくマージするには、MSBuild が Directory.Build.props ファイルを見つけたら、さらにスキャンを停止することを考慮する必要があります。 スキャンを続行して外部ファイルにマージするには、次のコードを両方の内部ファイルに配置します。

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

MSBuild の一般的なアプローチの概要は次のとおりです。

  • 特定のプロジェクトに対して、MSBuild はソリューション構造内で最初に見つかった Directory.Build.props を、ディレクトリの上層に向かって検索し、見つけたものを既定の設定と統合して、それ以上のスキャンを停止します。
  • 複数レベルで検索して結合する場合は、"内部" ファイルから "外部" ファイルを <Import...> します (前のコードを参照)。
  • "外部" ファイル自体もその上に何かをインポートしない場合は、そこでスキャンが停止します。

または簡単に言えば、何もインポートしない最初の Directory.Build.props は、MSBuild が停止する場所です。

インポート プロセスをより明示的に制御するには、プロパティ $(DirectoryBuildPropsPath)$(ImportDirectoryBuildProps)$(DirectoryBuildTargetsPath)、および $(ImportDirectoryBuildTargets)を使用します。 プロパティ $(DirectoryBuildPropsPath) は、使用する Directory.Build.props ファイルへのパスを指定します。同様に、$(DirectoryBuildTargetsPath)Directory.Build.targets ファイルへのパスを指定します。

ブール型のプロパティ $(ImportDirectoryBuildProps)$(ImportDirectoryBuildTargets) は既定で true に設定されているため、通常、MSBuild はこれらのファイルを検索しますが、MSBuild がファイルをインポートできないように false に設定できます。

この例では、前処理された出力を使用して、プロパティを設定する場所を決定します。

設定する特定のプロパティの使用状況を分析するには、/preprocess または /pp 引数を指定して MSBuild を実行します。 出力テキストは、暗黙的にインポートされる Microsoft.Common.props などのシステム インポートや、独自のインポートなど、すべてのインポートの結果です。 この出力では、プロパティを設定する必要がある場所を、その値が使用される場所と関連付けて確認できます。

たとえば、単純な .NET Core または .NET 5 以降のコンソール アプリ プロジェクトがあり、通常は obj中間出力フォルダーをカスタマイズするとします。 このパスを指定するプロパティは BaseIntermediateOutputです。 TargetFrameworkなど、既に設定されている他のさまざまなプロパティと共にプロジェクト ファイル内の PropertyGroup 要素にこれを配置しようとすると、プロパティが有効にならないことがプロジェクトをビルドするときに検出されます。 /pp オプションを使用して MSBuild を実行し、出力で BaseIntermediateOutputPathを検索すると、その理由がわかります。 この場合、BaseIntermediateOutput は読み取られ、Microsoft.Common.propsで使用されます。

Microsoft.Common.props にコメントがあり、プロパティ BaseIntermediateOutput はここで、別のプロパティ (MSBuildProjectExtensionsPath) によって使用される前に設定する必要があることが示されています。 また、BaseIntermediateOutputPath が最初に設定されたときに、既存の値のチェックが行われ、未定義の場合は objに設定されることがわかります。

<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>

したがって、この配置は、このプロパティを設定するには、これより前の場所で指定する必要があることを示します。 前処理された出力のこのコードの直前に、Directory.Build.props がインポートされていることがわかります。そのため、そこに BaseIntermediateOutputPath 設定でき、必要な効果を得るのに十分な早い段階で設定されます。

次の省略された前処理された出力は、BaseIntermediateOutput 設定を Directory.Build.propsに配置した結果を示しています。 標準インポートの先頭にあるコメントには、ファイル名と、通常、そのファイルがインポートされる理由に関する有用な情報が含まれます。

<?xml version="1.0" encoding="IBM437"?>
<!--
============================================================================================================================================
c:\source\repos\ConsoleApp9\ConsoleApp9\ConsoleApp9.csproj
============================================================================================================================================
-->
<Project DefaultTargets="Build">
  <!--
============================================================================================================================================
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk">
  This import was added implicitly because the Project element's Sdk attribute specified "Microsoft.NET.Sdk".

C:\Program Files\dotnet\sdk\7.0.200-preview.22628.1\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.props
============================================================================================================================================
-->
  <!--
***********************************************************************************************
Sdk.props

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.
***********************************************************************************************
-->
  <PropertyGroup xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <!--
      Indicate to other targets that Microsoft.NET.Sdk is being used.

      This must be set here (as early as possible, before Microsoft.Common.props)
      so that everything that follows can depend on it.

      In particular, Directory.Build.props and nuget package props need to be able
      to use this flag and they are imported by Microsoft.Common.props.
    -->
    <UsingMicrosoftNETSdk>true</UsingMicrosoftNETSdk>
    <!--
      Indicate whether the set of SDK defaults that makes SDK style project concise are being used.
      For example: globbing, importing msbuild common targets.

      Similar to the property above, it must be set here.
    -->
    <UsingNETSdkDefaults>true</UsingNETSdkDefaults>
  </PropertyGroup>
  <PropertyGroup Condition="'$(MSBuildProjectFullPath)' == '$(ProjectToOverrideProjectExtensionsPath)'" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <MSBuildProjectExtensionsPath>$(ProjectExtensionsPathForSpecifiedProject)</MSBuildProjectExtensionsPath>
  </PropertyGroup>
  <!--<Import Project="$(AlternateCommonProps)" Condition="'$(AlternateCommonProps)' != ''" />-->
  <!--
============================================================================================================================================
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="'$(AlternateCommonProps)' == ''">

C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
  <!--
***********************************************************************************************
Microsoft.Common.props

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) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
  <PropertyGroup>
    <ImportByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportByWildcardBeforeMicrosoftCommonProps>
    <ImportByWildcardAfterMicrosoftCommonProps Condition="'$(ImportByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportByWildcardAfterMicrosoftCommonProps>
    <ImportUserLocationsByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardBeforeMicrosoftCommonProps>
    <ImportUserLocationsByWildcardAfterMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardAfterMicrosoftCommonProps>
    <ImportDirectoryBuildProps Condition="'$(ImportDirectoryBuildProps)' == ''">true</ImportDirectoryBuildProps>
  </PropertyGroup>
  <!--
      Determine the path to the directory build props file if the user did not disable $(ImportDirectoryBuildProps) and
      they did not already specify an absolute path to use via $(DirectoryBuildPropsPath)
  -->
  <PropertyGroup Condition="'$(ImportDirectoryBuildProps)' == 'true' and '$(DirectoryBuildPropsPath)' == ''">
    <_DirectoryBuildPropsFile Condition="'$(_DirectoryBuildPropsFile)' == ''">Directory.Build.props</_DirectoryBuildPropsFile>
    <_DirectoryBuildPropsBasePath Condition="'$(_DirectoryBuildPropsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectoryBuildPropsFile)'))</_DirectoryBuildPropsBasePath>
    <DirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsBasePath)' != '' and '$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('$(_DirectoryBuildPropsBasePath)', '$(_DirectoryBuildPropsFile)'))</DirectoryBuildPropsPath>
  </PropertyGroup>
  <!--
============================================================================================================================================
  <Import Project="$(DirectoryBuildPropsPath)" Condition="'$(ImportDirectoryBuildProps)' == 'true' and exists('$(DirectoryBuildPropsPath)')">

c:\source\repos\ConsoleApp9\Directory.Build.props
============================================================================================================================================
-->
  <!-- Directory.build.props
-->
  <PropertyGroup>
    <BaseIntermediateOutputPath>myBaseIntermediateOutputPath</BaseIntermediateOutputPath>
  </PropertyGroup>
  <!--
============================================================================================================================================
  </Import>

C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
  <!--
      Prepare to import project extensions which usually come from packages.  Package management systems will create a file at:
        $(MSBuildProjectExtensionsPath)\$(MSBuildProjectFile).<SomethingUnique>.props

      Each package management system should use a unique moniker to avoid collisions.  It is a wild-card import so the package
      management system can write out multiple files but the order of the import is alphabetic because MSBuild sorts the list.
  -->
  <PropertyGroup>
    <!--
        The declaration of $(BaseIntermediateOutputPath) had to be moved up from Microsoft.Common.CurrentVersion.targets
        in order for the $(MSBuildProjectExtensionsPath) to use it as a default.
    -->
    <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
    <BaseIntermediateOutputPath Condition="!HasTrailingSlash('$(BaseIntermediateOutputPath)')">$(BaseIntermediateOutputPath)\</BaseIntermediateOutputPath>
    <_InitialBaseIntermediateOutputPath>$(BaseIntermediateOutputPath)</_InitialBaseIntermediateOutputPath>
    <MSBuildProjectExtensionsPath Condition="'$(MSBuildProjectExtensionsPath)' == '' ">$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>
    <!--
        Import paths that are relative default to be relative to the importing file.  However, since MSBuildExtensionsPath
        defaults to BaseIntermediateOutputPath we expect it to be relative to the project directory.  So if the path is relative
        it needs to be made absolute based on the project directory.
    -->
    <MSBuildProjectExtensionsPath Condition="'$([System.IO.Path]::IsPathRooted($(MSBuildProjectExtensionsPath)))' == 'false'">$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))</MSBuildProjectExtensionsPath>
    <MSBuildProjectExtensionsPath Condition="!HasTrailingSlash('$(MSBuildProjectExtensionsPath)')">$(MSBuildProjectExtensionsPath)\</MSBuildProjectExtensionsPath>
    <ImportProjectExtensionProps Condition="'$(ImportProjectExtensionProps)' == ''">true</ImportProjectExtensionProps>
    <_InitialMSBuildProjectExtensionsPath Condition=" '$(ImportProjectExtensionProps)' == 'true' ">$(MSBuildProjectExtensionsPath)</_InitialMSBuildProjectExtensionsPath>
  </PropertyGroup>
  ...