Версіонування артефактів складання Gradle використовуючи git імена тегів, бранчів і комітів

З переїздом з SVN на GIT і gitlab (плюс переїзд з Jenkins на Gitlab-CI, але його використання також згадаємо), постало питання версионирования одержуваних артефактів складання програми.

В SVN був усім звичний номер ревізії, монотонно збільшується з кожним комітом. Його було зручно додавати в номер версії, і це вирішувало більшість проблем. Але git звичайно надає безліч булочок, і варто переконувати керівництво і всі команду перевести проект на нього…
Зате довелося відбудувати заново процес версионирования одержуваних артефактів складання.

У підсумку зупинилися на дуже хорошому Gradle плагіні github.com/nemerosa/versioning, про його використанні я і збираюся розповісти.


Проблема
У нас в додатку Gradle використовується давно, і для SVN просто використовувалася наколенная функція, що дісталася у спадок, написана прямо у файлі build.gradle. Благо, серед інших достоїнств Gradle можна згадати що це прекрасний мову Groovy, і він нічим вас не обмежує в написанні логіки білду — підключив необхідні бібліотеки Java світу, і вперед, хоч усі додаток перепиши в одному файлі!

Втім, ви ж розумієте згубність такого підходу? Якщо логіка отримання номера версією займає більше 5-10 рядків, а також якщо ми будемо з будь-якого приводу городити свій костур, то підтримувати це буде просто неможливо дуже скоро…

Подібні «рішення» ручного парсинга ви можете побачити наприклад у статті Jenkins для Android на чистій системі і без UI або Через терни до складання де пропонується викликати вручну git describe і парсити висновок регулярними виразами…

Хотілося чогось більш простого, надійного і спочатку робітника.

Наш workflow і хотілки
У нашому додатку збирається парочка jar файлів, 3 war артефакту, 3 RPM їх включають, і врешті-Docker образ програми, з встановленими RPM, який після автоматичного тестування тут же на gitlab-ci відправляється в приватний репозиторій.

Загалом, з переходом на git/gitlab ми дотримуємося логіки, успадкованої від стандартного github flow з невеликими змінами, а це значить для версионирования:
  • Ми хочемо відрізняти локальні білди, від білдів на CI
  • Хочемо відрізняти білд з форк (або future branch) від релізної. При цьому треба додавати шматок хеш коміта щоб точно знати звідки він зібраний
  • Для релізів ми вирішили використати створення тегів прямо через WEB інтерфейс gitlab — це зручно. Але при цьому вже не потрібен хеш тому що це не дуже гарно виглядає для користувачів, а тег в гіті нормальний, Read-only (і не вимагає жодних хуків як в svn) та однозначно ідентифікує комміт, зручним ім'ям, яке ми дали. Тобто у версії вже повинен використовуватися він
  • Плюс нам потрібен структурований об'єкт версії, для прописування деякі частини системи (php, html) для деяких тасок
  • Нам також необхідно експортувати цю інформацію назовні, для gitlab-ci щоб він знав, які контейнери, яких версій потрібно підняти на наступних кроках для тестування


Пропоноване рішення: Gradle plugin net.nemerosa:versioning
Подивившись навколо на наявні плагіни для gradle, знайшов ось такий прекрасний варіант: github.com/nemerosa/versioning

Його документація відразу підкуповує — все просто, логічно і зрозуміло для чого зроблено.
Плюс до всього семантичне поділ на release, future

Спробуємо у справі
Отже підключити до проекту дуже просто, слідуємо інструкції:
plugins {
id 'net.nemerosa.versioning' version '2.4.0'
}


Все, в більшості випадків вже можна використовувати версію своїх білд-скриптах далі, де вона потрібна:
version = versioning.info.full


Ну або ближче до справи, скажімо в імені war артефакту:
war {
archiveName = "portal-api##${versioning.info.full}.war"
}


Після складання з бранчу future-1 ми отримаємо файл приблизно такого іменування: portal-api##future-1.3e46dc.war (у прикладі використовується іменування у стилі Tomcat). Варіанти настройки і парсинга значень для більш цікавих ситуацій розберемо далі.

Відразу ж доступно 2 завдання:
versionDisplay — показує інформацію та версіях і виводить на консоль. Дуже зручно у налагодженні та versionFile — створює файл build/version.properties з готовими змінними, для імпорту в bash скрипти назовні:

> ./gradlew versionDisplay
:versionDisplay
[version] scm = git
[version] branch = release/0.3
[version] branchType = release
[version] branchId = release-0.3
[version] commit = da50c50567073d3d3a7756829926a9590f2644c6
[version] full = release-0.3-da50c50
[version] base = 0.3
[version] build = da50c50
[version] display = 0.3.0


