Статья-туториал от ведущего Java-разработчика "ITQ Group" Константина Киселевича.

Дорогой Junior и все, кто занимается copy-past конфигов Gradle.
В этой статье я хочу простым языком рассказать о gradl'овой конфигурации сборки проекта, чтобы вы не боялись использовать Gradle.
Давайте начнем с того, что после xml'ного Maven'а, действительно, непонятно, что значит каждая строчка из следующего примера:
buildscript {
ext {
springBootVersion = '2.7.4'
lombokVersion = '1.18.24'
h2Version = '2.1.214'
orikaCoreVersion = '1.5.4'
queryDslVersion = '4.2.2'
javaxVersion = '1.3.2'
sonarqubeVersion = '3.0'
}
}
plugins {
id('java-library')
id('org.springframework.boot').version("${springBootVersion}").apply(false)
id('org.sonarqube').version("${sonarqubeVersion}").apply(false)
}
allprojects {
repositories {
mavenCentral()
}
}
subprojects {
apply(plugin: 'java-library')
apply(plugin: 'org.sonarqube')
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
withSourcesJar()
withJavadocJar()
}
dependencies {
api(platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}"))
compileOnly("org.projectlombok:lombok:${lombokVersion}")
annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
}
}
dependencies {
implementation(project('deveducate-web'))
}
Почему один dependencies {} вложен в subprojects {}, а второй - не вложен. Что такое buildscript {} и toolchain {}. Вопросов много, ответы в статье, но самый главный вопрос - что за программа этот текст читает и интерпретирует ?
Groovy
Давайте начнем с последнего вопроса и немного углубимся в историю.
Где-то в середине нулевых у программистов Java появились вопросы к языку - почему же так медленно появляются новые фичи? Действительно, посмотрим историю версий Java:
J2SE 5.0 сентябрь 2004
Java 6 декабрь 2006
Java 7 июль 2011
Java 8 март 2014
За 10 лет всего 4 версии. Прямо скажем, не так и много, как хотелось.
Ответом на такое медленное развитие стал язык Groovy. Его девиз: release early, release often (внедряй фичи раньше и чаще других).
Некоторые фичи Groovy перекочевали в другие языки:
синтаксис для оператора Элвиса "?:" (теперь в PHP и Kotlin);
оператор безопасной навигации "?." (теперь в C#, Swift, Kotlin и Ruby - как &.);
оператор <=> (теперь в PHP и Ruby).
Как называется программа ?
Программа называется Gradle. Скачать ее вы можете с сайта по ссылке.
Установка Gradle.
На выбор доступны два варианта работы с Gradle в проекте:
через файл gradle;
через файл gradlew, где w означает wrapper (обертка).
В чем разница ?
Разница в удобстве для ваших коллег. В случае использования обертки gradlew версия gradle будет одинаковой у всей команды, каждому из коллег не придется вручную качать дистрибутив gradle с сайта и проделывать манипуляции с распаковкой.
Итак.
Представим, что на вас возложили инициативу начать писать код проекта и использовать gradle в качестве сборщика проекта. Это большое доверие :)
Выберем директорией проекта C:\doit.
Поскольку вы первый в команде, вам необходимо самостоятельно скачать Gradle. Зайти на сайт по ссылке, скачать релиз и распаковать в какую-нибудь временную директорию вне директории проекта, например, C:\tmp.
Примерное содержание директории должно быть таким:
/bin
/init.d
/lib
README
Нужный нам файл gradle находится в директории /bin.
Вы бережете время коллег, которым предстоит вместе с вами разрабатывать проект, и не хотите заставлять их так же вручную качать более 100мб дистрибутива Gradle и повторять ваши шаги.
Поэтому вы откроете терминал (cmd), зайдете в директорию проекта cd C:\doit и запустите C:\tmp\bin\gradle init.
В предложенных вариантах создания gradle проекта выберете basic. Это пустой проект.
В директории проекта C:\doit появились самые необходимые файлы: gradlew, settings.gradle и build.gradle и даже .gitignore.

