Автоматизация сборки .NET приложений — часть 2 / Хабр

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

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

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

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

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

Что это?

Данный пост будет об автоматических системах сборки. Думаю, интуитивно понятно что это и зачем нужно, но для формальности написать нужно — это такие программные продукты, которые на основе некоторой конфигурации могут «собрать» ваш проект.

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

Небольшой перечень для ясности:


Наиболее популярными представителями подобных систем являются Ant Ivy, Maven.

High-level архитектура

Архитектура всех build-систем сводится к следующему:

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

Для людей незнакомых с терминологией: репозиторий — некоторое структурированное хранилище некоторых данных. Например, различных вспомогательных библиотек/плагинов/скриптов/документации/исходников и т.д.

На этом общую часть о build-системах закончу и перейду к более конкретным вещам.

Maven


Так как переписывать то, что написали другие было бы неправильно, поэтому приведу перечень статей по теме, которые уже есть на хабре о Maven и где описано конкретно об этой системе — что это, как работает и на каких принципах основана.

Советую читать именно в этом порядке:

  • Maven — зачем? — вводная статья о Maven, где описано как устроена конфигурация модуля/проекта, жизненный цикл сборки.
  • Apache Maven — основы — терминология, установка, сборка, создание проекта из архетипа, полезные ссылки
  • Maven — автоматизация сборки проекта — более детальное описание жизненного цикла сборки, описание настроек диплоя (deploy), на примере установки для Tomcat сервера.
  • Создание своих архетипов и каталогов в Maven — для людей уже «в теме» и желающих создавать свои архетипы, если по каким-то причинам не хватает существующих. Процесс описан довольно детально.

Автоматизация распределенной сборки

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

Зачем нужно было создавать новую систему сборки?

Как это часто бывает, развитие и усложнение проекта в какой-то момент привело к тому, что поддержка инфраструктуры сборки стала слишком накладной и этому поспособствовало несколько причин, полное перечисление которых заняло бы неприлично много места, поэтому позволим себе выделить только те, которые вызывали большее число нареканий от участников проекта:

Конечно, помимо проблем были и пожелания по реализации новых «фич», поэтому, когда было принято решение о разработке новой унифицированной системы сборки, которую назвали unimake, мы вполне определенно представляли каких целей хотим достичь:

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

Воспользуйтесь

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

Использование и отладка собственных тасков


Для использования в билд-скрипте собственных тасков нужно подключить свою скомпилированную библиотеку при помощи команды

loadtasks

, а потом вызвать нужные таски, используя имя, заданное в атрибуте

TaskName

Для отладки своего таска добавьте в нужное меcто вызов статического метода

System.Diagnostics.Debugger.Launch()

и соберите библиотеку в режиме Debug. При запуске таска через NAnt в указанном месте выполнение скрипта остановится и вам будет предложено запустить для отладки Visual Studio.

Модель сборки, общие положения


Сборка производится в отличной от исходников (

srcroot

) директории — директории сборки (

bldroot

). Каждая сборка проекта целиком определяется набором множеств:


Вариант конфигурации проекта

...
CONFIGS = base60 full60 
PLATFORMS = LINUX 
ARCHS = AMD64 JAVA .NET 
COMPILERS = GCC JAVAC MONO 

JAVAC_VERS = 1.4 1.5 1.6 
GCC_VERS = 4 
MONO_VERS = 3
…

HOST.PLT = LINUX
HOST.ARCH  = AMD64

DEBUG = RELEASE

Комбинация перечисленных параметров определяет все возможные варианты, которые предварительно фильтруется системой сборки с целью отсеять ненужные и не имеющие смысла комбинации.

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

Вариант описателя модуля

MODULE = example #наименование библиотеки

VERSIONS = #необходимы отдельные версии библиотеки для каждой версии проекта
VERSIONS_REQ:= $(CFG.VER) #версия библиотеки совпадает с версией проекта

LINK_TYPES = static dynamic #будут созданы статическая и разделяемая/динамическая библиотеки
THREAD_TYPES = mt #только многопоточная версия

DST_SRC = example.h #в целевую директорию помимо целей попадет и заголовочный файл 

DONT_BUILD_WATCOM = # не выполнять сборку, если компилятор — watcom (любой версии)
DONT_BUILD_WINCE = # не выполнять сборку если целевая платформа — WinCE

Описатель сборки объявляет цели, их состав, директивы, директории поиска, внешние и внутренние зависимости модуля.

Вариант описателя сборки