> ./gradlew versionFile
> cat build/version.properties
VERSION_BUILD=da50c50
VERSION_BRANCH=release/0.3
VERSION_BASE=0.3
VERSION_BRANCHID=release-0.3
VERSION_BRANCHTYPE=release
VERSION_COMMIT=da50c50567073d3d3a7756829926a9590f2644c6
VERSION_DISPLAY=0.3.0
VERSION_FULL=release-0.3-da50c50
VERSION_SCM=git


просто відмінно.

Кастомний логіка парсинга версій
Відразу хочеться зауважити, що є безліч опцій як парсити імена, обробляти префікси, суфікси, трактувати версії. Там же є і підтримка SVN до речі. Загалом вам в розділ customisation.

Однак, тут не без ложки дегдя. На момент коли я починав їм користуватися, документація виглядала інакше.
Так, можна задати своє замкнення як трактувати ім'я бранчу (наприклад 'release/1' вважати релізних, а 'qa/0.1' інакше):
versioning {
branchParser = { String branch, String separator = '/' ->
int pos = branch.indexOf(separator)
if (pos > 0) {
new BranchInfo(
type: branch.substring(0, pos),
base: branch.substring(pos + 1))
} else {
new BranchInfo(type: branch, base: ")
}
}
}


Це все здорово, але ми хочемо тег замість бранчу, якщо він є!?
Я не хотів відмовлятися від цієї ідеї. Зрозуміло запив тимчасовий воркараунд, але автору створив реквест зробити логіку парсинга більш загальної: github.com/nemerosa/versioning/issues/32

Damien Coraboeuf, який є автором цього плагіна, виявився дуже чуйним, і оперативним виправивши оперативно пару дрібних речей.
В загальному ж, як це часто буває, запропонував реалізувати мені самому, то що я пропоную.
Я пішов його раді — швиденько зварганив pull request.

Тепер, після його прийняття, ми отримуємо об'єкт інформації про коммите SCM (SVN або GIT) і вільні у виборі способу, як нам формувати версію. Наприклад, той же самий код, що наведений вище, може бути реалізований так:
versioning {
releaseParser = { scmInfo, separator = '/' ->
List<String> part = scmInfo.tag.split(separator) + "
new net.nemerosa.versioning.ReleaseInfo(type: part[0], base: part[1])
}
}


Те ж саме в замиканні full.

Що нам це дає? Ну наприклад, як було описано у вимогах, ми використовуємо це для того щоб брати в одному випадку ім'я бранчу, а в іншому ім'я тега, а не лише строковим поданням імені бранча. У нас це зараз виглядає приблизно так:
versioning{
full = { scmInfo ->
// Tag name, or '_branch_name_' if it is not 'master'
(scmInfo.tag ?: ( 'master' == scmInfo.branch ? ": "_${scmInfo.branch}_." ) + scmInfo.abbreviated).toLowerCase().replaceAll(/[^a-z0-9-_\.]/, '_')
}
}

До нижнього регістру наводиться для використання в тегах Docker образів.

Як я згадував, опції цим не вичерпуються, ми також контролюємо dirty-суфікс, а також час складання додаємо в цей же об'єкт, використовуючи meta-магію Groovy…

Інтеграція з модулем CI
Ну і раз вже я взявся розповідати про зручну інтеграцію, відразу варто звернути увагу на один підводний камінь, про який я теж спіткнувся. А в цьому плагіні вже подбали!

Зазначений код працював чудово, був протестований, закоммичен. Але перший же пуш і білд на CI принесли дивний результат — ім'ям бранчу стало щось на зразок HEAD.

Насправді причина проста, якщо подивимося що робить білдер він же збирає не гілку, а конкретний комміт. На момент складання, в цій же гілці можуть чи вже інші. Тому, він завжди робить checkout по імені хеш коміта. Таким чином, ми отримуємо git репозиторій в змозі detached head.

Як я, забігаючи вперед вже сказав, ця ситуація нормальна і більшість працюють так, а в даному плагіні просто потрібно прописати одну сходинку, із зазначенням імені зовнішньої змінної або змінних, з яких потрібно взяти справжнє ім'я бранчу, для gitlab-ci мені потрібно було просто додати:
branchEnv = ['CI_BUILD_REF_NAME']


У Jenkins такі переменные також були додані досить давно по запросу JENKINS-30252. Так, якщо ви хочете підтримати відразу обидві системи, ви можете просто написати:
branchEnv = ['CI_BUILD_REF_NAME', 'GIT_LOCAL_BRANCH']


Я сподіваюся вам буде зручніше працювати з версіями gradle. Так, і всіляко рекомендую ставити баги або писати реквесты автору — він дуже оперативно на них відповідає. Доброго кодинга!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.