В C:\doit\gradle\wrapper\gradle-wrapper.properties указана версия gradle:
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
Если вы добавите сгенерированные файлы в git, то у всех коллег будет одна и та же версия gradle и дистрибутив автоматически скачается при выполнении любой команды, например,
gradlew help
Таким образом, обертка gradle wrapper позволит всей команде вести разработку под одинаковой версией gradle и сама скачает/распакует дистрибутив в домашнюю директорию пользователя: ~\.gradle\wrapper\dists.
Gradle написан на Groovy + Kotlin.
Выше мы поговорили о скорости развития Groovy. Это важно, но существует еще одна причина в пользу языка.
Почему же Groovy был выбран для написания сценариев сборки проектов?
Ответ заключается в DSL. Groovy позволяет легко создавать новые специфические языки программирования под нужную нам задачу.
В Groovy для создания DSL реализуется концепция Метапрограммирования (по ссылке с диаграммой исполнения missingMethod).
Один из классов этой концепции - DelegatingScript:
Посмотрите пример и вы все поймете. Документация.
Cобственный язык (DSL)
Пример абстрактного файла для нашего выдуманного скриптового языка my.best.dsl.
foo(1,2) {
// код метода
}
bar = "Hello world!";
А вот код класса на Groovy, который читает, парсит и выполняет скрипт my.best.dsl:
/**
* класс с методом foo() и сеттером setBar()
*/
class MyDSL {
public void foo(int x, int y, Closure z) { ... }
public void setBar(String a) { ... }
}
CompilerConfiguration cc = new CompilerConfiguration();
cc.setScriptBaseClass(DelegatingScript.class.getName());
GroovyShell sh = new GroovyShell(cl,new Binding(),cc);
DelegatingScript script = (DelegatingScript)sh.parse(new File("my.best.dsl"))
script.setDelegate(new MyDSL());
script.run();
DelegatingScript парсит наш выдуманный язык, на котором мы написали скрипт my.best.dsl.
Но у DelegatingScript нет метода foo или setBar. Поэтому мы подсказываем, что исполнение методов необходимо делегировать объекту new MyDSL() (в строке script.setDelegate(new MyDSL())). То есть вызвать эти методы у объекта new MyDSL()
Заметьте, что метод foo последним аргументом имеет замыкание Closure.
public void foo(int x, int y, Closure z) { ... }
А в нашем придуманном языке вызов метода следующий:
foo(1,2) {
// код метода
}
Вот так в Groovy можно передавать аргументы: 1, 2 в скобках () и замыкание вне скобок ()если аргумент типа Closure последний в списке аргументов.
Надеюсь, теперь стало чуть понятнее, что такое в build.gradle
dependencies {
implementation(project('deveducate-web'))
}
dependencies(Closure c) - это метод с одним аргументом - функцией, тело которой содержит вызов метода implementation(...).
Согласитесь, насколько легко реализовать свой скриптовый язык при помощи Groovy.
Создатели сборщика проектов Gradle так же оценили эту возможность и придумали свой довольно простой язык.
Он действительно несложный. В нем, по сути, всего три главных метода: plugins(), buildScript(), task(). Аргументами этих методов являются функции, тело которых вы пишете в фигурных скобках { ... }.
Чуть-чуть подробнее. В примере выше методы foo и setBar принадлежали классу MyDSL. А какому классу/интерфейсу принадлежат plugins и buildScript ?
Они принадлежат интерфейсу org.gradle.api.Project: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#buildscript-groovy.lang.Closure-
(Реализует же этот интерфейс класс org.gradle.api.internal.project.DefaultProject. Рекомендую клонировать сорцы gradle https://github.com/gradle/gradle и покопаться в них)
Прокрутите вверх интерфейс Project, указанный в ссылке выше. Посмотрите, сколько еще методов вы можете использовать в своем build.gradle. Документация по методам Gradle доступна на https://docs.gradle.org/current/dsl/index.html
В общем, если в build.gradle вам непонятно какое-то ключевое слово, открывайте интерфейс Project и ищите это ключевое слово там. Если не найдете, то, скорее всего, это название метода/свойства из подключенного вами плагина.
Про плагины Gradle. Зачем они нужны?
Можно, например, в build.gradle вручную на языке Groovyнаписать скрипт сборки jar, прописать директории, в которых ожидается исходный код, запустить команду компиляции, команду тестов и так далее.
Но, к счастью, все это уже написано за нас и упаковано в плагин java-library, который поставляется вместе с Gradle. (Плагин java-library расширяет возможности плагина java, наследует его возможности)
Чистый Gradle без плагинов позволяет запускать следующие задачи:
c:\doit>gradlew tasks
> Task :tasks
------------------------------------------------------------
Tasks runnable from root project 'doit'
------------------------------------------------------------
Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.
Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'doit'.
dependencies - Displays all dependencies declared in root project 'doit'.
dependencyInsight - Displays the insight into a specific dependency in root project 'doit'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
outgoingVariants - Displays the outgoing variants of root project 'doit'.
projects - Displays the sub-projects of root project 'doit'.
properties - Displays the properties of root project 'doit'.
resolvableConfigurations - Displays the configurations that can be resolved in root project 'doit'.
tasks - Displays the tasks runnable from root project 'doit'.
To see all tasks and more detail, run gradlew tasks --all
To see more detail about a task, run gradlew help --task <task>
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
Давайте подключим плагин java-library в наш проект: в самом начале build.gradle пропишем:
plugins {
id('java-library')
}
После подключения плагина появились новые таски для работы с проектом:
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
check - Runs all checks.
classes - Assembles main classes.
clean - Deletes the build directory.
compileJava - Compiles main Java source.
compileTestJava - Compiles test Java source.
jar - Assembles a jar archive containing the main classes.
javadoc - Generates Javadoc API documentation for the main source code.
processResources - Processes main resources.
processTestResources - Processes test resources.
test - Runs the test suite.
testClasses - Assembles test classes.
Итак, подключили плагин. А что такое плагин - это класс, реализующий интерфейс Plugin<Project>. Пример плагина HelloPlugin:
class HelloPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the HelloPlugin'
}
}
}
}
У объекта Project появился новый метод/task hello. Точно так же плагин Java-library добавляет новые методы/таски и многое другое (в том числе и соглашения, например, что исходный код ищется по пути src/main/java).
Смотрите документацию по Java-library на странице https://docs.gradle.org/current/userguide/building_java_projects.html и https://docs.gradle.org/current/userguide/java_library_plugin.html
Плагины Java и Java-library немного отличаются. Второй плагин наследует первый и добавляет, например к implementation() новый метод api().
Теперь вы можете указать в подпроекте implementation("org.apache.commons:commons-lang3:3.5"), если ни один тип или метод из commons-lang не станет частью публичного API.
Теперь, если версия commons-lang изменится на 3.6, то подпроекты, зависимые от текущего подпроекта, не будут перекомпилироваться.
И наоборот для зависимости в api().
Все это ускоряет сборку на больших проектах.
Пример!
Пришла пора создать маленький Java проект из одного класса, выводящего в консоль стандартное Hello world.
Очистим рабочую C:\doit от старых файлов. Запустим в командной строке C:\tmp\bin\gradle init, которая добавит Gradle Wrapper, а так же создаст build.gradle.

