Глава 22. Жизненный цикл сборки.

Ранее мы говорили, что ядро Gradle - язык для программирования, основанного на зависимостях. В терминах Gradle это означает, что вы можете определять задачи и зависимости между ними. Gradle гарантирует, что задачи выполняются в порядки, опредляемом их зависимостями и, что каждая задача выполняется только один раз. Эти задачи формируют направляенный ациклический граф. Есть инструменты для сборки, которые строят такой граф зависмостей во время выполнения задач. Gradle строит завершенный граф зависимостей до выполнения первой задачи. Это находится в самом сердце Gradle и делаем множество вещей возможными, которые в противном случае были бы невозможны.

Ваши сборочные скрипты конфигурируют этот граф. Таким образом, они, строго говоря, сборочные конфигурационные скрипты.

22.1. Фазы сборки.

У сборки Gradle есть три отдельных фазы.

Инициализация

Gradle поддерживает одно и многопроектные сборки. Во время фазы инициализации, он какие проекты принимают участие в сборке и создает экземпляр Project для каждого.

Конфигурация

Во время этой фазы, объекты проектов конфигурируются. Сборочные скрипты всех проектов, принимающих участие в сборке, выполняются. Gradle 1.4 представляет инкубационную функцию по запросу, называемую конфигурация по требованию. В этом режиме, Gradle настраивает только важные проекты (смотрите секцию под названием "Конфигурация по требованию").

Выполнение

Gradle вычисляет подмножество задач, созданных и настроенных во время фазы конфигурации, для выполнения. Подмножество вычисляется по именам задач, переданным в качестве аргуменов команде gradle, и текущей папке. Затем Gradle выполняет каждую из выбранных задач.

22.2. Файл настроек.

Наряду с файлами сборочных скриптов, Gradle определяет файл настроек. Файл настроек вычисляет с помощью соглашения по именам. По умолчанию, имя этого файла - settings.gradle. Далее в этой главе, будет объясняться как Gradle ищет файл настроек.

Файл настроек выполняется во время фазы инициализации. В многопроектной сборке файл settings.gradle должен находится в корневом проекте иерархии. Это требуется, потому что в файле настроек определяется какие проекты принимают участие в многопроектной сборке (смотрите Главу 26 Многопроектные сборки). Для однопроектной сборки файл настроек необязателен. Наряду с определением включенных проектов, вам может понадобиться добавить библиотеки в путь к классам вашего сборочного скрипта (смотрите главу 43 Организация логики сборки). Давайте немного проанализируем однопроектную сборку:

Пример 22.1. Однопроектная сборка

settings.gradle

println 'This is executed during the initialization phase.'
	  

build.gradle

println 'This is executed during the configuration phase.'

task configured {
    println 'This is also executed during the configuration phase.'
}

task test {
    doLast {
        println 'This is executed during the execution phase.'
    }
}

task testBoth {
    doFirst {
      println 'This is executed first during the execution phase.'
    }
    doLast {
      println 'This is executed last during the execution phase.'
    }
    println 'This is executed during the configuration phase as well.'
}
	  

Вывод команды gradle test testBoth

> gradle test testBoth
This is executed during the initialization phase.
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.
:test
This is executed during the execution phase.
:testBoth
This is executed first during the execution phase.
This is executed last during the execution phase.

BUILD SUCCESSFUL

Total time: 1 secs
	  

Для сборочного скрипта обращение к свойству или вызов метода передаются к объекту проекта. Так же, обращение к свойству или вызов метода в файле настроек передаются к объекту настроек. Чтобы узнать больше, взгляните на класс Settings в документации API.

22.3. Многопроектные сборки.

Многопроектная сборка - это сборка, в которой вы собираете больше одного проекта во время одиночного выполнения Gradle. Вы должны объявить проекты, принимающие участие в такой сборке, в файле настроек. Гораздо больше говорится о многопроектных сборках в главе посвященной этой тему (смотрите Главу 26 Многопроектные сборки).

22.3.1. Местонахождения проектов

Многопроектная сборка всегда представляется деревом с одиночным корнем. Каждый элементы в дереве представляет проект. У проекта есть путь, который указывает его местоположение в дереве многопроектной сборки. В большинстве случаев, путь проекта согласуется с физическим местонахождением проекта в файловой системе. Однако, такое поведение настраиваемое. Дерево проектов создается в файле settings.gradle. По умолчанию, предполагается, что местонахождение файла настроек, также является местонахождением корневого проекта. Но вы можете переопределить его расположение в файле настроек.

