Gradle: Better Way To Build

    Ни один проект с использованием платформы 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
       /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 и многом-многом другом. Но об этом, быть может, в других статьях.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 14
    • 0
      очень интересно и очень познавательно, спасибо!
      Но у меня есть один вопрос, как этой штукой собрать веб проэкт у которого все зависимые jar лежат в папке проекта web/WEB-INF/lib?
      • +1
        Вам поможет plugin под названием War

        Он добавит обработку каталога src/main/webapp, содержимое которого будет скопированно и упаковано в war файл.

        в каталог WEB-INF/lib будут автоматичкски скопированы все зависимости main.runtumeClasspath

      • 0
        Отличная статья! Ждем продолжения.
        • 0
          Продолжение обязательно будет.
        • 0
          Можно всё-таки сравнение с maven одних и тех же действий?
          • 0
            Такое прямое сравнение заняло бы очень много времени. Потому что в Maven слишком много действий :)

            В качестве дискуссии Maven-Gradle готовится к публикации перевод блог-поста Why Gradle, посвященного переводу Hibernate Core с maven2 на gradle
            • 0
              Еще по поводу сравнения вам может быть интересен вот этот перевод: habrahabr.ru/blogs/java/106717/
            • 0
              Спасибо автору за интересный обзор и представление нового build-management тула.
              Инструмент действительно гибкий и в тоже время очень мощный.
              + избавление от xml настроек
              больше CoC.
              • +1
                Если вам понравилось избавление от xml, то не пройдите мимо следующей статьи. Оттуда вы узнаете, на сколько это может быть удобно.
                • 0
                  Давайте, давайте. Не затягивайте.

                  Уже попробовал, очень понравился принцип работы — простота, прозрачность и при этом в моих руках остаются мощные возможности.

                  До этого использовал Maven, но xml-синтаксис трудно воспринимать.
            • 0
              В android-studio проект собирается при параметрах
              JDK location ...\1.8…
              compileSdkVersion 19
              buildToolsVersion «25.0.0»
              minSdkVersion 15
              targetSdkVersion 25
              А вручную приходится
              JAVA_HOME=c:\Progra~1\Java\jdk1.7.0_79
              BUILD_TOOLS=%ANDROID_HOME%\build-tools\23.0.1
              ANDROID_JAR=%ANDROID_HOME%/platforms/android-19/android.jar
              иначе ошибки несовместимости.
              Что такого делает gradle не пойму.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.