Даже если мы вызываем задачу для конкретного подпроекта, то все-равно выполняется конфигурация для всех проектов многопроектной сборки. Это сделано для обеспечения гибкости в доступе и в изменяемости проектной модели из любой задачи. Факт, что этап выполнения не наступает пока не будут считаны настройки всех задач сборки, даже если задача которую мы вызываем находится первой в файле сборки, означает, что выполнение задачи будет обеспечено точной и полной настройкой текущей сборки проекта.
"Конфигурация по потребности"
Режим, который планируется быть включенным по умолчанию в будущем, при котором, при вызове задачи будут считываться только настройки задач, от которых находится в зависимости вызываемая задача. Это позволяет значительно уменьшить время выполнения огромных многомодульных проектов.
В этом режиме сборка происходит по следующей схеме:
- Настройки коренного проекта всегда считываются. Благодаря этому поддерживается общая настройка: блоки скриптов allprojects or subprojects.
- Если билд выполняется из директории какого-то подпроекта, то он тоже принимает участие в настройке, но только если в билде не указана конкретная задача или же задача из его сборки.
- Происходит настройка проектов из дерева зависимостей вызываемой задачи.
- Происходит настройка проектов из зависимостей по иерархическим путям (
someTask.dependsOn(":someOtherProject:someOtherTask").
- Происходит настройка проектов из зависимостей по иерархическим путям упомянутым через командную строку или Tooling API.
Сейчас эта "фича в инкубаторе" может включаться через:
gradle.properties( build dir | user home | command line -Dsome.property ):
org.gradle.configureondemand=true
Межпроектная настройка и впрыскивание настроек (cross project configuration & configuration injection)
Можно получить доступ до любого проекта из любого другого файла сборки, если они находятся в одном многомодульном проекте. Project имеет метод project, который принимает путь, а возвращает Project экземпляр, указанного по пути проекта.
Closure cl = { task -> println "I'm $task.project.name" }
task('hello').doLast(cl)
project(':bluewhale') {
task('hello').doLast(cl)
}
Возможность обслуживания многопроектной сборки одним файлом сборки
Вовсе не обязательно под каждый модуль держать отдельный файл сборки. Все/или часть можно решить в одном коренном благодаря allprojects and subprojects.
Build layout:
water/
build.gradle
settings.gradle
bluewhale/
krill/
settings.gradle:
include 'bluewhale', 'krill'
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
Также есть возможность
фильтровать подпроекты. Например метод Project.configure ожидает список и применяет конфигурации в проекты из этого списка:
configure(subprojects.findAll {it.name != 'tropicalFish'}) {
hello {
doLast {
println '- I love to spend time in the arctic waters.'
}
}
}
tropicalFish/build.gradle(в остальных билдфайлах ставим тру):
ext.arctic = false
build.gradle
...
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.' }
}
}
}
}
Фишка в том, что замыкание, которое мы определяем, оценивается после оценки скриптов сборки подпроектов.
Устройство логики сборки
- Если логика повторяется больше чем в одном замыкании таски, нужно эту логику выносить в метод(он попадает в инстанцию Project).
- Если логика повторяется в подпроектах, ее нужно выносить в метод отцовского проекта.
- Если логика вычурная, чтобы поместиться в одном методе, тогда создаем целый. Такой класс размещается в специализированной директории(./buildSrc), и Gradle его компилирует и добавляет в класспаз срипта сборки.
Унаследованные свойства и методы
В подпроектах видны свойства и методы отцовского.
build.gradle:
// Define an extra property
ext.srcDirName = 'src/java'
// Define a method
def getSrcDir(project) {
return project.file(srcDirName)
}
child/build.gradle
task show {
doLast {
// Use inherited property
println 'srcDirName: ' + srcDirName
// Use inherited method
File srcDir = getSrcDir(project)
println 'srcDir: ' + rootProject.relativePath(srcDir)
}
}
Впрыснутые настройки
build.gradle
subprojects {
// Define a new property
ext.srcDirName = 'src/java'
// Define a method using a closure as the method body
ext.srcDir = { file(srcDirName) }
// Define a task
task show {
doLast {
println 'project: ' + project.path
println 'srcDirName: ' + srcDirName
File srcDir = srcDir()
println 'srcDir: ' + rootProject.relativePath(srcDir)
}
}
}
// Inject special case configuration into a particular project
project(':child2') {
ext.srcDirName = "$srcDirName/legacy"
}
child1/build.gradle
// Use injected property and method. Here, we override the injected value
srcDirName = 'java'
def dir = srcDir()
Настройка с использованием внешних скриптов сборки
build.gradle
apply from: 'other.gradle'
other.gradle
println "configuring $project"
task hello {
doLast {
println 'hello from other script'
}
}
Сборка исходников в buildSrc
Gradle при запуске проверяет нет ли в проекте директории buildSrc, если есть - то все что там, компилируется, тестируется и добавляется в класспаз. Это место для кастомный тасок и плагинов. В многомодульном проекте такая папка может быть только одна.
По умолчанию применяется всегд следующий Default buildSrc build script, добавляя в папку buildSrc скрипты, мы расширяем его:
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
Выполнение другого файла сборки Gradle из текущего.
Для этого существует таска GradleBuild
build.gradle:
task build(type: GradleBuild) {
buildFile = 'other.gradle'
tasks = ['hello']
}
other.gradle:
task hello {
doLast {
println "hello from the other build."
}
}
> gradle -q build
hello from the other build.
Внешние зависимости для скрипта сборки
Делается это через метод buildscript.
build.gradle
import org.apache.commons.codec.binary.Base64
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
}
}
task encode {
doLast {
def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
println new String(encodedString)
}
}
> gradle -q encode
aGVsbG8gd29ybGQK
В многомодульном проекте зависимости объявлены в родительском проекте доступны во всех сборках подпроектов.
Зависимостями скриптов сборки могут быть и плагины.
Правила выполнения для многопроектной сборки
- Если с отцовской директории, то проходится по всем подпроектам собирая настройки и запускает после этого указанную таску в тех проектах, в которых она настроена.
- Если с конкретного подпроекта, то подымается к отцовскому файлу сборки, собираем настройки по всем проектам, но запускается таска только в текущем подпроекте(директории) и в подпроектах, который дочерние к текущему(поддиректории к текущей).
Выполнение задач по абсолютному пути
Если мы находимся в какой-то дочерней директории/проекте, но нам нужно выполнить задачу из ряда других подпроектов, то нам не обязательно покидать текущую директорию -- мы можем указать абсолютные путь интересующих нас задач в проектах.
> gradle -q :hello :krill:hello hello
: -- обращение к рут-проекту
:hello -- это задача в коренном проете
:krill:hello -- вызов задачи в подпректе krill
hello -- вызов задачи в текущей директории
Зависимости
Есть зависимости времени настраивания и есть зависимости времени выполнения
По умолчанию задачи выполняются после сортировки в алфавитном порядке абсолютных путей задач.
Порядок выполнения задач, кроме алфавитного порядка, можно предопределять через свойство dependsOn:
task consume(dependsOn: ':producer:produce') {
doLast {
println("Consuming message: ${rootProject.producerMessage}")
}
}
Иногда когда нужно указать порядок не только на этапе выполнения, но и на этапе оценки/считывания настроек, для этого evaluationDependsOn(это
зависимости времени настраивания).
evaluationDependsOn(':producer')
def message = rootProject.producerMessage
task consume {
doLast {
println("Consuming message: " + message)
}
}