В C:\doit появился gradle wrapper.

Осталось добавить Java класс в C:\doit\src\main\java.

Класс HelloWorld:
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
И подключить плагин java-library в build.gradle, как делали выше.
plugins {
id('java-library')
}
Неплохо, уже можно и собрать наше маленькое приложение. Вспомним про документацию к Java-library https://docs.gradle.org/current/userguide/building_java_projects.html и https://docs.gradle.org/current/userguide/java_library_plugin.html или https://docs.gradle.org/current/dsl/. Поищем что-то по слову jar. Найдем таск jar.
jar - Собирает jar-архив с main классом.
Запускаем:
C:\doit>.\gradlew jar
Готово - появился C:\doit\build\libs\doit.jar, который можем запустить в командной строке через C:\doit>java -cp "C:\doit\build\libs\doit.jar" HelloWorld
Приходится указывать название класса при запуске jar'ника. Можно переместить название класса в файл манифеста MANIFEST.MF, согласно требованиям Java. Для этого откроем страницу building_java_projects или org.gradle.api.tasks.bundling.Jar и поищем по слову manifest. Находим пример:
jar {
manifest {
attributes("Implementation-Title": "Gradle",
"Implementation-Version": archiveVersion)
}
}
Для запуска jar файла из командной строки без указания класса manifest должен содержать строку:
Main-Class: HelloWorld
Добавим в build.gradle:
jar {
manifest {
attributes 'Main-Class': 'HelloWorld'
//или равнозначно
//attributes('Main-Class': 'HelloWorld')
}
}
Почему такая странная запись через двоеточие? Так представлен элемент HashMap в Groovy, как параметр метода https://docs.gradle.org/current/javadoc/org/gradle/api/java/archives/Manifest.html

Прочтите The Groovy Development Kit https://www.groovy-lang.org/groovy-dev-kit.html
c:\doit>.\gradlew jar
c:\doit>java -jar build\libs\doit.jar
Hello world!
Разобраться несложно. Главное - не копипастить, а проходить каждую строчку в чужих примерах по документации.
Рекомендую статьи:
Комментарии (8)

kovserg
23.01.2023 18:19А как профилировать gradle и понять чем он там занимался 10минут?

slonopotamus
24.01.2023 10:07Точно так же как профилировать любое другое приложение на Java? И еще есть
gradle build --scan

YuNastasiya
24.01.2023 14:02В статье https://developer.android.com/studio/build/profile-your-build рекомендуют использовать "gradle-profiler" или "gradlew --profile". Так же рекомендуют использовать Build Analyzer https://developer.android.com/studio/build/build-analyzer, по сути, графический вариант gradlew --profile

Stingray42
24.01.2023 11:31+6Я боюсь использовать gradle не из-за groovy dsl, а из-за того что каждый мажорный релиз в нем меняется формат конфигурации, и все гайды со stackoverflow перестают работать. Нельзя просто настроить и забыть, в отличие от великолепного во всех отношениях maven.

Felan
24.01.2023 18:46+1Отличная статья.
А вот тут нет опечатки?
"Теперь вы можете указать в подпроектеimplementation("org.apache.commons:commons-lang3:3.5"), если не ни один тип или метод изcommons-langне станет частью публичного API. "
engineit
Спасибо. Полезная статья