Глава 26. Многопроектные сборки.

Мощная поддержка многопроектных сборок - одна из привлекательнейщих уникальных возможностей Gradle. Эта тема требует наибольшего интеллектуального напряжения.

Многопроектная сборка в Gradle состоит из одного корневого проекта и одного или более подпроектов, у которых тоже могут быть подпроекты.

26.1. Межпроектная конфигурация.

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

Давайте начнем с очень простой многопроектной сборки. По своей сути, Gradle инструмент сборки общего назначения, так что проекты не обязаны быть Java-проектами. Нашы первые примеры будут о морской жизни.

26.1.1. Настройка и выполнение.

В Секции Фазы сборки описываются фазы любой сборки Gradle. Давайте поближе рассмотрим фазы настройки и выполнение многопроектной сборки. Настройки здесь означает выполнение файла build.gradle, откуда вытекает, например, загрузка всех плагинов, которые были объявлены с использованием 'apply plugin'. По умолчанию, настройка всех проектов происходит до выполнения какой-либо задачи. Это означает, что запрос на выполнение любой задачи одиночного проекта влечет за собой настройку всех проектов многопроектной сборки. Причина, по которой каждый проект нуждается в настройки, - поддержка гибкого доступа и изменения любой части модели проекта Gradle.

Настройка по требованию

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

Режим настройки по требованию пытается настроить только те проекты, которые связаны с запрашиваемыми задачами, то есть он выполняет только те файлы проектов build.gradle, которые участвую в сборке. Таким образом, время настройки огромной многопроектной сборки может быть снижено. В долгосрочной перспективе, этот режим станет режимо по умолчанию, возможно даже единственным для выполнения сборки Gradle. Так как функция настройка по требованию инкубационная, то не всякая сборка гарантированно работает корректно. Функция должна работать довольно хорошо для многопроектных сборок, в которых есть раздельные проекты (Секция 26.9 Раздельные проекты). В режиме 'настройка по требованию' проекты настраиваются так:

  • Корневой проект всегда настраивается. Таким образом, обычная общая конфигурация поддерживается (блоки скрипта allprojects или subprojects).
  • Проект в папке, откуда была запущена сборка, тоже настраивается, но только когда Gradle выполняется без задач. Таким образом задачи по умолчанию ведут себя корректно, когда проекты настраиваются по требованию.
  • Стандартные зависимости проектов поддерживаются и связанные проектны настраиваются. Если у проекта A есть зависимость от проекта B, тогда сборка A влечет за собой настройку обоих проектов.
  • Зависимости задач объявленных посредством пути задач - поддерживаются и влекут за собой настройку связанных проектов. Пример:

    someTask.dependsOn(":someOtherProject:someOtherTask")

  • Запрос задачи посредством пути задачи из командной строки (или инструментального API) влечет за собой настройку связанного проекта. Например, сборка 'projectA:projectB:someTask' вызывает настройку projectB.

Нетерпится попробовать новую возможность? Для настройки по требованию каждого запуска сборки, смотрите Секцию Настройка среды сборки с помощью gradle.properties. Для настройки по требованию только для данной сборки, пожалуйста, смотрите Приложение D, Командная строка Gradle.

26.1.2. Задание общего поведения.

Давайте взглянем на несколько примеров со следующим деревом проектов. Это многопроектная сборка с корневым проектов под названием water и подпроектом bluewhale.

Пример 26.1. Многопроектное дерево - проекты water и bluewhale

Разметка сборки

water/
  build.gradle
  settings.gradle
  bluewhale/
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/firstExample/water в дистрибутиве Gradle '-all'.

settings.gradle

include 'bluewhale'
	  

И где же сборочный скрипт для проекта bluewhale. В Gradle такие скрипты необязательны. Очевидно, что для сборки с одним проектом, проект без сборочного скрипта не имеет смысла. Для многопроектных сборок ситуация другая. Давайте посмотрим на сборочный скрипт для проекта water и выполним его:

Пример 26.2. Сборочный скрипт (родительского) проекта water

build.gradle

Closure cl = { task -> println "I'm $task.project.name" }
task('hello').doLast(cl)
project(':bluewhale') {
    task('hello').doLast(cl)
}
	  

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

> gradle -q hello
I'm water
I'm bluewhale
	  

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

Нам не очень нравится сборочный скрипт проекта water. Неудобно добавлять задачи явно для каждого проекта. Можно сделать лучше. Сначала давайте добавим проект с именем krill в нашу многопроектную сборку.

