Привет всем читателям !
Не так давно я начал использовать для сборки сервером непрерывной интеграции некоторых проектов NAnt наряду с уже освоенным MSBuild. Как всегда, в процессе работы обнаруживаются бонусы с разными знаками (как плюс, так и минус). Тех, кому интересны детали сборки разными движками (MSBuild, NAnt) в контексте сервера CI, с удовольствием приглашаю под кат.
Процесс сборки
Сначала вкратце рассмотрим процесс сборки, то есть список фаз которые проходят исходные файлы, чтобы стать конечным продуктом:
- чистка сборочной папки от результатов предыдущей сборки
- инициализация процесса сборки (например, установка нужной версии в файлы кода)
- компиляция файлов с кодом
- прогон тестов
- создание файлов специального назначения на основе бинарных файлов (инсталляторы msi, пакеты NuGet и т.п.)
- публикация результатов сборки (выкладка на FTP, nuget push)
Эта модель сборки достаточно упрощена, чтобы не загромождать обсуждение несущественными деталями. Так фаза компиляции может включать в себя загрузку пакетов из хранилища, фаза тестирования разбиваться на прогоны модульных и интеграционных тестов. Возможность адаптации инструмента сборки под нужный процесс, на мой взгляд, является достаточно существенным фактором в выборе.
Поддержка процесса сборки в MSBuild
В виду того, что MSBuild является штатным сборщиком для IDE (Visual Studio, SharpDevelop), то изначально работали с ним.
Для сборки проекта целиком использовалась пара дополнительных скриптов. Почему именно пара, а не один, спросите вы. Всё достаточно просто: MSBuild не умеет подгружать targets-файлы в процессе выполнения скрипта. Он сначала импортирует все заданные в скрипте файлы, а затем стартует процесс сборки. Поэтому первый скрипт отвечал только за скачивание дополнительных targets-файлов из репозитория NuGet, а второй, пользуясь всем свалившимся на него богатством, выполнял сценарий сборки.
Для сборки отдельных проектов использовались сгенерированные IDE файлы-csproj с лёгким обогащением их своими переменными. Оказалось, что с наследованием проектов у MSBuild дело обстоит туго. Для правильного прогона родительский скрипт должен был полностью передавать весь набор параметров внутрь дочернего, простого определения свойства в теле родителя было недостаточно. Это, конечно, не смертельно, но заставляет нас делать лишнюю работу.
Если с тремя первыми фазами, особых проблем нет, то реализация оставшихся требует некоторой сноровки. Т.к. файлы csproj используются IDE для управления проектом, то сильно менять их нельзя. Выходом стало написание в главном скрипте сборки сценария всех оставшихся фаз. Плюсы такого решения в том, что всё описано в одном месте. Однако, когда число production и парных им unit-test проектов возрастает до десятка, поддерживать такой скрипт сборки становится сложно.
IDE любят генерировать отдельные выходные папки на каждую комбинацию Platform/Configuration, поэтому определить в процессе сборки, в какой папке у тебя находятся dll-ки от Reference-проекта не всегда является тривиальной задачей.
Если мы решаем использовать у себя в проекте репозиторий пакетов NuGet, то тут же получаем дублирование. Ссылка на dll из пакета будет присутствовать в двух местах, а именно в файлах csproj и packages.config. У клиента Nuget имеются известные проблемы при поддержании синхронности двух этих файлов. В частности, обновление пакетов не всегда приводит к желаемому результату.
Поддержка процесса сборки в NAnt
NAnt на сегодняшний день имеет актуальную версию 0.92 (релиз лета 2012 года), поэтому в своих изысканиях я опирался именно на неё.
Здесь мы, пожалуй, начнём с выявленных недостатков.
Основные IDE (Visual Studio, SharpDevelop) не любят NAnt и «из коробки» формат его проектов не поддерживают. Хотя, ради полноты картины можно отметить, что одна из старых версий SharpDevelop NAnt поддерживала. Почему в текущей ветке развития SharpDevelop поддержка NAnt свёрнута в ноль для меня пока остаётся загадкой.
NAnt не обладает некоторыми проприетарными бонусами, позволяющими ему собирать некоторые типы проектов без помощи MSBuild (например, WPF). О минимизации последствий работы с MSBuild скажем чуть позже.
Что же мы имеем из приятных бонусов?
Работа с файлами по маскам позволяет нам минимизировать объём работы по поддержке единожды написанных скриптов.
Наследование свойств между проектами позволяет нам не заботиться о явной передаче всех значений между проектами разных уровней. Например, легко организовать сбор всех результатов компиляции в одной папке на уровне Solution вместо размножения папок bin, obj в каждом отдельном проекте.
Императивность работы NAnt позволяет легко модифицировать отдельные target'ы проектов по своему усмотрению. Реализация типовой задачки: скопировать помимо библиотек еще и пару самодельных конфигов при помощи BeforeBuild и AfterBuild событий (внутри них не используются задачи MSBuild, а обычно используются cmd- или Powershell-скрипты) смотрится «костыльной» по сравнению с явным использованием родной задачи NAnt copy.
Система вывода штатного сборщика Java Ant давно и хорошо парсится на сборочных серверах, соответственно с NAnt особых проблем нет. Для сервера сборки Jenkins (Hudson) наличествует отдельный плагин для NAnt.
NAnt, в отличие от MSBuild, умеет динамически загружать наборы дополнительных задач при явном на то указании внутри скриптов или загружает их автоматически, если их нужным образом расположить рядом с NAnt.exe. Таким образом, сам сборщик может распространяться способом «xcopy», что значительно облегчает использование единой конфигурации сборщика параллельно на нескольких машинах. Из полезных библиотек с дополнениями штатного набора задач хочется отметить:
- NAnt Contrib большой набор дополнений для NAnt, обеспечивающий в том числе взаимодействие с MSBuild и Subversion
- Wix позволяет собирать инсталляторы MSI
- Nuget бонусы для работы с менеджером пакетов
По итогам всего выше сказанного для меня оптимальным сочетанием стало такое: сборка MSBuild при работе в IDE, запуск NAnt при работе сервера CI.
Сборка проекта WPF
В заключение своего поста хотел рассказать о ложке мёда в бочке дёгтя, а именно о сборке проекта WPF при помощи NAnt. Скажу сразу, волшебства по полному преодолению двойной компиляции (xaml -> cs -> dll) не будет. Однако, обучить MSBuild ходить в лоточек под зорким присмотром NAnt всё-таки можно.
При запуске MSBuild в контексте сборки NAnt у нас в наличии две ключевые проблемы:
- MSBuild ошибочно думает, что проекты зависимостей нужно собирать
- MSBuild ищет dll с зависимостями там, где они должны были оказаться после сборки проектов-зависимостей
Первая проблема решается установкой ключа сборки /p:BuildProjectReferences=false.
Вторая в лоб не решается, поэтому подойдём немного с иной стороны. Содержать статическую версию файла csproj для прогона сборки через NAnt нерентабельно, т.к. поддерживать придётся обе версии. Значит, нужно генерировать такую версию «на лету». Возьмём и напишем небольшое преобразование XSL, запустим его через задачу style NAnt и скормим MSBuild скорректированный проект. При запуске задачи style передаётся один параметр, содержащий путь к уже скомпилированным файлам DLL.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msbuild="http://schemas.microsoft.com/developer/msbuild/2003">
<xsl:output indent="yes" method="xml"/>
<xsl:param name="dllPath"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="//msbuild:ItemGroup/msbuild:ProjectReference">
<xsl:element name="Reference" namespace="http://schemas.microsoft.com/developer/msbuild/2003">
<xsl:attribute name="Include">
<xsl:value-of select="msbuild:Name/text()"/>
</xsl:attribute>
<xsl:element name="HintPath" namespace="http://schemas.microsoft.com/developer/msbuild/2003">
<xsl:value-of select="$dllPath"/>
<xsl:value-of select="msbuild:Name/text()"/>.dll</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="*|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Таким образом, нам удаётся добиться того, что MSBuild собирает свой проект изолированно, ничего не подозревая о других проектах. Как говорят люди «меньше знаешь, лучше спишь», что для управления MSBuild весьма актуально.
Место (вместо) выводов
Как и чем пользоваться всегда надо решать самому или внутри команды, тщательно взвесив все «за» и «против».
Начать лучше всего с MSBuild, т.к. он обычно уже установлен на машине разработчика и не требует лишних телодвижений по основной настройке.
Работа с NAnt тоже не слишком сложна, но требует от разработчика некоторого «поворота»
Автор: unlocker