| 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
build.gradle
- это файл, который описывает gradle-проект. Про него мы поговорим далее отдельно.wrapper
- это отдельная фишка Gradle. Для того, чтобы версия билд-системы, выполняющей билды проекта была консистентна не зависимо от места исполнения сборки, в Gradle есть wrapper. Как это работает? Командаgradlew
работает абсолютно так же, как и стандартная командаgradle
, за исключением того, чтоgradlew
ещё и загружает заранее определённую версию самого Gradle, для использования во время сборки. Буквально, Вы определяете версию Gradle, на которой сборка Вашего проекта происходит, и далее, другие разработчики и / или скрипты на CI выполняют сборку проекта через командыgradlew
. Wrapper, в этом случае, гарантирует, что, где бы ни собирался Ваш проект, он будет собран версией Gradle, определённой Вами.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.