Используем сборочные скрипты на F# / C# в TFS 2012

от автора

Думаю, не я один вспоминаю Microsoft нехорошими словами, когда приходится модифицировать и/или расширять так называемый шаблон сборочного процесса (build process template) в TFS. Под катом история о том, как мы перешли от Xaml к скриптам на F# / C#. Как мы пытались интегрировать Fake в TFS, но в итоге получилось собственное решение — AnFake.

Статья будет полезна тем, кто использует TFS как CI-решение, но не в восторге от его шаблонов сборочного процесса.

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

Вот так выглядел «идеальный» процесс в наших глазах.

Регулярная сборка:

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

Поставка:

  • генерация release notes по багам и задачам из TFS;
  • публикация release notes на портале;
  • автоматическое прописывание номера сборки в багах и задачах;
  • выкладывание подготовленного поставочного архива в определенное место, для дальнейшего использования продуктом.

Рабочий процесс в нашей компании построен целиком на TFS-е, включая трекинг багов и задач; контроль версий и непрерывную интеграцию. CI-сборки в TFS-е задаются с помощью шаблона процесса (build process template). Первое, что мы попробовали сделать для реализации нашего «идеала» — это настроить шаблон процесса под себя и… столкнулись с рядом трудностей.

1. Часть сборочной логики меняется по мере развития модуля (в частности, подготовка тестовых семплов), т.е. эту логику желательно держать в системе контроля версий вместе с другими исходниками модуля. Но шаблон процесса в TFS 2012/2013 хранится отдельно и не версионируется (да, он лежит в VCS, но используется всегда только последняя версия). Почему это проблема? У нас как минимум две ветки: разработческая и стабильная (теоретически, могут еще возникать релизные ветки, если потребуется делать хотфиксы) и, по-крайней мере, некоторые шаги сборки в них отличаются, т.е. мы не можем собирать все ветки с помощью одного шаблона.

Возможные решения:

  • Версионировать шаблон «вручную», добавляя в название файла шаблона номер версии. В этом случае при слиянии веток нужно не забывать и про слияние шаблонов. А если вдруг кроме правки шаблона понадобятся собственные активности, то псевдо-версионирование придется применять еще и к dll-кам с этим активностями!
  • Вынести меняющуюся логику в скрипт и вызвать этот скрипт из шаблона. Скрипт хранить вместе с остальными исходниками модуля.

Мы выбрали скрипт. На первом этапе — обычный bat-файл.

2. Использование скрипта привело нас к другой проблеме — информативность логирования резко упала. Сообщения просто пишутся на консоль, у них нет уровней, нельзя отличить предупреждение от диагностики. Можно выделить только ошибку, записав сообщение в stderr. Но каждая строка, вычитываемая TFS-ом из stderr, будет им восприниматься как отдельная ошибка. Т.о., например, отформатированное 3-х строчное сообщение об ошибке в TFS-е будет светиться как 3 отдельные ошибки. «Не аккуратненько», но жить можно.

3. Скрипт начал расти. Кроме шага подготовки семплов в нем вскоре оказались и тесты производительности, и FP/TP тесты. Почему так? Казалось бы, запуск тестов должен быть в шаблоне. Дело в том, что данные тесты проводятся инструментами, которые также являются частью нашего модуля и, соответственно, развиваются вместе с ним, поэтому и способ запуска тоже меняется. Оставляя их в шаблоне, имеем уже рассмотренную в п.1 проблему.

Дальше — больше. Начало назревать желание иметь доступ из скрипта к свойствам текущей сборки (название ветки, номер версии и т.п.), а также связываться с другими сборками для организации конвейера.

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

Кандидатами для скрипта стали PSake, Rake и Fake. После беглого анализа PSake отпал из-за PowerShell-синтаксиса; Rake — т.к. с Ruby никто в нашей команде знаком не был; и остался Fake. Справедливости для, надо сказать, что F# на тот момент в команде тоже никто не знал, однако F# поддерживается Visual Studio из коробки, поэтому остановились на нем.