22.3.2. Построение дерева

В файле настроек вы можете использовать набор методов для построения дерева проектов. Иерархическое и плоское физическое расположение имеют особую поддержку.

Иерархическое расположение

Пример 22.2. Иерархическое расположение

settings.gradle

include 'project1', 'project2:child', 'project3:child1'
	  

Метод include принимает пути проектов в качестве аргументов. Путь проекта предполагается равным относительному физическому пути файловой системы. Например, путь 'services:api', по умолчанию, отображается в папку 'services/api'(относительно корневого проекта). Вам только необходимо указать листья дерева. Это означает, что включение пути 'services:hotels:api' в результате создаст три проекта: 'services', 'services:hotels' и 'services:hotels:api'.

Плоское расположение

Пример 22.3. Плоское расположение

settings.gradle

includeFlat 'project3', 'project4'
	  

Метод includeFlat примает имена папок в качестве аргумента. Эти папки должны существовать на одном уровне с папкой корневого проекта. Местоположения таких папкок рассматриваются как дочерние проекты корневого в многопроектном дереве.

22.3.3. Изменение элементов дерева проектов

Многопроектное дерево в файле настроек построено из так называемых описаний проектов. Вы можете изменить эти описания в файле настроек в любое время. Обратившись к описанию вы можете сделать:

Используя описание вы можете изменить имя, папку и сборочный файл проекта.

Пример 22.4. Изменение элементов дерева проектов

settings.gradle

println rootProject.name
println project(':projectA').name
	  

settings.gradle

rootProject.name = 'main'
project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
project(':projectA').buildFileName = 'projectA.gradle'
	  

Чтобы узнать больше, взгляните на класс ProjectDescriptor в документации API.

22.4. Инициализация.

Как Gradle узнает, одно или многопроектная это сборка? Если вы запустили многопроектную сборку из папки с файлом настроек, то это легко. Но Gradle позволяет вам выполнить сборку из любого подпроекта принимающего участие в сборке. Если вы выполняете Gradle внутри проекта, в котором нет файла settings.gradle, то он ищет этот файл следующим способом:

  • Он ищет в папки под названием master, у которой такой же уровень вложенности, что и у текущей.
  • Если он не найден, он обыскивает родительские папки.
  • Если все еще найден, сборка выполняется как однопроектная.
  • Если файл settings.gradle найден, Gradle проверяет, является ли текущий проект частью многопроектной иерархии, определенной в найденном файле. Если нет, сборка выполняется как однопроектная. В противном случае, как многопроектная.

Какова цель такого поведения? Gradle требуется определить является ли проект, в котором вы сейчас находитесь, подпроектом многопроектной сборки или нет. Конечно, если является, только он и проекты, от которых он зависит, собираются, но Gradle необходимо создать конфигурацию для всей многопроектной сборки (смотрите главу 26 Многопроектные сборки). Вы можете использовать опцию командной строки -u, чтобы сказать Gradle не искать родительской иерархии файл settings.gradle. Текущий проект затем всегда собирается как однопроектная сборка. Если текущий проект содержит файл settings.gradle, то опция -u не имеет значения. Такая сборка всегда выполняется как:

  • Однопроектная сборка, если файл settings.gradle не определяет многопроектную иерархию.
  • Многопроектная сборка, если файла settings.gradle определяет многопроектную иерархию.

Автоматический поиск файла settings.gradle работает для многопроектных сборок с физическим иерархическим или плоских распложением. Для плоского расположения вы должны дополнительно следовать соглашению об именах описанному выше ('master'). Gradle поддерживает произвольные физические расположения для многопроектной сборки, но для них необходимо выполнять сборку из папки, где находится файл настроек. Чтобы узнать как запускать частичные сборки из корня, смотрите Главу 26.4 Запуск задач по их абсолютному пути.

Gradle создает объект Project для каждого проекта, принимающего участие в сборке. Для многопроектной сборки это проекты, указанные в объекте Settings (плюс корневой проект). Каждый объект проекта имеет имя идентичное имени его вернеуровневой папки и каждый, за исключением корневого, имеет родительский проект. Любой проект может иметь дочерние проекты.

22.5. Конфигурация и выполнение однопроектной сборки.

Для однопроектной сборки последовательность выполняемых действий после фаз инициализации очень простая. Сборочный скрипт выполняется для объекта проекта, созданного во время фазы инициализации. Затем Gradle ищет задачи, чье имена идентичны переданным в качестве аргуменов командной строки. Если такие имена задач существуют, то они выполняются в отдельной сборке в том порядке, в котором вы их передали. Конфигурация и выполнение для многопроектных сборок обсуждаются в Главе 26 Многопроектные сборки.

22.6. Реагирование на жизненный цикл сборочного скрипта.

Вы сборочный скрипт может получать уведомления в процессе прохождения сборкой жизненного цикла. Эти уведомления в общем случае принимают две формы: вы можете или реализовать специфичный интерфейс слушателя, или можете предоставить замыкание, которое выполнится, когда придет уведомление. Примеры ниже используют замыкания. Для деталей об использовании интерфейсов слушателей, обратитесь к документации API.

22.6.1. Вычисление проекта

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

Ниже пример, который добавляет задачу test к каждому проекту, у которого свойство hasTests установлено в true.

Пример 22.5. Добавление задачи test каждому проекту, у которого установлено определенное свойство

build.gradle

allprojects {
    afterEvaluate { project ->
        if (project.hasTests) {
            println "Adding test task to $project"
            project.task('test') {
                doLast {
                    println "Running tests for $project"
                }
            }
        }
    }
}
	  

projectA.gradle

hasTests = true
	  

Вывод команды gradle -q test

> gradle -q test
Adding test task to project ':projectA'
Running tests for project ':projectA'
	  

В этом примере используется метод Project.afterEvaluate() для добавления замыкания, которое выполняется после вычисления проекта.

Также возможно получить уведомления, когда любой проект вычислен. Это пример выполняет небольшое пользовательское логгирование вычисления проекта. Обратите внимание, что уведомление afterProject получено, независимо от того, вычислился проект успешно или произошло исключение.

Пример 22.6. Уведомления

build.gradle

gradle.afterProject {project, projectState ->
    if (projectState.failure) {
        println "Evaluation of $project FAILED"
    } else {
        println "Evaluation of $project succeeded"
    }
}
	  

Вывод команды gradle -q test

> gradle -q test
Evaluation of root project 'buildProjectEvaluateEvents' succeeded
Evaluation of project ':projectA' succeeded
Evaluation of project ':projectB' FAILED
	  

Также вы можете добавить ProjectEvaluationListener к Gradle для получения этих событий.

22.6.2. Создание задачи

Вы можете получить уведомление немедленно после добавления задачи в проект. Это может быть использовано для установки некоторых значений по умолчанию или добавления поведения до того, как задача станет доступной в сборочном файле.

В следующем примере, устанавливается свойство srcDir каждой задачи, после ее создания.

Пример 22.7. Установка определенного свойства всех задач

build.gradle

tasks.whenTaskAdded { task ->
    task.ext.srcDir = 'src/main/java'
}

task a

println "source dir is $a.srcDir"
	  

Вывод команды gradle -q a

> gradle -q a
source dir is src/main/java
	  

Также вы можете добавить Action к TaskContainer для получения этих событий.

22.6.3. Готовность графа выполнения задач

Вы можете получить уведомлений сразу же после наполнения графа выполнения задач. Мы это уже видели в Секции 16.13 Конфигурация по направленному ациклическому графу.

Также вы можете добавить TaskExecutionGraphListener к TaskExecutionGraph для получения этих событий.

22.6.4. Выполнение задачи

Вы можете получить уведомление непосредственно до или после выполнения любой задачи.

В следующем примере логгируется начало и окончание выполнения каждой задачи. Обратите внимание, что уведомление afterTask получено в независимости от того, была ли завершена задача успешно или случилось исключение.

Пример 22.8. Логгирование начала и окончания выполнения каждой задачи

build.gradle

task ok

task broken(dependsOn: ok) {
    doLast {
        throw new RuntimeException('broken')
    }
}

gradle.taskGraph.beforeTask { Task task ->
    println "executing $task ..."
}

gradle.taskGraph.afterTask { Task task, TaskState state ->
    if (state.failure) {
        println "FAILED"
    }
    else {
        println "done"
    }
}
	  

Вывод команды gradle -q broken

> gradle -q broken
executing task ':ok' ...
done
executing task ':broken' ...
FAILED
	  

Также вы можете добавить TaskExecutionListener к TaskExecutionGraph для получения этих событий.