Пример 26.3. Многопроектное дерево - проекты water, bluewhale и krill

Разметка сборки

water/
  build.gradle
  settings.gradle
  bluewhale/
  krill/
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/addKrill/water в дистрибутиве Gradle '-all'.

settings.gradle

include 'bluewhale', 'krill'
	  

Теперь мы перепишем сборочный скрипт water и урежем его до одной линии.

Пример 26.4. Сборочный скрипт проекта water

build.gradle

allprojects {
    task hello {
        doLast { task ->
            println "I'm $task.project.name"
        }
    }
}
	  

Это круто или нет? Как это работает? API проекта предоставляет свойство allprojects, которое возвращает список, включащий текущий проект и все его подпроекты. Если вы вызовете allprojects с замыканием, инструкции из замыкания будут переданы проектам ассоциированным с allprojects. Также вы могли бы пройтись по всем проектам посредством allprojects.each, но это было бы менее кратко.

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

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

26.2. Настройка подпроектов.

API проекта также предоставляет свойство, с помощью которого можно обратиться только к подпроектам.

26.2.1. Задание общего поведения.

Пример 26.5. Задание общего поведения для всех проектов и подпроектов

build.gradle

allprojects {
    task hello {
        doLast { task ->
            println "I'm $task.project.name"
        }
    }
}
subprojects {
    hello {
        doLast {
            println "- I depend on water"
        }
    }
}
	  

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
I'm krill
- I depend on water
	  

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

26.2.2. Добавление специфичного поведения.

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

Пример 26.6. Задание специфичного поведения для определенного проекта

build.gradle

allprojects {
    task hello {
        doLast { task ->
            println "I'm $task.project.name"
        }
    }
}
subprojects {
    hello {
        doLast {
            println "- I depend on water"
        }
    }
}
project(':bluewhale').hello {
    doLast {
        println "- I'm the largest animal that has ever lived on this planet."
    }
}
	  

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
I'm krill
- I depend on water
	  

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

Пример 26.7. Задание специфичного поведения для проекта krill

Разметка сборки

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/spreadSpecifics/water в дистрибутиве Gradle '-all'.

settings.gradle

include 'bluewhale', 'krill'
	  

bluewhale/build.gradle

hello.doLast {
  println "- I'm the largest animal that has ever lived on this planet."
}
	  

krill/build.gradle

hello.doLast {
  println "- The weight of my species in summer is twice as heavy as all human beings."
}
	  

build.gradle

allprojects {
    task hello {
        doLast { task ->
            println "I'm $task.project.name"
        }
    }
}
subprojects {
    hello {
        doLast {
            println "- I depend on water"
        }
    }
}
	  

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
I'm krill
- I depend on water
- The weight of my species in summer is twice as heavy as all human beings.
	  

26.2.3. Фильтрация проектов.

Чтобы показать еще больше силы внедрения конфигурации, давайте добавим проект под названием tropicalFish и еще поведения к сборке посредством сборочного скрипта проекта water.

Фильтрация по имени

Пример 26.8. Добавление пользовательского поведения к некоторым проектам (отфильтрованным по имени)

Разметка сборки

water/
  build.gradle
  settings.gradle
  bluewhale/
    build.gradle
  krill/
    build.gradle
  tropicalFish/
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/addTropical/water в дистрибутиве Gradle '-all'.

settings.gradle

include 'bluewhale', 'krill', 'tropicalFish'
	  

build.gradle

allprojects {
    task hello {
        doLast { task ->
            println "I'm $task.project.name"
        }
    }
}
subprojects {
    hello {
        doLast {
            println "- I depend on water"
        }
    }
}
configure(subprojects.findAll {it.name != 'tropicalFish'}) {
    hello {
        doLast {
            println '- I love to spend time in the arctic waters.'
        }
    }
}
	  

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I love to spend time in the arctic waters.
- I'm the largest animal that has ever lived on this planet.
I'm krill
- I depend on water
- I love to spend time in the arctic waters.
- The weight of my species in summer is twice as heavy as all human beings.
I'm tropicalFish
- I depend on water
	  

Метод configure() принимает в качестве аргумента список и применяет конфигурацию к проектам в этом списке.

Фильтрация по свойствам

Использование имени проекта для фильтрации только одна из опций. Другая - использование дополнительных свойств проекта. (Чтобы узнать больше о дополнительных свойствах проекта, смотрите Секцию 18.4.2 Дополнительные свойства.)

Пример 26.9. Добавление пользовательского поведения к некоторым проектам (отфильтрованным по свойствам)

