Статья-туториал от ведущего 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!
Разобраться несложно. Главное - не копипастить, а проходить каждую строчку в чужих примерах по документации.
