Многомодульный Java-проект с Gradle. Шаг за шагом
Очень много статей о 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™ 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×86_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™ 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×86_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
$ ./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 архив)GradleGradle User GuideApache Logging ServicesApache MavenGroovy Language