В какой секции файла build gradle указываются зависимости приложения
Сложно, если не невозможно, создавать реальные приложения, которые не имеют внешних зависимостей. Вот почему управление зависимостями является жизненно важной частью каждого программного проекта.
В этом блоге рассказывается, как мы можем управлять зависимостями наших проектов с помощью Gradle. Мы научимся настраивать используемые репозитории и необходимые зависимости. Мы также применим эту теорию к практике, реализовав простой пример приложения.
Дополнительное чтение:
- Начало работы с Gradle: Введение поможет вам установить Gradle, описывает основные концепции сборки Gradle и описывает, как вы можете добавить функциональность в свою сборку с помощью плагинов Gradle.
- Начало работы с Gradle: наш первый Java-проект описывает, как вы можете создать проект Java с помощью Gradle и упаковать свое приложение в исполняемый файл JAR.
Введение в управление хранилищем
Репозитории по сути являются контейнерами зависимостей, и каждый проект может использовать ноль или более репозиториев.
Gradle поддерживает следующие форматы репозитория:
Давайте выясним, как мы можем настроить каждый тип репозитория в нашей сборке.
Добавление репозитория плюща в нашу сборку
Мы можем добавить репозиторий Ivy в нашу сборку, используя его URL-адрес или расположение в локальной файловой системе.
Если мы хотим добавить репозиторий Ivy, используя его URL-адрес, мы должны добавить следующий фрагмент кода в файл build.gradle :
Если мы хотим добавить репозиторий Ivy, используя его местоположение в файловой системе, нам нужно добавить следующий фрагмент кода в файл build.gradle :
Если вы хотите получить больше информации о настройке репозиториев Ivy, вам следует проверить следующие ресурсы:
Давайте продолжим и узнаем, как мы можем добавить репозитории Maven в нашу сборку.
Добавление репозиториев Maven в нашу сборку
Мы можем добавить репозиторий Maven в нашу сборку, используя его URL-адрес или расположение в локальной файловой системе.
Если мы хотим добавить репозиторий Maven, используя его URL, мы должны добавить следующий фрагмент кода в файл build.gradle :
Если мы хотим добавить репозиторий Maven, используя его местоположение в файловой системе, мы должны добавить следующий фрагмент кода в файл build.gradle :
У Gradle есть три «псевдонима», которые мы можем использовать, когда добавляем репозитории Maven в нашу сборку. Эти псевдонимы:
- Псевдоним mavenCentral () означает, что зависимости извлекаются из центрального хранилища Maven 2 .
- Псевдоним jcenter () означает, что зависимости извлекаются из репозитория JCenter Maven Bintray .
- Псевдоним mavenLocal () означает, что зависимости извлекаются из локального репозитория Maven.
Если мы хотим добавить центральный репозиторий Maven 2 в нашу сборку, мы должны добавить следующий фрагмент в наш файл build.gradle :
Если вы хотите получить больше информации о настройке репозиториев Maven, вам следует ознакомиться с разделом 50.6.4 Репозитории Maven Руководства пользователя Gradle .
Давайте продолжим и узнаем, как мы можем добавить репозитории плоских каталогов в нашу сборку.
Добавление хранилищ плоских каталогов в нашу сборку
Если мы хотим использовать репозитории плоских каталогов, мы должны добавить следующий фрагмент кода в наш файл build.gradle :
Это означает, что зависимости ищутся из каталога lib . Также, если мы хотим, мы можем использовать несколько каталогов, добавив следующий фрагмент в файл build.gradle :
Если вы хотите получить больше информации о хранилищах плоских каталогов, вы должны проверить следующие ресурсы:
Давайте продолжим и выясним, как мы можем управлять зависимостями нашего проекта с помощью Gradle.
Введение в управление зависимостями
После того, как мы настроили репозитории нашего проекта, мы можем объявить его зависимости. Если мы хотим объявить новую зависимость, мы должны выполнить следующие шаги:
- Укажите конфигурацию зависимости.
- Объявите необходимую зависимость.
Давайте внимательнее посмотрим на эти шаги.
Группировка зависимостей в конфигурации
В Gradle зависимости группируются в именованный набор зависимостей. Эти группы называются конфигурациями, и мы используем их для объявления внешних зависимостей нашего проекта.
- Зависимости, добавленные в конфигурацию компиляции , требуются при компиляции нашего исходного кода нашего проекта.
- Конфигурация времени выполнения содержит зависимости, которые требуются во время выполнения. Эта конфигурация содержит зависимости, добавленные в конфигурацию компиляции .
- Конфигурация testCompile содержит зависимости, необходимые для компиляции тестов нашего проекта. Эта конфигурация содержит скомпилированные классы нашего проекта и зависимости, добавленные в конфигурацию компиляции .
- Конфигурация testRuntime содержит зависимости, которые требуются при запуске наших тестов. Эта конфигурация содержит зависимости, добавленные в конфигурации compile , runtime и testCompile .
- Конфигурация архива содержит артефакты (например, файлы Jar), созданные нашим проектом.
- Группа конфигурации по умолчанию содержит зависимости, которые требуются во время выполнения.
Давайте продолжим и выясним, как мы можем объявить зависимости нашего проекта Gradle.
Объявление зависимостей проекта
Наиболее распространенные зависимости называются внешними зависимостями, которые находятся во внешнем репозитории. Внешняя зависимость определяется с помощью следующих атрибутов:
- Атрибут group идентифицирует группу зависимости (пользователи Maven знают этот атрибут как groupId ).
- Атрибут name идентифицирует имя зависимости (пользователи Maven знают этот атрибут как artifactId ).
- Атрибут version указывает версию внешней зависимости (пользователи Maven знают этот атрибут как версию ).
Эти атрибуты обязательны при использовании репозиториев Maven. Если вы используете другие репозитории, некоторые атрибуты могут быть необязательными.
Давайте предположим, что мы должны объявить следующую зависимость:
- Группа зависимости — ‘foo’.
- Название зависимости — ‘foo’.
- Версия зависимости — 0.1.
- Зависимость требуется при компиляции нашего проекта.
Мы можем объявить эту зависимость, добавив следующий код в файл build.gradle :
Мы также можем объявить зависимости нашего проекта, используя ярлык, следующий синтаксису: [группа]: [имя]: [версия] . Если мы хотим использовать форму ярлыка, мы должны добавить следующий фрагмент кода в файл build.gradle :
Мы также можем добавить несколько зависимостей к одной и той же конфигурации. Если мы хотим использовать «нормальный» синтаксис при объявлении наших зависимостей, мы должны добавить следующий фрагмент кода в файл build.gradle :
С другой стороны, если мы хотим использовать форму ярлыка, соответствующая часть файла build.gradle выглядит следующим образом:
Естественно, можно объявить зависимости, которые принадлежат разным конфигурациям. Например, если мы хотим объявить зависимости, которые принадлежат конфигурациям compile и testCompile , мы должны добавить следующий фрагмент кода в файл build.gradle :
Опять же, можно использовать ярлык формы. Если мы хотим объявить те же зависимости с помощью формы ярлыка, соответствующая часть файла build.gradle выглядит следующим образом:
Вы можете получить больше информации об объявлении ваших зависимостей, прочитав раздел 50.4 Как объявить ваши зависимости в Руководстве пользователя Gradle .
Теперь мы изучили основы управления зависимостями. Давайте перейдем к реализации нашего примера приложения.
Создание примера приложения
Требования нашего примера приложения описаны в следующем:
Давайте выясним, как мы можем выполнить эти требования.
Конфигурирование репозиториев нашей сборки
Одним из требований нашего примера приложения было то, что его скрипт сборки должен использовать центральный репозиторий Maven. После того, как мы настроили наш скрипт сборки для использования центрального репозитория Maven, его исходный код выглядит следующим образом (соответствующая часть выделена):
attributes 'Main-Class' : 'net.petrikainulainen.gradle.HelloWorld'Давайте двигаться дальше и объявим зависимости нашего примера приложения.
Объявление зависимостей нашего примера приложения
Мы должны объявить две зависимости в файле build.gradle :
После того, как мы объявили эти зависимости, файл build.gradle выглядит следующим образом (соответствующая часть выделена):
attributes 'Main-Class' : 'net.petrikainulainen.gradle.HelloWorld'Давайте двигаться дальше и напишем некоторый код.
Написание кода
Чтобы выполнить требования нашего примера приложения, «мы должны его чрезмерно спроектировать». Мы можем создать пример приложения, выполнив следующие действия:
Давайте пройдемся по этим шагам один за другим.
Сначала мы должны создать класс MessageService в каталоге src / main / java / net / petrikainulainen / gradle и реализовать его. После того, как мы это сделаем, его исходный код будет выглядеть следующим образом:
Во-вторых , мы создали MessageServiceTest в каталоге src / main / test / net / petrikainulainen / gradle и записали модульный тест в метод getMessage () класса MessageService . Исходный код класса MessageServiceTest выглядит следующим образом:
assertEquals( "Hello World!" , messageService.getMessage()); private static final Logger LOGGER = Logger.getLogger(HelloWorld. class );В-четвертых , мы должны настроить Log4j с помощью log4j.properties, который находится в каталоге src / main / resources . Файл log4j.properties выглядит следующим образом:
log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c <1>- %m\n1>Вот и все. Давайте выясним, как мы можем запустить тесты нашего примера приложения.
Запуск юнит-тестов
Мы можем запустить наш модульный тест с помощью следующей команды:
Когда наш тест пройден, мы видим следующий вывод:
Однако, если наш модульный тест не пройден, мы увидим следующий результат (интересный раздел выделен):
net.petrikainulainen.gradle.MessageServiceTest > getMessage_ShouldReturnMessageFAILED > There were failing tests. See the report at: file : ///Users/loke/Projects/Java/Blog/gradle-examples/dependency-management/build/reports/tests/index .html Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.Как мы видим, если наши модульные тесты не пройдены, описываются:
- какие тесты не удалось.
- сколько тестов было выполнено и сколько тестов не удалось.
- расположение отчета о тестировании, в котором содержится дополнительная информация о неудачных (и пройденных) тестах.
Когда мы запускаем наши модульные тесты, Gradle создает отчеты о тестировании в следующих каталогах:
- Каталог build / test-results содержит необработанные данные каждого запуска теста.
- Каталог build / reports / tests содержит HTML-отчет, который описывает результаты наших тестов.
Отчет о тестировании HTML является очень полезным инструментом, потому что он описывает причину, по которой наш тест не прошел Например, если наш модульный тест ожидает, что метод getMessage () класса MessageService возвращает строку «Hello Worl1d!», Отчет о тестировании HTML этого теста будет выглядеть следующим образом:
Давайте продолжим и выясним, как мы можем упаковать и запустить наш пример приложения.
В данном разделе описывается структура и основные элементы скрипта build.gradle .
В секции buildscript задается следующее:
Версия платформы, на которой основан данный проект.
Набор репозиториев, из которых будут загружаться зависимости проекта. Настройка доступа к репозиториям описана ниже.
Зависимости, используемые системой сборки, включая плагин CUBA для Gradle.
После секции buildscript объявляются несколько переменных, используемых далее в скрипте.
Логика сборки, специфичная для CUBA, сосредоточена в Gradle плагине cuba . Он подключается в корне скрипта и в секциях configure каждого модуля:
Параметры плагина cuba задаются в секции cuba :
Рассмотрим доступные параметры:
artifact - задает группу и версию собираемых артефактов проекта. Имена артефактов формируются на основе имен модулей, заданных в settings.gradle .
group - группа артефактов.
version - версия артефактов.
isSnapshot - если установлено в true , то в именах артефактов будет присутствовать суффикс SNAPSHOT .
Версию артефактов можно переопределить в командной строке, например:
tomcat - задает параметры сервера Tomcat, который используется для быстрого развертывания.
dir - расположение каталога установки Tomcat.
port - порт сервера; по умолчанию 8080.
debugPort - порт для подключения Java отладчика; по умолчанию 8787.
shutdownPort - порт для передачи команды SHUTDOWN ; по умолчанию 8005.
ajpPort - порт AJP connector; по умолчанию 8009.
ide - задает некоторые параметры для Studio и IDE.
vcs - тип используемой в проекте VCS. В данный момент поддерживаются только Git и svn .
copyright - текст Copyright Notice, вставляемый в начало файлов исходных текстов.
classComment - текст комментария, который будет расположен над объявлением класса в исходных текстах Java.
uploadRepository - задает параметры репозитория, в который будут выгружаться собранные артефакты проекта при выполнении задачи uploadArchives .
url - URL репозитория. По умолчанию используется репозиторий Haulmont.
user - имя пользователя репозитория.
password - пароль пользователя репозитория.
Параметры репозитория выгрузки артефактов можно передать в командной строке с помощью следующих аргументов:
Секции configure описывают конфигурацию модулей. Наиболее важная часть конфигурации - описание зависимостей, например:
Есть возможность добавлять зависимости через конфигурацию server для модулей core, web и portal (модули, имеющие задачу с типом CubaDeployment ). Это имеет смысл в некоторых случаях, например, когда для варианта развёртывания с помощью UberJar обращение к зависимости происходит до старта приложения, а сама зависимость нужна для всех вариантов развёртывания в конкретном модуле. Тогда объявление отдельно на уровне модуля (что нужно, например, для случая развертывания опции WAR) и через конфигурацию uberJar на уровне проекта вызовет ненужное дублирование зависимости для UberJar. Такие зависимости при выполнении задач deploy , buildWar и buildUberJar будут помещаться в серверные библиотеки.
Блок entitiesEnhancing позволяет описать конфигурацию bytecode enhancement (weaving) классов сущностей. Его нужно объявить как минимум в модуле global, однако можно включить его в конфигурацию каждого модуля по-отдельности.
Секции main и test здесь - это наборы исходников проекта и тестов, а необязательный параметр persistenceConfig позволяет явно указать набор файлов persistence.xml. Если не установлено, задача будет обрабатывать все персистентные сущности, перечисленные в файлах *persistence.xml , найденных в CLASSPATH.
Нестандартные зависимости модулей можно задавать в Studio в экране Project properties > Advanced. См. также контекстную помощь Studio.
В случае транзитивных зависимостей и конфликта версий будет использована стандартная стратегия разрешения версий Maven. Согласно этой стратегии, релизные версии имеют приоритет над snapshot-версиями, а более точный числовой квалификатор имеет приоритет над более общим. При прочих равных, строковые квалификаторы приоритизируются в алфавитном порядке. Пример:
Управление зависимостями – одна из наиболее важных функций в арсенале систем сборки. С приходом Gradle в качестве основной системы сборки Android-проектов в части управления зависимостями произошёл существенный сдвиг, закончилась эпоха ручного копирования JAR-файлов и долгих танцев с бубном вокруг сбоящих конфигураций проекта.
В статье рассматриваются основы управления зависимостями в Gradle, приводятся углублённые практические примеры, небольшие лайфхаки и ссылки на нужные места в документации.
Репозиторий
Объявление зависимостей
В приведённом выше примере вы видите сценарий сборки, в котором подключены две зависимости для различных конфигураций (compile и testCompile) компиляции проекта. JsonToken будет подключаться во время компиляции проекта и компиляции тестов проекта, jUnit только во время компиляции тестов проекта. Детальнее о конфигурациях компиляции — по ссылке.
Также можно увидеть, что jUnit-зависимость мы подключаем как динамическую(+), т.е. будет использоваться самая последняя из доступных версия 4.+, и нам не нужно будет следить за минорными обновлениями (рекомендую не использовать эту возможность в compile-типе компиляции приложения, т.к. могут появиться неожиданные, возможно, сложно локализуемые проблемы).
На примере с jUnit-зависимостью рассмотрим стандартный механизм Gradle по поиску необходимой зависимости:
Кэш
В Gradle реализована система кэширования, которая по умолчанию хранит зависимости в течение 24 часов, но это поведение можно переопределить.
После того, как время, установленное для хранения данных в кэше, вышло, система при запуске задач сначала проверит возможность обновления динамических (dynamic) и изменяемых (changing) зависимостей и при необходимости их обновит.
Также в системе сборки присутствуют два параметра, используя которые при запуске вы можете изменить политику кэширования для конкретного выполнения задачи.
– –offline – Gradle никогда не будет пытаться обратиться в сеть для проверки обновлений зависимостей.
– –refresh-dependencies – Gradle попытается обновить все зависимости. Удобно использовать при повреждении данных, находящихся в кэше. Верифицирует кэшированные данные и при отличии обновляет их.
Более детально про кэширование зависимостей можно прочитать в Gradle User Guide.
Виды зависимостей
Существует несколько видов зависимостей в Gradle. Наиболее часто используемыми являются:
– Внешние зависимости проекта — зависимости, загружаемые из внешних репозиториев;
– Проектные зависимости — зависимость от модуля (подпроекта) в рамках одного проекта;
– Файловые зависимости — зависимости, подключаемые как файлы (jar/aar архивы).
Также существуют зависимости клиентских модулей, зависимости Gradle API и локальные Groovy-зависимости. Они используются редко, поэтому в рамках данной статьи не будем их разбирать, но почитать документацию о них можно здесь.
Дерево зависимостей
Каждая внешняя или проектная зависимость может содержать собственные зависимости, которые необходимо учесть и загрузить. Таким образом, при выполнении компиляции происходит загрузка зависимостей для выбранной конфигурации и строится дерево зависимостей, человеческое представление которого можно увидеть, выполнив Gradle task ‘dependencies’ в Android Studio или команду gradle %module_name%:dependencies в консоли, находясь в корневой папке проекта. В ответ вы получите список деревьев зависимостей для каждой из доступных конфигураций.
Используя параметр configuration, указываем имя конфигурации, чтобы видеть дерево зависимостей только указанной конфигурации.
Возьмем специально подготовленные исходники репозитория, расположенного на github и попробуем получить дерево зависимостей для конкретной конфигурации (в данный момент проект находится в состоянии 0, т.е. в качестве build.gradle используется build.gradle.0):
Проанализировав дерево зависимостей, можно увидеть, что модуль app использует в качестве зависимостей две внешних зависимости (appcompat и guava), а также две проектных зависимости (first и second), которые в свою очередь используют библиотеку jsontoken версий 1.0 и 1.1 как внешнюю зависимость. Совершенно очевидно, что проект не может содержать две версии одной библиотеки в Classpath, да и нет в этом необходимости. На этом этапе Gradle включает модуль разрешения конфликтов.
Разрешение конфликтов
Gradle DSL содержит компонент, используемый для разрешения конфликтов зависимостей. Если посмотреть на зависимости библиотеки jsontoken на приведённом выше дереве зависимостей, то мы увидим их только раз. Для модуля second зависимости библиотеки jsontoken не указаны, а вывод самой зависимости содержит дополнительно ‘–> 1.1’, что говорит о том, что версия библиотеки 1.0 не используется, а автоматически была заменена на версию 1.1 с помощью Gradle-модуля разрешения конфликтов.
Для объяснения каким образом была разрешена конфликтная ситуация, также можно воспользоваться Gradle-таском dependencyInsight, например:
Стоит обратить внимание, что версия 1.1 выбирается в результате conflict resolution, также возможен выбор в результате других правил (например: selected by force или selected by rule). В статье будут приведены примеры использования правил, влияющих на стратегию разрешения зависимостей, и выполнив таск dependencyInsight вы сможете увидеть причину выбора конкретной версии библиотеки на каждом из приведённых ниже этапов. Для этого при переходе на каждый этап вы можете самостоятельно выполнить таск dependencyInsight.
При необходимости есть возможность переопределить логику работы Gradle-модуля разрешения конфликтов, например, указав Gradle падать при выявлении конфликтов во время конфигурирования проекта. (состояние 1)
После чего даже при попытке построить дерево зависимостей Gradle таски будут прерываться по причине наличия конфликта в зависимостях приложения.
У задачи есть четыре варианта решения:
Первый вариант – удалить строки, переопределяющие стратегию разрешения конфликтов.
Второй вариант – добавить в стратегию разрешения конфликтов правило обязательного использования библиотеки jsonToken, с указанием конкретной версии (состояние 2):
При применении этого варианта решения дерево зависимостей будет выглядеть следующим образом:
Третий вариант — добавить библиотеку jsonToken явно в качестве зависимости для проекта app и присвоить зависимости параметр force, который явно укажет, какую из версий библиотеки стоит использовать. (состояние 3)
А дерево зависимостей станет выглядеть следующим образом:
Четвёртый вариант – исключить у одной из проектных зависимостей jsontoken из собственных зависимостей с помощью параметра exclude. (состояние 4)
И дерево зависимостей станет выглядеть следующим образом:
Стоит отметить, что exclude не обязательно передавать оба параметра одновременно, можно использовать только один.
Но несмотря на правильный вывод дерева зависимостей, при попытке собрать приложение Gradle вернёт ошибку:
Добиться успешной сборки проекта можно тремя вариантами:
Первый — удалить guava из зависимостей модуля app. Если используется только та часть Guava, которая содержится в Google Collections, то предложенное решение будет неплохим.
Второй — исключить Google Collections из модуля first. Добиться этого мы можем используя описанное ранее исключение или правила конфигураций. Рассмотрим оба варианта, сначала используя исключения (состояние 5)
Пример использования правил конфигураций (состояние 6):
Дерево зависимостей для обеих реализаций исключения Google Collections будет идентично.
Третий вариант — использовать функционал подмены модулей (состояние 7):
Дерево зависимостей будет выглядеть следующим образом:
Нужно учесть, что если оставить предопределенную логику разрешения конфликтов, которая указывает прерывать сборку при наличии любого конфликта, то выполнение любого таска будет прерываться на этапе конфигурации. Другими словами, использование правил замены модулей является одним из правил стратегии разрешения конфликтов между зависимостями.
Также важно заметить, что последний из озвученных вариантов является самым гибким, ведь при удалении guava из списка зависимостей Gradle, Google Collections сохранится в проекте, и функционал, от него зависящий, сможет продолжить выполнение. А дерево зависимостей будет выглядеть следующим образом:
После каждого из вариантов мы достигнем успеха в виде собранного и запущенного приложения.
Но давайте рассмотрим другую ситуацию (состояние 8), у нас одна единственная сильно урезанная (для уменьшения размеров скриншотов) динамическая зависимость wiremock. Мы её используем сугубо в целях обучения, представьте вместо неё библиотеку, которую поставляет ваш коллега, он может выпустить новую версию в любой момент, и вам непременно необходимо использовать самую последнюю версию:
Дерево зависимостей выглядит следующим образом:
Как вы можете увидеть, Gradle загружает последнюю доступную версию wiremock, которая является beta. Ситуация нормальная для debug сборок, но если мы собираемся предоставить сборку пользователям, то нам определённо необходимо использовать release-версию, чтобы быть уверенными в качестве приложения. Но при этом в связи с постоянной необходимостью использовать последнюю версию и частыми релизами нет возможности отказаться от динамического указания версии wiremock. Решением этой задачи будет написание собственных правил стратегии выбора версий зависимости:
Стоит отменить, что данное правило применится ко всем зависимостям, а не только к wiremock.
После чего, запустив задачу отображения дерева зависимостей в информационном режиме, мы увидим, как отбрасываются beta-версии библиотеки, и причину, по которой они были отброшены. В конечном итоге будет выбрана стабильная версия 1.58:
Но при тестировании было обнаружено, что в версии 1.58 присутствует критичный баг, и сборка не может быть выпущена в таком состоянии. Решить эту задачу можно, написав ещё одно правило выбора версии зависимости:
После чего версия wiremock 1.58 будет также отброшена, и начнёт использоваться версия 1.57, а дерево зависимостей будет выглядеть следующим образом:
Заключение
Несмотря на то, что статья получилась достаточно объемной, тема Dependency Management в Gradle содержит много не озвученной в рамках этой статьи информации. Глубже погрузиться в этот мир лучше всего получится с помощью официального User Guide в паре с документацией по Gradle DSL, в изучение которых придется инвестировать немало времени.
Зато в результате вы получите возможность сэкономить десятки часов, как благодаря автоматизации, так и благодаря пониманию того, что необходимо делать при проявлении различных багов. Например, в последнее время достаточно активно проявляются баги с 65К-методов и Multidex, но благодаря грамотному просмотру зависимостей и использованию exclude проблемы решаются очень быстро.
$PATH:i Теперь, пожалуй, можно начинать знакомство.
Инициализация проекта Gradle
Сразу хочется отметить, что Gradle — это про выполнение задач, называемых task (буду называть их тасками). Таски предоставляются различными плагинами (plugins). Подробнее про плагины советую прочитать в официальной документации: "Using Gradle Plugins". Есть набор "Core Plugins", которые есть всегда, когда установлен Gradle. Есть разные категории этих плагинов, но нас интересует категория "Utility". В этом наборе есть плагин "Build Init Plugin", который предоставляет таски для инициализации (Initialization) Gradle проекта. Нас интересует создание типа проекта: "java-application". Выполним Gradle таск: gradle init --type java-application Ответим попутно на некоторые вопросы, например о том, что мы хотим использовать Groovy DSL (стандартный язык описания задач для Gradle) и фрэймворк тестирования JUnit (об этом мы поговорим в другом обзоре). После создания мы получим следующий набор файлов:
Во-первых, после инициализации мы получаем преднастроенный на нашу версию Gradle специальный враппер - это такой специальный скрипт. Про него подробнее советую прочитать в официальной документации — "The Gradle Wrapper". Во-вторых, мы видим Gradle Build Script – файл build.gradle. Это – главный файл, в котором описывается то, какие библиотеки и фрэймворки использует наш проект, какие плагины нужно подключить к проекту и описывает различные таски. Подробнее про данный файл советую прочитать в официальной документации: "Build Script Basics".
Plugins и Tasks
Если посмотреть сейчас на на содержимое Build Script, то мы увидим секцию plugins: Это те самые плагины, про которые мы говорили ранее. А если есть плагины, то есть и задачи, которые нам теперь доступны. Мы можем выполнить команду gradle tasks и увидеть, что мы сейчас можем сделать с проектом:
Например, выполнив gradle run мы запустим main класс нашего java приложения:
Как мы видим, снизу написано так же 2 actionable tasks: 1 executed, 1 up-to-date Что это значит? Это значит, что всего было выполнено 2 таска: Причём 1 действительно выполнен, а один не выполнялся, т.к. он up-to-date, то есть состояние актуальное и ничего выполнено не было. Мы можем выполнить так называемый "Dry Run": gradle run -m Выполним эту команду, мы увидим, какие будут выполнены таски, чтобы выполнить таск run:
- "Authoring Tasks": Описывает создание собственных задач.
- "Writing Custom Gradle Tasks": Руководство step by step по созданию задач.
- "Build Script Basics": Основа про задачи и их использование.
Dependencies
Одной из главных задач любой системы сборки – управление зависимостями, то есть тем, какие библиотеки/фрэймворки нужны нашему проекту. Система сборки должна обеспечить их наличие в нужный момент и нужным образом собрать конечный артефакт нашего приложения. По умолчанию после gradle init для java-application мы увидим следующее содержимое в билд скрипте: Тут сразу понятно, что мы подключаем. Но без некоторого понимания непонятно, что за implementation и testImplementation? Тут надо опять обратиться к документации Gradle, благо документация у Gradle написана прекрасно. Называется это "Managing Dependency Configurations". Как сказано в документации, каждая зависимость объявляется с некоторым scope - областью, в рамках которой данная зависимость будет доступна. Этот скоуп (scope) и обозначается некоторой конфигурацией (configuration), каждая из которых имеет уникальное имя. Так же интересно, что множество Gradle плагинов добавляют предзаданные конфигурации. Чтобы узнать, какие у нас есть конфигурации можно выполнить: gradle --console plain dependencies Таким образом мы увидим список всех доступных конфигураций и относящихся к ним зависимостей. Мы можем отфильтровать этот список так, чтобы видеть только сами доступные конфигурации: gradle --console plain dependencies | find " - " Как же понять, что нам использовать? Тут немного придётся почитать. Т.к. мы используем плагин "Java", то начнём с его документации и раздела "Dependency management". Тут мы видим, что раньше была конфигурация (он же скоуп), называемая "compile" и обозначала "зависимость, нужная во время компиляции". Но потом его заменили (на англ. Superseded) на implementation. Подробнее про замену можно прочитать в разделе "API and implementation separation". Получается, эта зависимость будет на "compile classpath". Но иногда мы хотим, чтобы наша зависимость была включена в итоговый артефакт. Зачем? Например, у нас будет выполняемый jar, который должен сам в себе содержать всё необходимое. Что же тогда нам делать? Во-первых, такой поддержки "из коробки" (то есть по умолчанию, без каких-либо дополнительных действий) нет. Объясняется это тем, что каждый хочет архив собрать по своему, а Gradle старается быть минималистичным. Мы так же не можем исполльзовать jar архивы на classpath (без дополнительных манипуляций в коде), т.к. это так не работает (Подробнее см. "Oracle: Adding Classes to the JAR File's Classpath"). Поэтому, самым красивым способом является следующий код в билд скрипте: В настройках таска jar мы указываем то, что будет добавлено в манифест jar-файла (см. "Oracle: Setting an Application's Entry Point"). А дальше мы говорим, что все зависимости, которые нужны были для компиляции, мы их включим в jar. Альтернативой может послужить использование плагина "Gradle Shadow Plugin". Может показаться сложным, но другие плагины могут упрощать жизнь. Например, при создании веб-приложения (в отличии от обычного выполняемого java приложения) мы будем использовать особый плагин - "Gradle War Plugin", который имеет другое поведение и там жизнь наша будет проще (все нужные зависимости будут сложены в отдельный особый каталог самим плагином. Такая работа регламентируется тем, как должны быть устроены веб-приложения. Но это уже совсем другая история).
Читайте также: