- PVSM.RU - https://www.pvsm.ru -
С переездом из SVN на GIT и gitlab (плюс переезд из Jenkins на Gitlab-CI, но его использование также упомянём), встал вопрос версионирования получаемых артефактов сборки приложения.
В SVN был всем привычный номер ревизии, монотонно увеличивающийся с каждым коммитом. Его было удобно добавлять в номер версии, и это решало большинство проблем. Но git конечно предоставляет множество плюшек, и стоило убеждать руководство и всё команду перевести проект на него…
Зато пришлось отстроить заново процесс версионирования получаемых артефактов сборки.
В итоге остановились на очень хорошем Gradle плагине github.com/nemerosa/versioning [1], о его использовании я и собираюсь рассказать.
У нас в приложении Gradle используется давно, и для SVN просто использовалась наколенная функция, доставшаяся по наследству, написанная прямо в файле build.gradle. Благо, среди других достоинств Gradle можно упомянуть что это прекрасный язык Groovy, и он ничем вас не ограничивает в написании логики билда — подключил необходимые библиотеки из Java мира, и вперёд, хоть всё приложение перепиши в одном файле!
Впрочем, вы же понимаете пагубность такого подхода? Если логика получения номера версии занимает больше 5-10 строчек, а также если мы будем по любому поводу городить свой костыль, то поддерживать это будет просто невозможно очень скоро…
Подобные «решения» ручного парсинга вы можете увидеть например в статье Jenkins для Android на чистой системе и без UI [2] или Через тернии к сборке [3] где предлагается вызывать вручную git describe и парсить вывод регулярными выражениями…
Хотелось чего-то более простого, надёжного и изначально рабочего.
В нашем приложении собирается парочка jar файлов, 3 war артефакта, 3 RPM их включающих, и в конце Docker образ приложения, с установленными RPM, который после автоматического тестирования тут же на gitlab-ci отправляется в приватный репозиторий.
В общем, с переходом на git/gitlab мы придерживаемся логики, унаследованной от стандартного github flow [4] с небольшими изменениями, а это значит для версионирования:
Посмотрев вокруг на имеющиеся плагины для gradle, нашёл вот такой прекрасный вариант: github.com/nemerosa/versioning [1]
Его документация сразу подкупает — всё просто, логично и понятно для чего сделано.
Плюс ко всему семантическое разделение на 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 [5]). Варианты настройки и парсинга значений для более интересных ситуаций разберём далее.
Сразу же доступно 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 [6].
Однако, тут не без ложки дёгдя. На момент когда я начинал им пользоваться, документация выглядела иначе [7].
Да, можно задать своё замыкание как трактовать имя бранча (например '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 [8]
Damien Coraboeuf [9], являющийся автором этого плагина, оказался очень отзывчивым, и оперативным исправив оперативно пару мелких вещей.
В общем же, как это часто бывает, предложил реализовать мне самому, то что я предлагаю.
Я последовал его совету — быстренько сварганил pull request [10].
Теперь, после его принятия, мы получаем объект информации о коммите 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 принесли странный результат — именем бранча стало нечто вроде HEAD.
В самом деле причина простая, если посмотрим что делает билдер он же собирает не ветку, а конкретный коммит. На момент сборки, в этой же ветке могут будь уже другие. Поэтому, он всегда делает checkout по имени хеша коммита. Таким образом, мы получаем git репозиторий в состоянии detached head [11].
Как я, забегая вперёд уже сказал, эта ситуация нормальная и большинство работают так, а в данном плагине просто нужно прописать одну строчку, с указанием имени внешней переменной или переменных, из которых нужно взять настоящее имя бранча, для gitlab-ci мне нужно было просто добавить:
branchEnv = ['CI_BUILD_REF_NAME']
В Jenkins такие переменные [12] также были добавлены достаточно давно по запросу JENKINS-30252 [13]. Так, если вы хотите поддержать сразу обе системы, вы можете просто написать:
branchEnv = ['CI_BUILD_REF_NAME', 'GIT_LOCAL_BRANCH']
Я надеюсь вам станет удобнее работать с версиями в gradle. Да, и всячески рекомендую ставить баги или писать реквесты автору — он очень оперативно на них отвечает. Хорошего кодинга!
Автор: Hubbitus
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/242781
Ссылки в тексте:
[1] github.com/nemerosa/versioning: https://github.com/nemerosa/versioning
[2] Jenkins для Android на чистой системе и без UI: https://habrahabr.ru/post/315804/
[3] Через тернии к сборке: https://habrahabr.ru/company/rtl-service/blog/307398/
[4] github flow: https://guides.github.com/introduction/flow/
[5] в стиле Tomcat: http://tomcat.apache.org/tomcat-8.0-doc/config/context.html#Naming
[6] customisation: https://github.com/nemerosa/versioning#customisation
[7] выглядела иначе: https://github.com/nemerosa/versioning/blob/d17a8ee9d007da002d8982d20389cefcdb262976/README.md#customisation
[8] github.com/nemerosa/versioning/issues/32: https://github.com/nemerosa/versioning/issues/32
[9] Damien Coraboeuf: https://github.com/dcoraboeuf
[10] pull request: https://github.com/nemerosa/versioning/pull/33
[11] detached head: https://marklodato.github.io/visual-git-guide/index-ru.html#detached
[12] переменные: https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin#GitPlugin-Environmentvariables
[13] JENKINS-30252: https://issues.jenkins-ci.org/browse/JENKINS-30252
[14] Источник: https://habrahabr.ru/post/321668/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.