Сборка проектов с помощью Gradle

Jan 3, 2019 06:19 · 962 words · 5 minute read gradle CI docker

На просторах Интернета существует множество статей и видеоуроков (как платных, так и бесплатных) о том, как установить 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 для статического анализа кода.

tweet Share