...
TARGET = $(MODULE) #целевой файл библиотеки будет иметь имя, совпадающее с названием модуля   расширение, определяемое типом цели и платформой (.so, .a, .dll и т.д.)

DEFINES = _VER=$(CFG_VER) SOME_DEFINES #дефайны общие для всех платформ
DEFINES_WINNT = EXAMPLE_WIN #директива только для Windows 
DEFINES_UNIX = EXAMPLE_POSIX #директива для всех *nix

CDIR    = $(MODROOT);$(MODROOT)/utils; #директории с исходниками
INCLDIR = $(MODROOT);$(ANOTHER_MOD); #директории поиска

OBJS = &
	example.obj # объектные файлы для всех платформ

OBJS_UNIX = & 
	charset.obj # дополнительные объектные файлы для *nix платформ 

SLIBS_WINNT = $(ANOTHER_LIB) oldnames #статические библиотеки для windows платформы...
SLIBS_UNIX  = $(ANOTHER_LIB) #статическая библиотека для *nix 
...

bldroot

структура директорий повторяет

srcroot

до уровня корней каждого модуля (

modsrc

), но уже в них, содержатся все фактические варианты, задаваемые допустимыми комбинациями общепроектных и модульных конфигураций. Под каждый из таких вариантов создается директория вида $(MODULE)/$(PLT)_$(ARCH)_$(CMPL)$(CMPLV)_$(TYPE)_$(CFG) (например example/LINUX_AMD64_GCC4_MD_R_base60), будем именовать далее эти директории как

modbld

Вариант содержимого modsrc

Вариант содержимого modbld

В каждой допустимой

modbld

в процессе выполнения обхода директорий создается три файла: опций компилятора (*.cfl в нашем случае), опций компоновщика (*.lnk — в примере) и вспомогательный makefile, которые предназначены для проведения компиляции и компоновки целей в обход общей системы сборки, что бывает часто востребовано для задач отладки. Таким образом, существует два варианта использования системы:


Схема вызовов для обоих случаев приведены на иллюстрациях ниже.

Автоматизация сборки .NET приложений — часть 2 / Хабр
Иллюстрация 1: Сборка всего проекта (1) приводит к формированию последовательности вызовов корневого make-файла (3) для всех возможных комбинаций опций сборки (2). В результате фильтрации (3) отсеиваются заведомо непригодные варианты. Файлы описатели модулей, (4) исходя из зависимостей и дополнительных параметров корректируют варианты. Описатели сборки (5) выполняют правила (6) и формируют целевые директории с результатами исполнения (7).Автоматизация сборки .NET приложений — часть 2 / Хабр
Иллюстрация 2: Обновление существующих модулей (1) работает по упрощенной схеме: вспомогательные правила в modbld (3) обновляют (4) свои цели без использования описателя модуля и фильтров.

Как уже упоминалось выше, все правила вынесены в отдельный модуль (unimake) на уровне проекта, который, помимо заданий самих правил, отвечает за хранение дерева зависимостей между модулями. При этом, каждый модуль из объявленных порождает отдельную цель с генерируемыми зависимыми целями.

Хранение и использование зависимостей между модулями

…
dep_example   = another 
dep_another   = 
…
module-deps = $(foreach name,$(DEP_$(1)), $(MOD_$(name)))
gen-module-deps = $(foreach name,$(DEP_$(1)), $(2)_$(MOD_$(name)))

!define gen-target 
$(1): .SYMBOLIC 
	@$(MAKE) MODULE=$(1)
!endef

!define gen-targets 
TARGETS_$(1) := $(foreach mod,$(ALL_MODULE_NAMES), $(1)_$(mod)) 
$(1): $$(TARGETS_$(1)) 
	@%null
!endef 

gen-targets-without-deps = $(foreach mod,$(ALL_MODULE_NAMES),$(gen-target ,$(mod)))

!eval $(gen-targets-without-deps) 
!eval $(gen-targets dep)

Благодаря встроенному парсеру файлов

linmodules имеется возможность отслеживает текущее положение модулей в дереве исходников и использовать простое определение пути.

Чтение и регистрация модулей и путей

#git modules 
LINMODS=$(modlist $(SRCROOT)/.linmodule) 
!define add-mod 
    MOD_$(1) = $$(modpath $(1)) 
!endef 
!eval $(foreach i,$(LINMODS),$(add-mod $(i)))

Написание собственных тасков

Таск (task) — это элемент билд-скрипта, который выполняет определенное действие (например, копирует файл в другую папку или запускает модульные тесты). Таск может содержать параметры. Например, таск

регистрирует COM-объекты из заданной библиотеки, а его параметром является путь к нужному файлу .dll.

