Расширение процесса сборки 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

  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

Приведенный ниже пример похож на пример с атрибутами 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-&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 вызывает целевой объект BeforeBuild перед основным целевым объектом CoreBuild, а целевой объект AfterBuild — после целевого объекта CoreBuild. По умолчанию пустые целевые объекты в общих целевых объектах ничего не делают, но их поведение по умолчанию можно переопределить, определив нужные целевые объекты в файле проекта. Методы, описанные ранее в этой статье, предпочтительны, но вы можете столкнуться с более старым кодом, использующим этот метод.

Если в проекте используется пакет 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>
    

    Обратите внимание, что Sdk атрибут на элементе верхнего уровня Project был удален.

  4. Выполните сборку файла проекта.

Таблица предопределенных целевых объектов

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

Имя целевого объекта 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 могут указывать, что целевой объект должен выполняться перед другим целевым объектом, но они оба необходимы в разных сценариях. Они отличаются тем, в каком целевом объекте указывается требование зависимостей. Вы можете контролировать только собственные целевые объекты и не можете безопасно изменять системные целевые объекты или другие импортированные целевые объекты, чтобы ограничения на выбор методов.

При создании пользовательского целевого объекта следуйте этим общим рекомендациям, чтобы убедиться, что целевой объект выполняется в указанном порядке.

  1. Используйте атрибут, чтобы указать целевые DependsOnTargets объекты, которые необходимо выполнить перед выполнением целевого объекта. Для цепочки целевых объектов, которые вы управляете, каждый целевой объект может указать предыдущий элемент цепочки в DependsOnTargets.

  2. Используйте BeforeTargets для любого целевого объекта, который вы не контролируете, что необходимо выполнить раньше (например BeforeTargets="PrepareForBuild" , для целевого объекта, который должен выполняться рано в сборке).

  3. Используйте AfterTargets для любого целевого объекта, который не контролируется, что гарантирует доступность необходимых выходных данных. Например, укажите AfterTargets="ResolveReferences" для чего-то, что изменит список ссылок.

  4. Их можно использовать в сочетании. Например, 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. Дополнительные сведения см. в статье Настройка сборки и