Разметка сборки

water/
  build.gradle
  settings.gradle
  bluewhale/
    build.gradle
  krill/
    build.gradle
  tropicalFish/
    build.gradle
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/tropicalWithProperties/water в дистрибутиве Gradle '-all'.

settings.gradle

include 'bluewhale', 'krill', 'tropicalFish'
	  

bluewhale/build.gradle

ext.arctic = true
hello.doLast {
  println "- I'm the largest animal that has ever lived on this planet."
}
	  

krill/build.gradle

ext.arctic = true
hello.doLast {
    println "- The weight of my species in summer is twice as heavy as all human beings."
}
	  

tropicalFish/build.gradle

ext.arctic = false
	  

build.gradle

allprojects {
    task hello {
        doLast { task ->
            println "I'm $task.project.name"
        }
    }
}
subprojects {
    hello {
        doLast {println "- I depend on water"}
        afterEvaluate { Project project ->
            if (project.arctic) { doLast {
                println '- I love to spend time in the arctic waters.' }
            }
        }
    }
}
	  

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

> gradle -q hello
I'm water
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
- I love to spend time in the arctic waters.
I'm krill
- I depend on water
- The weight of my species in summer is twice as heavy as all human beings.
- I love to spend time in the arctic waters.
I'm tropicalFish
- I depend on water
	  

В сборочном файле проекта water, мы используем уведомление afterEvaluate. Это означает, что замыкание, переданное нами, выполнится после вычисления сборочных скриптов подпроектов. Так как свойство arctic устанавливается в этих сборочных скриптах, мы делаем это таким способом. На эту тему вы можете узнать больше в Секции 26.6 Зависимости - Какие зависимости?.

26.3. Правила выполнения для многопроектных сборок.

Когда мы выполняли задачу hello из папки корневого проекта, все вело себя так, как ожидалось. Все задачи hello различных проектов выполнились. Давайте перейдем в папку bluewhale и посмотрим, что случится, если выполнить Gradle оттуда.

Пример 26.10. Запуск сборки из подпроекта

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

> gradle -q hello
I'm bluewhale
- I depend on water
- I'm the largest animal that has ever lived on this planet.
- I love to spend time in the arctic waters.
	  

Базовое правило, стоящее за поведением Gradle, просто. Gradle начинает поиск сверху вниз по иерархии, начиная с текущей папки, задач с именем hello и выполняет их. На одну вещь необходимо обратить внимание. Gradle всегда вычисляет каждый проект в многопроектной сборке и создает все существующие объекты задач. Затем, в соответствии с аргументами имени задачи и текущей папки, Gradle отфильтровывает задачи, которые нужно выполнить. Вследствие межпроектной конфигурации Gradle, каждый проект должен быть вычислен до выполнения любой задачи. Мы взглянем на это поближе в следующей секции. Давайте возьмем наш последний пример, связанный с морем и добавим задачу к bluewhale и krill.

Пример 26.11. Вычисление и выполнение проектов

bluewhale/build.gradle

ext.arctic = true
hello {
    doLast {
        println "- I'm the largest animal that has ever lived on this planet."
    }
}

task distanceToIceberg {
    doLast {
        println '20 nautical miles'
    }
}
	  

krill/build.gradle

ext.arctic = true
hello {
    doLast {
        println "- The weight of my species in summer is twice as heavy as all human beings."
    }
}

task distanceToIceberg {
    doLast {
        println '5 nautical miles'
    }
}
	  

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

> gradle -q distanceToIceberg
20 nautical miles
5 nautical miles
	  

Вот вывод без опции -q:

Пример 26.12. Вычисление и выполнение проектов

Вывод команды gradle distanceToIceberg

> gradle distanceToIceberg
:bluewhale:distanceToIceberg
20 nautical miles
:krill:distanceToIceberg
5 nautical miles

BUILD SUCCESSFUL

Total time: 1 secs
	  

Сборка выполнена из проекта water. Ни water, ни tropicalFish не имеют задачи с именем distanceToIceberg. Gradle это не волнует. Просто правило, упомянутое выше: выполнить все задачи вниз по иерархии, у которых это имя. Он ругается, только если нет такой задачи!

26.4. Запуск задач по их абсолютному пути.

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

Пример 26.13. Запуск задач по их абсолютному пути

Вывод команды gradle -q :hello :krill:hello hello

