Настройка сборки по папке

Вы можете добавить определенные файлы, импортируемые MSBuild, чтобы переопределить параметры свойств по умолчанию и добавить пользовательские целевые объекты. Область этих настроек можно контролировать на уровне папки, где размещаются эти файлы.

В этой статье рассматриваются настройки, применимые к следующим сценариям:

  • Настройка параметров сборки для многих проектов в решении
  • Настройка параметров сборки для многих решений в общем каталоге файлов
  • Настройка параметров сборки, которые могут отличаться для вложенных папок в сложной структуре папок
  • Переопределение параметров по умолчанию, папок сборки по умолчанию и других действий, заданных пакетом SDK, например Microsoft.Net.Sdk
  • Добавление или настройка целевых объектов сборки, которые применяются к любому количеству проектов или решений

При работе с проектами C++ можно также использовать методы, описанные в разделе "Настройка сборок C++".

Directory.Build.props и Directory.Build.targets

Вы можете добавить новое свойство в любой проект, определив свойство в единственном файле Directory.Build.props в корневой папке с исходным кодом.

При запуске MSBuild Microsoft.Common.props выполняет поиск структуры каталогов для файла Directory.Build.props. Если MSBuild находит файл, то импортирует его и считывает свойства, определенные в этом файле. Пользовательский файл Directory.Build.props содержит настройки персонализации для проектов в своем каталоге.

Аналогичным образом Microsoft.Common.targets ищет directory.Build.targets.

Directory.Build.props импортируется в начале последовательности импортированных файлов, что может быть важно, если необходимо задать свойство, используемое импортом, особенно те, которые неявно импортируются с помощью атрибута, например при использовании Sdk пакета SDK для .NET в большинстве файлов проекта .NET.

Примечание.

В файловых системах на основе Linux учитывается регистр. Убедитесь, что регистр имени файла Directory.Build.props совпадает точно или он не будет обнаружен во время процесса сборки.

Дополнительные сведения см. здесь на GitHub.

Пример Directory.Build.props

Например, если вы хотите предоставить всем проектам новую функцию Roslyn /deterministic через свойство $(Deterministic) в целевом объекте Roslyn CoreCompile, можно сделать следующее.

  1. Создайте в корне репозитория файл с именем Directory.Build.props.

  2. Добавьте в этот файл приведенный ниже XML-код.

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. Запустите MSBuild. Уже существующие в проекте файлы импорта Microsoft.Common.props и Microsoft.Common.targets находят новый файл и импортируют его.

Область поиска

При поиске файла 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 импортируется из Microsoft.Common.targets после импорта .targets файлов из пакетов NuGet. Таким образом, в нем могут быть переопределены свойства и целевые объекты, определенные в большей части логики сборки, а также заданы свойства для всех проектов, которые будут иметь приоритет над настройками отдельных проектов.

Если вам требуется задать свойство или определить целевой объект для отдельного проекта, которые будут переопределять любые ранее произведенные настройки, соответствующую логику следует помещать в файл проекта после окончательного импорта. Чтобы сделать это в проекте в стиле пакета SDK, сначала необходимо заменить атрибут стиля пакета SDK эквивалентными импортируемыми компонентами. См. статью Использование пакетов SDK проекта MSBuild.

Примечание.

Модуль 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), а также общие свойства для проектов исходного кода(2-src) и общие свойства для проектов тестирования(2-test).

Чтобы система MSBuild правильно объединила "внутренние" файлы (2-src и 2-test) с "внешним" файлом (1), обязательно учитывайте, что MSBuild прекращает сканирование при обнаружении файла Directory.Build.props. Чтобы продолжить сканирование и включить в слияние внешний файл, добавьте в оба внутренних файла следующее:

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

Ниже кратко описан основной алгоритм работы MSBuild.

  • Для каждого проекта MSBuild находит ближайший файл Directory.Build.props, расположенный выше в структуре решения, объединяет его с параметрами по умолчанию и на этом останавливает сканирование.
  • Если требуется найти и объединить несколько уровней, то <Import...> (показано ранее) внешний файл из "внутреннего" файла.
  • Если "внешний" файл также не импортирует над ним что-то выше, то сканирование останавливается там.

Проще говоря, MSBuild останавливает поиск на первом файле Directory.Build.props, который ничего не импортирует.

Чтобы управлять процессом импорта более явным образом, используйте свойства $(DirectoryBuildPropsPath), $(ImportDirectoryBuildProps)и $(ImportDirectoryBuildTargets)$(DirectoryBuildTargetsPath)т. д. Свойство $(DirectoryBuildPropsPath) указывает путь к используемому Directory.Build.props файлу. $(DirectoryBuildTargetsPath) Аналогичным образом указывает путь к файлу Directory.Build.targets .

Логические свойства $(ImportDirectoryBuildProps) и заданы true по умолчанию, поэтому MSBuild обычно выполняет поиск этих файлов, но их можно задать, чтобы false предотвратить $(ImportDirectoryBuildTargets) импорт MSBuild.

Пример

В этом примере показано, как использовать предварительно обработанные выходные данные, чтобы определить, где задать свойство.

Чтобы проанализировать использование определенного свойства, которое вы хотите задать, можно запустить MSBuild с /preprocess помощью аргумента или /pp аргумента. Выходной текст является результатом всех импортов, включая системные импорты, такие как Microsoft.Common.props , которые неявно импортированы, и любой из ваших собственных импортов. В этом выходных данных вы увидите, где необходимо задать свойство относительно того, где используется его значение.

Например, предположим, что у вас есть простой проект консольного приложения .NET Core или .NET 5 или более поздней версии, и вы хотите настроить промежуточную выходную папку, как правило obj. Свойство, указывающее этот путь BaseIntermediateOutput. Если вы попытаетесь поместить его в PropertyGroup элемент в файл проекта вместе с различными другими свойствами, которые уже установлены там, например TargetFramework, вы обнаружите, когда вы создаете проект, который свойство не вступают в силу. Если вы запускаете MSBuild с параметром /pp и ищете выходные данные 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>
  ...