Мы в TeamCity всегда уделяли особое внимание .NET, его многочисленным инструментам и фреймворкам тестирования. В этом посте мы хотим рассказать о недавних обновлениях в нашей поддержке .NET и поделиться примером демо-проекта, который их иллюстрирует.
Сейчас поддержка .NET в TeamCity реализована с помощью огромного набора специализированных «ранеров» и «билд фичей». Ранеры обеспечивают интеграцию билда со сторонним софтом, а фичи выступают функциональными надстройками билда.
До версии 2020.1, TeamCity предоставлял следующие .NET компоненты:
- MSBuild?? – ранер с поддержкой MSBuild и Mono XBuild
- Visual Studio (sln) – ранер, имитирующий Visual Studio IDE (devenv), используя MSBuild
- Visual Studio 2003 – то же что и предыдущий ранер, но с учетом специфики Visual Studio 2003
- Visual Studio Tests – ранер, запускающий Visual Studio и MS тесты
- .NET Process Runner – ранер, запускающий .NET приложения
- .NET CLI Runner – ранер, обеспечивающий интеграцию TeamCity и .NET Core CLI
- NUnit – ранер, запускающий NUnit-тесты
- Набор ранеров для NuGet – поддержка утилиты nuget.exe CLI для Windows
- Встроенный в TeamCity репозиторий пакетов NuGet
- TeamCity symbol server – накапливает и предоставляет отладочную информацию
- Интеграция с Azure DevOps (ранее Team Foundation Server)
- Duplicates Finder (ReSharper) – поиск дублирования кода
- Inspections (ReSharper) – инспекции кода на базе JetBrains Resharper
- FxCop – статический анализ качества кода на базе FxCop
- JetBrains dotTrace – тестирование производительности приложений с dotTrace Command-Line Profiler
- JetBrains dotMemory Unit – тестирование проблем памяти с JetBrains dotMemory Unit
- Поддержка платформы разработки 3D-приложений Unity
Такое разнообразие компонентов позволяет TeamCity использовать весь потенциал .NET, но имеет и минусы. Например, чтобы правильно сгруппировать части проекта и выбрать ранеры, чтобы построить приложения и протестировать их на требуемых ОС, каждый раз приходится учитывать множество факторов:
- Тип проекта
- Целевая платформа
- .NET-фреймворк
- Тестовый фреймворк
- Операционная система для тестирования
и т.д.
К счастью, с появлением .NET Core с открытым кодом и поддержкой кроссплатформенности, Microsoft постарались унифицировать и упорядочить инструменты разработки, объединив их в .NET SDK. Следующим шагом развития стал .NET 5 – он объединил .NET Core, .NET Framework, Xamarin и Mono. С версии 2020.1 TeamCity также объединил большинство своих компонентов, отвечающих за построение, тестирование и развертывание проектов, в один ранер .NET. Он консолидирует возможности ранеров из списка выше и предоставляет унифицированный подход. Мы надеемся, что это сильно упростит работу с .NET для наших пользователей. Новый ранер поддерживает:
- Команды .NET CLI
- Windows и кроссплатформенный MSBuild
- «Честный» Visual Studio IDE (devenv)
- Запуск Windows и кроссплатформенных тестов, в том числе NUnit и XUnit
- Запуск Windows, .NET процессов и командных скриптов на разных операционных системах
- Кроссплатформенную статистику покрытия кода
- Docker
Мы будем ограниченно поддерживать устаревшие ранеры, чтобы обеспечить плавную миграцию проектов на единый ранер .NET, но дальнейшего развития они не получат. Рекомендуем учесть это при создании новых конфигураций и при переходе на .NET 5 из последних версии Visual Studio и Rider.
Структура демо-проекта
Технически, ранер .NET – результат глубокой переработки привычного пользователям раннера .NET CLI. В нём появилась масса новых возможностей, которые мы хотим показать на примере демонстрационного .NET-проекта. Его исходный код и скрипт конфигураций TeamCity находятся в этом репозитории. А уже развернутый проект TeamCity можно посмотреть на этом демо-сервере.
Демо-проект .NET состоит из нескольких .NET проектов:
Название |
Тип проекта |
Описание |
Конфигурации TeamCity |
Развертывание |
.NET Standard 1.2 |
Библиотека общей логики |
NuGet |
||
.NET Standard 1.2 |
Библиотека настройки IoC |
NuGet |
||
.NET 5.0 |
Тесты общей логики |
|
||
.NET 5.0 |
Консольное приложение |
Docker |
||
.NET 5.0 |
Web приложение |
Docker |
||
.NET 4.8 WPF |
Десктопное приложение |
дистрибутив Windows |
||
UAP |
UWP-приложение |
пакет UWP |
||
.NET Standard 1.2 |
Общая библиотека представлений Xamarin |
пакет Android |
||
Xamarin Android |
Мобильное приложение Android |
пакет Android |
Для настройки CI/CD-процесса, мы используем иерархию из проектов и билд-конфигураций. Хотя TeamCity предоставляет дружественный пользовательский интерфейс, все проекты и конфигурации были созданы средствами TeamCity Kotlin DSL. Так удобнее версионировать настройки проектов, а затем делиться ими. При желании вы сможете использовать их на своем сервере, избежав ручной настройки. Более подробную информацию о том, как создавать конфигурации через код, используя DSL, можно найти здесь (на английском).
Для запуска конфигураций в туториале мы используем 2 типа агентов:
- Windows 10 x64 Pro 10.0.19041
- Visual Studio 2019 Pro 16.8.1
- Docker (Windows container) 19.03.13
- .NET SDK 5.0
- Ubuntu 16.04
- Docker 18.06.3
Первым шагом мы создали и разместили на GitHub стандартный проект на Maven, используя IntelliJ IDEA 2020.2.2 с поддержкой всех его возможностей: подсветкой кода, рефакторингом и т.д. Чтобы сделать поддержку проекта еще более удобной, в его DSL-настройках мы использовали наследование типов Kotlin.
Этот DSL-код нужно подключить к соответствующему проекту .NET в TeamCity. Если вы решите разместить проект у себя на сервере, создайте новый проект в TeamCity с VCS root’ом, указывающим на наш репозиторий с DSL-настройками. Затем, в секции Versioned Settings, включите синхронизацию настроек с системой версионирования и выберите формат настроек “Kotlin”:
После синхронизации настроек, TeamCity подгрузит DSL-код и создаст вложенные подпроекты и конфигурации из него.
В корневой проект .NET входят два подпроекта: «Building» и «Deployment». Они содержат соответствующие их названиям билд-конфигурации.
Помимо них, прямо в корневом проекте .NET лежат две общие билд-конфигурации – Build и Deploy. Первая собирает все приложения и пакеты из подпроектов:
вторая – разворачивает их, в данном случае в репозитории Docker и NuGet:
Все конфигурации сборки объединяет то, что каждая содержит набор системных параметров, передаваемых в .NET-команды в виде пар ключ-значение /p:key=value
. Например, когда в билд-конфигурации определен системный параметр system.configuration=Release
, то при запуске различных команд им передается параметр /p:configuration=Release
. Префикс system.
при этом пропадает, так как в TeamCity он указывает на тип параметра, а за его пределами будет лишним. Все приведенные ниже системные параметры, определенные в наших конфигурациях сборки, не специфичны для TeamCity и описаны в различной документации по .NET:
Параметр | Значение в конфигурациях TeamCity | Описание | |
Все конфигурации сборки | configuration | Release | MSBuild-конфигурация. |
VersionPrefix | 1.0.0 | Используется как базовая версия для приложений и пакетов. | |
VersionSuffix | beta%build.number% | Используется как пререлизная метка в версии для приложений и пакетов. | |
Build console and web | InvariantGlobalization | true | Выполнять приложение без доступа к ресурсам, специфичным культуре. |
Build Windows desktop | PublishDir | ../bin/Clock.Desktop/win/ | Определяет путь публикации приложения. |
AppxPackageDir | ../bin/Clock.Desktop.Uwp/win/ | Определяет путь публикации пакета UWP приложения. | |
Pack | Copyright | Copyright 2020 JetBrains | Метаданные NuGet-пакетов. |
Title | TeamCity .NET sample | ||
RepositoryType | git | ||
RepositoryUrl | https://github.com/JetBrains/teamcity-dotnet-samples.git | ||
RepositoryBranch | refs/heads/master |
Эти параметры можно включить непосредственно в проектные файлы или передавать через параметры командной строки. Лучше использовать системные параметры TeamCity, так как это позволяет не заботиться о деталях передачи специальных символов – TeamCity позаботится об этом сам. Рассмотрим конфигурации тестирования и сборки подробнее.
Конфигурации «Test on Windows и Test on Linux»
Две эти конфигурации тестируют общую логику приложений и собирают статистику покрытия кода в Windows агента #1 и в Linux Docker-контейнере mcr.microsoft.com/dotnet/core/sdk:5.0, используя один шаг сборки .NET. Для сценария Linux Docker, в UI он выглядит так:
В этом сценарии тесты и тестируемые библиотеки собираются и тестируются в Linux Docker-контейнере с .NET SDK 5.0. Конфигурация для тестов под Windows отличается лишь тем, что не использует Docker.
Тестовый проект Clock.Tests является приложением .NET 5.0, поэтому для построения и запуска тестов достаточно одной .NET Core CLI команды test
. Для сбора и анализа статистики покрытия кода используется кроссплатформенный инструмент для оценки покрытия кода JetBrains dotCover из пакета JetBrains.dotCover.DotNetCliTool, который устанавливается как инструмент TeamCity. В DSL, тестовые конфигурации имеют общего предка TestBase и две конфигурации для тестов на Linux и для тестов на Windows.
Конфигурации «Build console and web for win-x64» и «Build console and web for linux-x64»
Эти две TeamCity конфигурации для Linux и для Windows собирают по два проекта Clock.Console и Clock.Web и состоят из двух шагов сборки, соответствующих этим проектам. Конфигурации наследуются от BuildConsoleAndWebBase, который в свою очередь наследуется от базового типа для всех конфигураций сборки приложений – BuildBase. Оба шага можно было бы объединить в один, указав в Projects сразу два проекта, но в этом случае было бы сложно разделить бинарные файлы от разных приложений, как это сейчас сделано через outputDir. Так как оба приложения имеют тип .NET 5.0, как и в предыдущем случае, для построения и публикации достаточно одной .NET Core CLI команды publish
.
Вот как выглядит первый шаг в UI для Linux, который строит и публикует приложение Clock.Console в виде единственного запускаемого файла в папку, определенную в поле Output directory:
Для Windows, данный шаг отличается полем Runtime, в котором определено значение win-x64, и полем Output directory со значением bin/Clock.Console/win-x64.
Второй шаг для построения и публикации приложения Clock.Web для Linux отличается от первого шага, помимо пути к проектному файлу, полем Output directory со значением bin/Clock.Web/linux-x64. После завершения шагов, TeamCity публикует построенные приложения как артефакты билда.
Для win-x64:
- bin/Clock.Console/win-x64/Clock.Console
- bin/Clock.Console/win-x64/Clock.Web
Для linux-x64:
- bin/Clock.Console/linux-x64/Clock.Console
- bin/Clock.Console/linux-x64/Clock.Web
Конфигурация «Build Windows desktop»
В этой конфигурации всего один шаг:
На этом шаге запускается Windows MSBuild из Visual Studio 2019 на агенте #1 для выполнения «таргетов» Restore, Rebuild и Publish последовательно для каждого из двух проектов:
- Clock.Desktop/Clock.Desktop.csproj
- Clock.Desktop.Uwp/Clock.Desktop.csproj
Результаты сборок публикуются в разные директории, определенные в системных параметрах конфигураций PublishDir (для Clock.Desktop) и AppxPackageDir (для Clock.Desktop.Uwp). Эти директории далее публикуются как артефакты билда.
Конфигурация «Build Android app»
Эта конфигурация строит мобильное приложение Android, используя Windows MSBuild из Visual Studio 2019, на агенте #1:
Она аналогична предыдущей конфигурации, но вместо MSBuild «таргета» Publish здесь используется «таргет» SignAndroidPackage, чтобы опубликовать подписанный Android-пакет.
Конфигурация «Pack»
Эта конфигурация создает два NuGet-пакета и содержит один шаг .NET CLI с командой pack
, выполненной последовательно для двух проектов – Clock и Clock.IoC:
Такие метаданные для NuGet-пакетов как название, тип репозитория и т.д., определены через системные параметры конфигурации TeamCity – их список есть в таблице выше. После успешного выполнения этой конфигурации, в артефактах появляются NuGet-пакеты, готовые для отправки в репозиторий на этапе развертывания.
Конфигурация «Build»
Эта особая конфигурация TeamCity не содержит шагов сборки. Она предназначена для того, чтобы собрать все артефакты приложений и пакетов в один TeamCity-билд через артефакт-зависимости на остальные конфигурации сборки:
Конфигурация «Deploy»
Это похожая конфигурация для развертывания, которая не содержит шагов, а только зависимости на другие конфигурации:
- Push image … строит и публикует образы для Linux и Windows
- Push multi-arch image … создаёт и публикует манифесты для мульти-архитектурных образов Docker.
- Publish to NuGet публикует ранее созданные NuGet пакеты в TeamCity NuGet.
Чтобы подключить опубликованные NuGet-пакеты с общей логикой к проектам, можно использовать этот NuGet-источник. Для запуска приложений Clock.Console в Docker используйте команды:
docker pull nikolayp/clock-console
docker run -it --rm nikolayp/clock-console
А для Clock.Web:
docker pull nikolayp/clock-web
docker run -it --rm -p 5000:5000 nikolayp/clock-web
После запуска веб-приложения в контейнере, оно будет доступно по адресу http://localhost:5000/.
Все приложения и пакеты можно посмотреть в артефактах здесь.
Заключение
Microsoft активно развивает .NET 5, ведь он предоставляет разработчикам свободный и единый инструментарий. Мы в TeamCity верим, что наш обновленный ранер .NET позволит сделать непрерывную интеграцию проектов на .NET 5 удобной и предсказуемой.
Если вы уже пробовали новый ранер или просто хотите поделиться мнением о нем, пишите в комментариях. Всегда рады вашей обратной связи.
Bronx
Я смотрю в вашей статье довольно развесистые цепочки билд-конфигураций, и у меня родился вопрос слегка оффтопный. На меня недавно повесили работу с билдами в TC, я в этом ещё неопытен, и я столкнулся с проблемой: вот сделал я (или предыдущий инженер) цепочку конфигураций, в ней было сделано несколько релизов, эти билды пометили pinned для истории. А через некоторое время решили, что цепочка неоптимальна, и её надо поменять — часть конфигураций удалить, часть добавить. Но вот беда: нужно сохранить все pinned-билды и артефакты, в т.ч. промежуточные. Как это правильно сделать? Как удалять старые ненужные конфигурации, не теряя исторические билды?
NikolayPyanikov Автор
Вы можете сохранить «build configuration ID» в новых билдах или выполнить процедуру «Attach build history», подробнее на этой странице.
Bronx
В TC 2020.1.2 не вижу пункта "Attach build history" — это в 2020.2 появилось? Это действие сливает истории двух конфигураций вместе, или замещает одну историю другой?
pavelsher
Attach build history существует фактически для одного use case. Если вы храните настройки в DSL и решили поменять id некоторых конфигураций, то после применения DSL на сервере вы можете обнаружить что у этих конфигураций больше нет истории билдов. Так как история как раз к id и была привязана. Здесь как раз и используется Attach build history action для того чтобы найти потерянную историю и перепривязать её к нужной конфигурации. Action просто так не появляется, только если сервер видит что такая потерянная история есть. Мне кажется что Attach build history не совсем про ваш use case.
pavelsher
Тут противоречивые требования. С одной стороны хочется удалить что то, с другой хочется сохранить эти артефакты. Ну как вариант, можно артефакты перенести в другое место.
Либо, для релизов всегда делать копии проектов. Тогда релиз остаётся нетронутым, и продолжает работать как работал, а в транке/мастере всё переделывается. Как правило такой подход лучше потому что всегда должна быть возможность выпустить срочных хотфикс и для этого лучше оставлять релизные конфигурации нетронутыми, как они были в момент релиза.
Bronx
Удалить хочется билд-конфигурации (т.е. по сути шаги для создания билда), а сохранить хочется сделанные билды. Сейчас, по сути, история билдов прибита к конфигурациям, её нельзя оторвать, не потеряв, нельзя перенести в другое место (внутри ТС), нельзя слить две истории в одну.
Тогда потеряется мета-информация: теги, комменты, ссылки на багтрекер и проч. Но, возможно, вы правы, и действительно, использовать TC как постоянное хранилище артефактов — это неправильно, и нужно заводить отдельный сервис.
Если множить копии, тогда быстро упрёшься в лимит лицензии на число конфигураций.