Всем привет, сегодня я хотел бы рассказать вам о моём опыте работы с Gradle, не просто переписать мануал (хотя он отлично написан), но рассказать с какими реальными проблемами я столкнулся и как побеждал их, а также показать какие возможности предоставляет нам Gradle. Тема очень обширная поэтому, к сожалению я не смогу рассмотреть многие аспекты подробно и последовательно, надеюсь что читатели уже немного знакомы с Gradle и смогут понять суть описываемых решений.
Лирика
Важное замечание по поводу maven — многие задачи которые я делал с помощью Gradle я никогда не пытался сделать раньше maven-ом, поэтому сравнить многие моменты, что лучше, а что хуже я к сожалению не смогу.
Первое, что в нём понравилось, это наконец-то «прощай xml», о да, для меня это действительно как заново родиться, как же было невероятно приятно писать сборку на настоящем языке программирования, какая же это свобода и вдохновение.
В статье будут использоваться некоторые понятия из OSGI и eclipse rcp, я не буду заострять на них внимание, чтобы не отвлекаться от главной темы.
В приложении есть простенький проект с примерами скриптов и некоторыми исходниками используемыми в статье: тынц
Масштаб проблемы
Итак, что у нас было вначале: у нас имеется большая (по моим меркам) система запускаемая на OSGI платформе, проекты в системе это eclipse plug-in проекты, никакой интеграции с системой сборки нет, в качестве системы сборки используется ant, системы контроля зависимостей не было. Была задача сделать всё круто.
Итак я предложил Gradle мне дали добро, и началось.
Описание структуры проектов
Суть проблемы в том, что имеется директория с некой структурой в которой хранятся проекты.
Первое что нужно было сделать, это описать в скриптах сборки структуру директории и все имеющиеся проекты. Я это смог сделать с легкостью. В Gradle для этого используется понятие multi project. В корневой директории создаем файл settings.gradle и записываем пути к каждой папке где лежат проекты разделитель пути «:». Пример settings.gradle:
include "subproject-first"
include "subproject-first:child-first"
include "subproject-first:child-first:another-child:last-child"
include "subproject-first:child-first:another-child:another-last-child"
include "subproject-second"
Теперь мы можем посмотреть структуру нашей системы, выполнив команду:
gradle projects -q
Результат:
Настройка IDE
Следующим шагом была возможность импорта проектов в IDE. В Gradle есть плагины для idea и eclipse:
тынц
барабынц
У нас проекты очень жестко завязаны на eclipse из-за osgi, и куска eclipse-rcp который не позволял просто так взять target platform (это такая страшная штука из мира osgi, по сути это osgi framework и папка с кучей jar-ников) и использовать её в idea, но в общем это лирическое отступление.
Взяв eclipse плагин я смог настроить проекты именно так как мне надо было, а надо было мне сделать проекты как eclipse plug-in, прописать в настройках plugin dependencies, указать версию используемого компилятора. Пример настройки:
project.apply plugin: 'java'
project.apply plugin: 'eclipse'
project.apply plugin: 'idea'
project.eclipse {
jdt {
sourceCompatibility = '1.6'
}
project {
//применяем натуру и проект становится eclipse plug-in
natures 'org.eclipse.pde.PluginNature'
}
classpath {
downloadSources = true
downloadJavadoc = true
//используем java se 1.6 jre
containers.clear()
containers.add("org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6")
file {
//ручная правка .classpath добавление plugin-dependencies зависимостей (то есть зависимости от целевой платформы — есть такое понятие в osgi и эклипсе)
withXml {
Node node = it.asNode()
node.appendNode('classpathentry', [kind: 'con', path: 'org.eclipse.pde.core.requiredPlugins'])
}
}
}
}
То есть, как видите настроек для eclipse много, а то что нельзя сделать через api eclipse плагина, можно сделать вручную исправив xml файл настроек.
Зависимости
Следующий шаг, dependency management: документация.
Я раньше думал, что нет ничего лучше dependency managment в maven, сейчас я думаю, что я ошибался. Gradle может использовать зависимости из maven, ivy, и простые папки и файлы, да и вообще при желании можно сделать, что-то очень нестандартное, например использовать ftp сервер в качестве repository. Для примера, как прописываются зависимости:
project(":myproject") {
dependencies {
//зависимость от другого проекта
compile project(":other-my-project")
//берем зависимости из локальной папки
compile fileTree(dir: "libs", include: "*.jar")
//берем зависимость из maven репозитория
compile 'org.hibernate:hibernate-core:3.6.3.Final'
}
}
Но это только начало. В Gradle есть понятие configurations — это что-то похожее на группы в которые добавляются зависимости. То есть в нашем примере compile это и есть configuration, которую создает java plugin.
Эта возможность помогла мне красиво решить одну проблему с зависимостями, суть этой проблемы вот в чем, у нас ядро разрабатывают за границей, и результат к нам приходит в виде zip архива, внутри которого имеется множество различных файлов, в том числе необходимый набор библиотек. Этот архив заливается в artifactory внешними силами, а я в свою очередь получаю его из artifactory, сохраняю его в temp директорию, распаковываю, беру из распакованной папки все необходимые мне библиотеки.
Скачивание и распаковка архива:
//получаем путь к temp папке куда будем распаковывать архив
File sdkDir = getSdkDir(sdkVersion)
//создаем необходимые нам конфигурации
project.configurations {
sdk
libsFromSdk
}
//прописываем artifactory репозиторий откуда будем брать наш архив
project.repositories {
maven { url 'http://localrepo:8081/artifactory/repo' }
mavenCentral()
mavenLocal()
}
//прописываем зависимость
project.dependencies {
sdk group: 'com.kontora', name: 'sdk', version: sdkVersion, ext: 'zip'
}
Configuration cfg = project.configurations.sdk
//берем zip файл из зависимости
cfg.files.each { File zipArchive ->
//извлекаем архив в папку
project.ant.unzip src: zipArchive, dest: sdkDir.absolutePath
}
Обратите внимание, что весь набор действий запрограммирован с помощью dsl языка Gradle в терминах сборки. Нет необходимости писать свой низкоуровневый код на языке программирования.
Подключение зависимостей в проект из распакованного архива:
project.dependencies {
libsFromSdk project.fileTree(dir: "$pathToSdk/libs", include: "*.jar")
…
//копируем все зависимости из libsFromSdk в compile конфигурацию
project.compile libsFromSdk
}
И итоге мы получим все наши зависимости для компиляции.
Я люблю тебя код, что само по себе и не ново
Наши скрипты тем временем растут и увеличиваются, пора бы и подумать над вопросом копипасты дублирования кода. Предположим у нас есть программная система и мы все понимаем, что дублирование кода в системе это плохо, но почему-то дублирование строк текста в настройках системы сборки не считается чем-то неправильным. Я думаю причина этому, описание сборки в xml, ведь xml — это по сути конфигурационные файлы и применять правила программирования для файлов конфигурации не совсем корректно. Но вместе с тем писать в сотне файлов одинаковый текст конфигурации — это не совсем красиво, сложно для ориентации программиста и может стать причиной неявных и сложно обнаруживаемых ошибок в сборке. В Gradle eсли мы для сборки пишем код, то значит мы сможем избавиться от дублирования кода конфигурации в системе сборки, стандартным способом, как мы это делаем в обычном коде. И тут Gradle опять показал свою мощь и красоту.
Трям.
После некоторого этапа исследований я написал свой первый плагин. Оказывается можно создать папку buildSrc в корневой папке системы, которая будет java, groovy, scala etc проектом по вашему выбору, и при старте системы сборки, в первую очередь будет скомпилирован проект buildSrc и классами из этого проекта можно будет пользоваться в скриптах сборки. По мне так это гениально.
В итоге, например сейчас у меня имена и пути к проектам прописаны только в одном месте в классе в buildSrc, а все остальные скрипты пользуются этими переменными в которых хранятся имена проектов (конечно разговор о преимуществах и недостатках этого подхода это отдельный разговор), при этом у меня есть авто дополнение кода, я просто пишу в любом месте сборки примерно так: MyProjects.subproject.child.absolutePath. Помимо этого большая часть логики сборки лежит в плагинах там же в buildSrc.
Ниже приведен пример плагина и его вызова в скрипте сборки:
Пример плагина:
package org.gradle.example
import org.gradle.api.Project
import org.gradle.api.Plugin
class MyPlugin implements Plugin<Project>{
private static final String PLUGIN_EXTENSION = 'my'
Project project;
@Override
public void apply(Project project) {
MyPlugin plugin = project.extensions.create(PLUGIN_EXTENSION, MyPlugin.class)
plugin.project = project;
}
public void applyCommonSettings() {
project.apply plugin: 'java'
project.apply plugin: 'eclipse'
project.apply plugin: 'idea'
}
}
Пример вызова плагина:
project.apply plugin: MyPlugin
my{
applyCommonSettings()
}
А у вас есть точно такое же только с перламутровыми пуговицами?
И переходя к плагинам Gradle: у нас в системе есть разные проекты с разными структурами и настройками, Это java, flex, android проекты. И всё это хозяйство нужно настраивать по разному для экспорта в IDE. Например у нас бОльшая часть проектов — это Osgi проекты, другая часть — это простые проекты, у некоторых проектов имеется legacy структура (то есть только папка src) и т.д. И тут Gradle позволил легко справиться с этой задачей. Я написал несколько классов в которых указал какие у нас могут быть настройки, написал класс фабрики в котором описал все настройки каждого проекта, и когда происходит импорт проекта в ide, мы получаем эти настройки и применяем их. Примера кода я тут приводить не буду, потому что это просто groovy код с обычной логикой.
Также я смог написать свои собственные валидаторы проверяющие правильность структуры проектов.
Также планируется много всяких прикольных штук. К сожалению нет времени на то, чтобы рассказать про написание unit тестов для gradle плагинов, возможности логгирования и многое другое
Заключение
Я очень доволен гредлом, он оправдал себя на 101% желаю и вам познать радость работы с этой системой.
Автор: bynull