У Gradle уже есть мощный DSL, но Gradle не был бы Gradle, если бы мы сами не смогли расширить DSL. Возможно, у нас есть собственные соглашения об именах в нашей компании, или у нас есть специальный проблемный домен, который мы хотим выразить в скрипте сборки Gradle. Мы можем использовать ExtensionContainer, доступные через project.extensions, чтобы добавить новые концепции в наши сценарии сборки. На вебинаре по стандартизации вашей корпоративной среды Люком Дейли (Luke Daley) показано несколько примеров того, как расширить DSL. Также в samples папке дистрибутива Gradle приведены примеры того, как создать пользовательский DSL.
Давайте сначала создадим простое расширение DSL. Сначала мы определим новый класс CommonDependencies с методами для определения зависимостей в проекте Java. Мы хотим использовать эти методы с описательными именами в наших скриптах сборки. Чтобы добавить класс, мы используем create() метод ExtensionContainer. Первый аргумент — это имя, которое должно быть уникальным в сборке. Имя может использоваться вместе с блоком конфигурации в скрипте для вызова методов класса, который мы передаем в качестве второго аргумента. Наконец, мы можем передать аргументы конструктора для класса как последние аргументы create() метода.
/**
* Class for DSL extension. A default repository is added
* to the project. The use<name>() methods add
* dependencies to the project.
*/
class CommonDependencies {
/** Reference to project, so we can set dependencies/repositories */
final Project project
CommonDependencies(final Project project) {
this.project = project
// Set mavenCentral() repository for project.
project.repositories {
mavenCentral()
}
}
/**
* Define Spock for testCompile dependency
* @param version Version of Spock dependency with default 0.7-groovy-2.0
*/
void useSpock(final String version = '0.7-groovy-2.0') {
project.dependencies {
testCompile "org.spockframework:spock-core:$version"
}
}
/**
* Define Spring for compile dependency
* @param version Version of Spring dependency with default 3.2.3.RELEASE
*/
void useSpring(final String version = '3.2.3.RELEASE') {
project.dependencies {
compile "org.springframework:spring-core:$version"
}
}
}
// Add DSL extension 'commonDependencies' with class CommonDependencies
// passing project as constructor argument.
project.extensions.create('commonDependencies', CommonDependencies, project)
apply plugin: 'java'
// Use new DSL extension. Notice we can use configuration closures just
// like we are used to with other Gradle DSL methods.
commonDependencies {
useSpock()
useSpring '3.1.4.RELEASE'
}
// We can still use the Java plugin dependencies configuration.
dependencies {
compile 'joda-time:joda-time:2.1'
}
Мы можем вызвать dependencies задачу из командной строки, и мы видим, что все зависимости разрешены правильно:
$ gradle dependencies ... compile - Compile classpath for source set 'main'. +--- org.springframework:spring-core:3.1.4.RELEASE | +--- org.springframework:spring-asm:3.1.4.RELEASE | \--- commons-logging:commons-logging:1.1.1 \--- joda-time:joda-time:2.1 ... testCompile - Compile classpath for source set 'test'. +--- org.springframework:spring-core:3.1.4.RELEASE | +--- org.springframework:spring-asm:3.1.4.RELEASE | \--- commons-logging:commons-logging:1.1.1 +--- joda-time:joda-time:2.1 \--- org.spockframework:spock-core:0.7-groovy-2.0 +--- junit:junit-dep:4.10 | \--- org.hamcrest:hamcrest-core:1.1 -> 1.3 +--- org.codehaus.groovy:groovy-all:2.0.5 \--- org.hamcrest:hamcrest-core:1.3
Мы также можем использовать плагин для расширения Gradle DSL. В коде плагина мы используем тот же project.extensions.create() метод, чтобы он был более прозрачным для пользователя. Нам нужно только применить плагин к проекту, и мы можем использовать дополнительные методы DSL в сценарии сборки. Давайте создадим простой плагин, который расширит DSL концепцией книги и глав. Следующий скрипт сборки показывает, что мы можем сделать после применения плагина:
apply plugin: 'book'
book {
title 'Groovy Goodness Notebook'
chapter project(':chapter1')
chapter project(':chapter2')
}
Для этого мы сначала создаем следующую структуру каталогов с файлами:
+ sample + buildSrc + src/main/groovy/com/mrhaki/gradle + Book.groovy + BookPlugin.groovy + src/main/resources/META-INF/gradle-plugins + book.properties + book + build.gradle + chapter1/src/html + index.html + chapter2/src/html + index.html + settings.gradle
Book Класс будет добавлен в качестве расширения DSL. У класса есть метод для установки title свойства и метод для добавления глав, которые являются объектами проекта Gradle.
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/Book.groovy
package com.mrhaki.gradle
import org.gradle.api.*
class Book {
String title
List<Project> chapters = []
void title(final String title) {
this.title = title
}
void chapter(final Project chapter) {
chapters << chapter
}
}
Далее мы создаем BookPlugin класс. Плагин добавит Book класс как расширение DSL. Но мы также создаем задачу, aggregate которая будет посещать каждую определенную главу и затем копировать содержимое из scr/html папки в проекте главы в aggregate папку в папке сборки. Наконец, мы добавляем dist задачу, которая будет просто архивировать содержимое агрегированных файлов.
// File: buildSrc/src/main/groovy/com/mrhaki/gradle/BookPlugin.groovy
package com.mrhaki.gradle
import org.gradle.api.*
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.Zip
class BookPlugin implements Plugin<Project> {
void apply(Project project) {
project.configure(project) {
apply plugin: 'base'
def book = project.extensions.create 'book', Book
afterEvaluate {
// Create task in afterEvaluate, so chapter projects
// are resolved, otherwise chapters is empty.
tasks.create(name: 'aggregate') {
// Skip task if no chapters are defined.
onlyIf { !book.chapters.empty }
// Copy content in src/html of 'book' directory.
copy {
from file('src/html')
into file("${buildDir}/aggregate")
}
// Copy content in src/html of chapter directories.
book.chapters.each { chapterProject ->
copy {
from chapterProject.file('src/html')
into file("${buildDir}/aggregate/${chapterProject.name}")
}
}
}
}
tasks.create(name: 'dist', dependsOn: 'aggregate', type: Zip) {
from file("${buildDir}/aggregate")
}
}
}
}
Мы создаем файл, book.properties чтобы сообщить Gradle о нашем новом плагине:
# File: buildSrc/src/main/resources/META-INF/gradle-plugins/book.properties implementation-class=com.mrhaki.gradle.BookPlugin
Наш плагин закончен, поэтому мы можем добавить проект книги и некоторые проекты глав. В settings.gradle файле мы определяем включение для этих каталогов:
// File: settings.gradle include 'chapter1' include 'chapter2' include 'book'
В каталогах глав мы можем добавить некоторые примеры содержимого в src/html каталогах. И в book папке мы создаем следующий build.gradle файл:
// File: book/build.gradle
apply plugin: 'book'
book {
title 'Groovy Goodness Notebook'
chapter project(':chapter1')
chapter project(':chapter2')
}
Теперь из book папки мы можем запустить aggregate и dist задачи. Конечный результат — все файлы из src/html папки раздела находятся в build/aggregate папке. И в build/distributions папке у нас есть файл, book.zip содержащий файлы.
Код написан с Gradle 1.6.