Создание собственного типа сборки (Build Action) в Visual Studio 2013

Visual Studio и MSBuild имеют болльше возможности для расширения. Одной из таких возможностей является возможность определения каким именно способом будет обрабатываться конкретный файл в проекте. Здесь я покажу каким образом можно создавать собственные типы сборки и для чего это может быть нужно.

Я не буду пояснять синтаксис MSBuild, поэтому если вы не знакомы с ним, то перед прочтением предлагаю ознакомиться.

Разрабатывая приложения в Visual Studio, наверное, каждый сталкивался с параметром Build Action в окне свойств файла.

В зависимости от этого параметра, MSBuild корректирует своё поведение относительно того, что именно нужно сделать с конкретным файлом. Например, мы можем пометить файл как ресурс или указать, что изображение будет экраном приветствия.

Представленные типы могут изменяться в зависимости от типа проекта. Например, типы сборки для консольного приложения могут отличаться от таковых для приложения ASP.NET.

При разработке своих приложений может также потребоваться добавить новый тип сборки и дополнить его некоторым поведением. Это может быть полезно в случаях, когда нам нужно выполнить какой-либо пре- или пост-процессинг. Например, для web-приложений это может быть процессинг LESS/SASS или сборка JS-файлов при помощи GruntJS.

Существует множество других способ сделать то же самое. Например, использовать pre- и post-build events, которые можно задать в свойствах проекта. Однако, приведенное решение по моему мнению гораздо удобнее использовать повторно и переносить на новые проекты без боли (например, через Nuget).

Создаем новый тип сборки

Чтобы создать новый Build Type необходимо открыть файл проекта (.csproj и др.) и добавить секцию AvailableItemName. Предположим, мы будем создавать новый тип сборки для компиляции LESS, тогда это может выглядеть так:

<ItemGroup>
     <AvailableItemName Include="LessBuild" />
</ItemGroup>

Добавление этого кода в файл проекта достаточно для того, чтобы новый тип сборки появился в списке.

Если мы выберем какой-либо файл в проекте и зададим соответствующий тип сборки, то определение этого файла в проекте изменится следующим образом:

<ItemGroup>
  <LessBuild Include="StyleSheet1.less" />
</ItemGroup>

Если мы попробуем собрать проект с новым типом сборки, то проект успешно соберется, но с ресурсом, для которого этот тип сборки задан, ничего не произойдет. Для этого нам нужно сделать ещё кое-что.

Задаем поведение для нового типа сборки

При сборке ресурса нам может потребоваться запускать какую-то стороннюю утилиту или задачу MSBuild. Обработка нового типа сборки сводится к созданию и запуску нового Target в проекте MSBuild.

<Target Name="LessBuildTarget" AfterTargets="AfterBuild" Condition="'@(LessBuild)' != ''">
</Target>

Рассмотрим подробнее как это работает.

  1. Target запускается каждый раз после сборки проекта из-за параметра AfterTargets="AfterBuild". Если вам необходимо что-то запустить перед сборкой проекта, то используйте вместо этого BeforeTargets="BeforeBuild".
  2. Если в проекте присутствуют файлы, для которых задан соответствующий тип сборки, то переменная @(LessBuild) будет содержать их имена. Параметр Condition предотвращает запуск скрипта, если в проекте нет ни одного файла с соответствующим типом сборки.

После того, как входная точка создана, остается обработать соответствующие файлы. Переменная @(LessBuild) в этом случае является строкой, в которой перечислены все файлы проекта с соответствующим типом сборки, перечисленные через точку с запятой. Получим из этой строки список файлов:

<Target Name="LessBuildTarget" AfterTargets="AfterBuild" Condition="'@(LessBuild)' != ''">
     <ItemGroup>
          <_FilesToProcess Include="@(LessBuild)"/>
     </ItemGroup>
</Target>

И теперь нам остается только обработать эти файлы, запустив какую-то внешнюю утилиту, задачу MSBuild или как-то ещё.

<Target Name="LessBuildTarget" AfterTargets="AfterBuild" Condition="'@(LessBuild)' != ''">
     <ItemGroup>
          <_FilesToProcess Include="@(LessBuild)"/>
     </ItemGroup>

     <Exec Command="lesscompile.exe %(_FilesToProcess.FullPath)" />
</Target>

Разумеется, этот кусок скрипта следует переписать несколько иначе, исходя из текущих потребностей (например, обрабатывая это в несколько шагов). В данном случае я намеренно не хочу усложнять, чтобы идея осталась понятной.

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