В предыдущем топике я постарался вкратце рассказать, что же такое Gradle и на каких идеях он построен. Также была освещена концепция Source Sets и функциональность, с ней связанная.
Теперь я хотел бы рассказать о том, чем Gradle зацепил лично меня. Речь пойдёт о способах работы с задачами. Задача в Gradle — близкий аналог Ant Target. И, чтобы не путаться в терминах, под задачей (или task) далее по тексту всегда будет подразумеваться Gradle Task. Если речь будет идти о сущности из Ant, то это будет указано явно: Ant task.
Так вот, задачи в Gradle создаются при помощи специального dsl (domain specific language) на основе Groovy. И возможности, которые этот dsl предоставлет, на мой взгляд, почти безграничны в сравнении с ant или maven.
Начнем, пожалуй, с традиционного для программистов «Hello World». Пусть у нас есть пустой файл
Запускаем
Bingo! Но не впечатляет. Давайте попробуем кое-что еще:
Запускаем
То есть внутри определения задачи может находиться произвольный код на Groovy. И сами задачи — полноценный объект Groovy. А это значит, что у них есть свойства и методы, которые позволяют ими управлять. Например, добавляя новые действия.
Давайте посмотрим на более интересный пример.
Пусть у нас есть небольшой java проект. Вот его build.gradle:
и структура каталогов
Ничего сложного: версия, репозиторий Maven Central, две зависимости для компиляции, несколько packages. При запуске команды
В каталоге
Но с течением времени требования, как известно, меняются. Изменим требования в примере. Пусть нам понадобилось собрать Spring-related код в отдельные архивы, собрать отдельный jar с классами unit-тестов, и еще один jar с исходниками. В Gradle это решается следующим образом:
Запускаем
И видим:
Что произошло:
Интересно, а как это сделать в Maven?
Но жизнь не стоит на месте, и наши требования продолжают меняться. В одно прекрасное утро, Spring Foundation потребовали добавлять в манифест каждого jar, который имеет отношение к Spring и публикуется на Хабре, атрибут demo со значением habr.ru. Звучит странно, но нам все равно нужно их реализовывать. Добавим:
Запустим:
Обратите внимание на то, что многие задачи были отмечены UP-TO-DATE. Это еще одна изюминка Gradle — инкрементальная сборка. Но о ней в другой раз. Теперь если не полениться и посмотреть на содержимое манифестов архивов, то в относящихся к Spring можно обнаружить нужную строчку
Bingo!
Но требования Spring Foundation продолжают меняться. И теперь уже нужно рядом с каждым jar положить его контрольную сумму :) Лицензионная чистота — дело нешуточное, и мы вынуждены подчиниться. К сожалению, в Gradle нет встроенной поддержки операции вычисления MD5. Зато она есть в Ant. Ну так давайте ее и используем. Изменим последний фрагмент следующим образом:
И соберем на этот раз только злосчастные spring-related архивы:
Посмотрим, что получилось.
Выполнение задачи потребовало минимальных усилий.
Возможно, использованные в примере требования кажутся искусственными и немного наивными, но задачи, которые приходится решать в реальности, удивительно разнообразны.
В этой статье нам удалось с помощью небольшого объема Groovy-кода подстроиться под изменения требований и выполнить несколько задач, с которыми трудно было бы справиться средствами Ant или Maven. Использование гибкого языка программирования вместо xml развязывает вам руки и позволяет самостоятельно решать, как вы хотите выполнить вашу задачу.
Продолжение следует.
Теперь я хотел бы рассказать о том, чем Gradle зацепил лично меня. Речь пойдёт о способах работы с задачами. Задача в Gradle — близкий аналог Ant Target. И, чтобы не путаться в терминах, под задачей (или task) далее по тексту всегда будет подразумеваться Gradle Task. Если речь будет идти о сущности из Ant, то это будет указано явно: Ant task.
Так вот, задачи в Gradle создаются при помощи специального dsl (domain specific language) на основе Groovy. И возможности, которые этот dsl предоставлет, на мой взгляд, почти безграничны в сравнении с ant или maven.
Начнем, пожалуй, с традиционного для программистов «Hello World». Пусть у нас есть пустой файл
build.gradle
. Пишем:task hello << {
println 'Hello world!'
}
Запускаем
>gradle -q hello
Hello world!
Bingo! Но не впечатляет. Давайте попробуем кое-что еще:
task upper << {
String someString = 'mY_nAmE'
println "Original: " + someString
println "Upper case: " + someString.toUpperCase()
4.times { print "$it " }
}
Запускаем
>gradle -q upper
Original: mY_nAmE
Upper case: MY_NAME
0 1 2 3
То есть внутри определения задачи может находиться произвольный код на Groovy. И сами задачи — полноценный объект Groovy. А это значит, что у них есть свойства и методы, которые позволяют ими управлять. Например, добавляя новые действия.
Давайте посмотрим на более интересный пример.
Пусть у нас есть небольшой java проект. Вот его build.gradle:
apply plugin: 'java'
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
testCompile group: 'junit', name: 'junit', version: '4.7'
}
и структура каталогов
/projectAlpha /src /test /main /java /my /own /code /spring /db /plugin /auth
Ничего сложного: версия, репозиторий Maven Central, две зависимости для компиляции, несколько packages. При запуске команды
>gradle build
В каталоге
projectAlpha/build/libs
будет собран архив projectAlpha-1.0.jar
. Все в полном соответствии с соглашениями. Maven сделал бы все точно так же.Но с течением времени требования, как известно, меняются. Изменим требования в примере. Пусть нам понадобилось собрать Spring-related код в отдельные архивы, собрать отдельный jar с классами unit-тестов, и еще один jar с исходниками. В Gradle это решается следующим образом:
task sourcesJar(type: Jar) {
appendix = 'sources'
from sourceSets.main.allJava
}
task testJar(type: Jar) {
appendix = 'test'
from sourceSets.test.classes
}
jar {
exclude 'my/spring/**'
}
task springDbJar(type: Jar) {
appendix = 'spring-db'
from sourceSets.main.classes
include 'my/spring/db/**'
}
task springAuthJar(type: Jar) {
appendix = 'spring-auth'
from sourceSets.main.classes
include 'my/spring/auth/**'
}
task springPluginJar(type: Jar) {
appendix = 'spring-plugin'
from sourceSets.main.classes
include 'my/spring/plugin/**'
}
Запускаем
>gradle assemble
И видим:
projectAlpha>dir /b build\libs
projectAlpha-1.0.jar
projectAlpha-sources-1.0.jar
projectAlpha-spring-auth-1.0.jar
projectAlpha-spring-db-1.0.jar
projectAlpha-spring-plugin-1.0.jar
projectAlpha-test-1.0.jar
Что произошло:
- Мы определили две новых задачи с типом Jar:
sourcesJar
иtestJar
. Для описания содержимого архива используются уже знакомые вам source Sets. Еще задается атрибутappendix
, который, как нетрудно догадаться, будет включен в имя архива после версии. - Мы изменили заданную по умолчанию задачу
jar
(она определяется в plugin'е) таким образом, чтобы в основной архив не попадали классы из определённых packages. - Мы определили еще 3 задачи для сборки трёх отдельных архивов с модулями для Spring.При вызове задачи
assemble
система сборки самостоятельно выбрала все задачи, формирующие архивы (Zip, Jar..), и выполнила их. Предварительно обработав зависимости от source sets и скомпилировав нужные классы, как и в предыдущей статье.
Интересно, а как это сделать в Maven?
Но жизнь не стоит на месте, и наши требования продолжают меняться. В одно прекрасное утро, Spring Foundation потребовали добавлять в манифест каждого jar, который имеет отношение к Spring и публикуется на Хабре, атрибут demo со значением habr.ru. Звучит странно, но нам все равно нужно их реализовывать. Добавим:
tasks.withType(Jar).matching { task -> task.archiveName.contains('spring') }.allObjects { task ->
task.manifest {
attributes demo: 'habr.ru'
}
}
Запустим:
projectAlpha>gradle assemble
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:sourcesJar UP-TO-DATE
:springAuthJar
:springDbJar
:springPluginJar
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:testJar UP-TO-DATE
:assemble
Обратите внимание на то, что многие задачи были отмечены UP-TO-DATE. Это еще одна изюминка Gradle — инкрементальная сборка. Но о ней в другой раз. Теперь если не полениться и посмотреть на содержимое манифестов архивов, то в относящихся к Spring можно обнаружить нужную строчку
Manifest-Version: 1.0
demo: habr.ru
Bingo!
Но требования Spring Foundation продолжают меняться. И теперь уже нужно рядом с каждым jar положить его контрольную сумму :) Лицензионная чистота — дело нешуточное, и мы вынуждены подчиниться. К сожалению, в Gradle нет встроенной поддержки операции вычисления MD5. Зато она есть в Ant. Ну так давайте ее и используем. Изменим последний фрагмент следующим образом:
def allSpringJars = tasks.withType(Jar).matching { task -> task.archiveName.contains('spring') }
allSpringJars.allObjects { task ->
configure(task) {
manifest {
attributes demo: 'habr.ru'
}
doLast {
ant.checksum(file: archivePath, todir: archivePath.parentFile)
}
}
}
task springJars(dependsOn: allSpringJars)
И соберем на этот раз только злосчастные spring-related архивы:
projectAlpha>gradle clean springJars
:clean
:compileJava
:processResources UP-TO-DATE
:classes
:springAuthJar
:springDbJar
:springPluginJar
:springJars
BUILD SUCCESSFUL
Total time: 5.015 secs
Посмотрим, что получилось.
c:\Work\Gradle\tasksAreCode\projectAlpha>dir /b build\libs
projectAlpha-spring-auth-1.0.jar
projectAlpha-spring-auth-1.0.jar.MD5
projectAlpha-spring-db-1.0.jar
projectAlpha-spring-db-1.0.jar.MD5
projectAlpha-spring-plugin-1.0.jar
projectAlpha-spring-plugin-1.0.jar.MD5
Выполнение задачи потребовало минимальных усилий.
Возможно, использованные в примере требования кажутся искусственными и немного наивными, но задачи, которые приходится решать в реальности, удивительно разнообразны.
В этой статье нам удалось с помощью небольшого объема Groovy-кода подстроиться под изменения требований и выполнить несколько задач, с которыми трудно было бы справиться средствами Ant или Maven. Использование гибкого языка программирования вместо xml развязывает вам руки и позволяет самостоятельно решать, как вы хотите выполнить вашу задачу.
Продолжение следует.