Поделиться через


Расширение процесса сборки 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 всех общих целевых объектах. Например, целевой BuildDependsOnTargets объект содержит значение атрибута "$(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, описанных ранее в этой статье, вместо атрибута, используемого Visual Studio при создании файлов проекта, следует использовать метод Sdk ручного импорта.

<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. Создайте файл проекта.

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

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

Имя целевого объекта Описание
BeforeCompile, AfterCompile Задачи, вставляемые в один из этих целевых объектов, выполняются до или после завершения компиляции ядра. Большинство настроек выполняются в одном из этих двух целевых объектов.
BeforeBuild, AfterBuild Задачи, вставляемые в одну из этих целевых задач, запускаются до или после всего остального в сборке. Заметка:BeforeBuild и AfterBuild целевые объекты уже определены в комментариях в конце большинства файлов проекта, что позволяет легко добавлять события до и после сборки в файл проекта.
BeforeRebuild, AfterRebuild Задачи, вставляемые в один из этих целевых объектов, выполняются до или после вызова основных функций перестроения. Порядок выполнения целевого объекта в Microsoft.Common.target: BeforeRebuild, CleanBuild, а затем 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. См. Настройте вашу сборку.