Глава 18. Написание сборочных скриптов.

В этой главе мы посмотрим на некоторые детали написания сборочного скрипта.

18.1 Язык сборки Gradle.

Gradle предоставляет предметно-ориентированный язык или DSL (Domain Specific Language) для описания сборок. Этот язык сборки основан на Groovy, с некоторыми добавлениями, делающими описание сборки более легким.

Сборочный скрипт может содержать любой элемент языка Groovy. Gradle предполагает, что любой сборочный скрипт использует кодировку UTF-8.

18.2 API проекта.

В Главе 46 Быстрый старт Java, мы используем, например, метод apply(). Откуда же он пришел? Ранее мы говорили, что сборочный скрипт определяет проект в Gradle. Для каждого проекта в сборке, Gradle создает объект типа Project и связывает объект Project со сборочным скриптом. Когда сборочный скрипт выполняется, он настраивает объект Project:

  • Вызов любого метода в вашем сборочном скрипте, который не определен, перенаправляется к объекту Project.
  • Обращение к любому свойству в сборочном скрипте, которое еще не определено, перенаправляется к объекту Project.

Давайте попробует это проверить и получить доступ к свойству name объекта Project.

Помощь в написании сборочных скриптов

Всегда помните, что ваш сборочный скрипт, всего лишь Groovy код, управляет API Gradle. И интерфейс Project стартовая точка для получения доступа ко всему в API Gradle. Так что, если вы гадаете, что за 'tags' доступны в ваше сборочном скрипте, можете начать с документации по интерфейсу Project.

Пример 18.1. Получение доступа к свойству объекта Project

build.gradle

println name
println project.name
	  

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

> gradle -q check
projectApi
projectApi
	  

Оба утверждения println печатают одно и то же свойство. Первое использует автоперенаправление к объекту Project для свойств, не определенных в сборочном скрипте. Другое использует свойство project доступное любому скрипту сборки, которое возвращает связанные объект Project. Только, если вы создаете свойство или метод с таким же именем как член объекта Project, тогда вам нужно использовать свойство project.

18.2.1. Стандартные свойства проекта.

У объекта Project есть несколько стандартных свойств, которые доступны в вашем сборочном скрипте. В таблице ниже перечеслены несколько часто используемых.

Таблица 18.1. Свойства проекта
ИмяТипЗначение по умолчанию
projectProjectЭкземпляр объекта Project.
nameStringИмя папки проекта.
pathStringАбсолютный путь к проекту.
descriptionStringОписание проекта.
projectDirFileПапка, содержащая сборочный скрипт.
buildDirFileprojectDir/build.
groupObjectНе определено.
versionObjectНе определено.
antAntBuilderЭкземпляр объекта AntBuilder.

18.3 Script API.

Когда Gradle выполняет скрипт, он его компилирует в класс, который реализует Script. Это означает, что все свойства и методы, объявленные в интерфейсе Script доступны в вашем скрипте.

18.4 Объявление переменных.

Есть два тип переменных, которые могут быть объявлены в сборочном скрипте: локальные переменные и дополнительные свойства.

18.4.1. Локальные переменные.

Локальные переменные объявляются ключевым словом def. Они видны только в области определения. Локальные переменные - это свойство нижележащего языка Groovy.

Пример 18.2. Использование локальных переменных

build.gradle

def dest = "dest"

task copy(type: Copy) {
    from "source"
    into dest
}
	  

18.4.2. Дополнительные свойства.

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

Пример 18.3. Использование дополнительных свойств

build.gradle

apply plugin: "java"

ext {
    springVersion = "3.1.0.RELEASE"
    emailNotification = "build@master.org"
}

sourceSets.all { ext.purpose = null }

sourceSets {
    main {
        purpose = "production"
    }
    test {
        purpose = "test"
    }
    plugin {
        purpose = "production"
    }
}

task printProperties {
    doLast {
        println springVersion
        println emailNotification
        sourceSets.matching { it.purpose == "production" }.each { println it.name }
    }
}
	  

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

> gradle -q printProperties
3.1.0.RELEASE
build@master.org
main
plugin
	  