К сожалению, Fake с TFS-ом не дружит. Но что, мы не программисты что ли?! Прикрутили сами. Можно сказать, проблемы 1 и 3 решились. Однако проблема 2 усугубилась, т.к. теперь вся сборка шла в скрипте, результат, показываемый в Visual Studio, выглядел удручающе — предупреждений не видно (в том числе и от компилятора); какие этапы выполнялись — не видно; ошибки показываются жуткими «портянками». Пытались порешать эту проблему, написав расширение для Fake, но в Fake не оказалось единой точки логирования, из которой можно было бы перенаправить структурированный вывод в TFS. Попутно возникло еще несколько неудовлетворенностей (подробнее можно почитать тут).

Тем не менее, идея использовать полноценный язык программирования для описания сборки понравилась. В итоге я решил на досуге воплотить идею Fake-а в собственном исполнении. Упор был сделан на:

  • расширяемость (например, подключаемый html-отчет о сборке или публикация результатов тестов в базу данных и т.п.);
  • интеграцию с различными внешними системами (первым в очереди был TFS);
  • читабельный и понятный лог и вывод ошибок.

Кроме того, вспомнив свои первые попытки понять F#-скрипты, я подумал: «неплохо было бы иметь возможность писать скрипты и на С# тоже» и включил это требование в скоуп работ. Да, можно писать сборочный скрипт на C#, правда, к сожалению, IntelliSense в этом случае не работает — не знает студия, что C# может быть скриптом.

