Gradle 101: Основы

| February 4, 2023

Gradle - это ещё одна билд-система. Помимо gradle в мире java разработки существует ещё 2 билд-системы - Ant и Maven.

Что такое билд-системы?

И gradle, и maven, и ant отвечают за сборку java проектов. Java, как платформа, даёт на руки разработчикам все необходимые инструменты для сборки собственных проектов - компилятор и classpath. Но в своём ванильном виде, эти инструменты далеко не так удобны, как можно было бы себе представить. Именно по этой причине, для того, чтобы разработчикам упростить процедуру сборки и упаковки их приложений и были созданы билд-системы.

Билд-системы скрывают сложность взаимодействия с компилятором и classpath’ом, переводя взаимодействие разработчика с ними, на более привычные технологии. Maven и Ant используют xml, Gradle использует Groovy и, с недавних пор, Kotlin.

Сами по себе, билд-системы отличаются ключевыми идеями построения java проектов. Если Ant следует императивному подходу, то есть, просит разработчика явно выстраивать процесс сборки, то gradle и maven следуют декларативному подходу, то есть, простят разработчика описать, что должно получиться в итоге сборки.

Как начать использовать gradle?

Чтобы начать использовать Gradle в Ваших проектах, для начала его нужно установить. Сделать это можно следуя инструкции из официального гайда.

После установки, в терминале станет доступна новая команда gradle. Именно с помощью неё мы и будем осуществлять всё взаимодействие с билд-системой.

Чтобы начать использовать gradle в своём проекте, достаточно выполнить следующую команду:

gradle init

Причём, эта команда может быть использована чтобы:

  • сгенерировать свежий Gradle проект
  • сынтегрировать Gradle в существующий maven проект

Если выполнить эту команду в существующем maven-проекте, то gradle проанализирует конфигурацию maven-проекта и попытается сгенерировать аналог с gradle.

Если выполнить эту команду в пустой директории, то gradle предложит ответить на ряд вопросов, после чего, сгенерирует свежий Java проект с поддержкой gradle.

Создание нового проекта

Давайте попробуем создать новый gradle проект. Выполним gradle init в пустой директории.

% gradle init
Starting a Gradle Daemon (subsequent builds will be faster)

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4]

Нас интересует Java приложение, поэтому выберем 2 и продолжим:

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6]

и сразу же встречаем ещё одну особенность Gradle - он поддерживает разные языки программирования. Для нашего урока нас интересует Java, поэтому выбираем 3 и идём дальше:

Split functionality across multiple subprojects?:
  1: no - only one application project
  2: yes - application and library projects
Enter selection (default: no - only one application project) [1..2]

Здесь он предлагает нам задуматься о многомодульном проекте. Дело в том, что для удобства и ускорения процесса сборки, Вы можете выстроить цепочку модулей с зависимостями друг от друга. Gradle легко позволяет это сделать. Однако, поддержка нескольких модулей не является частью данного урока, поэтому выбираем 1, и продолжаем:

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2]

Затем, gradle предлагает выбрать для DSL билд скрипта. Что это значит? Фактически, на этом шаге мы делаем выбор в пользу языка программирования на котором будут описаны наши файлы gradle-проекта (аналог pom.xml, для знатоков maven). Изначально, для этих целей в gradle использовался Groovy. В последние годы, была добавлена поддержка и Kotlin. Выбираем 1 и смотрим дальше:

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4]

Здесь gradle предлагает нам добавить интеграцию с тестовыми фреймворками в проект. Предлагаю использовать JUnit 5, выбрав 4.

Заключительные две вещи, о которых gradle Вас спросит, это название проекта и основной пакет. Вы вольны подставить свои, для этого урока Я буду использовать следующие:

Project name (default: temp): gradle-console-app
Source package (default: gradle.console.app): ru.anverbogatov

Всё. Новый gradle-проект успешно сгенерирован.

Было сложно? Нет, но долго. Однако, на выходе Вы получаете абсолютно свежий, готовый к разработке Java проект.

Gradle-проект

Если заглянуть в директорию с получившимся проектом, то мы увидим следующую картину:

% tree
.
├── app
│   ├── build.gradle <-- 1
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── ru
│       │   │       └── anverbogatov
│       │   │           └── App.java
│       │   └── resources
│       └── test
│           ├── java
│           │   └── ru
│           │       └── anverbogatov
│           │           └── AppTest.java
│           └── resources
├── gradle
│   └── wrapper <-- 2
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew <-- 2
├── gradlew.bat
└── settings.gradle <-- 3

15 directories, 8 files
  1. build.gradle - это файл, который описывает gradle-проект. Про него мы поговорим далее отдельно.
  2. wrapper - это отдельная фишка Gradle. Для того, чтобы версия билд-системы, выполняющей билды проекта была консистентна не зависимо от места исполнения сборки, в Gradle есть wrapper. Как это работает? Команда gradlew работает абсолютно так же, как и стандартная команда gradle, за исключением того, что gradlew ещё и загружает заранее определённую версию самого Gradle, для использования во время сборки. Буквально, Вы определяете версию Gradle, на которой сборка Вашего проекта происходит, и далее, другие разработчики и / или скрипты на CI выполняют сборку проекта через команды gradlew. Wrapper, в этом случае, гарантирует, что, где бы ни собирался Ваш проект, он будет собран версией Gradle, определённой Вами.
  3. settings.gradle - файл с настройками всего gradle-проекта

build.gradle файл

