Gradle - writedown-team/writedown GitHub Wiki

Gradle

Gradle можно считать менеджером всего проекта. Он в общем и целом ничего по смыслу про ваш проект не знает, и работает на макроуровне. Именно поэтому его можно использовать как для такого языка, так и для сякого.

Модули

Что он умеет: разделить ваш проект на модули, чтобы части кода, которые не должны знать друг про друга, друг про друга не знали. Пример: сервер и клиент. Сервер не должен напрямую мочь вызывать клиентский код (в частности ввиду невозможности этого), и наоборот. Вместо этого, и сервер и клиент знают про общий интерфейс, через который и общаются. Этот интерфейс, разумеется, представляет собой ещё один модуль в Gradle. Про отношение "что-то знает про что-то" также говорят "что-то зависит от чего-то" и называют зависимостью. На самом деле что такое отношение зависимости понятно, и также понятно, что оно может включать в себя не только знание. Просто чему-то одному для работы может требоваться что-то другое.

СЕРВЕР → ИНТЕРФЕЙС ← КЛИЕНТ

Зависимости часто обозначаются стрелочками (в частности, между модулями).

Можно считать, что модуль — максимальная по включению единица компиляции, в рамках которой все сущности знают друг про друга. Тогда становится очевидным следующее: модули не могут зависеть друг от друга по циклу. Иначе можно считать, что каждый из них знает про каждый, достаточно пройтись по стрелочкам. Тогда все эти модули можно считать одним модулем нафик.

Один ньюанс: в Gradle модули называются "projects". В этом тексте эта терминология использоваться не будет, но знать надо.

Взаимодействие

Что из себя представляет проект, которая собирается при помощи Gradle? Во-первых, каждый модуль, начиная с корневого, может кастомизироваться при помощи файлов (билд-скриптов) build.gradle (build.gradle.kts) и settings.gradle (.kts).

Там описывается структура и всякие детали сборки модуля, а в корневом ещё и всего проекта. Внутри написан код на языке Groovy (точнее, на его Gradle-диалекте), если если расширение .gradle, и на на языке Kotlin Scripting (точнее, на его Gradle-диалекте), если его расширение .gradle.kts.

Groovy — язык с динамической типизацией, является надмножетсвом Java (то есть в нём можно писать на Java), с кучей своих фич, поэтому как правило Джаву под ним не разглядеть. На него также сильно повлиял Питон. Примеры в тексте будут на Groovy.

Хочется заметить, что использование .kts никак не связано с использованием Котлина в вашем проекте, эти вещи независимы. Поддержка .gradle.kts встроена в Gradle начиная с 5.0.

Gradle — ориентированный на командную строку инструмент. Чтобы собрать проект, например, нужно из папки с проектом выполнить gradle build. Таким образом мы запустим таску build из корневого модуля. Список тасок корневого модуля можно получить, выполнив таску tasks. Таски других модулей выполняются командой gradle :submodule:subsubmodule:task. При желании корневую таску можно тоже запускать в этом формате: gradle :build. Таски, как и модули, зависят друг от друга, т.е. например при выполнении run сначала выполнится build.

Плагины

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

// build.gradle
plugins {
    id "org.jetbrains.kotlin.jvm" version "1.3.41"
}
buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
    }
}

apply plugin: 'kotlin'

Второй способ считается устаревшим, но в исключительных ситуациях приходится к нему прибегать. В частности, в первом способе сложно вынести версию в отдельную переменную, так как секция plugins обрабатывается более тупым (но быстрым) препроцессором.

Плагины добавляют таски и модифицируют имеющиеся, сильно меняя функционал Gradle. В качестве примеров плагинов есть не только поддержка языков, но и поддержка технологий, требующих особого подхода (JavaFX), просто упрощающие работу с имеющимися технологиями (shadowJar, который умеет компоновать все зависимости JVM-проекта в один .jar с приложением, делая его независимым от библиотек).

Плагины, как и библиотеки, задаюстя координатами: "группа:имя:версия". При запуске Gradle они скачиваются из предопределённых или заданных в скриптах репозиториев: мест, куда публикуются плагины или библиотеки.

Конфигурация зависимостей

Как между модулями, так и от библиотек.

dependencies {
    implementation "io.ktor:ktor-server-jetty:$ktor_version"
    implementation project(':interface')
}

Этим мы объявили, что текущему модулю требуется библиотека ktor-server-jetty определённой версии, и модуль :interface.

Помимо implementation, можно объявлять зависимости словом api. В этом случае эта зависимость будет видна тем, кто будет зависеть от данного модуля. Например, если вы добавляете функционал к чему-то, то это что-то стоит объявить как api, так как иначе то, к чему вы добавляете функционал, не будет доступно использующему. А если вы используете что-то внутренне, и тем, кто будет зависеть от вас, знать про это не нужно, объявлять это стоит словом implementation.

Цикл работы

Сначала Gradle находит и скачивает (если их нет) все плагины, так как они могут на корню изменить то, как воспринимаестя остаток скрипта. Затем происходить конфигурация: Gradle вычисляет граф зависимостей между модулямя, библиотеками, тасками. А потом он нужные таски выполняет. При этом up-to-date таски не выполняются, и всё что можно берётся из кэшей. Независимые таски могут выполняться параллельно, если это включить. Понимает, что нужно пересобрать, а что осталось по-старому, подсчитывая хэш-суммы файлов.

Чтобы не требовать от пользователя или сервера CI (на котором например автоматически прогоняются тесты) иметь установленный Gradle, существует Gradle wrapper. Это легковесный jar (находится в папке gradle/wrapper в корне проекта), который просто скачивает Gradle и запускает, если он скачан. Помимо этого создаются shell-скрипт gradlew для Unix-подобных систем и gradlew.bat для Windows, которые запускают gradle-wrapper, и работают интерфейсом для скачанного Gradle. Взаимодействовать с ними можно командами вида ./gradlew build. Сгенерировать Gradle wrapper для нового проекта можно командой gradle wrapper.

IDEA для получения информации о структуре проекта запускает его конфигурацию, и, имея на руках граф, уже способна по-умному взаимодействовать с проектом. Запускать таски, понимать где чьи модули и т.д. Каждый раз, когда билд-скрипты изменяются, нужно заново вызывать синхронизацию Gradle с Идеей, чтобы она начала понимать что изменилось. Синхронизация может скрываться за такими словами как Refresh Gradle project, но обычно она просто Gradle sync. Хочется отметить, что на самом деле вызывать синк после каждого изменения не всегда нужно, так как, например, чтобы запускать таски, Идее не обязательно понимать хоть что либо, кроме наличия этой таски. И всегда можно вернуться к командной строке :)