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