Настройка сборки по папке
Вы можете добавить определенные файлы, импортируемые MSBuild, чтобы переопределить параметры свойств по умолчанию и добавить пользовательские целевые объекты. Область этих настроек можно контролировать на уровне папки, где размещаются эти файлы.
В этой статье рассматриваются настройки, применимые к следующим сценариям:
- Настройка параметров сборки для многих проектов в решении
- Настройка параметров сборки для многих решений в общем каталоге файлов
- Настройка параметров сборки, которые могут отличаться для вложенных папок в сложной структуре папок
- Переопределите параметры по умолчанию, папки сборки по умолчанию и другие действия, заданные пакетом SDK, например
Microsoft.Net.Sdk
- Добавление или настройка целевых объектов сборки, которые применяются к любому количеству проектов или решений
Если вы работаете с проектами C++, можно также использовать методы, описанные в статье Настройка сборок C++.
Directory.Build.props и Directory.Build.targets
Вы можете добавить новое свойство в каждый проект, определив его в одном файле с именем Directory.Build.props в корневой папке, содержащей источник.
При запуске MSBuild Microsoft.Common.props ищет в структуре каталогов файл Directory.Build.props. Если он находит его, он импортирует файл и считывает свойства, определенные в нем. Directory.Build.props — это определяемый пользователем файл, который предоставляет настройки для проектов в каталоге.
Аналогичным образом Microsoft.Common.targets ищет Directory.Build.targets.
Directory.Build.props импортируется рано в последовательности импортированных файлов, что может быть важно, если необходимо задать свойство, используемое импортом, особенно те, которые неявно импортируются с помощью атрибута Sdk
, например при использовании пакета SDK для .NET в большинстве файлов проекта .NET.
Заметка
Файловые системы под управлением Linux чувствительны к регистру. Убедитесь, что регистру имени файла Directory.Build.props соответствует точно, иначе он не будет обнаружен при сборке.
Дополнительные сведения см. в этой проблеме GitHub.
Пример Directory.Build.props
Например, вот файл Directory.Build.props, который задает выходной каталог для всех проектов в решении Visual Studio. Выходные данные каждого проекта размещаются под его собственным названием. В этом примере файл Directory.Build.props находится в папке решения с большим количеством проектов в вложенных папках под ним. Свойство $(MSBuildProjectName)
дает имя каждого проекта. Так как файл Directory.Build.props импортируется в каждый проект во время собственной сборки, он оценивается в правильное значение для каждого отдельного проекта в решении.
Очистите решение для удаления старых выходных файлов.
msbuild /t:Clean SolutionName.sln
Создайте файл в корне репозитория с именем Directory.Build.props.
Добавьте следующий XML-код в файл.
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
Заметка
Свойство
$(OutDir)
— это абсолютный путь к выходным данным, и его использование позволяет обойти создание вложенных папок для конфигурации, целевой платформы или среды выполнения, которые обычно применяются в проектах .NET. Попробуйте использовать свойствоBaseOutputPath
вместо этого, если вы хотите создать обычные вложенные папки в пользовательском пути вывода.Запустите MSBuild. Существующие импорты Microsoft.Common.props и Microsoft.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 импортируется из Microsoft.Common.targets после импорта файлов .targets
из пакетов NuGet. Таким образом, он может переопределить свойства и цели, определенные в большей части логики сборки, или задать свойства для всех проектов независимо от настроек отдельных проектов.
Если необходимо задать свойство или определить целевой объект для отдельного проекта, который переопределяет все предыдущие параметры, поместите эту логику в файл проекта после окончательного импорта. Чтобы сделать это в проекте в стиле 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), общие свойства для проектов src(2-src)и общие свойства для тестовых проектов (2-тестовые).
Чтобы MSBuild правильно объединить "внутренние" файлы (2-src и 2-test) с "внешним" файлом (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 обычно выполняет поиск этих файлов, но их можно задать для false
, чтобы предотвратить импорт 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>
...
Связанное содержимое
- Настройтевашу сборку.