> gradle -q :hello :krill:hello hello
I'm water
I'm krill
- I depend on water
- The weight of my species in summer is twice as heavy as all human beings.
- I love to spend time in the arctic waters.
I'm tropicalFish
- I depend on water
	  

Сборка выполняется из проекта tropicalFish. Мы выполняем задачи hello проектов water, krill и tropicalFish. Первые две задачи указаны по их абсолютному пути, последняя задач выполняется с использованием механизма совпадения имени, описанного выше.

26.5. Пути проектов и задач.

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

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

26.6. Зависимости - Какие зависимости?

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

26.6.1. Зависимости выполнения.

Пример 26.14. Зависимости и порядок выполнения

Разметка сборки

messages/
  build.gradle
  settings.gradle
  consumer/
    build.gradle
  producer/
    build.gradle
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/dependencies/firstMessages/messages в дистрибутиве Gradle '-all'.

build.gradle

ext.producerMessage = null
	  

settings.gradle

include 'consumer', 'producer'
	  

consumer/build.gradle

task action {
    doLast {
        println("Consuming message: ${rootProject.producerMessage}")
    }
}
	  

producer/build.gradle

task action {
    doLast {
        println "Producing message:"
        rootProject.producerMessage = 'Watch the order of execution.'
    }
}
	  

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

> gradle -q action
Consuming message: null
Producing message:
	  

Это не совсем то, чего мы хотим. Если ничего другого не задано, Gradle выполняет задачи в буквенно-цифровом порядке. Таким образом, Gradle выполнит ':consumer:action' перед ':producer:action'. Давайте попробует разрешить эту ситуацию с помощью хака и переименуем проект 'producer' в 'aProducer'.

Пример 26.15. Зависимости и порядок выполнения

Разметка сборки

messages/
  build.gradle
  settings.gradle
  aProducer/
    build.gradle
  consumer/
    build.gradle
	  

build.gradle

ext.producerMessage = null
	  

settings.gradle

include 'consumer', 'aProducer'
	  

aProducer/build.gradle

task action {
    doLast {
        println "Producing message:"
        rootProject.producerMessage = 'Watch the order of execution.'
    }
}
	  

consumer/build.gradle

task action {
    doLast {
        println("Consuming message: ${rootProject.producerMessage}")
    }
}
	  

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

> gradle -q action
Producing message:
Consuming message: Watch the order of execution.
	  

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

Пример 26.16. Зависимости и порядок выполнения

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

> gradle -q action
Consuming message: null
	  

Проблема в том, что задачи 'action' не связаны. Если вы выполняете сборку из проекта 'messages', Gradle выполняет их обе, потому что у них одинаковые имена и находятся ниже по иерархии. В последнем примере, только одна задачи 'action' находится ниже по иерархии и, таким образом, только она и выполняется. Нам нужно что-то получше этого хака.

Объявление зависимостей

Пример 26.17. Объявление зависимостей

Разметка сборки

messages/
  build.gradle
  settings.gradle
  consumer/
    build.gradle
  producer/
    build.gradle
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/dependencies/messagesWithDependencies/messages в дистрибутиве Gradle '-all'.

build.gradle

ext.producerMessage = null
	  

settings.gradle

include 'consumer', 'producer'
	  

consumer/build.gradle

task action(dependsOn: ":producer:action") {
    doLast {
        println("Consuming message: ${rootProject.producerMessage}")
    }
}
	  

producer/build.gradle

task action {
    doLast {
        println "Producing message:"
        rootProject.producerMessage = 'Watch the order of execution.'
    }
}
	  

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

> gradle -q action
Producing message:
Consuming message: Watch the order of execution.
	  

Выполнение команды из папки consumer дает:

Пример 26.18. Объявление зависимостей

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

> gradle -q action
Producing message:
Consuming message: Watch the order of execution.
	  

Теперь все работает лучше, потому что мы объявили, что задача 'action' в проекте 'consumer' имеет зависимость выполнения от задачи 'action' в проекте 'producer'.

Сущность межпроектных зависимостей задач

Само собой, зависимости задач между различными проектами не ограничены задачами с одинаковыми именами. Давайте изменим имена наших задач и выполним сборку.

Пример 26.19. Межпроектные зависимости задач

consumer/build.gradle

task consume(dependsOn: ':producer:produce') {
    doLast {
        println("Consuming message: ${rootProject.producerMessage}")
    }
}
	  

producer/build.gradle

task produce {
    doLast {
        println "Producing message:"
        rootProject.producerMessage = 'Watch the order of execution.'
    }
}
	  

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

> gradle -q consume
Producing message:
Consuming message: Watch the order of execution.
	  

26.6.2. Настройка временных зависимостей.

Давайте посмотрим на еще один пример с нашей сборкой producer-consumer перед тем, как мы вступим на землю Java. Мы добавляем свойство к проекту 'producer' и создаем настройку временной зависимости от 'consumer' к 'producer'.

Пример 26.20. Настройка временных зависимостей

consumer/build.gradle

def message = rootProject.producerMessage

task consume {
    doLast {
        println("Consuming message: " + message)
    }
}
	  

producer/build.gradle

rootProject.producerMessage = 'Watch the order of evaluation.'
	  

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

> gradle -q consume
Consuming message: null
	  

По умолчанию, порядок вычисления проектов - буквенно-цифровой (для одинакового уровня вложенности). Поэтому проект 'consumer' вычисляется до проекта 'producer' и значение свойства 'producerMessage' выставляется после его чтения проектом 'consumer'. Gradle предлагает решение этой проблемы.

Пример 26.21. Настройка временных зависимостей - evaluationDependsOn

consumer/build.gradle

evaluationDependsOn(':producer')

def message = rootProject.producerMessage

task consume {
    doLast {
        println("Consuming message: " + message)
    }
}
	  

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

> gradle -q consume
Consuming message: Watch the order of evaluation.
	  

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

Пример 26.22. Настройка временных зависимостей

consumer/build.gradle

task consume {
    doLast {
        println("Consuming message: ${rootProject.producerMessage}")
    }
}
	  

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

> gradle -q consume
Consuming message: Watch the order of evaluation.
	  

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

Чтобы изменить порядок настройки по умолчанию на 'снизу вверх', используйте метод 'evaluationDependsOnChildren()'.

На одном уровне вложенности, порядок настройки зависит от буквенно-цифровой позиции. Обычный случай использования, иметь многопроектные сборки, которые делят общий жизненный цикл (например, все проекты пользуются плагином Java). Если вы объявляете зависимости выполнения посредством dependsOn между двумя различными проектами, поведение этого метода по умолчанию также создает зависимость конфигурации между двумя проектами. Таким образом, вероятно, что вам не придется явно задавать зависимости конфигурации.

26.6.3. Примеры из жизни.

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

Пример 26.23. Настройка временных зависимостей

Разметка сборки

webDist/
  settings.gradle
  build.gradle
  date/
    src/main/java/
      org/gradle/sample/
        DateServlet.java
  hello/
    src/main/java/
      org/gradle/sample/
        HelloServlet.java
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/dependencies/webDist в дистрибутиве Gradle '-all'.

settings.gradle

include 'date', 'hello'
	  

build.gradle

allprojects {
    apply plugin: 'java'
    group = 'org.gradle.sample'
    version = '1.0'
}

subprojects {
    apply plugin: 'war'
    repositories {
        mavenCentral()
    }
    dependencies {
        compile "javax.servlet:servlet-api:2.5"
    }
}

task explodedDist(type: Copy) {
    into "$buildDir/explodedDist"
    subprojects {
        from tasks.withType(War)
    }
}
	  

У нас есть интересный набор зависимостей. Очевидно, что у проектов date и hello есть конфигурационная зависимость от webDist, так как все логика сборки для проектов webapp добавлена webDist. Зависимость выполнения в другом направлении, так как webDist зависит от артефактов сборки date и hello. Есть еще третья зависимость. У проекта webDist есть конфигурационная зависимость от date и hello, потому что ему нужно знать archivePath. Но он запрашивает эту информацию во время выполнения. Поэтому здесь нет циклической зависимости.

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

26.7. Зависимости проекта от библиотек

Что, если одному проекту требуется jar-файл, выданный другим, в его пути компиляции и не просто jar-файл, но также все его переходные зависимости? Очевидно, что это очень распространенный прецедент для многопроектных сборок Java. Как уже упоминалось в Секции 25.4.3 Зависимости от проектов, Gradle предлагает для этого зависимости проекта от библиотек.

Пример 26.24. Зависимости проекта от библиотек

Разметка сборки

java/
  settings.gradle
  build.gradle
  api/
    src/main/java/
      org/gradle/sample/
        api/
          Person.java
        apiImpl/
          PersonImpl.java
  services/personService/
    src/
      main/java/
        org/gradle/sample/services/
          PersonService.java
      test/java/
        org/gradle/sample/services/
          PersonServiceTest.java
  shared/
    src/main/java/
      org/gradle/sample/shared/
        Helper.java
	  

