Gradle: Tasks Are Code

    В предыдущем топике я постарался вкратце рассказать, что же такое Gradle и на каких идеях он построен. Также была освещена концепция Source Sets и функциональность, с ней связанная.

    Теперь я хотел бы рассказать о том, чем 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 развязывает вам руки и позволяет самостоятельно решать, как вы хотите выполнить вашу задачу.

    Продолжение следует.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 48

      0
      То есть внутри определения задачи может находиться произвольный код на Groovy. И сами задачи — полноценный объект Groovy. А это значит, что у них есть свойства и методы, которые позволяют ими управлять. Например, добавляя новые действия.

      Супер!

      Автору спасибо.
        0
        Да, это весьма существенное новшество — полноценный язык для сборки.
        +3
        Я считаю, что за подобным подходом есть будущее сборок. XML описание сборки, которая чуточку нетревиальнее чем есть в документации — это какой то кошмар. Говорю имея за плечами громадный опыт msbuild и nant
          0
          Мне кажется наличие таких мощных инструментов для многих сыграет важную роль в определении конечного jvm based функционального языка. Получается что сейчас groovy начинает потихоньку перевешивать scala. Посмотрим как дальше пойдет.
            0
            амм… начинает? потихоньку? я скажу только одно слово: «Grails»… так уж вышло, что в наше время успех ЯП определяется во многом наличием крутого веб-фрэймворка… И что-то я давненько не встречал упоминаний Lift вообще-то…
              0
              вы это о чем?
                0
                об «определении конечного jvm based функционального языка», не? в том смысле, что groovy/grails как-то уделывает scala/lift… -> приток новичков в комьюнити groovy -> дополнительный стимул для развития языка… далее по кругу. это на самом деле был оффтопик… вообще-то.
                  0
                  а откуда дровишки? почем значете что уделывает?
                    0
                    отец, слышишь, рубит, а Я отвожу… отец рубит, понимаете? на самом деле, исключительно субъективно. в смысле упоминаний больше попадается на глаза. шум, короче говоря. no offence… я сам как-бы нуб в java мире, и мне не хочется делать ставку на язык, который в перспективе зачахнет, или станет уделом небольшой группы снобов… опять же, если я не в теме, или не там смотрю, или утрирую, например и развожу панику, ткните и объясните. полезно будет. и не только мне, я думаю.
                      0
                      и ps ещё: я даже про clojure и то больше вижу упоминаний в последнее время, чем про scala…
              0
              ээ… начинает потихоньку? groovy уже давно стал де-факто «внутренним скриптовым языком» во многих java-проектах — да хоть с том же андроиде.
                0
                ну или скажем first-class bundled поддержка groovy в IDEA (даже опенсорсной CE) и NetBeans
                • UFO just landed and posted this here
                    0
                    под андроидом я хотел сказать Android SDK, для layoutopt он используется
                  +1
                  Groovy совсем не конкурент Scala. А вот их комбинация как скриптового и основного языка — это уже интересно.
                    0
                    groovy++ — вполне себе потенциальный и очень сильный конкурент
                      0
                      Частично согласен, но это пока не очень популярная ветка и так не очень популярного языка. ;)
                        0
                        на фоне скалы это уже достаточно много ;)
                          0
                          :) Scala имеет непопулярность лишь первого порядка.
                      0
                      а расскажите какие ниши они занимают?
                        +1
                        Scala — это естественный заменитель Java + мощный язык для написания действительно сложных вещей.

                        Groovy же очень хорош для разных скриптов (в т.ч. встраиваемых) и темплейтов. Много удобных расширений по сравнению с JDK для работы с файлами, строками, XML.
                    0
                    еще еще еще…
                      –2
                      Концепция хорошая. Непонятно только, почему язык — не Java. Забабахать хорошую модель классов, читабельность будет не сильно хуже.

                      Если речь идет о скриптах, то уже давно есть интерпретатор Java — BeanShell
                        0
                        на groovy можно писать синтасисом java'ы, но зачем?
                          0
                          Одна из ключевых особенностей Gradle — предоставление пользователю большой свободы в расширении Convention-over-Configuration. Возможности по расширению у языков со статической типизацией априори на много меньше, чем у языков с динамической типизацией.

                          Т.е., Gradle на Java выглядел бы очень и очень многословно. А раз многословно, то появляются проблемы с решением задач и дальнейшим их сопровождением.
                          0
                          gradle клёвый, да.
                          жаль пока интеграции с IDEA/TeamCity нет и с андроидом я его не смог подружить :(
                            0
                            Интеграция с IDEA висит в YouTrack и за нее активно голосуют, присоединяйтесь.

                            Что касается поддержки в Teamcity, то она появилась в последнем EAP — см. release notes. Думаю, к выпуску 6.0 Gradle будет поддерживаться вполне прилично.
                              0
                              и про голосование, и про EAP я, конечно же, знаю, но в 10ке этого не будет точно, да и когда ещё мы на 6.0 перейдём…
                                0
                                Эх, искренне хотел вас порадовать :)
                                  0
                                  на самом деле пока что меня останавливает только отсутствие поддержки андроида
                                    0
                                    Хотел спросить, чем плох вот этот плагин: https://github.com/jvoegele/gradle-android-plugin/wiki/
                                      0
                                      уже не работает, увы :(
                                      см. issues
                            0
                            Обратите внимание на то, что многие задачи были отмечены UP-TO-DATE.


                            Главное что бы это все нормально работало, а не как в Maven, на разросшемся проекте после внесения правок в исходники maven install собирает какую-то кашу из старых/новых сырцов, приходится делать maven clean install. Но это так оффтоп.

                            На самом деле хотелось бы понять насколько сложен переход с maven, и даст ли он какие-то плоды. Например maven иногда глючит с депсами, в офлайне вешает всю систему сборки эклипса на глухо, ломает периодически debug (эклипс не может найти сырцы проектов). И это только мои личные проблемы с maven на самом деле их больше.

                            И тем не менее я не ощущаю в чем профит от Gradle, я конечно признаю гибкое конфигурирование проекта это круто, но где это применять людям которые не разрабатывают hibernate-core?

                            Как в грейдле реализовано разрешение конфликтов? Если один проект тянет хибернейт 3.1 а другой 3.5 а третий 2.0, что будет? Где спецификация?

                            Понимаете совсем не хочется менять шило на мыло.
                              0
                              Еще есть такая порочная настройка в Eclipse: Maven -> Resolve workspace dependencies. Для Gradle есть похожий аналог, или нужно будет депы инсталить в репозитарий после каждого изменения исходного кода?
                                0
                                Ни в коем случае не призываю бросить все и мигрировать на Gradle. Это слишком.

                                Инкрементальная сборка действительно нормально работает. Каши из старых и новых сырцов пока не встречалась.

                                Переход с maven2 возможен. Вплоть до скриптов автоматического преобразования. Решит ли это все ваши проблемы? Сомневаюсь. Но точно даст возможность попытаться их решить своими собственными руками, а не рытьем документации maven2

                                Про разрешение конфликтов в зависимостях. Управление зависимостями в Gradle осуществляется средствами Apache Ivy — вот спецификации. Что касательно преимуществ перед Maven2 — то вот небольшое сравнение.

                                Решать вам.
                                  0
                                  Про Gradle и Maven2: habrahabr.ru/blogs/java/106717/
                                  0
                                  К Maven 3 тоже добавили поддержку JVM языков: polyglot.sonatype.org/index.html
                                    0
                                    Хочу сделать запускаемый jar-файл, требуемые бибилиотеки к которому прописаны в манифесте через classpath. Как-то так:

                                    Class-Path: libs/xxx.jar…

                                    Как это правильно сделать?
                                      0
                                      Если грубо, то достаточно воспользоваться примером из статьи и заменить
                                       attributes demo: 'habr.ru'
                                      

                                      На набор нужных вам атрибутов

                                        0
                                        Если не лень провести 2-3 эксперимента, то можно попробовать сформировать значние атрибута class-path на основе зависимостей в sourceSet.main
                                          0
                                          Решение нашел, забыл написать об этом.
                                          Думаю, что пригодится кому-либо:

                                          gradle.taskGraph.whenReady { taskGraph ->

                                          def classpath = sourceSets.main.compileClasspath.collect{
                                          jarDependenciesLib + File.separator + it.name
                                          }.join(', ')
                                          jar.manifest.attributes 'Main-Class': mainclass, 'Class-Path': classpath

                                          }
                                            0
                                            началось… всё, хана концепту…
                                              0
                                              Чего вы от меня ожидали? С Groovy почти не знаком, не говоря уже о Gradle.
                                              Дорогу осилит идущий… это я к тому, что со временем получится и более грамотный скрипт.
                                                0
                                                Ну уж, «хана». Человек попробовал. И получил требуемый результат.
                                                Возможно, это стоило бы сделать подругому. Я бы, к примеру, перенёс этот код внутрь task'a «jar». А вы?
                                                  0
                                                  Я не об этом. Пример чётко показывает, что люди начинают сразу же колошматить свои «анто-велосипеды». Главная мысль мавена ведь: 1 проект -> 1 артифакт + дока + тесты + конфиг, то есть — берёшь человека на работу, а ему знакома уже эта конструкция. Что мы видем из выше изложенного: берём человека на работу и начинается почему-так-и-почему-не-этак-и-как-это-работает-вообще (не Gradle, а build проекта).

                                                  пс: 2 раза толстой книгой по рукам! +))
                                        0
                                        Блин, какой кошмар! Полная свобода действий — это конечно же круто, но где в проекте вы видели только спецов? Это ведь будет выглядеть так же, как «чёрт-его-знает-какие-ант-скрипты-в-которых-уже-не-понять-что-к-чему». То есть, на эту штуку нужно сажать архитектора и билд-менеджера, а другим просто лупить толстой книгой по рукам!
                                          0
                                          В принципе да, так и есть. Частично от этого предохраняет активное использование Convention-over-Configuration. Частично — дисциплина :)

                                          На maven тоже можно такого наворотить — сам черт не разберёт :)
                                            0
                                            Думаю, что зря драматизируете.
                                            Чем подход к написанию билд-скриптов отличается от обычного программирования? Никто не отменял простейших принципов разумности, понятности, простоты кода.

                                            Из всего можно сделать говно.

                                          Only users with full accounts can post comments. Log in, please.