Многомодульный 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 из стандартных источников)
Шаг 2. Пустой проект, плагины (plugin), обертка (wrapper)
Создадим папку проекта и в ее корне сделаем файл build.gradle со следующим содержимым:
Давайте, рассмотрим подробнее, что мы написали в файле. Тут используется динамический язык Groovy. Использование полноценного языка программирования в gradle дает большую свободу в сравнении со сборщиками пакетов, использующих декларативные языки.
В этом файле мы подключаем плагины java и application . Плагин java содержит в себе такие полезные задачи, как jar — собрать jar архив, compileJava — скомпилировать исходные коды и др. Подробнее о плагине можно почитать тут. Плагин application содержит в себе задачи: run — запуск приложения; installApp — установка приложения на компьютер, эта задача создает исполняемые файлы для *nix и для windows (bat файл); distZip — собирает приложение в zip архив, помещая туда все jar файлы, а также специфические для операционной системы скрипты. Подробнее о плагине в документации.
Теперь остановимся подробней на задаче wrapper . Эта очень полезная задача, наверное, самое гениальное решение, призванное облегчить жизнь программистам. Выполнив $ gradle wrapper , получим следующий результат:
Мы видим, что скрипт создал нам исполняемые файлы gradlew для *nix, gradlew.bat для Windows, а также папки gradle и .gradle. Скрытую папку .gradle можно не включать в репозиторий, там содержатся библиотеки зависимостей. Все основное лежит в gradle и в самом файле gradlew. Теперь мы смело может отдавать наш проект любому человеку, имеющему jdk нужной версии, и он самостоятельно сможет скомпилировать, собрать, установить проект, используя ./gradlew . Заметьте, что моя версия gradle (см. результат команды $ gradle -version выше) отличается от той, которую я указал в файле build.gradle, но это не страшно, поскольку после запуска задачи wrapper, мы получим необходимую версию gradle.
Теперь вместо gradle можно смело использовать gradlew . Кстати, выполнение команды $ ./gradlew без параметров создаст папку .gralde и скачает туда все зависимые библиотеки (о зависимостях ниже). Но выполнение этой команды не обязательно, так как при любом запуске gradle (gradlew), будут проверяться зависимости и скачиваться недостающие файлы. Поэтому, получив проект, в котором лежат файлы gradlew, можно сразу запускать нужную задачу, список которых можно получить по команде ./gradlew tasks
Итоги второго шага (вывод сокращен):
Шаг 3. Заполняем пробелы
На данном этапе мы уже можем выполнять несколько задач gradle . Мы можем даже собрать jar файл, но ничего кроме пустого манифеста там не будет. Настало время написать код. Gradle использует по умолчанию такую же структуру каталогов, что и Maven, а именно
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 (не обязательно, подробности ниже)
Рассмотрим изменения в файле build.gradle . Мы добавили переменную mainClassName . Она указывает главный класс нашего приложения и используется плагином application в задаче run . Именно этот класс будет запущен. Также мы добавили переменные sourceCompatibility и targetCompatibility , присвоив им значение JavaVersion.VERSION_1_7 . Это переменные из плагина java , показывают, какая версия jdk нам нужна при сборке. Следующий блок — repositories . В этом блоке мы подключаем репозиторий Maven. Gradle прекрасно с ним “дружит”. Блок dependencies содержит зависимости нашего приложения. Тонкости настройки смотрим в документации. Здесь мы указываем, что для задачи compile необходимо наличие log4j. В примере указан сокращенный синтаксис. Можно написать развернутый вариант и выглядеть он будет так:
Для сравнения аналогичный блок в maven:
Также можно настраивать зависимости от файлов 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 будет выводить сообщения также в консоль.
Видно, что скачивается недостающая библиотека, и продемонстрировано ее использование.
Шаг 4. Достижение цели
У нас уже есть проект, который работает, собирается и запускается через gradle. Осталось доделать самую малость: реализовать многомодульность, заявленную в заголовке статьи, или multi-project, если пользоваться терминологией gradle. Создадим две директории в корне проекта: main_project и library_project . Теперь переместим папку src и файл build.gradle в только что созданную директорию main_project , и создадим в корне новый файл settings.gradle с таким содержимым (об этом файле подробнее тут):
В этом файле мы говорим, как называется наш проект и какие папки подключать (фактически самостоятельные 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
build.gradle для этого подпроекта намного проще. Используем плагин java и выставляем переменные с версией JDK. В рамках данной статьи этого достаточно. Теперь мы хотим, чтобы gradle узнал о подпроeкте library_project , опишем это в файле settings.gradle :
Теперь мы может собрать jar файл, содержащий нашу библиотеку, командой $ ./gradlew library_project:jar .
Полученный файл можно найти по адресу:
А теперь давайте добавим использование класса Simple в main_project . Для этого нужно в файл
Дополнение от MiniM: В gradle символ «:» используется вместо «/» и для более ветвистой структуры ссылки на проект могут выглядеть так «:loaders:xml-loader»
Шаг 5 (заключительный). Убираем мусор
Основная цель достигнута, но на данном этапе могли возникнуть вполне закономерные вопросы о дублировании информации в build файлах, более глубокой настройке gradle, а также о том, что изучать дальше. Для самостоятельного изучения, я советую ознакомиться с содержимым ссылок в конце статьи. А пока, давайте приведем в порядок наши build файлы, создав build.gradle в корне проекта и изменив содержимое остальных build файлов
В корневом 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, и побудила к более подробному изучению и последующему использованию в своих проектах этого инструмента.
Настройка Eclipse¶
Эта статья рассказывает о том, как настроить Ваше рабочее пространство Eclipse для разработки плагинов со SpongeAPI и :doc: создавать системы, такие как Maven или Gradle `.
Туториал от Mumfrey показывает установку рабочего пространства Sponge в Eclipse, используя новые возможности в Buildship и ForgeGradle. Туториал можно посмотреть тут.
Gradle¶
Подготовка Gradle¶
Gradle уже присутствует в Eclipse начиная с релиза Neon (Июнь 22, 2016) и более новых версий. Данные шаги требуются только для старых версий.
Для начала Вы должны установить Gradle Integration Plugin перед использованием Gradle в Eclipse. Это необходимо сделать перед созданием Вашего первого проекта.
Как правило, не требуется никаких дополнительных модулей, которые поставляются вместе с Gradle, так что Вы можете снять флажки выбора плагинов во время установки.
- Откройте Eclipse.
- Нажмите Help > Eclipse Marketplace .
- Найдите Gradle Integration Plugin .
- Установите Gradle Integration Plugin.
Создание проекта¶
- Откройте Eclipse.
- Нажмите File > New > Other .
- Выберите Gradle > Gradle Project .
- Нажмите Next .
- Введите имя проекта, а затем нажмите кнопку Finish .
Редактирование build-скрипта¶
- Откройте build.gradle в навигаторе.
- Отредактируйте build-скрипт, как описано в статье Настройка Gradle .
- Кликните правой кнопкой мыши на вашем проекте и выберите Gradle > Refresh Gradle Project .
Импорт Вашего проекта¶
Если вы уже начали работу с вашим проектом и хотите снова импортировать его позже, вам необходимо его импортировать вместо повторного создания внутри вашей IDE:
- Нажмите File > Import
- Выберите Gradle > Gradle Project
- Перейдите в корневую папку проекта
- Нажмите Finish
Maven¶
Создание проекта¶
- Откройте Eclipse.
- Нажмите File > New > Other .
- Выберите Maven на левой стороне всплывающего окна и выберите Maven Project на правой стороне.
- Нажмите Next .
- Выберите Create a simple project , если вам требуются более продвинутые настройки.
- Введите данные Group >Основной класс плагина .
- Artifact > myplugin .
- Your Version is up to you. See Номер версии for details.
Eclipse часто не открывает новые проекты сразу после их создания. Если так и происходит, попробуйте закрыть окно приветствия Eclipse; Ваш проект должен быть открыт за ним.
Редактирование конфигурации проекта¶
- Откройте pom.xml в навигаторе.
- Отредактируйте build-скрипт, как описано в статье Настройка Maven .
- Обновите Ваш проект Maven.
Импорт Вашего проекта¶
Если вы уже начали работу с вашим проектом и хотите снова импортировать его позже, вам необходимо его импортировать вместо повторного создания внутри вашей IDE:
- Нажмите File > Import
- Выберите Maven > Existing Maven Projects
- Перейдите в корневую папку проекта
- Нажмите Finish
Git Интеграция¶
Eclipse предоставляет документацию по использованию плагина Eclipse EGit:
Установка gradle для eclipse
Gradle и Eclipse
После того как вы создали libGDX проект, время начать разработку приложения в Eclipse. Перед тем как импортировать ваш проект в Eclipse, убедитесь, что вы настроили среду разработки .
Импорт проекта
Перейдите File -> Import -> Gradle -> Gradle Project , нажмите кнопку Browse и перейдите в корневую папку вашего проекта, затем нажмите кнопку Build Model . Через некоторое время, вы увидите корневой проект и подпроекты (android, core, desktop, html, ios). Выберите все проекты и нажмите кнопку Finish .
Время импорта проекта
В первый раз процесс импорта проекта может занять минуту или две, так как Gradle и некоторые зависимости будут загружаться в фоновом режиме.
Общие проблемы
Папка рабочего пространства(workspace) Eclipse, не должна быть такой же, как корневая папка вашего проекта (подробнее о проблеме).
Если вы сталкиваетесь с проблемами из-за отсутствия артефакта validation-api:1.0.0.GA, тогда удалите Maven кэш в C:\Users\username\.m2 или /Users/username/.m2 или /home/username/.m2 .
При импорте первого проекта появляется сообщение об ошибке: com/github/jtakakura/gradle/plugins/robovm/RoboVMPlugin : Unsupported major.minor version 51.0 . Убедитесь, что вы используете Oracle JDK версии 7 или выше.
Запуск проекта
- Desktop: щелкните правой кнопкой мыши на desktop проекте, Run As -> Java Application . Выберите стартер класс (например, DesktopLauncher.java).
- Android: убедитесь, что у вас есть подключенное устройство и что оно появилось в DDMS (см. Android Руководство разработчика). Щелкните правой кнопкой мыши на Android проекте, Run As -> Android Application .
- iOS RoboVM: щелкните правой кнопкой мыши на robovm проекте, Run As -> iOS Device App для запуска на подключенном устройстве или Run As -> iOS Simulator App для запуска на iOS симуляторе
- HTML5: щелкните правой кнопкой мыши на html проекте, Run As -> External Tools Configuration . Создайте новую конфигурацию двойным кликом по Program в левой боковой панели. Задайте имя конфигурации, например, GWT SuperDev. В поле местоположения укажите gradlew.bat (Windows) или gradlew (Linux, Mac) файл. Установите рабочий каталог как корневую папку вашего проекта. Укажите html:superDev в качестве аргумента. Нажмите ‘Apply’, затем ‘Run’. Подождите, пока не увидите сообщение The code server is ready. в окне просмотра консоли, затем откройте URL http://localhost:8080/html. Вы можете оставить сервер работающим. Если вы изменили код или assets, просто нажмите в браузере кнопку SuperDev Refresh . Это действие перекомпилирует ваше приложение и перезагрузит страницу.
После того, как будет исправлена ошибка в Gradle tooling API, мы сможем просто запускать HTML5 с помощью Gradle интеграции. На данный момент, процесс Gradle будет работать постоянно, даже в случае отмены.
Отладка проекта
Следуйте инструкции для запуска проекта, но вместо запуска через Run as запустите вашу конфигурацию через Debug as .
RoboVM и отладка проекта
RoboVM в настоящее время не поддерживает отладку.
Отладка html сборки может быть сделана в браузере следующим образом:
Как и прежде запустите конфигурацию superDev. Перейдите по ссылке http://localhost:8080/html, нажмите кнопку SuperDevRefresh и затем Compile . Нажмите клавишу F12 в браузере Chrome, чтобы открыть средства разработки, перейдите на вкладку Sources и найдите Java файл, который вы хотите отладить. Устанавливайте точки останова, делайте пошаговое выполнение и проверяйте переменные. Нажмите кнопку SuperDev Refresh в случае, если вы изменили код или assets (оставляйте работающим процесс сервера!).
Упаковка проекта
Проще всего упаковывать приложение из командной строки или с помощью задач Gradle изнутри Eclipse. Чтобы увидеть соответствующие задачи Gradle, смотрите документацию командной строки Gradle.
Gradle и решение задач автоматизации
Лирика
Важное замечание по поводу maven — многие задачи которые я делал с помощью Gradle я никогда не пытался сделать раньше maven-ом, поэтому сравнить многие моменты, что лучше, а что хуже я к сожалению не смогу.
Первое, что в нём понравилось, это наконец-то «прощай xml», о да, для меня это действительно как заново родиться, как же было невероятно приятно писать сборку на настоящем языке программирования, какая же это свобода и вдохновение.
В статье будут использоваться некоторые понятия из OSGI и eclipse rcp, я не буду заострять на них внимание, чтобы не отвлекаться от главной темы.
В приложении есть простенький проект с примерами скриптов и некоторыми исходниками используемыми в статье: тынц
Масштаб проблемы
Итак, что у нас было вначале: у нас имеется большая (по моим меркам) система запускаемая на OSGI платформе, проекты в системе это eclipse plug-in проекты, никакой интеграции с системой сборки нет, в качестве системы сборки используется ant, системы контроля зависимостей не было. Была задача сделать всё круто.
Итак я предложил Gradle мне дали добро, и началось.
Описание структуры проектов
Суть проблемы в том, что имеется директория с некой структурой в которой хранятся проекты.
Первое что нужно было сделать, это описать в скриптах сборки структуру директории и все имеющиеся проекты. Я это смог сделать с легкостью. В Gradle для этого используется понятие multi project. В корневой директории создаем файл settings.gradle и записываем пути к каждой папке где лежат проекты разделитель пути «:». Пример settings.gradle:
Теперь мы можем посмотреть структуру нашей системы, выполнив команду:
Настройка IDE
Следующим шагом была возможность импорта проектов в IDE. В Gradle есть плагины для idea и eclipse:
тынц
барабынц
У нас проекты очень жестко завязаны на eclipse из-за osgi, и куска eclipse-rcp который не позволял просто так взять target platform (это такая страшная штука из мира osgi, по сути это osgi framework и папка с кучей jar-ников) и использовать её в idea, но в общем это лирическое отступление.
Взяв eclipse плагин я смог настроить проекты именно так как мне надо было, а надо было мне сделать проекты как eclipse plug-in, прописать в настройках plugin dependencies, указать версию используемого компилятора. Пример настройки:
То есть, как видите настроек для eclipse много, а то что нельзя сделать через api eclipse плагина, можно сделать вручную исправив xml файл настроек.
Зависимости
Следующий шаг, dependency management: документация.
Я раньше думал, что нет ничего лучше dependency managment в maven, сейчас я думаю, что я ошибался. Gradle может использовать зависимости из maven, ivy, и простые папки и файлы, да и вообще при желании можно сделать, что-то очень нестандартное, например использовать ftp сервер в качестве repository. Для примера, как прописываются зависимости:
Но это только начало. В Gradle есть понятие configurations — это что-то похожее на группы в которые добавляются зависимости. То есть в нашем примере compile это и есть configuration, которую создает java plugin.
Эта возможность помогла мне красиво решить одну проблему с зависимостями, суть этой проблемы вот в чем, у нас ядро разрабатывают за границей, и результат к нам приходит в виде zip архива, внутри которого имеется множество различных файлов, в том числе необходимый набор библиотек. Этот архив заливается в artifactory внешними силами, а я в свою очередь получаю его из artifactory, сохраняю его в temp директорию, распаковываю, беру из распакованной папки все необходимые мне библиотеки.
Скачивание и распаковка архива:
Обратите внимание, что весь набор действий запрограммирован с помощью dsl языка Gradle в терминах сборки. Нет необходимости писать свой низкоуровневый код на языке программирования.
Подключение зависимостей в проект из распакованного архива:
И итоге мы получим все наши зависимости для компиляции.
Я люблю тебя код, что само по себе и не ново
Наши скрипты тем временем растут и увеличиваются, пора бы и подумать над вопросом копипасты дублирования кода. Предположим у нас есть программная система и мы все понимаем, что дублирование кода в системе это плохо, но почему-то дублирование строк текста в настройках системы сборки не считается чем-то неправильным. Я думаю причина этому, описание сборки в xml, ведь xml — это по сути конфигурационные файлы и применять правила программирования для файлов конфигурации не совсем корректно. Но вместе с тем писать в сотне файлов одинаковый текст конфигурации — это не совсем красиво, сложно для ориентации программиста и может стать причиной неявных и сложно обнаруживаемых ошибок в сборке. В Gradle eсли мы для сборки пишем код, то значит мы сможем избавиться от дублирования кода конфигурации в системе сборки, стандартным способом, как мы это делаем в обычном коде. И тут Gradle опять показал свою мощь и красоту.
После некоторого этапа исследований я написал свой первый плагин. Оказывается можно создать папку buildSrc в корневой папке системы, которая будет java, groovy, scala etc проектом по вашему выбору, и при старте системы сборки, в первую очередь будет скомпилирован проект buildSrc и классами из этого проекта можно будет пользоваться в скриптах сборки. По мне так это гениально.
В итоге, например сейчас у меня имена и пути к проектам прописаны только в одном месте в классе в buildSrc, а все остальные скрипты пользуются этими переменными в которых хранятся имена проектов (конечно разговор о преимуществах и недостатках этого подхода это отдельный разговор), при этом у меня есть авто дополнение кода, я просто пишу в любом месте сборки примерно так: MyProjects.subproject.child.absolutePath. Помимо этого большая часть логики сборки лежит в плагинах там же в buildSrc.
Ниже приведен пример плагина и его вызова в скрипте сборки:
А у вас есть точно такое же только с перламутровыми пуговицами?
И переходя к плагинам Gradle: у нас в системе есть разные проекты с разными структурами и настройками, Это java, flex, android проекты. И всё это хозяйство нужно настраивать по разному для экспорта в IDE. Например у нас бОльшая часть проектов — это Osgi проекты, другая часть — это простые проекты, у некоторых проектов имеется legacy структура (то есть только папка src) и т.д. И тут Gradle позволил легко справиться с этой задачей. Я написал несколько классов в которых указал какие у нас могут быть настройки, написал класс фабрики в котором описал все настройки каждого проекта, и когда происходит импорт проекта в ide, мы получаем эти настройки и применяем их. Примера кода я тут приводить не буду, потому что это просто groovy код с обычной логикой.
Также я смог написать свои собственные валидаторы проверяющие правильность структуры проектов.
Также планируется много всяких прикольных штук. К сожалению нет времени на то, чтобы рассказать про написание unit тестов для gradle плагинов, возможности логгирования и многое другое