Примечание: Код для этого примера может быть найден в папке samples/userguide/multiproject/dependencies/java в дистрибутиве Gradle '-all'.

У нас есть проекты 'shared', 'api' и 'personService'. У проекта 'personService' есть библиотечная зависимость от двух других проектов. У проекта 'api' библиотечная зависимость от проекта 'shared'.

Пример 26.25. Зависимости проекта от библиотек

settings.gradle

include 'api', 'shared', 'services:personService'
	  

build.gradle

subprojects {
    apply plugin: 'java'
    group = 'org.gradle.sample'
    version = '1.0'
    repositories {
        mavenCentral()
    }
    dependencies {
        testCompile "junit:junit:4.12"
    }
}

project(':api') {
    dependencies {
        compile project(':shared')
    }
}

project(':services:personService') {
    dependencies {
        compile project(':shared'), project(':api')
    }
}
	  

Вся логика сборки находится в файле 'build.gradle' корневого проекта. 'Библиотечная' зависимость - специальная форма зависимости выполнения. Вследствие этой зависимости, другой проект собирается первым и jar-файл с классами добавляется в путь к классам. Также в путь к классам добавляются зависимости другого проекта. Так что вы можете зайти в папку 'api' и запустить 'gradle compile'. Сначала собирается проект 'shared' и затем 'api'. Зависимости проектов делают возможным частичные многопроектные сборки.

Если вы пришли с Maven, этого вам может быть достаточно для счастья. Если вы пришли с Ivy, вы можете ожидать некоторого более тонкого контроля. Gradle предлагает вам:

Пример 26.26. Тонкий контроль зависимостей

build.gradle

subprojects {
    apply plugin: 'java'
    group = 'org.gradle.sample'
    version = '1.0'
}

project(':api') {
    configurations {
        spi
    }
    dependencies {
        compile project(':shared')
    }
    task spiJar(type: Jar) {
        baseName = 'api-spi'
        dependsOn classes
        from sourceSets.main.output
        include('org/gradle/sample/api/**')
    }
    artifacts {
        spi spiJar
    }
}

project(':services:personService') {
    dependencies {
        compile project(':shared')
        compile project(path: ':api', configuration: 'spi')
        testCompile "junit:junit:4.12", project(':api')
    }
}
	  

Плагин Java по умолчанию добавляет jar-файл в библиотеки вашего проекта, который содержит все классы. В этом примере мы создаем дополнительную библиотеку, содержащую только интерфейсы проекта 'api'. Мы назначаем ее новой конфигурации зависимостей. Для personService мы объявляем, что проект должен компилироваться только с интерфейсами 'api', но тестироваться с классами из 'api'.

26.7.1. Отключение сборки зависимых проектов.

Иногда вам не нужно, чтобы зависимые проекты собирались, когда вы делаете частичную сборку. Чтобы отключить сборку таких проектов, вы можете запустить Gradle с опцией -a.

26.8. Параллельное выполнение проектов

У увеличением количества ядер процессора на машинах разработчиков и серверах непрерывной интеграции, важно, что у Gradle есть возможность полностью использовать вычислительные ресурсы. Точнее, параллельное выполнение пытается:

  • Снизить полное время многопроектной сборки, где выполнение связано с IO или по каким-либо другим причинам не использует все доступные ресурсы процессора.
  • Предоставить более быструю обратную связь при выполнении маленьких проектов без ожидания завершения остальных.

Хотя Gradle уже предлагает параллельное выполнение тестов посредством Test.setMaxParallelForks(int), возможность, описанная в этой секции, параллельное выполнение на уровне проекта. Эта возможность инкубационная.

Параллельное выполнение проектов позволяет раздельным проектам в раздельной многопроектной сборке выполняться параллельно (также смотрите Секция 26.9 Раздельные проекты). Хотя параллельное выполнение не требует строго разделения в время настройки, долгосрочная цель - предоставить мощный набор возможностей, которые будут доступны для полностью раздельных проектов. Такие возможности включают:

  • Секцию под названием 'Настройка по требованию'.
  • Настройку проектов параллельно.
  • Повторное использование конфигурации для неизменившихся проектов.
  • Проверки актуальности на уровне проекта.
  • Использование предварительно собранных артефактов вместо сборки зависимых проектов.

