Сборка проектов с помощью Gradle
Jan 3, 2019 06:19 · 972 words · 5 minute read
На просторах Интернета существует множество статей и видеоуроков (как платных, так и бесплатных) о том, как установить Gradle, написать собственную конфигурацию сборки (файл build.gradle
) и, наконец, собрать Java-приложение с помощью Gradle. В данной статье мы не будем рассматривать эти простейшие вещи, а разберемся как следовать принципу DRY (Don’t Repeat Yourself) при работе с большим количеством схожих проектов!
Считаем, что Gradle уже установлен и работает.
Допустим, вы работаете с большим количеством проектов, в которых конфигурация сборки (build.gradle
) частично или полностью повторяется. Рано или поздно этот копипаст вам надоест и захочется избавиться от избыточности (как мы это уже делали с описаниями пайплайнов).
В данном случае для решения задачи как нельзя лучше подходят инит-скрипты. Эти скрипты очень похожи на остальные гредл-скрипты, но запускаются перед сборкой (before the build starts) и зачастую используются в следующих случаях:
- для конфигурации сборки на уровне организации (пути к кастомным плагинам или репозиториям);
- для установки параметров сборки в зависимости от текущего окружения (машина разработчика / CI-сервер);
- для настройки персональной информации, необходимой для сборки, в зависимости от пользователя (к примеру, учетные данные для доступа к БД или репозиторию);
- для определения специфических параметров, зависящих от машины, на которой выполняется сборка (например, путь к JDK);
- для регистрации или тонкой настройки логгеров.
Существует несколько вариантов использования гредловских инит-скриптов, например:
- из командной строки, с помощью параметра
-I
(--init-script
), за которым следует путь к скрипту. Этот параметр можно использовать несколько раз, для указания нескольких инит-скриптов; - разместить файл
init.gradle
(илиinit.gradle.kts
для Kotlin) в каталогеUSER_HOME/.gradle/
; - разместить один или несколько файлов с расширением
.gradle
(или.init.gradle.kts
для Kotlin) в директорииUSER_HOME/.gradle/init.d/
(наш вариант для машины разработчика); - разместить один или несколько файлов с расширением
.gradle
(или.init.gradle.kts
для Kotlin) в папкеGRADLE_HOME/init.d/
(наш вариант для CI).
К слову, если инит-скриптов несколько, то они все будут выполнены в порядке описанном выше. Если же инит-скрипты находятся в одном каталоге, то выполняться они будут в алфавитном порядке.
Рассмотрим самый простой пример из документации Gradle. Допустим, build.gradle
выглядит так:
repositories {
mavenCentral()
}
task showRepos {
doLast {
println "All repos:"
println repositories.collect { it.name }
}
}
Инит-скрипт init.gradle
содержит строки:
allprojects {
repositories {
mavenLocal()
}
}
Для теста в командной строке выполним задачу showRepos:
gradle --init-script init.gradle -q showRepos
All repos:
[MavenLocal, MavenRepo]
Как видим, репозиторий mavenLocal
был добавлен к списку репозиториев, перечисленных в конфигурации сборки (build.gradle
).
Разберем более сложный и реальный пример. Создаем файл build-scripts.gradle
следующего содержания:
gradle.projectsLoaded {
rootProject.allprojects {
if (System.getenv('BUILD_ENVIRONMENT') ?: 'DEVELOPER' == 'CI') {
buildscript {
repositories {
maven { url 'http://artifactory:8081/artifactory/libs-release/' }
}
dependencies {
classpath 'net.ltgt.gradle:gradle-apt-plugin:0.19'
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE'
classpath 'io.spring.gradle:dependency-management-plugin:1.0.6.RELEASE'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
classpath 'com.sun.xml.bind:jaxb-core:2.3.0.1'
classpath 'com.sun.xml.bind:jaxb-impl:2.3.0.1'
classpath 'javax.xml.bind:jaxb-api:2.3.1'
classpath 'javax.activation:activation:1.1.1'
}
}
} else {
buildscript {
repositories {
maven {
url 'https://company.jfrog.io/company/libs-release/'
credentials {
username "${artifactoryUser}"
password "${artifactoryPassword}"
}
}
}
dependencies {
classpath 'net.ltgt.gradle:gradle-apt-plugin:0.18'
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE'
classpath 'io.spring.gradle:dependency-management-plugin:1.0.6.RELEASE'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
classpath 'com.sun.xml.bind:jaxb-core:2.3.0.1'
classpath 'com.sun.xml.bind:jaxb-impl:2.3.0.1'
classpath 'javax.xml.bind:jaxb-api:2.3.1'
classpath 'javax.activation:activation:1.1.1'
}
}
}
}
}
В данном примере если сборка происходит на CI-сервере (значение переменной BUILD_ENVIRONMENT
равно “CI”), то в качестве репозитория сторонних библиотек используется локальный jfrog-artifactory, доступный на порту 8081 без авторизации. Если же сборка производится на машине разработчика (BUILD_ENVIRONMENT
равно “DEVELOPER”) то, используются личные учетные данные для доступа к приватному JFrog-репозиторию. Логин/пароль в данном случае должны находиться в файле USER_HOME/.gradle/gradle.properties
:
...
artifactoryUser=admin
artifactoryPassword=Very$tr0ngPaS$wd
Создадим еще один инит-скрипт, в котором будем проверять/устанавливать версию для проекта:
allprojects {
afterEvaluate { project ->
project.plugins.withId("java") {
try {
project.version = System.getenv('PROJECT_VERSION') ?: System.properties['user.name'] + "_DEV"
println "'java' plugin common config"
println " |- set project version: ${project.version}"
println ''
} catch (UnknownPluginException) {
println UnknownPluginException
}
}
}
}
Здесь для всех проектов, в которых используется плагин java, будет устанавливаться версия из переменной окружения “PROJECT_VERSION”, или, если такой переменной окружения нет, версия проекта будет равна имени пользователя с префиксом _DEV
.
Размещаем данные инит-скрипты в директории USER_HOME/.gradle/init.d/
. Теперь во всех проектах, в конфигурации сборки (build.gradle
) секция buildscript
и установка версии проекта будет соответствовать описанным в инит-скриптах. Именно за счет этого можно избавиться от избыточности и копипасты в настройках сборки с помощью Gradle.
Следующим логичным шагом будет возможность использования инит-скриптов всеми членами команды разработки - проще всего поместить эти скрипты в git-репозиторий. Опять же, для удобства настройки можно создать небольшой скрипт - назовем его initilaze-gradle.sh
- с таким содержимым:
#!/usr/bin/env bash
LINK_OR_DIR="${HOME}/.gradle/init.d"
if [ -d "${LINK_OR_DIR}" ]; then
if [ -L "${LINK_OR_DIR}" ]; then
# It is a symlink!
rm "${LINK_OR_DIR}"
else
# It's a directory!
rm -rf "${LINK_OR_DIR}"
fi
fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
ln -s ${DIR}/init.d ${LINK_OR_DIR}
while true; do
read -p "Do you wish add credentials template to ${HOME}/.gradle/gradle.properties? " yn
case $yn in
[Yy]* ) echo "artifactoryUser=" >> ${HOME}/.gradle/gradle.properties; echo "artifactoryPassword=" >> ${HOME}/.gradle/gradle.properties; break;;
[Nn]* ) exit;;
* ) echo "Please answer yes or no.";;
esac
done
Теперь разработчику достаточно склонировать git-репозиторий с инит-скриптами гредла, единожды запустить скрипт initilaze-gradle.sh
(для создания символьной ссылки на каталог со скриптами) и добавить свои учетные данные для доступа к JFrog. Благодаря созданию символьной ссылки в будущем при обновлении инит-скриптов в git-репозитории достаточно будет выполнить
git pull
и “свежие” скрипты станут доступны по пути USER_HOME/.gradle/init.d/
.
Так как на CI-сервере абсолютно все сборки запускаются в docker-контейнерах, для использования данных инит-скриптов нам нужно добавить их в docker-образ. В моем случае инструкции по сборке образа (Dockerfile) выглядят так:
FROM openjdk:11.0.1-jdk-slim
ENV GRADLE_HOME /opt/gradle
ENV GRADLE_VERSION 5.0
ARG GRADLE_DOWNLOAD_SHA256=6157ac9f3410bc63644625b3b3e9e96c963afd7910ae0697792db57813ee79a6
RUN apt update && apt install -y wget
RUN set -o errexit -o nounset \
&& echo "Downloading Gradle" \
&& wget --no-verbose --output-document=gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
\
&& echo "Checking download hash" \
&& echo "${GRADLE_DOWNLOAD_SHA256} *gradle.zip" | sha256sum --check - \
\
&& echo "Installing Gradle" \
&& unzip gradle.zip \
&& rm gradle.zip \
&& mv "gradle-${GRADLE_VERSION}" "${GRADLE_HOME}/" \
&& ln --symbolic "${GRADLE_HOME}/bin/gradle" /usr/bin/gradle
ENV BUILD_ENVIRONMENT=CI
COPY init.d/ ${GRADLE_HOME}/init.d/
WORKDIR /project
Больше информации о использовании инит-скриптов в Gradle можно найти в официальной документации, а в следующей статье рассмотрим использование плагина checkstyle для статического анализа кода.