В этом примере, блок ext добавляет два дополнительных свойства к объекту project. К тому же, свойство с именем purpose, добавленное к каждому набору исходных кодов, устанавливается ext.purpose в null (null - допустимое значение). Как только свойства добавлены, их можно считывать и устанавливать как предопределенные свойства.

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

Чтобы узнать детали о дополнительных свойствах и их API, смотрите класс ExtraPropertiesExtension в документации по API.

18.5 Настройка произвольных объектов.

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

Пример 18.4. Настройка произвольных объектов

build.gradle

task configure {
    doLast {
        def pos = configure(new java.text.FieldPosition(10)) {
            beginIndex = 1
            endIndex = 5
        }
        println pos.beginIndex
        println pos.endIndex
    }
}
	  

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

> gradle -q configure
1
5
	  

18.6 Настройка произвольных объектов, используя внешний скрипт.

Вы можете настраивать произвольные объекты, используя внешний скрипт.

Пример 18.5. Настройка произвольных объектов с использованием скрипта

build.gradle

task configure {
    doLast {
        def pos = new java.text.FieldPosition(10)
        // Apply the script
        apply from: 'other.gradle', to: pos
        println pos.beginIndex
        println pos.endIndex
    }
}
	  

other.gradle

	    // Set properties.
beginIndex = 1
endIndex = 5
	  

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

> gradle -q configure
1
5
	  

18.7 Некоторые основы Groovy.

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

18.7.1. Groovy JDK.

Groovy добавляет кучу полезных методов в стандартные классы Java. Например, Iterable получает метод each, который последовательно проходит по всех элементам Iterable:

Пример 18.6. Методы Groovy JDK

build.gradle

// Iterable gets an each() method
configurations.runtime.each { File f -> println f }
	  

Взгляните на http://groovy-lang.org/gdk.html, чтобы узнать детали.

18.7.2. Аксессоры свойств.

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

Пример 18.7. Аксессоры свойств

build.gradle

// Using a getter method
println project.buildDir
println getProject().getBuildDir()

// Using a setter method
project.buildDir = 'target'
getProject().setBuildDir('target')
	  

18.7.3. Необязательные круглые скобки в вызовах методов.

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

Пример 18.8. Вызов метода без круглых скобок

build.gradle

test.systemProperty 'some.prop', 'value'
test.systemProperty('some.prop', 'value')
	  

18.7.4. Литералы листов (List) и ассоциативных массивов (Map).

Groovy предоставляет некоторые сокращения для задания экземпляров List и Map. Оба типа литералов прямолинейны, но литералы ассоциативных массивов имеют некоторые интересные ухищрения.

Например, метод 'apply' (который обычно применяет плагины) в действительности принимает ассоциативный массив. Однако, когда у вас есть строка наподобие 'apply plugin: 'java'', в действительности не используется литерал ассоциативного массива, а используются 'именованные параметры', синтакс которых почти такой же, как у литералов ассоциативных массивов(без окружающие квадратных скобок). Список именованных параметров преобразовывается в ассоциативный массив, когда метод вызывается, но сначала он им не является.

Пример 18.9. Литералы списков и ассоциативных массивов

build.gradle

// List literal
test.includes = ['org/gradle/api/**', 'org/gradle/internal/**']

List list = new ArrayList()
list.add('org/gradle/api/**')
list.add('org/gradle/internal/**')
test.includes = list

// Map literal.
Map map = [key1:'value1', key2: 'value2']

// Groovy will coerce named arguments
// into a single map argument
apply plugin: 'java'
	  

18.7.5. Замыкания в качестве последнего параметра метода.

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

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

build.gradle

repositories {
    println "in a closure"
}
repositories() { println "in a closure" }
repositories({ println "in a closure" })
	  

18.7.6. Объект delegate замыкания.

Каждое замыкание имеет объект delegate, который Groovy использует для поиска ссылок на переменные и методы, которые не являются локальными переменными или параметрами замыкания. Gradle использует это для настройки замыканий, где объекту delegate присваивается объект, который надо настроить.

Пример 18.11. Объекты delegate замыкания

build.gradle

dependencies {
    assert delegate == project.dependencies
    testCompile('junit:junit:4.12')
    delegate.testCompile('junit:junit:4.12')
}
	  

18.8 Импорты по умолчанию.