В результате получился весьма приличный инструмент (я назвал его AnFake = Another F# Make), который может быть полезен всем, кто «воюет» с TFS-ом. Давайте посмотрим, как это выглядит и что он может (в данный момент AnFake в основном рассчитан на TFS, поэтому дальнейшее изложение пойдет в контексте TFS-а).

Пусть у нас есть solution под названием Demo, который лежит в системе контроля версий TFS:

$/TeamProject/Demo     /dev          /Demo.App          /Demo.Lib          /Demo.Lib.Test          Demo.sln 

Пусть также у нас настроен workspace ‘Demo.dev’ с единственным мапингом:

$/TeamProject/Demo/dev: C:\Projects\Demo.dev 

(далее везде предполагается, что используется схема “один workspace на ветку”)

Открываем C:\Projects\Demo.dev\Demo.sln в Visual Studio. Устанавливаем AnFake как NuGet пакет:

PM> Install-Package AnFake 

При установке пакета в корневой папке solution-а будет создано несколько файлов:

  • build.fsx — базовый сборочный скрипт, включающий вызов MSBuild и запуск тестов;
  • anf.cmd — алиас для вызова AnFake (чтобы не писать каждый раз ./packages/AnFake.x.y.z/bin/AnFake.exe);
  • .workspace — текстовый файл с описанием мапингов из workspace-а, в рамках которого был скачен текущий solution;
  • .nuget\NuGet.config — [создается только, если его не было] содержит опцию disableSourceControlIntegration, чтобы предотвратить комит бинарных файлов пакетов в VCS.

(кстати, установочный скрипт — это тоже F#-скрипт для AnFake)

Базовый скрипт build.fsx выглядит следующим образом:

Tfs.PlugIn()  let out = ~~".out" let productOut = out / "product" let testsOut = out / "tests" let tests = !!"*/*.Test.csproj" let product = !!"*/*.csproj" - tests  "Clean" => (fun _ ->         let obj = !!!"*/obj"     let bin = !!!"*/bin"      Folders.Clean obj     Folders.Clean bin     Folders.Clean out ) "Compile" => (fun _ ->     MsBuild.BuildRelease(product, productOut)     MsBuild.BuildRelease(tests, testsOut) ) "Test.Unit" => (fun _ ->      VsTest.Run(testsOut % "*.Test.dll") ) "Test" <== ["Test.Unit"] "Build" <== ["Compile"; "Test"] 

…то же на C#

Tfs.PlugIn();  var outDir = ".out".AsPath(); var productOut = out / "product"; var testsOut = out / "tests"; var tests = "*/*.Test.csproj".AsFileSet(); var product = "*/*.csproj".AsFileSet() - tests;  "Clean".AsTarget().Do(() =>  {     var obj = "*/obj".AsFolderSet();     var bin = "*/bin".AsFolderSet();      Folders.Clean(obj);     Folders.Clean(bin);     Folders.Clean(out); }); "Compile".AsTarget().Do(() =>  {     MsBuild.BuildRelease(product, productOut);     MsBuild.BuildRelease(tests, testsOut); }); "Test.Unit".AsTarget().Do(() =>  {     VsTest.Run(testsOut % "*.Test.dll"); }); "Test".AsTarget().DependsOn("Test.Unit"); "Build".AsTarget().DependsOn("Compile", "Test"); 

В принципе, этого уже достаточно, чтобы запустить локальную сборку:

PM> .\anf Build 

(здесь мы использовали Package Manager Console, но AnFake можно запускать из любой консоли командной строки)

В результате получим примерно такой отчет:

Видим, что компиляция прошла с одним предупреждением; было выполнено 2 теста, один из которых Skipped. Ok, комитаем изменения. Комит будет содержать файл .nuget/packages.config (сюда NuGet прописывает пакеты solution-уровня) и файлы, созданные во время установки AnFake-а в корневой папке solution-а.

Теперь запустим эту же сборку через TFS. Для этого нужно установить специальный шаблон AnFakeTemplate.xaml (делается только один раз для team project-а):

PM> .\anf "[AnFakeExtras]/vs-setup.fsx" "BuiltTemplate" -p "TeamProject" 

где вместо TeamProject, естественно, нужно подставить имя вашего проекта в TFS-е.

Команда создаст временный workspace с именем AnFake.BuildTemplate.yyyymmdd.hhmmss; выкачает во временную папку $/TeamProject/BuildProcessTemplates; добавит шаблон AnFakeTemplate.xaml и несколько сопутствующих библиотек. Команда ничего НЕ комитает автоматически, дабы не вызвать бурю справедливого возмущения. Поэтому идем в Visual Studio -> Team Explorer -> Pending Changes, переключаемся на workspace AnFake.BuildTemplate, просматриваем изменения (убеждаемся, что там ничего лишнего) и комитаем.

Теперь можем создать определение сборки (build definition):

  • Идем в Visual Studio -> Team Explorer -> Builds, выбираем New Build Definition.
  • На вкладке Process, в секции Build Process Templates нажимаем Show details.
  • Нажимаем New и в поле Version control path вводим (или выбираем через Source Control Explorer) $/TeamProject/BuildProcessTemplates/AnFakeTemplate.v2.xaml; жмем Ok (делается только один раз для team project-а)
  • В выпадающем списке Build process file выбираем AnFakeTemplate.v2.xaml и сохраняем.

Запускаем сборку по только что созданному определению: Queue Build в контекстном меню. В результате получаем вот такой отчет:

Шаблон AnFakeTemplate делает три простых шага:

  • Выкачивает solution из системы контроля версий.
  • Восстанавливает NuGet-пакеты solution-уровня (т.е. скачивает собственно AnFake).
  • Передает управление в AnFake.
  • Все остальное определяется уже в скрипте.

Шаблон достаточно простой и сохраняет совместимость на протяжении целого ряда версий. Таким образом вы можете апгрейдить пакет AnFake без необходимости обновления шаблона. Можно даже в разных solution-ах иметь разные версии AnFake и они будут благополучно собираться одним шаблоном.

Я продемонстрировал базовый сценарий интеграции AnFake в TFS. Однако интеграция не ограничивается шаблоном: есть возможность из скрипта обращаться к свойствам текущей сборки; получать доступ к артефактам других сборок; есть даже возможность организовать конвейер. Кроме того, AnFake предоставляет дополнительную автоматизацию при работе с мапингами и workspace-ами: мапинги можно хранить в VCS вместе с остальными исходниками проекта, workspace будет создаваться и обновляться автоматически.

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

Буду благодарен за обратную связь. Спасибо.

ссылка на оригинал статьи http://habrahabr.ru/post/256543/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *