[Из песочницы] Используем сборочные скрипты на 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 можно запускать из любой консоли командной строки)В результате получим примерно такой отчет:

722a5b35133548668540619d39e8756b.png

Видим, что компиляция прошла с одним предупреждением; было выполнено 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 в контекстном меню. В результате получаем вот такой отчет: 0cf5f080c3d24eb3bc996f52b415d8c5.pngШаблон AnFakeTemplate делает три простых шага:

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

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

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

© Habrahabr.ru