Очень много статей о Gradle написано. И со своей стороны хотелось бы добавить в копилку такую пошаговую инструкцию, прочтение которой, я надеюсь, позволит тем, кто плохо знаком с Gradle, “распробовать” и продолжить самостоятельно изучать этот инструмент.
Данная статья не будет подробно описывать такие темы, как плагины gradle (plugin), задачи (task), зависимости (dependencies), автоматическое тестирование и прочие прелести этого сборщика проектов. Во-первых, каждая тема заслуживает отдельной статьи или даже серии статей, а во-вторых, на эти темы уже есть статьи на хабре, например: Gradle: Tasks Are Code, Gradle: Better Way To Build. А еще на официальном сайте Gradle есть прекрасно написанный Gradle User Guide. Я же cфокусирую внимание на непосредственном решении поставленной задачи, и все сопутствующие темы будут описаны в рамках этой самой задачи.
Сначала определимся с целью, что же мы хотим получить на выходе? А цель указана в заголовке статьи. Мы хотим получить проект с несколькими модулями, который собирается с помощью Gradle. И так, приступим.
Шаг 1. Установка gradle
Примечение: Если выхотите просто “поиграть” с gradle, скачав файлы для статьи, или вам достались чужие исходники с волшебным файлом gradlew (gradlew.bat) в корне проекта, то устанавливать gradle не обязательно.
Gradle можно поставить, скачав последнюю версию со страницы загрузок Gradle или воспользовавшись менеджером пакетов в вашей любимой ОС (прим. Я ставил на Mac OS через brew и на Debian через apt-get из стандартных источников)
Результат первого шага:
$ gradle -version
------------------------------------------------------------
Gradle 1.11
------------------------------------------------------------
Build time: 2014-02-11 11:34:39 UTC
Build number: none
Revision: a831fa866d46cbee94e61a09af15f9dd95987421
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.9.2 compiled on July 8 2013
Ivy: 2.2.0
JVM: 1.8.0_05 (Oracle Corporation 25.5-b02)
OS: Mac OS X 10.9.3 x86_64
Шаг 2. Пустой проект, плагины (plugin), обертка (wrapper)
Создадим папку проекта и в ее корне сделаем файл build.gradle
со следующим содержимым:
{project_path}/build.gralde
apply plugin: “java”
apply plugin: “application”
task wrapper(type: Wrapper) {
gradleVersion = '1.12'
}
Давайте, рассмотрим подробнее, что мы написали в файле. Тут используется динамический язык Groovy. Использование полноценного языка программирования в gradle дает большую свободу в сравнении со сборщиками пакетов, использующих декларативные языки.
В этом файле мы подключаем плагины java
и application
. Плагин java
содержит в себе такие полезные задачи, как jar — собрать jar архив, compileJava — скомпилировать исходные коды и др. Подробнее о плагине можно почитать тут. Плагин application
содержит в себе задачи: run — запуск приложения; installApp — установка приложения на компьютер, эта задача создает исполняемые файлы для *nix и для windows (bat файл); distZip — собирает приложение в zip архив, помещая туда все jar файлы, а также специфические для операционной системы скрипты. Подробнее о плагине в документации.
Теперь остановимся подробней на задаче wrapper
. Эта очень полезная задача, наверное, самое гениальное решение, призванное облегчить жизнь программистам. Выполнив $ gradle wrapper
, получим следующий результат:
$ gradle wrapper
:wrapper
BUILD SUCCESSFUL
Total time: 7.991 secs
$ ls -a
. .. .gradle build.gradle gradle gradlew gradlew.bat
Мы видим, что скрипт создал нам исполняемые файлы gradlew для *nix, gradlew.bat для Windows, а также папки gradle и .gradle. Скрытую папку .gradle можно не включать в репозиторий, там содержатся библиотеки зависимостей. Все основное лежит в gradle и в самом файле gradlew. Теперь мы смело может отдавать наш проект любому человеку, имеющему jdk нужной версии, и он самостоятельно сможет скомпилировать, собрать, установить проект, используя ./gradlew
. Заметьте, что моя версия gradle (см. результат команды $ gradle -version
выше) отличается от той, которую я указал в файле build.gradle, но это не страшно, поскольку после запуска задачи wrapper, мы получим необходимую версию gradle.
$ ./gradlew -version
------------------------------------------------------------
Gradle 1.12
------------------------------------------------------------
Build time: 2014-04-29 09:24:31 UTC
Build number: none
Revision: a831fa866d46cbee94e61a09af15f9dd95987421
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013
Ivy: 2.2.0
JVM: 1.8.0_05 (Oracle Corporation 25.5-b02)
OS: Mac OS X 10.9.3 x86_64
Теперь вместо gradle
можно смело использовать gradlew
. Кстати, выполнение команды $ ./gradlew
без параметров создаст папку .gralde
и скачает туда все зависимые библиотеки (о зависимостях ниже). Но выполнение этой команды не обязательно, так как при любом запуске gradle (gradlew), будут проверяться зависимости и скачиваться недостающие файлы. Поэтому, получив проект, в котором лежат файлы gradlew, можно сразу запускать нужную задачу, список которых можно получить по команде ./gradlew tasks
Итоги второго шага (вывод сокращен):
$ ./gradlew tasks
:tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------
Application tasks
-----------------
distTar - Bundles the project as a JVM application with libs and OS specific scripts.
distZip - Bundles the project as a JVM application with libs and OS specific scripts.
installApp - Installs the project as a JVM application along with libs and OS specific scripts.
run - Runs this project as a JVM application
...
Other tasks
-----------
wrapper
...
To see all tasks and more detail, run with --all.
BUILD SUCCESSFUL
Total time: 7.808 secs
Шаг 3. Заполняем пробелы
На данном этапе мы уже можем выполнять несколько задач gradle
. Мы можем даже собрать jar файл, но ничего кроме пустого манифеста там не будет. Настало время написать код. Gradle использует по умолчанию такую же структуру каталогов, что и Maven, а именно
src
-main
-java
-resources
-test
-java
-resources
main/java
— это java-файлы нашей программы, main/resources
— это остальные файлы (*.properties, *.xml, *.img и прочие). В test
находятся файлы необходимые для тестирования.
Поскольку тестирование в этой статье рассматриваться не будет, обойдемся созданием папки src/main
со всеми вложенными и приступим к созданию нашего приложения. А приложение — это Hello World, в котором будем использовать библиотеку Log4j. Как раз и разберемся, как в gradle работают зависимости. Внесем изменения в файл build.gradle
, создадим файл com/example/Main.java
с главным классом приложения в папке src/main/java
, а также файл с настройками Log4j src/main/resources/log4j.xml
. И файл gradle.properties
(не обязательно, подробности ниже)
{project_path}/build.gradle
apply plugin: "java"
apply plugin: "application"
mainClassName = "com.example.Main"
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
repositories {
mavenCentral()
}
dependencies {
compile "log4j:log4j:1.2.17"
}
jar {
manifest.attributes("Main-Class": mainClassName);
}
task wrapper(type: Wrapper) {
gradleVersion = "1.12"
}
{project_path}/gradle.properties
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home/
{project_path}/src/main/java/com/example/Main.java
package com.example;
import org.apache.log4j.Logger;
public class Main {
private static final Logger LOG = Logger.getLogger(Main.class);
public static void main(String[] args) {
LOG.info("Application started");
System.out.println("I'm the main project");
LOG.info("Application finished");
}
}
{project_path}/src/main/resources/log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%p %c: %m%n"/>
</layout>
</appender>
<root>
<priority value ="debug" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
Рассмотрим изменения в файле build.gradle
. Мы добавили переменную mainClassName
. Она указывает главный класс нашего приложения и используется плагином application
в задаче run
. Именно этот класс будет запущен. Также мы добавили переменные sourceCompatibility
и targetCompatibility
, присвоив им значение JavaVersion.VERSION_1_7
. Это переменные из плагина java
, показывают, какая версия jdk нам нужна при сборке. Следующий блок — repositories
. В этом блоке мы подключаем репозиторий Maven. Gradle прекрасно с ним “дружит”. Блок dependencies
содержит зависимости нашего приложения. Тонкости настройки смотрим в документации. Здесь мы указываем, что для задачи compile
необходимо наличие log4j. В примере указан сокращенный синтаксис. Можно написать развернутый вариант и выглядеть он будет так:
complie group: 'log4j', name: 'log4j', version: '1.2.17'
Для сравнения аналогичный блок в maven:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
Также можно настраивать зависимости от файлов compile files('libs/a.jar', 'libs/b.jar')
и от подпроектов compile project(':library_project')
.
Последнее добавление в build.gradle
— это блок jar
. Он также относится к плагину java
. Содержит в себе дополнительную информацию для сборки jar-файла. В данном случае мы добавляем в манифест главный класс, воспользовавшись объявленной выше переменной mainClassName
.
Далее необязательный файл gradle.properties
. Описание этого файла разбросано по всей докментации, немного находится здесь и здесь. В данном случае мы фактически переопределяем переменную JAVA_HOME
. Это актуально, когда у вас несколько версий jdk, как в моем случае, вы могли обратить внимание в начале статьи, $ gradle -version
показывает, что моя версия JVM: 1.8.0_05 (Oracle Corporation 25.5-b02)
.
Я думаю, подробно останавливаться на файлах src/main/java/Main.java
и src/main/resources/log4j.xml
не имеет смысла, так как все предельно просто. Отправляем два сообщения в Logger, сообщение «I'm the main project» выводим в консоль. В файле настроек log4j написано, что наш logger будет выводить сообщения также в консоль.
Итоги третьего шага:
$ ./gradlew run
:compileJava
Download http://repo1.maven.org/maven2/log4j/log4j/1.2.17/log4j-1.2.17.jar
:processResources
:classes
:run
INFO com.example.Main: Application started
I'm the main project
INFO com.example.Main: Application finished
BUILD SUCCESSFUL
Total time: 14.627 secs
Видно, что скачивается недостающая библиотека, и продемонстрировано ее использование.
Шаг 4. Достижение цели
У нас уже есть проект, который работает, собирается и запускается через gradle. Осталось доделать самую малость: реализовать многомодульность, заявленную в заголовке статьи, или multi-project, если пользоваться терминологией gradle. Создадим две директории в корне проекта: main_project
и library_project
. Теперь переместим папку src
и файл build.gradle
в только что созданную директорию main_project
, и создадим в корне новый файл settings.gradle
с таким содержимым (об этом файле подробнее тут):
{project_path}/settings.gradle
rootProject.name = 'Gradle_Multiproject'
include 'main_project'
В этом файле мы говорим, как называется наш проект и какие папки подключать (фактически самостоятельные gradle проекты). На данном этапе нам нужна одна папка main_project
. После таких изменений мы можем выполнить $ ./gradlew run
или с указанием конкретного подпроекта $ ./gradlew main_project:run
, и получим тот же результат, что и в конце шага 3. То есть работающий проект. Также можем выполнять все прочие команды jar, build, installApp и так далее. Gradle, если не указывать конкретного подпроекта, будет запускать задачу во всех подключенных подпроектах, у которых эта задача есть (например, если плагин application подключен только к одному подпроекту, у нас это будет main_project, команда $ ./gradlew run
запустит run только этого подпроекта)
Теперь создадим код в нашем library_project
. Создаем build.gradle
и src/main/java/com/example/library/Simple.java
{project_path}/library_project/build.gradle
apply plugin: "java"
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
{project_path}/library_project/src/main/java/com/example/library/Simple.java
package com.example.library;
public class Simple {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
build.gradle
для этого подпроекта намного проще. Используем плагин java и выставляем переменные с версией JDK. В рамках данной статьи этого достаточно. Теперь мы хотим, чтобы gradle узнал о подпроeкте library_project
, опишем это в файле settings.gradle
:
{project_path}/settings.gradle
rootProject.name = 'Gradle_Multiproject'
include 'main_project', 'library_project'
Теперь мы может собрать jar файл, содержащий нашу библиотеку, командой $ ./gradlew library_project:jar
.
$ ./gradlew library_project:jar
:library_project:compileJava
:library_project:processResources UP-TO-DATE
:library_project:classes
:library_project:jar
BUILD SUCCESSFUL
Total time: 10.061 secs
Полученный файл можно найти по адресу: {project_path}/library_project/build/libs/library_project.jar
.
А теперь давайте добавим использование класса Simple
в main_project
. Для этого нужно в файл {project_path}/main_project/build.gradle
добавить строчку compile project(":library_project")
в блок dependencies
, которая сообщает, что для выполнения задачи compile в этом модуле нужен проект library_project
.
{project_path}/main_project/build.gradle (блок dependencies
)
dependencies {
compile "log4j:log4j:1.2.17"
compile project(":library_project")
}
{project_path}/main_project/src/main/java/com/example/Main.java
package com.example;
import org.apache.log4j.Logger;
import com.example.library.Simple;
public class Main {
private static final Logger LOG = Logger.getLogger(Main.class);
public static void main(String[] args) {
LOG.info("Application started");
System.out.println("I'm the main project");
Simple simple = new Simple();
simple.setValue(10);
System.out.println("Value from Simple: " + simple.getValue());
LOG.info("Application finished");
}
}
Можно проверять.
Итог четвертого шага:
$ ./gradlew run
:library_project:compileJava UP-TO-DATE
:library_project:processResources UP-TO-DATE
:library_project:classes UP-TO-DATE
:library_project:jar UP-TO-DATE
:main_project:compileJava
:main_project:processResources UP-TO-DATE
:main_project:classes
:main_project:run
INFO com.example.Main: Application started
I'm the main project
Value from Simple: 10
INFO com.example.Main: Application finished
BUILD SUCCESSFUL
Total time: 11.022 secs
Шаг 5 (заключительный). Убираем мусор
Основная цель достигнута, но на данном этапе могли возникнуть вполне закономерные вопросы о дублировании информации в build файлах, более глубокой настройке gradle, а также о том, что изучать дальше. Для самостоятельного изучения, я советую ознакомиться с содержимым ссылок в конце статьи. А пока, давайте приведем в порядок наши build файлы, создав build.gradle
в корне проекта и изменив содержимое остальных build файлов
{project_path}/build.gradle
apply plugin: "idea"
apply plugin: "eclipse"
subprojects {
apply plugin: "java"
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
repositories {
mavenCentral()
}
}
task wrapper(type: Wrapper) {
gradleVersion = "1.12"
}
{project_path}/main_project/build.gradle
apply plugin: "application"
version = '1.0'
mainClassName = "com.example.Main"
dependencies {
compile "log4j:log4j:1.2.17"
compile project(":library_project")
}
jar {
manifest.attributes("Main-Class": mainClassName);
}
{project_path}/build.gradle
version = "1.1_beta"
В корневом build.gradle
мы будем хранить то, что относится ко всем проектам (на самом деле, можно хранить вообще все настройки, но я предпочитаю разделять большие файлы) и то, что не нужно в подпроектах, например, wrapper нам нужен только один, в корне.
В блок subprojects
мы помещаем настройки подпроектов, а именно: подключаем плагин java — он нужен всем; выставляем версию jdk; подключаем maven-репозиторий. Также в этом файле мы подключаем плагины idea и eclipse. Эти плагины содержат задачи для генерации файлов проектов для соответствующих IDE. И сюда же переносим задачу wrapper. Она нужна только в корне, чтобы создать общие для всех файлы gradlew.
В подпроектах мы убрали все лишнее и добавили переменную version
. Значение этой переменной будет добавляться к jar файлам, например, вместо library_project.jar теперь будет library_project-1.1.beta.jar.
Помимо блока subprojects
, можно использовать allprojects
или project(':project_name')
. Подробнее тут.
На этом я закончу. Надеюсь, данная статья вызвала интерес у людей, не знакомых с Gradle, и побудила к более подробному изучению и последующему использованию в своих проектах этого инструмента.
Спасибо за внимание.
Полезные ссылки
Исходники проекта, созданного в статье, на bitbucket (zip архив)
Gradle
Gradle User Guide
Apache Logging Services
Apache Maven
Groovy Language
Автор: evsan