Как работает параллельное выполнение? Во-первых, вам надо сказать Gradle использовать параллельный режим. Вы можете использовать аргумент командной строки (Приложение D, Командная строка Gradle) или настроить вашу среду сборки (Секция 12.1 Настройка среды сборки с помощью gradle.properties). Если вы не предоставили какое число параллельных потоков использовать, Gradle попытается выбрать правильное на основе доступных ядер процессора. Как параллельный поток всецело владеет данным проектов во время выполнения задачи. Это означает, что две задачи из одного и того же проекта никогда не выполнятся параллельно. Таким образом, только многопроектные сборки воспользоваться преимуществами параллельного выполнения. Зависимости задач полностью поддерживаются и параллельные потоки сначала возьмутся выполнять вышерасположенные задачи. Помните, что буквенно-цифровое планирование разделенных задач, известное из последовательного выполнения, не работает в параллельном режиме. Вам нужно убедиться, что зависимости задач объявлены корректно, для избежания проблем с порядком.

Предупреждение: знайте, что порядок задач не выполняется строго при использование параллельного выполнения и это может вести неожиданным результатам. Частый случай, в котором всплывает это ограничение, использование задачи clean предоставленной плагином base в комбинации с любой другой задачей, производящей вывод, для многопроектной сборки параллельно. Например, давайте представим, что есть многопроектная сборка с проектом A и проектом B, где B зависит от A. Запуск gradle clean build --parallel может привести к следующей ситуации:

  • A:clean выполнится после A:jar.
  • B зависит от A и ему требуется jar-файл A. Однако, B:classes упадет, так как выполняется после A:clean, которая удаляет нужный для компиляции B jar-файл.

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

26.9. Раздельные проекты

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

