четверг, 9 марта 2017 г.

Gradle Multiproject build

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

"Конфигурация по потребности"

Режим, который планируется быть включенным по умолчанию в будущем, при котором, при вызове задачи будут считываться только настройки задач, от которых находится в зависимости вызываемая задача. Это позволяет значительно уменьшить время выполнения огромных многомодульных проектов.
В этом режиме сборка происходит по следующей схеме:
- Настройки коренного проекта всегда считываются. Благодаря этому поддерживается общая настройка: блоки скриптов 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)
    }
}

Комментариев нет:

Отправить комментарий