Автоматизация сборки на .Net с использованием NuGet

Что имелось вначале


Крупная enterprise-система, являющаяся основной платформой компании. В состав входит ядро системы и набор плагинов под разные задачи. Плагины развиваются независимо друг от друга, требуют внесения изменений и расширений в общие библиотеки.

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

Поэтому было принято волевое решение создать инфраструктуру и настроить сценарии для автоматизации сборки проекта под разные плагины.

При решении задачи использовались следующие инструменты: NuGet, TeamCity, NAnt, Visual Studio 2010, SlowCheetah.

Зачем это нужно


Воспользуйтесь Тестом Джоэла для оценки своей работы. Действия, описанные в моем посте, покрывают пункты 2 и 3.

Решение задачи


Постепенно проект эволюционировал до разделения всех плагинов в отдельные проекты, создания под каждый проект своего репозитория в VCS (Version Control System). Проекты получили жесткие версии, устанавливаемые на билд-сервере (TeamCity). Версия файлов сборок составлялась из 4 цифр: мажорная и минорная версии функционала, номер ревизии в VCS и номер попытки запуска билд-конфигурации.

[assembly: AssemblyVersion("1.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Основные модули объединялись по функциональному назначению в несколько пакетов с помощью NuGet-а. Состав пакетов был описан nuspec-файлами, и пакеты пересобирались с новой версией после каждого коммита в ветку для релиза (срабатывал триггер в TeamCity). NuGet-пакеты хранились на билд-сервере и были доступны по локальной сети через расшаренный каталог.

Пример nuspec-файла
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>Core</id>
    <version>1.0.0.0</version>
    <authors>DefaultCompanyName</authors>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>
        Ключевые проекты приложения
    </description>
    <frameworkAssemblies>
      <frameworkAssembly assemblyName="System.Configuration" targetFramework="net35" />
      <frameworkAssembly assemblyName="System.Core" targetFramework="net35"/>
      <frameworkAssembly assemblyName="System.Drawing" targetFramework="net35"/>
      <frameworkAssembly assemblyName="System.Runtime.Serialization" targetFramework="net35"/>
      <frameworkAssembly assemblyName="System.ServiceModel" targetFramework="net35"/>
      <frameworkAssembly assemblyName="System.Windows.Forms" targetFramework="net35"/>
      <frameworkAssembly assemblyName="System.Xml" targetFramework="net35"/>
    </frameworkAssemblies>
  </metadata>
  <files>
     <file src="core\bin\Release\*.dll" target="lib" />
  </files>
</package>


Основным инструментом сборки был выбран NAnt и MSBuild. Проекты csproj расширялись дополнительными инструкциями для трансформации конфигов и скачивания зависимых библиотек.

При сборке проектов происходило определение зависимостей. Функция NuGet-а Enable-PackageRestore (с версии 1.6 включена в расширение для Visual Studio) позволяет выполнять автоматический поиск и скачивание nuget-пакетов нужных версий в процессе компиляции. Нет необходимости храненить бинарные файлы используемых библиотек в VCS. При выполнении сборки проекта на билд-сервере выполнялся аналогичный сценарий. Достаточно в репозитории проекта хранить саму утилиту NuGet с набором target-ов для MSBuild-а.

Изменения в файле проекта
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    ...
    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
    <RestorePackages>true</RestorePackages>
    ...
    <SlowCheetahTargets Condition=" '$(SlowCheetahTargets)'=='' ">..\.slowCheetah\SlowCheetah.Transforms.targets</SlowCheetahTargets>
    ...
    <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
    <Import Project="$(SlowCheetahTargets)" Condition="Exists('$(SlowCheetahTargets)')" />
</Project>

Каждому плагину требовалось модифицировать конфигурацию системы, добавляя или изменяя элементы в файл конфига. Я использовал проект SlowCheetah, выполняющий трансформацию xml-файлов. Файлы конфигов включались в nuget-пакеты с указанием target=«content» и добавлялись как файл проекта. Трансформация добавляла два файла. Например, для файла Client.config создаются файлы Client.Debug.config и Client.Release.config. В зависимости от текущей конфигурации, используемой при построении проекта, применятся та или иная трансформация.

Можно настроить артефакты, чтобы собраные проекты были доступны для скачивания с TeamCity через веб-интерфейс в виде zip-файла.

Заключение


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

Ссылки


NuGet
TeamCity
NAnt
SlowCheetah
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    0
    Почему использовался SlowCheetah, а не стандартное средство трансформации конфигов?
      0
      Например какое, для ASP.Net? Я больше не знаю. Или писать xslt-преобразование? Так это нужно въезжать во всю эту кухню, ведь не все используют их в повседневной жизни. А в данном случае это удобный гибкий инструмент с простым синтаксисом и быстрым внедрением в реальный проект.
        0
        Не знал, что он только для веб-проектов.
          0
          Не только. Хотя таржеты действительно из веба, использовать их можно где угодно:
           <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
           <Target Name="BeforeBuild">
            <TransformXml Source="hibernate.config" Transform="hibernate.$(ActiveConfig).config" Destination="$(TargetDir)\Config\hibernate.config" />
           </Target>


          * This source code was highlighted with Source Code Highlighter.
      0
      1) номер ревизии в VCS я не рекомендую использовать ибо после 2^16 у вас начнутся проблемы.
      2) Каждому плагину требовалось модифицировать конфигурацию системы, добавляя или изменяя элементы в файл конфига. А это (если речь идет про app.config) может статься из-за незнания кастомный конфиг секций и возможности выноса их в отдельные файлы…
        0
        С помощью трансформаций мы покрываем задачи добавления сервисов в секцию WCF, регистрацию плагинов в кастомной секции, добавление регистраций в unity-контейнер, включение логгеров в релиз-конфиграции и т.п.
          0
          А еще это может делать всё сетап. Если он, конечно, имеется.
        0
        Чуть-чуть сумбурно написано, и ощущение что не хватает вступления… но это ощущения, а в целом — огромное спасибо за статью. Сейчас решаю похожие проблемы, и статья дала много ключевых слов для дальнейшего изучения вопроса.
          0
          Согласен, что сумбурно, но хотелось запостить побыстрее. Думаю, напиши еще парочку статей на эту тему, где всё будет подробнее.
          0
          Наша команда испытывает необходимость в подобном решении, по этому тема очень интересна. Спасибо, за материал. Есть пара вопросов по вашей реализации.

          При сборке проектов происходило определение зависимостей. Функция NuGet-а Enable-PackageRestore (с версии 1.6 включена в расширение для Visual Studio) позволяет выполнять автоматический поиск и скачивание nuget-пакетов нужных версий в процессе компиляции. Нет необходимости храненить бинарные файлы используемых библиотек в VCS. При выполнении сборки проекта на билд-сервере выполнялся аналогичный сценарий. Достаточно в репозитории проекта хранить саму утилиту NuGet с набором target-ов для MSBuild-а.

          Вы референсите общий компонент с указанием необходимой версии или нет? Если взять такой пример. Есть компонент Company.Core, который вам надо «разделить» между продуктами A и B. Вы вносите изменение в Company.Core, и собираете проекты A и B. Оба продукта получат обновленную Company.Core? Если так, то в ситуации когда Company.Core был модифицирован для нужд продукта A, то продукт B нуждается в регресионном тестировании. Не сталкивались с такой ситуацией?

          Также очень инетересен процес дебага. Реально в локальном окружении использовать Debug версию Company.Core, а при сборке Release.

          Не пробовали шарить JavaScript code между несколькими проектами? Если да, то там есть какие-то подводные камни? Можно сделать auto resolve на момент билда?
            0
            Можно обновлять версии пакетов в плагинах по необходимости, не сразу как опубликуется новая версия, чтобы отложить тестирование до более подходящего времени. Более усложненные сценарии мы не применяли.

            Для дебага скомпилировнные в debug-конфигурации сборки ядра размещаются в output-директории плагина. В свойствах проекта плагина (это class library) при отладке указывается исполняемый сторонний файл. Далее можно запускать выполнение, ставить точки останова и т.п. Для тестирования интерфейса, например, можно подключать фейковые сервисы, используя конфигурацию ioc-контейнера и директивы компиляции.

            С js не проделывал такое, но можете посмотреть документацию nuget, там есть примеры с css.

          Only users with full accounts can post comments. Log in, please.