Два проекта называются раздельными, если они прямо не обращаются к моделям проекта друг друга. Раздельные проекты могут взаимодействовать только в терминах зависимостей: зависимостей проекта (Секция 25.4.3 Зависимости от проектов и/или зависимостей задач (Секция 16.5 Зависимости задач). Любые другие формы взаимодействия проектов (т.е. изменение объекта другого проекта или чтение значение из объекта другого проекта) ведут к тому, что проекты будут связаны. Последствие связывания во время фазы конфигурации таково, что если Gradle вызван с опцией 'настройка по требованию', результат сборки может быть поврежден несколькими способовами. Последствие связывания во время фазы выполнения состоит в том, что, если Gradle вызывается с опцией параллельного выполнения, задача одного проекта может слишком поздно повлиять на проект, собирающийся параллельно. Gradle не пытается отследить связывание и предупредить пользователя, так как слишком много вариантов связать проекты.

Типичный способ связать проекты, использование внедрения конфигурации (Секция 26.1 Межпроектная конфигурация). Это может быть очевидно не сразу, но использование ключевых возможностей Gradle, таких как ключевые слова allprojects и subprojects, автоматически влечет за собой связывание ваших проектов. Это происходит потому, что эти ключевые слова используются в файле build.gradle, который определяет проект. Часто это 'корневой проект', который не делает ничего, кроме задания общей конфигурации, но поскольку Gradle затрагивает его, это полноправный проект и, используя allprojects, этот проект фактически связывается со всеми остальными. Связывание корневого проекта с подпроектами не влияет на 'настройку по требованию', использование allprojects и subprojects в любом файле build.gradle подпроектов, повлияет.

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

Для того, чтобы правильно воспользоваться межпроектной конфигурацией и не встретиться с проблемами для опций параллельного режима и 'настройки по требованию', следуйте этим рекомендациям:

  • Избегайте ссылок на другие подпроекты из файла build.gradle какого-либо подпроекта; предпочитайте перекрестную настройку из корневого проекта.
  • Избегайте изменения конфигурации других проектов во время выполнения.

26.10. Многопроектная сборка и тестирование

Задача build из плагина Java обычно используется для компиляции, тестирования и выполнения проверок стиля код (если используется плагин CodeQuality) для одиночного проекта. В многопроектных сборках часто вы можете захотеть выполнить эти задачи для ряда проектов. Задачи buildNeeded и buildDependents могут в этом помочь.

Взгляните на Пример 26.25 Зависимости проекта от библиотек. В этом примере проект ':services:personservice' зависит от двух проектов, ':api' и ':shared'. Также проект ':api' зависит от проекта ':shared'.

Предположим, что вы работаете над одиночным проектом, ':api'. Вы внесли изменения, но не собирали весь проект со времени выполнения очистки. Вы хотите собрать необходимые вспомогательные jar-файлы, но выполнить только проверку качества кода и юнит-тесты в измененном проекте. Задача build сделает это.

Пример 26.27. Сборка и тестирование одиночного проекта

Вывод команды gradle :api:build

> gradle :api:build
:shared:compileJava
:shared:processResources
:shared:classes
:shared:jar
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build

BUILD SUCCESSFUL

Total time: 1 secs
	  

Пока вы работаете в обычном цикле разработки, непрерывно собирая и тестируя изменения в проекте ':api' (зная, что только изменяете файлы в одном этом проекте), вы не захотите терпеть издержки на сборку ':shared:compile', чтобы посмотреть что было изменено в проекте ':shared'. Добавление опции '-a' повлечет за собой использование Gradle закэшированных jar-файлов для разрешения любых библиотечных зависимостей проекта и он не будет пытаться повторно собрать зависимые проекты.

Пример 26.28. Частичная сборка и тестирование одиночного проекта

Вывод команды gradle -a :api:build

> gradle -a :api:build
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build

BUILD SUCCESSFUL

Total time: 1 secs
	  

Если вы только что получили новые версии исходных кодов из системы контроля версий, которые включают изменения в других проектах, от которых зависит ':api', вы может понадобиться не только собрать все зависимые проекты, но также протестировать их. Задача buildNeeded также тестирует все проекты из библиотечных зависимостей проекта конфигурации testRuntime.

Пример 26.29. Сборка и тестирование зависимых проектов

Вывод команды gradle :api:buildNeeded

> gradle :api:buildNeeded
:shared:compileJava
:shared:processResources
:shared:classes
:shared:jar
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build
:shared:assemble
:shared:compileTestJava
:shared:processTestResources
:shared:testClasses
:shared:test
:shared:check
:shared:build
:shared:buildNeeded
:api:buildNeeded

BUILD SUCCESSFUL

Total time: 1 secs
	  

Возможно, что вы также захотите отрефакторить некоторую часть проекта ':api', которая используется в других проектах. Если вы сделаете такой тип изменения, то недостаточно протестировать только проект ':api', вам также нужно все проекты, которые зависят от него. Задача buildDependents протестирует все проекты, у которых есть библиотечная зависимость проекта (в конфигурации testRuntime) на данный проект.

Пример 26.30. Сборка и тестирование проектов, зависящих от данного

Вывод команды gradle :api:buildDependents

> gradle :api:buildDependents
:shared:compileJava
:shared:processResources
:shared:classes
:shared:jar
:api:compileJava
:api:processResources
:api:classes
:api:jar
:api:assemble
:api:compileTestJava
:api:processTestResources
:api:testClasses
:api:test
:api:check
:api:build
:services:personService:compileJava
:services:personService:processResources
:services:personService:classes
:services:personService:jar
:services:personService:assemble
:services:personService:compileTestJava
:services:personService:processTestResources
:services:personService:testClasses
:services:personService:test
:services:personService:check
:services:personService:build
:services:personService:buildDependents
:api:buildDependents

BUILD SUCCESSFUL

Total time: 1 secs
	  

Наконец, вам может понадобиться собрать и протестировать все во всех проектах. Любая задача, которую вы запустите из папки корневого проекта, повлечет за собой запуск задач с таким же именем во всех дочерних. Так что вам просто надо запустить 'gradle build' для сборки и тестирования всех проектов.

26.11. Мультипроект и buildSrc

Секция 43.3 Исходники сборки в проекте buildSrc говорит нам, что мы можем положить логику сборки для компиляции и тестирования в специальную папку buildSrc. В многопроектной сборки такая папка может быть только одна и она должна находиться в корневой папке.

26.12. Наследование свойств и методов

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

Наследование методов может быть интересно, так как внедрение конфигурации Gradle пока не поддерживает методы (но будет в следующих выпусках).

Должно быть вы размышляете, зачем реализовывать возможность, которая не нравится. Одна из причин, потому что другие инструменты ее предлагают и нам хотелось иметь галочку в сравнении возможностей :). И нам нравится давать выбор своим пользователям.

26.13. Заключение

Написание этой главы было достаточно трудным делом и ее чтение может быть таким же. Наше последнее сообщение в этой главе - многопроектные сборки в Gradle обычно не сложные. Есть пять элементов, о которых вам нужно помнить: allprojects, subprojects, evaluationDependsOn, evaluationDependsOnChildren и библиотечные зависимости проекта. С этими элементами, и помня о том, что у Gradle есть фазы настройки и выполнения, вы уже имеете большую гибкость. Но когда вы начинаете подниматься в гору, Gradle не становится препятствием и обычно действует в унисом и возносит вас на вершину.