Tags: continuous deployment, разработка ПО, CI/CD, DevOps, системы сборки, gradle
PAA: ???
Labels: gradle, kotlin
В некоторых проектах сборке отводится роль Золушки. Основные усилия команда сосредоточивает на разработке кода. А самой сборкой могут заниматься люди, далёкие от разработки (например, отвечающие за эксплуатацию, либо за развёртывание).
Если сборка хоть как-то работает, то её предпочитают не трогать, и речь об оптимизации
не заходит.
Вместе с тем в больших гетерогенных проектах
сборка оказывается достаточно сложной и к ней вполне можно подходить как
к самостоятельному проекту. Если же относиться к сборке как к второстепенному проекту,
то в результате будет получен неудобоваримый императивный скрипт, поддержка которого
будет в значительной степени затруднена.
В этой заметке мы рассмотрим, по каким критериям мы выбирали инструментарий, а
[в следующей](build-systems-2-gradle-howto-ru.md) —
каким образом этот инструментарий используем.
[](https://opensource.com/article/19/7/cicd-pipeline-rule-them-all)
## Общая модель сборки проектов
Модель сборки проектов во всех рассматриваемых инструментах
представляет собой [направленный граф без циклов](https://ru.wikipedia.org/wiki/Ориентированный_ациклический_граф)
([орграф, DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph)), а не иерархическую структуру,
типичную для структурного подхода
(когда процедура вызывает другие процедуры, а затем пользуется результатами).
Связано это с тем, что при разработке проекта вносятся небольшие изменения и бо́льшая часть
операций по сборке не требуется. То есть организация проекта в форме орграфа является
основой для того, чтобы выполнялись только те действия,
которые необходимы для ближайшей задачи, тем самым часто используемые операции
будут выполняться достаточно быстро.
Узлами в графе являются цели или задачи. Цели — результаты, которые необходимо достичь;
а задачи — операции, которые надо выполнить, чтобы достичь текущей цели. При этом задача может
быть запущена только в том случае, когда все зависимости удовлетворены.
Такая модель схватывает структуру проекта и позволяет достигать не одну конечную цель,
а несколько разных целей, на основе общей системы целей/задач. Эти несколько разных целей
сосуществуют вместе и их подзадачи пересекаются. Например, сборка, тестирование и
генерация документации взаимосвязаны как раз таким образом.
Поверх этой основной модели в ряде инструментов реализованы более высокоуровневые модели.
## Декларативный или императивный стиль
При написании программ часто противопоставляются императивный и декларативный стили программирования.
Под *императивным* стилем понимается описание последовательности действий, описывающих, **как**
прийти к какому-то результату. Причём сам результат не описывается.
При использовании *декларативного* стиля описывается желаемый результат, **что** мы хотим получить.
Последовательность действий при этом выбирается инструментом самостоятельно.
Такое разделение в некоторой степени условно. Сравним, например, такие программы:
«`kotlin
fun loadPerson(file: File): Person = TODO()
val ivan = loadPerson(«ivan»)
«`
и
«`kotlin
fun personDefinedInFile(file: File): Person = TODO()
val ivan = personDefinedInFile(«ivan»)
«`
Второй пример выглядит более декларативным, хотя разница всего лишь в названии функции.
Здесь можно отметить, что код, являющийся императивным на одном уровне абстракции, может оказаться
декларативным на другом уровне абстракции.
В описанной выше модели структура целей может считаться декларативной, т.к. мы называем желаемый результат,
а описание шагов по достижению какой-либо цели является императивной программой.
В некоторых инструментах, рассмотренных ниже, могут быть и другие декларативные элементы.
## Выбор инструмента
При старте проекта иногда бывает можно оценить, насколько сложной окажется интеграция проекта.
В нашем случае оказалось, что требуется собирать несколько модулей node.js, несколько go-lang,
осуществлять развёртывание нескольких модулей terraform (и ни одного jvm-модуля).
В других аналогичных проектах, развивавшихся «органически», сборка осуществлялась с
использованием `make`, а в скриптах сборки использовались `bash`, `perl`,
`python`, `php` и др. Механизм сборки было трудно поддерживать, производительность
оставляла желать лучшего и ряд возможностей не был реализован.
Для нового проекта мы задумались, какой инструмент взять за основу системы
сборки. Оценили такие варианты:
— make,
— maven,
— sbt,
— gradle/groovy,
— gradle/kotlin.
<cut/>
### Make
Эта программа создана достаточно давно, широко применяется во многих проектах и, в целом,
позволяет реализовывать сборку достаточно больших и сложных проектов.
Плюсы
— наличие компетенции;
— похож на shell.
Минусы
— поддерживается только базовая модель целей и задач, нет поддержки понятия проекта;
— запутанный синтаксис;
— глобальные состояния;
— нет поддержки плагинов, трудно повторно использовать части кода;
— отсутствует возможность декларативного описания сборки; только императивный код.
В отсутствии ограничений, в скриптах сборки можно встретить
— генерацию шаблонов с помощью perl;
— установку недостающих исполняемых файлов путём исполнения .sh скриптов из интернета
при каждом запуске скрипта;
— вызов make-файлов для подпроектов с нестандартными названиями целей;
— отсутствие согласованной обработки ошибок.
Такие неожиданности затрудняют поддержку и делают сборку небезопасной операцией.
### Maven
Maven стал революцией в системах сборки в момент своего появления. Идеи декларативного
описания проектов, широкого применения конвенций, использования плагинов,
хранения артефактов в репозиториях,
использования системы идентификации, включающей версии — всё это обеспечило
признание и широкое использование во многих JVM-проектах по сей день.
Плюсы
— наличие компетенции (часть команды имеет богатый опыт работы с Maven-проектами);
— развитая модель проектов и подпроектов;
— поддержка плагинов;
— декларативная модель;
— maven wrapper.
Минусы
— слабая поддержка других технологий;
— наличие трудно-преодолеваемых ограничений;
— трудоёмкость реализации плагинов;
— отсутствие удобной возможности реализации императивных скриптов;
— жёсткая структура жизненного цикла;
— не очень удобный XML-формат.
Отсутствие императивных скриптов является и плюсом и минусом. С одной стороны,
декларативный подход обеспечивает жёсткое разделение кода и модели, с другой стороны,
задачи сборки зачастую требуют отдельных вставок императивной логики, и решение таких задач
в maven’е мучительно.
### Sbt
Sbt — инструмент сборки Scala-проектов. Появился примерно в то же время, что и gradle.
Плюсы
— развитый язык;
— поддержка плагинов и императивных вставок;
— поддержка инкрементной сборки;
— поддержка непрерывной сборки по мере изменения;
— параллельное исполнение.
Минусы
— неожиданная модель (вместо задач — «настройки»);
— неявные зависимости через `.value`;
— слабая поддержка других технологий (go, node.js);
— неожиданный синтаксис.
Sbt в целом выглядит как специализированная система для сборки Scala-проектов, нежели чем универсальный инструмент сборки любых проектов.
### Gradle
Gradle появился в 2007 году в качестве ответа на основные ограничения maven — отсутствие императивного
кода, трудность реализации плагинов, неудобство при нестандартных операциях.
Gradle базируется на идеях, предложенных Maven’ом, развивает их и меняет акценты.
Основными частями модели gradle являются:
— задачи (Task) — выполняемые операции, узел графа зависимостей, имя+описание;
— проект — логическая единица организации кода, совокупность и scope задач,
точка подключения плагинов;
— плагин — возможность или фича, добавляемая в проект. Среди прочего — набор задач;
— зависимости, проверка up-to-date.
Важным усовершенствованием стало использование DSL (domain specific language),
основанного на императивном языке, с помощью которого формируются элементы декларативной модели,
а также решаются императивные задачи.
Плюсы
— поддержка модели проектов;
— поддержка декларативного (основанного на модели) и императивного подхода одновременно;
— поддержка инкрементной сборки;
— непревзойденная гибкость;
— превосходная документация (для двух диалектов сразу);
— очень быстрая работа (даже определения задач выполняются только в случае необходимости);
— кросс-платформенность — работает везде;
— gradle wrapper — небольшой скрипт для загрузки и запуска gradle правильной версии;
разработчикам не требуется вручную настраивать утилиты и обновлять при изменении версии в репозитории;
— удобный и понятный DSL.
Минусы
— отсутствие компетенции (до этого gradle широко не применялся членами команды);
— насколько мне известно, отсутствуют
механизмы защиты от чрезмерного использования императивного кода. Необходимы дисциплина и
следование рекомендованным практикам при разработке скриптов сборки во
избежание макаронного кода;
— не поддерживаются иные механизмы зависимостей, кроме JVM (maven-repository, ivy2);
— необходимость предпринимать определённые усилия для того, чтобы каждая задача поддерживала
проверку up-to-date (полезную для инкрементной сборки). В частности, для каждой задачи необходимо
описать входные и выходные данные. В принципе, обычные возможности DAG доступны без усилий,
но gradle позволяет достичь ещё более высокой скорости работы при условии
указания входов и выходов.
#### Выбор диалекта — gradle/groovy или gradle/kotlin
Изначально gradle использовался с помощью groovy-DSL. В дальнейшем был разработан
DSL на основе Kotlin’а.
Плюсы Kotlin’а
— компилируемый строго-типизированный язык:
— защита от ошибок на этапе компиляции,
— поддержка intelli-sense,
— безопасный рефакторинг,
— хорошая поддержка DSL;
— простой синтаксис, меньше бойлерплейта, по сравнению с Java;
— достаточно много сахара.
Минусы
— на просторах интернета большинство примеров — для groovy, вначале бывает трудно сообразить,
как переписать пример на kotlin’е;
— изначально был сделан gradle/groovy DSL, поэтому некоторые элементы
неидеально представлены в kotlin’е (`extra`, имена задач, …);
— немного повышается порог входа в связи с необходимостью освоения нового языка.
## Заключение
По итогам сравнения имеющихся инструментов сборки проекта мы решили
попробовать в нашем проекте реализовать сборку и CI/CD с использованием gradle/kotlin.
Этот вариант обладает рядом преимуществ в сравнении с реализацией сборки проекта на основе make/shell.
### Gradle/kotlin vs make
Ниже приведены сравнительные преимущества gradle/kotlin по отношению к make:
Преимущества:
— высочайшая скорость работы. Команда Gradle прикладывает постоянные усилия
в направлении улучшения скорости и в реализации инструментов, способствующих
реализации высокоскоростных скриптов. Можно добиться того, что все задачи,
не требующие выполнения, будут пропущены. А задачи, требующие выполнения — выполнены
только для изменившихся файлов.
— унификация языка. Все задачи решаются в рамках одного
компилируемого строго-типизированного языка с согласованным и продуманным синтаксисом — Kotlin.
Нет необходимости изучать особенности режимов make, различия версий shell-интерпретаторов,
варианты обработки параметров командной строки в разных утилитах,
отдельные языки программирования для шаблонов (php?, perl?).
За счёт использования современного языка со статической
типизацией, исключается множество классов ошибок, характерных для скриптовых языков.
— декларативная модель проектов/подпроектов и плагинов поверх
декларативного орграфа задач. В make — только непосредственно императивные задачи.
— возможность комбинирования декларативного и императивного подхода.
Несмотря на то, что декларативный подход обеспечивает понятность и чистоту кода,
лёгкость поддержки, возможности комбинирования компонентов, императивный подход
может оказаться незаменимым в силу своей гибкости. Новые задачи можно вначале
решить императивным способом (ad-hoc), а затем обобщить и выделить в форме
декларативных конфигурируемых плагинов.
— возможность создания повторно-используемых плагинов. Причем написание таких плагинов не
вызывает особых сложностей и плагинам предоставляется удобный API с широкими возможностями.
В случае make стандартных механизмов не предусмотрено, из-за чего возникает дублиирование
кода и переизобретение велосипедов.
— платформа JVM, на которой реализованы библиотеки на все случаи жизни.
В make некоторые задачи требуют установки платформо-специфических приложений
Недостатки:
— неудобно вызывать shell-команды. Для каждой команды надо создать и настроить task.
— более высокие требования к инженерной культуре — язык со статической типизацией, декларативная
модель проекта, использование развитых концепций (свойства, вывод зависимостей, проверки up-to-date).
В следующей части рассмотрим некоторые особенности применения gradle/kotlin к сборке
не-JVM проектов.
### Благодарности
Хотелось бы поблагодарить @nolequen, @Starcounter, @tovarischzhukov
за конструктивную критику черновика статьи.
ссылка на оригинал статьи https://habr.com/ru/post/550524/
Добавить комментарий