build.gradle - это файл с описанием Вашего gradle-проекта, а если быть точнее, gradle-модуля. Пожалуй, с ним, разработчику приходится иметь дело чаще всего.

Давайте на него взглянём:

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Java application project to get you started.
 * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
 * User Manual available at https://docs.gradle.org/7.6/userguide/building_java_projects.html
 */

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'

    // This dependency is used by the application.
    implementation 'com.google.guava:guava:31.1-jre'
}

application {
    // Define the main class for the application.
    mainClass = 'ru.anverbogatov.App'
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

Сам Gradle помогает нам, разработчикам, быстре понять, что там происходит, вставляя комментарии во все секции. Что этот синтаксис означает и как с ним работать - мы поговорим в отдельной статье о Gradle DSL. А пока, давайте пробежимся по основным элементам этого файла:

plugins {...} // 1
repositories {...} // 2
dependencies {...} // 3
application {...} // 4
tasks.named('test') {...} // 5

1. plugins

build.gradle начинается с определения секции plugins. Эта секция содержит в себе определения подключаемых плагинов, которые необходимы для организации процесса сборки Gradle-проекта. Каждый плагин отвечает за конкретные действия (например, есть плагин, который организует сборку Spring-проекта или есть отдельный плагин, который позволяет разработчику проекта, получать отчёт о устаревших зависимостях в проекте).

Сам Gradle добавляет в секцию плагинов один-единственный плагин под названием application. Из комментария в секции plugins в  build.gradle файле можно понять, что конкретно этот плагин даёт возможность выполнять сборку Java проектов.

2. repositores

Далее идёт секция repositories. Аналогично и Maven, данная секция позволяет определять репозитории, в которых будет выполнен поиск зависимостей проекта. Причём подключать можно как свои, закрытые репозитории (Nexus, Artifactory и так далее), так и открытые репозитории Maven.

3. dependencies

Секция dependencies - самая популярная секция для разработчиков приложений. Дело в том, что именно в ней определяются зависимости Вашего проекта - то, какие именно библиотеки и фреймворки будут использованы в Вашем приложении, а так же их версии, и скоупы.

4. application

Секция application содержит конфигурацию класса Вашего приложения, в котором находится точка входа в приложения (зачастую, класс с методом main(String[] args)).

5. tasks

И, наконец, последняя секция написана не так, как предыдущие.

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

Дело в том, что tasks это коллекция тасок (задач), с которыми умеет работать Gradle. Через tasks Вы вольны определять свои новые задачи, которые можно использовать во время сборки собственых проектов. Например, что просиходит конкретно в этом примере выше:

tasks.named('test')

добавляет новую задачу в список поддерживаемых в Вашем проекте. Название, как несложно догадаться - test.

Далее,

// Use JUnit Platform for unit tests.
useJUnitPlatform()

добавляет поддержку JUnit тестов в проект. Если быть точнее, эта директива позволяет Gradle находить и выполнять тесты JUnit 5 в проекте. Если бы мы использовали JUnit 4, то директива была бы следующей:

useJUnit()

End-2-end процесс

Ну и теперь, давайте посмотрим на то, как это всё работает. В основной директории проекта, можно выполнить следующую команду в терминале:

./gradlew test

И мы получим следующий результат:

% ./gradlew test
Starting a Gradle Daemon (subsequent builds will be faster)

BUILD SUCCESSFUL in 3s
3 actionable tasks: 3 executed

Что происход когда мы вызываем эту команду?

Прежде всего, каждый раз, когда мы используем wrapper (а в этом случае, происходит именно это, внимание на команду - gradlew), скачивается указанная в gradle-wrapper.properties версия Gradle и используется для выполнения сборки проекта. Затем, выполняется проверка, запущен ли Gradle Daemon.

Gradle Daemon это фоновый процесс, который значительно ускоряет сборку - пересборку Ваших проектов. А далее уже происходит анализ того, в какой последовательности будут выполнятся таски для осуществления сборки проекта.

В нашем примере, мы просим Gradle выполнить задачу, которую сами и определили - задачу, под названием task. Gradle понимает, что для того, чтобы найти и прогнать JUnit тесты, необходимо сам проект для начала скомпилировать, выполнив сборку. А для того, чтобы выполнить сборку, необходимо скачать зависимости проекта.

Выше, Я описал укороченную версию того, как сработает Gradle в случае сборки моего демонстрационного проекта. Для того, чтобы получить полную информацию о том, что делает Gradle, можно воспользоваться флагом --info:

% ./gradlew test --info

И вот, что на самом деле выполнит Gradle для сборки и прогона тестов в проекте:

% ./gradlew test --info
...
> Configure project :
...
> Configure project :app
...
> Task :app:compileJava UP-TO-DATE
...
> Task :app:processResources NO-SOURCE
...
> Task :app:classes UP-TO-DATE
...
> Task :app:compileTestJava UP-TO-DATE
...
> Task :app:processTestResources NO-SOURCE
...
> Task :app:testClasses UP-TO-DATE
...
> Task :app:test UP-TO-DATE
...
BUILD SUCCESSFUL in 360ms
3 actionable tasks: 3 up-to-date

Заключительная часть

Gradle - мощный и гибкий инструмент для организации сборки Ваших Java проектов. Однако, мне, было не очень просто разобраться в нём, когда Я только-только перешёл на него с Maven.

Надеюсь этой статьёй Я помог Вам справится с первоначальным дискомфортом от начала использования Gradle.

Информацией, данной в этой статье, изучение Gradle для Java разработчика - не ограничивается. В следующей статье - поговорим о Gradle DSL.

Список материалов