NAnt уже содержит

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

Каждый таск — это класс .NET, унаследованный от базового класса NAnt.Core.Task (из библиотеки NAnt.Core.dll). Для написания собственного таска открываем Visual Studio, создаем новый проект Class Library, добавляем ссылку на NAnt.Core.dll и создаем примерно такой класс:

[TaskName("my-task")]
public class MyTask : NAnt.Core.Task
{
	protected override void ExecuteTask()
	{
		// TODO: здесь выполняются нужные действия
		System.IO.File.WriteAllText("file.txt", "Hello, world!");
	}
}

NAnt.Core.Task

— абстрактный класс, все наследники которого должны содержать собственную реализацию абстрактного метода

void ExecuteTask()

— именно в нем и выполняются те действия, для которых предназначен таск. Для того, чтобы задать имя XML-тэга, которое будет потом использоваться в билд-скриптах, нужно пометить созданный класс атрибутом

TaskName

Для передачи параметров добавьте в класс свойства и отметьте их атрибутом TaskAttribute. Во время выполнения в этих свойствах будут находиться значения, указанные в параметрах таска в билд-скрипте.Обратите внимание, для передачи в параметрах пути к файлам удобнее использовать свойства типа System.IO.FileInfo, а не string.

[TaskAttribute("path", Required = true)]
public string Path { get; set; }

Для записи сообщений в лог используйте метод

Log

из базового класса.

Log(Level.Warning, "Файл не найден");

Обзор

Исторически автоматизация сборки осуществлялась за счет make-файлы. Сегодня есть две основные категории инструментов:[1]

Утилита автоматизации сборки
Сюда входят такие утилиты, как Делать, Грабли, CMake, MSBuild, Муравей, Maven или же Gradle (Java) и т. Д. Их основная цель — создать строить артефакты посредством таких действий, как компиляция и связывание исходного кода.
Серверы автоматизации сборки
Это общие веб-инструменты, которые запускают утилиты автоматизации сборки по расписанию или по триггеру; а непрерывная интеграция server — это тип сервера автоматизации сборки.

В зависимости от уровня автоматизации возможна следующая классификация:

Список программного обеспечения для каждого из них можно найти в список ПО для автоматизации сборки.

Преимущества

Преимущества автоматизации сборки для проектов разработки программного обеспечения включают:

Прочее

За последние несколько лет мне периодически приходилось писать собственные таски для решения разных специфических задач. Я добавлял все их в

Путаница


На хабре есть ряд статей о Maven, но, к сожалению, все они находятся в разных хабах, т.к. отдельного хаба для билд систем не существует.

Поэтому, я решил «не быть как все» и выбрать именно общий хаб «Программирование», а не «Java», «Веб-разработка» и др.

Реализация

Описанный в предыдущем разделе подход был реализован нами для инфраструктуры проекта ЛИНТЕР. И, несмотря на то, что произошло это относительно недавно (около полугода назад) система уже положительно зарекомендовала себя с точки зрения простоты использования, масштабируемости и производительности.

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

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

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

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

Пример nuspec-файла

Связь с непрерывной доставкой и непрерывной интеграцией

Автоматизация сборки считается первым шагом на пути к внедрению культуры непрерывная доставка и DevOps. Автоматизация сборки в сочетании с непрерывная интеграция, развертывание, автоматизация выпуска приложений, и многие другие процессы помогают продвинуть организацию вперед в установлении передовых практик доставки программного обеспечения.[4][как? ]

Серверы автоматизации сборки

Хотя серверы сборки существовали задолго до серверов непрерывной интеграции, они обычно являются синонимами серверов непрерывной интеграции, однако сервер сборки также могут быть включены в ARA инструмент или ALM инструмент.

Типы серверов

Утилиты для автоматизации сборки

Утилиты Build-Automation позволяют автоматизировать простые, повторяемые задачи. При использовании инструмента он рассчитает, как достичь цели, выполняя задачи в правильном, определенном порядке и выполняя каждую задачу. Инструменты сборки различаются двумя способами: ориентированными на задачи и ориентированными на продукт.

Инструменты, ориентированные на задачи, описывают зависимость сетей с точки зрения конкретной поставленной задачи, а инструменты, ориентированные на продукт, описывают вещи с точки зрения продуктов, которые они создают.[2]

Заключение

Целью данной статьи было донести до читателя информацию о системах сборки в целом, зачем они нужны и какие задачи способны выполнять.

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

Продолжение: Build Systems — Local Repository

Оцените статью
OverComp.ru