Для того, чтобы сделать сборочные скрипты более краткими, Gradle автоматически добавляет туда набор импортов. Это означает, что вместо использования throw new org.gradle.api.tasks.StopExecutionException(), вы можете просто напечатать throw new StopExecutionException().

В списке ниже перечислены импорты, добавляемые к каждому скрипту:

Рисунок 18.1. gradle-imports
import org.gradle.*
import org.gradle.api.*
import org.gradle.api.artifacts.*
import org.gradle.api.artifacts.cache.*
import org.gradle.api.artifacts.component.*
import org.gradle.api.artifacts.dsl.*
import org.gradle.api.artifacts.ivy.*
import org.gradle.api.artifacts.maven.*
import org.gradle.api.artifacts.query.*
import org.gradle.api.artifacts.repositories.*
import org.gradle.api.artifacts.result.*
import org.gradle.api.artifacts.transform.*
import org.gradle.api.attributes.*
import org.gradle.api.component.*
import org.gradle.api.credentials.*
import org.gradle.api.distribution.*
import org.gradle.api.distribution.plugins.*
import org.gradle.api.dsl.*
import org.gradle.api.execution.*
import org.gradle.api.file.*
import org.gradle.api.initialization.*
import org.gradle.api.initialization.dsl.*
import org.gradle.api.invocation.*
import org.gradle.api.java.archives.*
import org.gradle.api.logging.*
import org.gradle.api.logging.configuration.*
import org.gradle.api.plugins.*
import org.gradle.api.plugins.announce.*
import org.gradle.api.plugins.antlr.*
import org.gradle.api.plugins.buildcomparison.gradle.*
import org.gradle.api.plugins.jetty.*
import org.gradle.api.plugins.osgi.*
import org.gradle.api.plugins.quality.*
import org.gradle.api.plugins.scala.*
import org.gradle.api.publish.*
import org.gradle.api.publish.ivy.*
import org.gradle.api.publish.ivy.plugins.*
import org.gradle.api.publish.ivy.tasks.*
import org.gradle.api.publish.maven.*
import org.gradle.api.publish.maven.plugins.*
import org.gradle.api.publish.maven.tasks.*
import org.gradle.api.publish.plugins.*
import org.gradle.api.reporting.*
import org.gradle.api.reporting.components.*
import org.gradle.api.reporting.dependencies.*
import org.gradle.api.reporting.dependents.*
import org.gradle.api.reporting.model.*
import org.gradle.api.reporting.plugins.*
import org.gradle.api.resources.*
import org.gradle.api.specs.*
import org.gradle.api.tasks.*
import org.gradle.api.tasks.ant.*
import org.gradle.api.tasks.application.*
import org.gradle.api.tasks.bundling.*
import org.gradle.api.tasks.compile.*
import org.gradle.api.tasks.diagnostics.*
import org.gradle.api.tasks.incremental.*
import org.gradle.api.tasks.javadoc.*
import org.gradle.api.tasks.scala.*
import org.gradle.api.tasks.testing.*
import org.gradle.api.tasks.testing.junit.*
import org.gradle.api.tasks.testing.testng.*
import org.gradle.api.tasks.util.*
import org.gradle.api.tasks.wrapper.*
import org.gradle.authentication.*
import org.gradle.authentication.aws.*
import org.gradle.authentication.http.*
import org.gradle.buildinit.plugins.*
import org.gradle.buildinit.tasks.*
import org.gradle.caching.*
import org.gradle.external.javadoc.*
import org.gradle.ide.visualstudio.*
import org.gradle.ide.visualstudio.plugins.*
import org.gradle.ide.visualstudio.tasks.*
import org.gradle.ivy.*
import org.gradle.jvm.*
import org.gradle.jvm.application.scripts.*
import org.gradle.jvm.application.tasks.*
import org.gradle.jvm.platform.*
import org.gradle.jvm.plugins.*
import org.gradle.jvm.tasks.*
import org.gradle.jvm.tasks.api.*
import org.gradle.jvm.test.*
import org.gradle.jvm.toolchain.*
import org.gradle.language.assembler.*
import org.gradle.language.assembler.plugins.*
import org.gradle.language.assembler.tasks.*
import org.gradle.language.base.*
import org.gradle.language.base.artifact.*
import org.gradle.language.base.plugins.*
import org.gradle.language.base.sources.*
import org.gradle.language.c.*
import org.gradle.language.c.plugins.*
import org.gradle.language.c.tasks.*
import org.gradle.language.coffeescript.*
import org.gradle.language.cpp.*
import org.gradle.language.cpp.plugins.*
import org.gradle.language.cpp.tasks.*
import org.gradle.language.java.*
import org.gradle.language.java.artifact.*
import org.gradle.language.java.plugins.*
import org.gradle.language.java.tasks.*
import org.gradle.language.javascript.*
import org.gradle.language.jvm.*
import org.gradle.language.jvm.plugins.*
import org.gradle.language.jvm.tasks.*
import org.gradle.language.nativeplatform.*
import org.gradle.language.nativeplatform.tasks.*
import org.gradle.language.objectivec.*
import org.gradle.language.objectivec.plugins.*
import org.gradle.language.objectivec.tasks.*
import org.gradle.language.objectivecpp.*
import org.gradle.language.objectivecpp.plugins.*
import org.gradle.language.objectivecpp.tasks.*
import org.gradle.language.rc.*
import org.gradle.language.rc.plugins.*
import org.gradle.language.rc.tasks.*
import org.gradle.language.routes.*
import org.gradle.language.scala.*
import org.gradle.language.scala.plugins.*
import org.gradle.language.scala.tasks.*
import org.gradle.language.scala.toolchain.*
import org.gradle.language.twirl.*
import org.gradle.maven.*
import org.gradle.model.*
import org.gradle.nativeplatform.*
import org.gradle.nativeplatform.platform.*
import org.gradle.nativeplatform.plugins.*
import org.gradle.nativeplatform.tasks.*
import org.gradle.nativeplatform.test.*
import org.gradle.nativeplatform.test.cunit.*
import org.gradle.nativeplatform.test.cunit.plugins.*
import org.gradle.nativeplatform.test.cunit.tasks.*
import org.gradle.nativeplatform.test.googletest.*
import org.gradle.nativeplatform.test.googletest.plugins.*
import org.gradle.nativeplatform.test.plugins.*
import org.gradle.nativeplatform.test.tasks.*
import org.gradle.nativeplatform.toolchain.*
import org.gradle.nativeplatform.toolchain.plugins.*
import org.gradle.platform.base.*
import org.gradle.platform.base.binary.*
import org.gradle.platform.base.component.*
import org.gradle.platform.base.plugins.*
import org.gradle.play.*
import org.gradle.play.distribution.*
import org.gradle.play.platform.*
import org.gradle.play.plugins.*
import org.gradle.play.plugins.ide.*
import org.gradle.play.tasks.*
import org.gradle.play.toolchain.*
import org.gradle.plugin.devel.*
import org.gradle.plugin.devel.plugins.*
import org.gradle.plugin.devel.tasks.*
import org.gradle.plugin.repository.*
import org.gradle.plugin.use.*
import org.gradle.plugins.ear.*
import org.gradle.plugins.ear.descriptor.*
import org.gradle.plugins.ide.api.*
import org.gradle.plugins.ide.eclipse.*
import org.gradle.plugins.ide.idea.*
import org.gradle.plugins.javascript.base.*
import org.gradle.plugins.javascript.coffeescript.*
import org.gradle.plugins.javascript.envjs.*
import org.gradle.plugins.javascript.envjs.browser.*
import org.gradle.plugins.javascript.envjs.http.*
import org.gradle.plugins.javascript.envjs.http.simple.*
import org.gradle.plugins.javascript.jshint.*
import org.gradle.plugins.javascript.rhino.*
import org.gradle.plugins.javascript.rhino.worker.*
import org.gradle.plugins.signing.*
import org.gradle.plugins.signing.signatory.*
import org.gradle.plugins.signing.signatory.pgp.*
import org.gradle.plugins.signing.type.*
import org.gradle.plugins.signing.type.pgp.*
import org.gradle.process.*
import org.gradle.process.daemon.*
import org.gradle.testing.base.*
import org.gradle.testing.base.plugins.*
import org.gradle.testing.jacoco.plugins.*
import org.gradle.testing.jacoco.tasks.*
import org.gradle.testing.jacoco.tasks.rules.*
import org.gradle.testkit.runner.*
import org.gradle.util.*