Глава 10. Составные сборки.

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

10.1. Что такое составная сборка?

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

Составные сборки позволяют вам:

  • объединять сборки, которые обычно разрабатываются независимо, например, когда вы пытаесь исправить ошибку в библиотеке, которую использует ваше приложение;
  • разделить большие многопроектные сборки на более изолированные, меньшие кусочки, которые могут работать независимо или совместно, в зависимости от нужд.

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

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

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

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

10.2. Определение составной сборки.

Представленные ниже примеры, демонстрируют различные способы объединения двух сборок Gradle, которые обычно разрабатываются раздельно, в составную сборку. В этих примерах, многопроектная сборка my-utils выдает двех различные java-библиотеке (number-utils и string-utils) и сборка my-app выдает исполняемый файл, использующий функции из этих библиотек.

Cборка my-app не имеет прямой зависимости от my-utils. Вместо этого, она объявляет бинарную зависимость на библиотеке, выдаваемые my-utils.

Пример 10.1. Зависимости my-app

my-app/build.gradle

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'idea'

group "org.sample"
version "1.0"

mainClassName = "org.sample.myapp.Main"

dependencies {
    compile "org.sample:number-utils:1.0"
    compile "org.sample:string-utils:1.0"
}

repositories {
    jcenter()
}
	  

Примечание: код для этого примера можно найти в папке samples/compositeBuilds/basic в дистрибутиве Gradle "-all".

10.2.1. Определение составной сборки через --include-build.

Аргумент командной строки --include-build превращает исполняемую сборку в составную, подменяя зависимости из включенной сборки в исполняемую.

Пример 10.2. Объявление составной сборки с помощью командной строки.

Вывод команды gradle --include-build ../my-utils run

> gradle --include-build ../my-utils run
[composite-build] Configuring build: /home/user/gradle/samples/compositeBuilds/basic/my-utils
:compileJava
:my-utils:number-utils:compileJava
:my-utils:number-utils:processResources NO-SOURCE
:my-utils:number-utils:classes
:my-utils:number-utils:jar
:my-utils:string-utils:compileJava
:my-utils:string-utils:processResources NO-SOURCE
:my-utils:string-utils:classes
:my-utils:string-utils:jar
:processResources NO-SOURCE
:classes
:run
The answer is 42

BUILD SUCCESSFUL
	

10.2.2. Определение составной сборки через settings.gradle.

Объявление включенной сборки в файле settings.gradle с использованием Settings.includeBuild(java.lang.Object), дает возможность сделать приведенную выше компоновку постоянной. Файл settings.gradle можно использовать для добавления подпроектов и включенных сборок в одно и то же время. Включенные сборки добавляются по местоположению. Смотрите пример ниже, чтобы узнать детали.

10.2.3. Определение отдельной составной сборки.

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

Пример 10.3. Объявление отдельной составной сборки.

settings.gradle

rootProject.name='adhoc'

includeBuild '../my-app'
includeBuild '../my-utils'
	

В таком сценарии, 'основная' сборка исполняемая сборка - составная и в ней не определено никаких полезных задач. Для того, чтобы выполнить задачу 'run' в сборке 'my-app', составная сборка должна определить делегирующую задачу.

Пример 10.4. Полагаемся на задачу из включенной сборки.

build.gradle

task run {
    dependsOn gradle.includedBuild('my-app').task(':run')
}
	

Более подробно о задачах, которые полагаются на включенную сборку, ниже.

10.2.4. Ограничения на включенные сборки.

Большинство сборок могут быть включены в составную, но есть некоторые ограничения.

Каждая включенная сборка:

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

По умолчанию, Gradle будет настраивать каждую включенную сборку, чтобы вычислить зависимости, которые она может предоставить. Алгоритм этого прост: Gradle просмотрит group и name проектов во включенной сборки и заменит зависимости проектов для любой внешней зависимости совпадающей с ${project.group}:${project.name}.

Бывают случаи, когда определенные Gradle'ом по умолчанию замены неэффективны или некорректны для этой составной сборки. В таких случаях возможно явно объявить замены для включенной сборки. Возьмем для примера однопроектную сборку 'unpublished', которая выдает вспомогательную java-библиотеку, но не объявляет атрибут group:

Пример 10.5. Сборка, не объявляющая атрибут group.

build.gradle

apply plugin: 'java'
	

Когда эта сборка будет включена в составную, Gradle попытается заменить зависимый модуль 'undefined:unpublished' ('undefined' - значение по умолчанию для project.group и 'unpublished' - имя корневого проекта). Ясно, что это будет бесполезно в составной сборке. Для использования неопубликованной библиотеки неизменной в составной сборке, она должна явно объявить замены, которые предоставляются:

Пример 10.6. Объявление зависимостей для включенной сборки.

settings.gradle

rootProject.name = 'app'

includeBuild('../anonymous-library') {
    dependencySubstitution {
        substitute module('org.sample:number-utils') with project(':')
    }
}
	

С такой конфигурацией, составная сборка 'my-app' заменит любую зависимость на org.sample:number-utils зависимостью на корневой проект 'unpublished'.

10.3.1. Случаи, в которых замены включенной сборки должны быть объявлены.

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

10.3.2. Случаи, в которых замены составной сборки не сработают.

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

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

Сборки, которые используют эти функции, работают некорректно при включении в составную сборку. В будущем мы планируем это исправить.

10.4. Полагаемся на задачи из включенной сборки.

Хотя включенные сборки изолированны друг от друга и не могут объявлять прямые зависимости, составная сборка может объявлять зависимости на задачу своих включенных сборок. Включенные сборки доступны через использование Gradle.getIncludedBuilds() и Gradle.includedBuild(java.lang.String) и ссылка на задачу получается с помощью метода IncludedBuild.task(java.lang.String).

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

Пример 10.7. Полагаемся на одиночную задачу из включенной сборки.

build.gradle

task run {
    dependsOn gradle.includedBuild('my-app').task(':run')
}
	
Пример 10.8. Полагаемся на задачи во всех включенных сборках.

build.gradle

task publishDeps {
    dependsOn gradle.includedBuilds*.task(':uploadArchives')
}
	

10.5. Текущие ограничения и планы на будущее для составных сборок.

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

Ограничения текущей реализации включают:

Улучшения, которые мы планируем в предстоящих выпусках, включают: