Глава 16. Основы скрипта сборки.

16.1. Проекты и задачи.

Все в Gradle покоится на двух базовых концептах: проектах и задачах.

Каждая сборка Gradle состоит из одного или более проектов. Что представляет проект, зависит от того, что вы делаете с Gradle. Например, проект может представлять библиотеку jar или веб-приложение. Он может представлять распространяемый zip-архив собранный из jar-файлов созданных другими проектами. Проект не обязательно представляет из себя что-то, что необходимо собирать. Он может быть чем-то, что необходимо сделать, например, развертка вашего приложения для предпроизводственной или производственной сред. Не беспокойтесь, если сейчас это вам кажется немного неопределенным. Поддержка сборки-по-соглашению Gradle добавляет более конкретное определение того, чем же является проект.

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

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

16.2. Здравствуй мир.

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

Попробуйте создать следующий сборочный скрипт называнный build.gradle.

Пример 16.1. Ваш первый сборочный скрипт

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}
	  

В оболочке командной строки, перейдите в папку, содержащую сборочный скрипт и выполните его с помощью команды gradle -q hello:

Пример 16.2. Выполнение сборочного скрипта

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

> gradle -q hello
Hello world!
	  

Что делает -q?

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

Что же здесь происходит? В сборочном скрипте определена одна задача, названная hello и к ней добавлено действие. Когда вы запускаете gradle hello, Gradle выполняет задачу hello, которая в свою очередь выполняет действие предоставленное вами. Это действие - всего лишь замыкание, содержащее немного кода Groovy на выполнение.

Если вы думаете, что это выглядит очень похоже на цели Ant, то будете правы. Задачи Gradle эквивалентны целям Ant, но, как вы увидите, они гораздо более мощные. Мы используем отличную от Ant терминологию, так как думаем, что слово 'задача' более выразительное, чем 'цель'. К несчастью, из-за этого возникает столкновение с терминологией Ant, так как его команды, такие как javac или copy, называются задачами. Так что когда мы говорим о задачах, мы всегда имеем ввиду задачи Gradle, которые эквивалентны целям Ant. Если мы разговариваем о задачах Ant (командах), то мы явно говорим 'задача Ant'.

16.3. Краткое определение задачи.

Использование этой функциональности не рекомендуется и она будет удалена без замены в Gradle 5.0. Вместо нее используйте методы Task.doFirst(org.gradle.api.Action) и Task.doLast(org.gradle.api.Action) для определения действия, как будет показано в последующих примерах в этой главе.

Ниже показан краткий способ определения задачи, наподобие hello, показанной выше, выглядящий более лаконично.

Пример 16.3. Краткое определение задачи

build.gradle

task hello << {
    println 'Hello world!'
}
	  

И снова мы видим определение задачи под названием hello с единственным замыканием на выполнение. Оператор << просто другое название для doLast.

16.4. Сборочные скрипты - это код.

Сборочные скрипты Gradle дают вам полную мощь Groovy. Взгляните на следующий пример:

Пример 16.4. Использование Groovy в задачах Gradle

build.gradle

task upper {
    doLast {
        String someString = 'mY_nAmE'
        println "Original: " + someString
        println "Upper case: " + someString.toUpperCase()
    }
}
	  

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

> gradle -q upper
Original: mY_nAmE
Upper case: MY_NAME
	  

Или еще один:

Пример 16.5. Использование Groovy в задачах Gradle

build.gradle

task count {
    doLast {
        4.times { print "$it " }
    }
}
	  

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

> gradle -q count
0 1 2 3 
	  

16.5. Зависимости задач.

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

Пример 16.6. Объявление задачи, которая зависит от другой задачи

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}
task intro(dependsOn: hello) {
    doLast {
        println "I'm Gradle"
    }
}
	  

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

> gradle -q intro
Hello world!
I'm Gradle
	  

Для добавления зависимости, соответствующая задача не обязательно должна существовать.

Пример 16.7. Ленивый dependsOn - другая задача не существует (пока)

build.gradle

task taskX(dependsOn: 'taskY') {
    doLast {
        println 'taskX'
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}
	  

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

> gradle -q taskX
taskY
taskX
	  

Зависимость задачи taskX от taskY объявлена еще до определения taskY. Это очень важно для многопроектных сборок. Зависимости задач более детально обсуждаются в Секции 19.4 Добавление зависимостей к задаче.

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

16.6. Динамические задачи.

Мощь Groovy может быть использована для горазда большего, чем определение того, что может делать задача. Например, она может быть использована для динамического создания задач.

Пример 16.8. Динамическое создание задачи

build.gradle

4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}
	  

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

> gradle -q task1
I'm task number 1
	  

16.7. Управление существующими задачами.

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

Пример 16.9. Получение доступа к задаче через API - добавление зависимости

build.gradle

4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}
task0.dependsOn task2, task3
	  

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

> gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0
	  

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

Пример 16.10. Получение доступа к задаче через API - добавление поведения

build.gradle

task hello {
    doLast {
        println 'Hello Earth'
    }
}
hello.doFirst {
    println 'Hello Venus'
}
hello.doLast {
    println 'Hello Mars'
}
hello {
    doLast {
        println 'Hello Jupiter'
    }
}
	  

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

> gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter
	  

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

16.8. Сокращенная нотация.

Существует удобная нотация для получения доступа к существующей задаче. Каждая задача доступна как свойство сборочного скрипта:

Пример 16.11. Обращение к задаче как к свойству сборочного скрипта

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}
hello.doLast {
    println "Greetings from the $hello.name task."
}
	  

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

> gradle -q hello
Hello world!
Greetings from the hello task.
	  

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

16.9. Дополнительные свойства задачи.

Вы можете добавить свои собственные свойства к задаче. Для добавления свойства с именем myProperty, присвойте ext.myProperty начальное значение. С этой точки, свойство можно считывать и записывать, как и предопределенное свойство задачи.

Пример 16.12. Добавление дополнительных свойств к задаче

build.gradle

task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties {
    doLast {
        println myTask.myProperty
    }
}
	  

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

> gradle -q printTaskProperties
myValue
	  

Дополнительные свойства не ограничиваются задачами. О них вы можете прочесть больше в Секции 18.4.2 Дополнительные свойства.

16.10. Использование задач Ant.

Задачи Ant - объект первого класса в Gradle. Gradle предоставляет прекрасную интеграцию для задач Ant просто полагаясь на Groovy. Groovy поставляется с фантастическим AntBuilder. Использование задач Ant из Gradle удобнее и мощнее, чем их использование в файле build.xml. Из примера ниже, вы можете понять как выполнять задачи Ant и как получить доступ к свойствам Ant:

Пример 16.13. Использование AntBuilder для выполнения цели ant.loadfile

build.gradle

task loadfile {
    doLast {
        def files = file('../antLoadfileResources').listFiles().sort()
        files.each { File file ->
            if (file.isFile()) {
                ant.loadfile(srcFile: file, property: file.name)
                println " *** $file.name ***"
                println "${ant.properties[file.name]}"
            }
        }
    }
}
	  

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

> gradle -q loadfile
 *** agile.manifesto.txt ***
Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration  over contract negotiation
Responding to change over following a plan
 *** gradle.manifesto.txt ***
Make the impossible possible, make the possible easy and make the easy elegant.
(inspired by Moshe Feldenkrais)
	  

Существует еще много вещей, которые вы можете сделать с Ant в вашем сборочном скрипте. Вы можете выяснть больше в Главе 21 Использование Ant из Gradle.

16.11. Использование методов.

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

Пример 16.14. Использование методов для организации логики вашей сборки

build.gradle

task checksum {
    doLast {
        fileList('../antLoadfileResources').each { File file ->
            ant.checksum(file: file, property: "cs_$file.name")
            println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
        }
    }
}

task loadfile {
    doLast {
        fileList('../antLoadfileResources').each { File file ->
            ant.loadfile(srcFile: file, property: file.name)
            println "I'm fond of $file.name"
        }
    }
}

File[] fileList(String dir) {
    file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
}
	  

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

> gradle -q loadfile
I'm fond of agile.manifesto.txt
I'm fond of gradle.manifesto.txt
	  

Дальше вы увидите, что такие методы можно сделать общими для подпроектов в многопроектное сборке. Если ваша логика сборки становится сложнее, Gradle предлагает вам другие очень удобные способы для ее организации. Им посвящена целая глава. Смотрите Главу 43 Организация логики сборки.

16.12. Задачи по умолчанию.

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

Пример 16.15. Определение задачи по умолчанию

build.gradle

defaultTasks 'clean', 'run'

task clean {
    doLast {
        println 'Default Cleaning!'
    }
}

task run {
    doLast {
        println 'Default Running!'
    }
}

task other {
    doLast {
        println "I'm not a default task!"
    }
}
	  

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

> gradle -q
Default Cleaning!
Default Running!
	  

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

16.13. Конфигурация по направленному ациклическому графу.

Как мы опишем позже более детально (смотрите Главу 22 Жизненный цикл сборки), в Gradle есть фаза конфигурации и фаза выполнения. После фазы конфигурации, Gradle знает все задачи, которые должны быть выполнены. Gradle предлагает вам хук, чтобы вы могли воспользоваться этой информацией. Пример использования, проверка на существование задачи release среди задач на выполнение. Основываясь на этой информации, вы можете присвоить различные значения некоторым переменным.

В следующем примере, в результате выполнения задач distribution и release переменной version устанавливаются разные значения.

Пример 16.16. Разные последствия сборки в зависимости от выбранной задачи

build.gradle

task distribution {
    doLast {
        println "We build the zip with version=$version"
    }
}

task release(dependsOn: 'distribution') {
    doLast {
        println 'We release now'
    }
}

gradle.taskGraph.whenReady {taskGraph ->
    if (taskGraph.hasTask(release)) {
        version = '1.0'
    } else {
        version = '1.0-SNAPSHOT'
    }
}
	  

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

> gradle -q distribution
We build the zip with version=1.0-SNAPSHOT
	  

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

> gradle -q release
We build the zip with version=1.0
We release now
	  

Важная вещь то, что whenReady влияет на задачу release до того как задача выполнится. Это работает и даже в том случае, когда задача release - не основная задача (т.е. задача передаваемая команде gradle).

16.14. Что дальше?

В этой главе мы первый раз взглянули на задачи. Но это не конец истории о задачах. Если вы хотите погрузиться в детали, смотрите Главу 19 Еще о задачах.

В противном случае, продолжите изучение в Главе 46 Быстрый старт Java и Главе 7 Основы управления зависимостями.