У 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.