Ни один проект с использованием платформы Java (и не только) не обходится без инструментов сборки (если только это не «Hello, world!»). Рано или поздно, но собирать дистрибутив руками надоедает. Да и компилировать из консоли было бы неплохо, если в проекте используется несколько разных IDE. А перед сборкой дистрибутива было бы здорово проставить номер версии в его имени. И unit тесты прогнать — не зря же Kent Beck книжки пишет. А там и Continues Integration на горизонте маячит. И здорово было бы научить CI сервер это все делать самостоятельно. Одним словом, есть уйма задач.
Раз есть задачи, то есть и решения. Думаю, большинство разработчиков хоть раз, но сталкивались с Ant. Очень многие используют Maven. Есть другие, не такие распространённые инструменты: GAnt, Buildr, и др. Каждый из них обладает набором своих плюсов и минусов, но сегодня я хочу представить вам кое-что новенькое. Gradle.
Gradle пытается объединить в себе все плюсы Ant, Maven и Ivy. И представить то, что получилось, с помощью Groovy. Теперь вместо того, чтобы скрещивать Batch-скрипты, java и xml-файлы конфигурации, можно просто написать несколько строчек кода на диалекте Groovy и радоваться жизни. Диалект специально разработан для описания сборки, тестирования, развертывания, экспорта и любых других действий над проектом, которые только могут прийти вам в голову.
Т.к. Gradle работает в запущеной JVM, он успешно использует библиотеки задач Ant, средства управления зависимостями Apache Ivy и другие существующие инструменты (TestNG, JUnit, Surefire, Emma, и т.п.). В принципе, несложно интегрировать в сборку любой инструмент, работающий в jvm. В придачу ко всему, диалект Groovy, используемый в Gradle, дает вам полную свободу действий. Совсем полную. Хотите условные выражения? Пожалуйста! Хотите циклы? Милости просим! Читать и писать файлы? Работать с сетью? Генерировать на лету собственные задачи? Все что угодно! И на нормальном, человеческом языке программирования, а не жутковатыми xml-конструкциями.
Интересная возможность: соответствующим образом настроенный Gradle-проект можно собрать на машине пользователя, на которой Gradle не установлен. Все, что требуется, — положить в дерево исходников 4 файла (которые Gradle сгенерирует для вас): 2 исполняемых для Win/*nix, 1 файл настроек и маленький jar. Всего на ~20Kb. После этого проект можно собрать на любой машине, где есть доступ к Сети. Скрипт сам позаботится о скачивании правильной версии Gradle, о настройке и запуске сборки.
Миграция на Gradle очень проста. Например, сборку maven2 можно преобразовать в сборку Gradle автоматически (с сохранением настроенных зависимостей, артефактов, версий и подпроектов). И миграция уже началась. Сейчас этот инструмент используют проекты: Grails, Spring Security, Hibernate Core и даже GAnt (честно, GAnt собирается при помощи Gradle!).
Похвалили, теперь нужно продемонстрировать в действии.
Для начала создадим шаблонный java проект, чтобы продемонстрировать использование 'build-by-convention'. А затем попытаемся его немного видоизменить, добавив в структуру файлов набор автоматизированных интеграционных тестов, чтобы показать, насколько большую свободу в использовании 'convention' предоставляет Gradle. В примере преднамеренно не упоминаются файлы исходников, т.к. не в них смысл.
Пусть у нас есть структура проекта (вы видели ее уже тысячу раз):
Создаем в каталоге project пустой файл build.gradle. Записываем туда одну строчку:
Запускаем команду gradle build и получаем:
В консоли видим выполнение последовательности задач (Gradle Tasks), которые являются близким аналогом Ant Targets. В каталоге /project/build можно найти скомпилированные классы (в т.ч., аккуратно упакованные в jar), отчеты по выполнению тестов и другие результаты сборки.
Все это пока что ничем не отличается от того, к чему привыкли многочисленные участники проектов с использованием Maven. Те же каталоги, такой же pom.xml (только называется build.gradle). Но не спешите расчехлять тухлые помидоры и позвольте продемонстрировать одну из интересных возможностей Gradle.
Добавим интеграционные тесты. Создадим для них отдельную ветку каталогов:
и добавим в build.gradle следующий код:
В терминах Gradle source set — набор файлов и ресурсов, которые должны компилироваться и запускаться вместе. Приведенный выше фрагмент определяет новый source set с именем integTest. По умолчанию, исходники и ресурсы будут браться из
Будут автоматически сформированы три новых task'a: компиляция (compileIntegTestJava), обработка ресурсов (processIntegTestResource) и объединяющая их integTestClasses. Попробуем запустить:
Ценой двух строчек мы получили 3 новых task'a и добавили в сборку проекта новый каталог. Но как только дело дойдет до написания этих тестов, мы обнаружим, что нам нужны все зависимости основного проекта, да еще и скомпилированные классы в придачу.
Не вопрос, пишем:
Блок
Строка
Наверное, при сборке нам пригодится JUnit. И неплохо было бы еще и запустить эти тесты. Опишем еще одну конфигурацию зависимостей.
Блок
Задачу для запуска тестов всё-таки пришлось написать. Впрочем, это было несложно: достаточно указать, откуда брать тесты и с каким classpath их запускать, т.е. «что». «Как» Gradle определит самостоятельно.
Теперь мы готовы:
Обратите внимание, что Gradle обработал зависимость
В итоге, нам удалось собрать стандартную конфигурацию проекта, совершенно безболезненно расширить её и описать схему зависимостей между подзадачами с использованием наследования. Все это с помощью простого и вполне читабельного языка.
О чем еще не сказано ни слова: о работе с task-ами, об инкрементальной сборке, о работе с подпроектами, о работе с Maven репозиториями, об интеграции с Ant, о стандартных plugin-ах, о init-scripts и многом-многом другом. Но об этом, быть может, в других статьях.
Раз есть задачи, то есть и решения. Думаю, большинство разработчиков хоть раз, но сталкивались с Ant. Очень многие используют Maven. Есть другие, не такие распространённые инструменты: GAnt, Buildr, и др. Каждый из них обладает набором своих плюсов и минусов, но сегодня я хочу представить вам кое-что новенькое. Gradle.
Gradle пытается объединить в себе все плюсы Ant, Maven и Ivy. И представить то, что получилось, с помощью Groovy. Теперь вместо того, чтобы скрещивать Batch-скрипты, java и xml-файлы конфигурации, можно просто написать несколько строчек кода на диалекте Groovy и радоваться жизни. Диалект специально разработан для описания сборки, тестирования, развертывания, экспорта и любых других действий над проектом, которые только могут прийти вам в голову.
Т.к. Gradle работает в запущеной JVM, он успешно использует библиотеки задач Ant, средства управления зависимостями Apache Ivy и другие существующие инструменты (TestNG, JUnit, Surefire, Emma, и т.п.). В принципе, несложно интегрировать в сборку любой инструмент, работающий в jvm. В придачу ко всему, диалект Groovy, используемый в Gradle, дает вам полную свободу действий. Совсем полную. Хотите условные выражения? Пожалуйста! Хотите циклы? Милости просим! Читать и писать файлы? Работать с сетью? Генерировать на лету собственные задачи? Все что угодно! И на нормальном, человеческом языке программирования, а не жутковатыми xml-конструкциями.
Интересная возможность: соответствующим образом настроенный Gradle-проект можно собрать на машине пользователя, на которой Gradle не установлен. Все, что требуется, — положить в дерево исходников 4 файла (которые Gradle сгенерирует для вас): 2 исполняемых для Win/*nix, 1 файл настроек и маленький jar. Всего на ~20Kb. После этого проект можно собрать на любой машине, где есть доступ к Сети. Скрипт сам позаботится о скачивании правильной версии Gradle, о настройке и запуске сборки.
Миграция на Gradle очень проста. Например, сборку maven2 можно преобразовать в сборку Gradle автоматически (с сохранением настроенных зависимостей, артефактов, версий и подпроектов). И миграция уже началась. Сейчас этот инструмент используют проекты: Grails, Spring Security, Hibernate Core и даже GAnt (честно, GAnt собирается при помощи Gradle!).
Похвалили, теперь нужно продемонстрировать в действии.
Для начала создадим шаблонный java проект, чтобы продемонстрировать использование 'build-by-convention'. А затем попытаемся его немного видоизменить, добавив в структуру файлов набор автоматизированных интеграционных тестов, чтобы показать, насколько большую свободу в использовании 'convention' предоставляет Gradle. В примере преднамеренно не упоминаются файлы исходников, т.к. не в них смысл.
Пусть у нас есть структура проекта (вы видели ее уже тысячу раз):
/project /src /main /java /resources /test /java /resources
Создаем в каталоге project пустой файл build.gradle. Записываем туда одну строчку:
apply plugin:'java'
Запускаем команду gradle build и получаем:
>gradle build
:compileJava
:processResources
:classes
:jar
:assemble
:compileTestJava
:processTestResources
:testClasses
:test
:check
:build
BUILD SUCCESSFUL
Total time: 4.116 secs
В консоли видим выполнение последовательности задач (Gradle Tasks), которые являются близким аналогом Ant Targets. В каталоге /project/build можно найти скомпилированные классы (в т.ч., аккуратно упакованные в jar), отчеты по выполнению тестов и другие результаты сборки.
Все это пока что ничем не отличается от того, к чему привыкли многочисленные участники проектов с использованием Maven. Те же каталоги, такой же pom.xml (только называется build.gradle). Но не спешите расчехлять тухлые помидоры и позвольте продемонстрировать одну из интересных возможностей Gradle.
Добавим интеграционные тесты. Создадим для них отдельную ветку каталогов:
/project /src /main /test /integTest /java /resources
и добавим в build.gradle следующий код:
sourceSets {
integTest
}
В терминах Gradle source set — набор файлов и ресурсов, которые должны компилироваться и запускаться вместе. Приведенный выше фрагмент определяет новый source set с именем integTest. По умолчанию, исходники и ресурсы будут браться из
/project/src/<имя source set>/java
и /project/src/<имя source set>/resources
соответственно. Java Plugin, который мы подключили в начале, задает два стандартных source set: main
и test
.Будут автоматически сформированы три новых task'a: компиляция (compileIntegTestJava), обработка ресурсов (processIntegTestResource) и объединяющая их integTestClasses. Попробуем запустить:
>gradle integTestClasses
:compileIntegTestJava
:processIntegTestResources
:integTestClasses
BUILD SUCCESSFUL
Total time: 1.675 secs
Ценой двух строчек мы получили 3 новых task'a и добавили в сборку проекта новый каталог. Но как только дело дойдет до написания этих тестов, мы обнаружим, что нам нужны все зависимости основного проекта, да еще и скомпилированные классы в придачу.
Не вопрос, пишем:
configurations {
integTestCompile { extendsFrom compile }
}
sourceSets {
integTest{
compileClasspath = sourceSets.main.classes + configurations.integTestCompile
}
}
Блок
Congfigurations
описывает конфигурации зависимостей. Каждая конфигурация может объединять maven артефакты, наборы локальных файлов и др. Новые конфигурации могут наследоваться от существующих. В данном случае, мы наследуем конфигурацию зависимостей для компиляции интеграционных тестов от конфигурации compile
. Эта конфигурация — стандартная (заданная plugin) для компиляции main
Строка
sourceSets.main.classes + configurations.integTestCompile
обозначает объединение наборов файлов. main.classes
— каталог, где будут находиться *.class файлы, полученные при сборке main
.Наверное, при сборке нам пригодится JUnit. И неплохо было бы еще и запустить эти тесты. Опишем еще одну конфигурацию зависимостей.
configurations {
integTestCompile { extendsFrom compile }
integTestRuntime { extendsFrom integTestCompile, runtime }
}
repositories {
mavenCentral()
}
dependencies {
integTestCompile "junit:junit:4.8.1"
}
sourceSets {
integTest{
compileClasspath = sourceSets.main.classes + configurations.integTestCompile
runtimeClasspath = classes + sourceSets.main.classes + configurations.integTestRuntime
}
}
task integrationTest(type: Test) {
testClassesDir = sourceSets.integTest.classesDir
classpath = sourceSets.integTest.runtimeClasspath
}
Блок
Repositories
подключит нам maven central (и другие репозитории на наш выбор). Блок dependencies
добавит зависимость от артефакта к нашей конфигурации. runtimeClasspath = classes + sourceSets.main.classes + configurations.integTestRuntime
объединит файлы из integTest.classes
, main.classes
и integTestRuntime
.Задачу для запуска тестов всё-таки пришлось написать. Впрочем, это было несложно: достаточно указать, откуда брать тесты и с каким classpath их запускать, т.е. «что». «Как» Gradle определит самостоятельно.
Теперь мы готовы:
>gradle clean integrationTest
:clean
:compileJava
:processResources
:classes
:compileIntegTestJava
:processIntegTestResources UP-TO-DATE
:integTestClasses
:integrationTest
BUILD SUCCESSFUL
Total time: 4.195 secs
Обратите внимание, что Gradle обработал зависимость
integTest.compileClasspath
от main.classes
и собрал source set main
прежде, чем собирать интеграционные тесты!В итоге, нам удалось собрать стандартную конфигурацию проекта, совершенно безболезненно расширить её и описать схему зависимостей между подзадачами с использованием наследования. Все это с помощью простого и вполне читабельного языка.
О чем еще не сказано ни слова: о работе с task-ами, об инкрементальной сборке, о работе с подпроектами, о работе с Maven репозиториями, об интеграции с Ant, о стандартных plugin-ах, о init-scripts и многом-многом другом. Но об этом, быть может, в других статьях.