Расширение процесса сборки Visual Studio
Процесс сборки Visual Studio определяется рядом файлов MSBuild .targets
, импортируемых в файл проекта. Эти импорты являются неявными, если обычно используется пакет SDK в качестве проектов Visual Studio. Один из этих импортированных файлов — Microsoft.Common.targets — можно расширить, чтобы выполнять настраиваемые задачи в нескольких точках в процессе сборки. В этой статье объясняется три метода, которые можно использовать для расширения процесса сборки Visual Studio:
Создайте пользовательский целевой объект и укажите, когда он должен выполняться с помощью
BeforeTargets
иAfterTargets
атрибутами.Переопределите свойства,
DependsOn
определенные в общих целевых объектах.Переопределите определенные предопределенные целевые объекты, определенные в общих целевых объектах (Microsoft.Common.targets или файлы, импортируемые им).
Атрибуты AfterTargets и BeforeTargets
Вы можете использовать AfterTargets
и BeforeTargets
атрибуты в пользовательском целевом объекте, чтобы указать, когда он должен выполняться.
В приведенном ниже примере показано, как использовать атрибут 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>
Предупреждение
Не забудьте использовать имена, отличные от предопределенных целевых объектов (например, пользовательский целевой объект CustomAfterBuild
сборки , а не AfterBuild
), так как эти предопределенные целевые объекты переопределяются импортом пакета SDK, который также определяет их. См. таблицу в конце этой статьи для списка предопределенных целевых объектов.
Расширение свойств DependsOn
Другим способом расширения процесса сборки является использование DependsOn
свойств (например, BuildDependsOn
для указания целевых объектов, которые должны выполняться до стандартного целевого объекта).
Этот метод предпочтительнее переопределить предопределенные целевые объекты, которые рассматриваются в следующем разделе. Переопределение предопределенных целевых объектов — это старый метод, который по-прежнему поддерживается, но, так как MSBuild оценивает определение целевых объектов последовательно, невозможно запретить другому проекту, который импортирует проект, переопределить уже переопределенные целевые объекты. Например, последний целевой объект AfterBuild
, определенный в файле проекта, после импорта всех остальных проектов будет использоваться в процессе сборки.
Вы можете защититься от непреднамеренных переопределений целевых объектов, переопределив DependsOn
свойства, используемые в атрибутах во DependsOnTargets
всех общих целевых объектах. Например, целевой объект 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
Определите предопределенное
DependsOn
свойство в общих целевых объектах, которые необходимо переопределить. В следующей таблице приведен список часто переопределенныхDependsOn
свойств.Определите еще один экземпляр свойства или свойств в конце файла проекта. Включите исходное свойство, например
$(BuildDependsOn)
, в новое свойство.Определите пользовательские целевые объекты до или после определения свойства.
Выполните сборку файла проекта.
Часто переопределяемые свойства DependsOn
Имя свойства | Добавленные целевые объекты выполняются до этой точки: |
---|---|
BuildDependsOn |
Основная точка входа сборки. Переопределите это свойство, если вы хотите вставить пользовательские целевые объекты до или после всего процесса сборки. |
RebuildDependsOn |
Rebuild |
RunDependsOn |
Выполнение окончательных выходных данных сборки (если это .EXE) |
CompileDependsOn |
Компиляция (Compile целевой объект). Переопределите это свойство, если вы хотите вставить пользовательские процессы до или после шага компиляции. |
CreateSatelliteAssembliesDependsOn |
Создание вспомогательных сборок |
CleanDependsOn |
Целевой Clean объект (удаление всех промежуточных и конечных выходных данных сборки). Переопределите это свойство, если вы хотите очистить выходные данные из пользовательского процесса сборки. |
PostBuildEventDependsOn |
Целевой PostBuildEvent объект |
PublishBuildDependsOn |
Публикация сборки |
ResolveAssemblyReferencesDependsOn |
Целевой ResolveAssemblyReferences объект (поиск транзитивного закрытия зависимостей для заданной зависимости). См. раздел ResolveAssemblyReference . |
Пример: BuildDependsOn и CleanDependsOn
Приведенный ниже пример похож на пример с атрибутами BeforeTargets
и AfterTargets
, и в нем решается аналогичная задача. В нем сборка расширяется путем добавления с помощью атрибута BuildDependsOn
собственной задачи CustomAfterBuild
, которая копирует выходные файлы после сборки, а также добавления соответствующей задачи CustomClean
с помощью CleanDependsOn
.
В этом примере проект имеет стиль пакета SDK. Как упоминалось в примечании о проектах в стиле пакета SDK ранее в этой статье, вместо атрибута Sdk
, применяемого средой Visual Studio при создании файлов проекта, необходимо использовать импорт вручную.
<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->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
<Copy SourceFiles="@(_FilesToCopy)"
DestinationFiles="@(_FilesToCopy->'$(_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 вызывает целевой объект BeforeBuild
перед основным целевым объектом CoreBuild
, а целевой объект AfterBuild
— после целевого объекта CoreBuild
. По умолчанию пустые целевые объекты в общих целевых объектах ничего не делают, но их поведение по умолчанию можно переопределить, определив нужные целевые объекты в файле проекта. Методы, описанные ранее в этой статье, предпочтительны, но вы можете столкнуться с более старым кодом, использующим этот метод.
Если в проекте используется пакет SDK (например Microsoft.Net.Sdk
), необходимо внести изменения с неявного на явный импорт, как описано в явном и неявном импорте.
Переопределение предопределенного целевого объекта
Если проект использует
Sdk
атрибут, измените его на явный синтаксис импорта. См . явные и неявные импорты.Выберите в списке стандартных целевых объектов предварительно заданный целевой объект, который требуется переопределить. В следующей таблице приведен полный список целевых объектов, которые можно безопасно переопределить.
Определите целевой объект или целевые объекты в конце файла проекта, непосредственно перед тегом
</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>
Обратите внимание, что
Sdk
атрибут на элементе верхнего уровняProject
был удален.Выполните сборку файла проекта.
Таблица предопределенных целевых объектов
В следующей таблице показаны все целевые объекты в общих целевых объектах, которые можно переопределить.
Имя целевого объекта | Description |
---|---|
BeforeCompile , AfterCompile |
Задачи, добавленные в один из этих целевых объектов, выполняются до или после основной компиляции. Основная часть настроек выполняется в одном из этих двух целевых объектов. |
BeforeBuild , AfterBuild |
Задачи, добавленные в один из этих целевых объектов, выполняются до или после остальной части сборки. Примечание. Целевые объекты BeforeBuild и AfterBuild уже определены в комментариях в конце большинства файлов проекта, поэтому вы можете легко добавлять события до и после сборки в файл проекта. |
BeforeRebuild , AfterRebuild |
Задачи, добавленные в один из этих целевых объектов, выполняются до или после вызова основной функции перестроения. В Microsoft.Common.targets действует следующий порядок выполнения целевых объектов: BeforeRebuild , Clean , Build , а затем AfterRebuild . |
BeforeClean , AfterClean |
Задачи, добавленные в один из этих целевых объектов, выполняются до или после вызова основной функции очистки. |
BeforePublish , AfterPublish |
Задачи, добавленные в один из этих целевых объектов, выполняются до или после вызова основной функции публикация. |
BeforeResolveReferences , AfterResolveReferences |
Задачи, добавленные в один из этих целевых объектов, выполняются до или после разрешения ссылок на сборки. |
BeforeResGen , AfterResGen |
Задачи, добавленные в один из этих целевых объектов, выполняются до или после создания ресурсов. |
В системе сборки и пакете SDK для .NET есть еще много целевых объектов, см . целевые объекты MSBuild — пакет SDK и целевые объекты сборки по умолчанию.
Рекомендации по пользовательским целевым объектам
Свойства DependsOnTargets
и BeforeTargets
могут указывать, что целевой объект должен выполняться перед другим целевым объектом, но они оба необходимы в разных сценариях. Они отличаются тем, в каком целевом объекте указывается требование зависимостей. Вы можете контролировать только собственные целевые объекты и не можете безопасно изменять системные целевые объекты или другие импортированные целевые объекты, чтобы ограничения на выбор методов.
При создании пользовательского целевого объекта следуйте этим общим рекомендациям, чтобы убедиться, что целевой объект выполняется в указанном порядке.
Используйте атрибут, чтобы указать целевые
DependsOnTargets
объекты, которые необходимо выполнить перед выполнением целевого объекта. Для цепочки целевых объектов, которые вы управляете, каждый целевой объект может указать предыдущий элемент цепочки вDependsOnTargets
.Используйте
BeforeTargets
для любого целевого объекта, который вы не контролируете, что необходимо выполнить раньше (напримерBeforeTargets="PrepareForBuild"
, для целевого объекта, который должен выполняться рано в сборке).Используйте
AfterTargets
для любого целевого объекта, который не контролируется, что гарантирует доступность необходимых выходных данных. Например, укажитеAfterTargets="ResolveReferences"
для чего-то, что изменит список ссылок.Их можно использовать в сочетании. Например,
DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile"
.
Явные и неявные импорты
Проекты, созданные Visual Studio, обычно используют Sdk
атрибут в элементе проекта. Эти типы проектов называются проектами в стиле SDK. Ознакомьтесь с пакетами SDK для проекта MSBuild. Приведем пример:
<Project Sdk="Microsoft.Net.Sdk">
Когда проект использует Sdk
атрибут, два импорта неявно добавляются, один в начале файла проекта и один в конце.
Неявные импорты эквивалентны оператору импорта, как показано в первой строке в файле проекта, после Project
элемента:
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
и следующая инструкция импорта в качестве последней строки в файле проекта:
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
Этот синтаксис называется явным импортом пакета SDK. При использовании этого явного синтаксиса следует опустить Sdk
атрибут в элементе проекта.
Неявный импорт пакета SDK эквивалентен импорту определенных "common" .props
или .targets
файлов, которые являются типичной конструкцией в старых файлах проекта, например:
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
и
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Все старые ссылки должны быть заменены явным синтаксисом пакета SDK, показанным ранее в этом разделе.
С помощью явного синтаксиса пакета SDK можно добавить собственный код до первого импорта или после окончательного импорта пакета SDK. Это означает, что вы можете изменить поведение, задав свойства перед первым импортом, который вступит в силу в импортированном .props
файле, и вы можете переопределить целевой объект, определенный в одном из файлов ПАКЕТА SDK .targets
после окончательного импорта. С помощью этого метода можно переопределить BeforeBuild
или AfterBuild
как описано далее.
Следующие шаги
Существует гораздо больше возможностей для настройки сборки с помощью MSBuild. Дополнительные сведения см. в статье Настройка сборки и