Глава 43. Организация логики сборки.

Gradle предоставляет много способов организации вашей логики сборки. Во-первых, вы можете разместить вашу логику сборки прямо в замыкании действия задачи. Если несколько задач делят одну и ту же логику, вы можете вынести ее в метод. Если несколько проектов многопроектной сборки разделяют некоторую логику, вы можете определить метод в родительском проекте. Если логика сборки становится слишком сложной, чтобы ее можно было смоделировать методами, тогда вам, вероятно, стоит реализовать ее с помощью классов. С Gradle это легко. Просто бросьте ваши классы в определенную папку и Gradle автоматически скомпилирует их и разместит в пути к классам вашего сборочного скрипта.

Вот несколько способов организовать вашу логику сборки:

  • Простые объекты Groovy. Вы можете объявить и использовать простые объекты Groovy (POGO) непосредственно в вашем сборочном скрипте. В конце концов, сборочный скрипт пишется на Groovy, а он предоставляет кучу превосходных способов организовать код.
  • Наследуемые свойства и методы. В многопроектной сборке подпроекты наследуют свойства и методы родительского проекта.
  • Внедрение конфигурации. В многопроектной сборке, проект (обычно корневой) может внедрить свойства и методы в другой проект.
  • Проект buildSrc. Киньте исходные коды ваших сборочных классов в определенную папку и Gradle автоматически скомпилирует их и включит в путь к классам сборочного скрипта.
  • Общие скрипты. Задайте конфигурацию во внешней сборке и примените скрипт к нескольким проектам, возможно в различных сборках.
  • Пользовательские задачи. Разместите вашу логику сборки в пользовательской задачи и используйте ее в нескольких местах.
  • Пользовательские плагины. Разместите вашу логику сборки в пользовательском плагине и примените его в нескольких проектах. Плагин должен находиться в пути к классам сборочного скрипта. Для этого вы можете использовать исходные коды сборки или добавить внешнюю библиотеку, которая содержит плагин.
  • Внешние библиотеки. Используйте внешние библиотеки непосредственно в вашем сборочном скрипте.

43.1. Наследуемые свойства и методы.

Любой метод или свойство, определенное в сборочном скрипте проекта, также видимо всем подпроектам. Вы можете это использовать, чтобы определить общие конфигурации и извлечь логику сборки в методы, которые могут быть использованы подпроектами.

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

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)
    }
}
	  

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

> gradle -q show
srcDirName: src/java
srcDir: child/src/java
	  

43.2. Внедренная конфигурация.

Вы можете использовать технику внедрения конфигурации, обсуждавшуюся в Секции 26.1 Межпроектная конфигурация и Секции 26.2 Конфгурация подпроектов, чтобы внедрить свойства и методы в различные проекты. В общем случае, это лучше, чем наследование, по нескольким причинам: внедрение происходит явно, вы можете внедрить различную логику в различные проекты и вы можете внедрить любой тип конфгиурации, такой как хранилища, плагины, задачи и так далее. Следующий пример показывает как это работает.

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

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()
	  

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

> gradle -q show
project: :child1
srcDirName: java
srcDir: child1/java
project: :child2
srcDirName: src/java/legacy
srcDir: child2/src/java/legacy
	  

43.3. Настройка проекта с использованием внешнего скрипта.

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

Пример 43.3. Настройка проекта с использованием внешнего сборочного скрипта

build.gradle

apply from: 'other.gradle'
	  

other.gradle

println "configuring $project"
task hello {
    doLast {
        println 'hello from other script'
    }
}
	  

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

> gradle -q hello
configuring root project 'configureProjectUsingScript'
hello from other script
	  

43.4. Сборка исходных кодов проекта buildSrc.

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

В многопроектной сборке может быть только одна папка buildSrc, которая должна находиться в папке корневого проекта.

Ниже приведен сборочный скрипт, который по умолчанию применяет Gradle к проекту buildSrc:

apply plugin: 'groovy'
dependencies {
    compile gradleApi()
    compile localGroovy()
}
	  

Это означает, что вы можете просто положить ваши исходные коды в эту папку и придерживаться соглашения разметки для проектов Java/Groovy (смотрите Таблицу 47.4 Плагин Java - разметка проекта по умолчанию).

Если вам требуется больше гибкости, вы можете предоставить свой собственный build.gradle. Gradle применяет сборочный скрипт по умолчанию в не зависимости от того, указан ли другой или нет. Это означает, что вам необходимо объявить только дополнительные вещи. Ниже пример. Обратите внимание, что в этом примере нет необходимости объявлять зависимость от Gradle API, так как это сделано сборочным скриптом по умолчанию:

Пример 43.4. Пользовательский сборочный скрипт проекта buildSrc

buildSrc/build.gradle

repositories {
    mavenCentral()
}

dependencies {
    testCompile 'junit:junit:4.12'
}
	  

Проект buildSrc может быть многопроектной сборкой, так же как и любая другая обычная сборка. Однако, все проекты, которые должны находиться в пути к классам действительной сборки, должны быть зависимостями runtime корневого проекта в buildSrc. Вы можете сделать это, добавить следующее к конфигурации каждого проекта, который хотите экспортировать:

Пример 43.5. Добавление подпроектов в корневой проект buildSrc

buildSrc/build.gradle

rootProject.dependencies {
  runtime project(path)
}
	  

Примечание: код этого примера можно найти в папке samples/multiProjectBuildSrc дистрибутива Gradle '-all'.

43.5. Запуск еще одной сборки из сборки.

Вы можете использовать задачу GradleBuild. Вы можете использовать свойство dir или buildFile для указания того, какую сборку выполнять и свойство tasks для указания того, какие задачи выполнять.

Пример 43.6. Запуск еще одной сборки из сборки

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

> gradle -q build
hello from the other build.
	  

43.6. Внешние зависимости сборочного скрипта.

Если вашему скрипту требуются внешние библиотеки, вы можете добавить их в путь к классам в самом сборочном скрипте. Вы делаете это с помощью метода buildscript(), передавая замыкание, которое задает путь к классам сборочного скрипта.

Пример 43.7. Объявление внешних зависимостей сборочного скрипта

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
    }
}
	  

Замыкание, переданное в метод buildscript(), настраивает экземпляр ScriptHandler. Вы задаете путь к классам, добавляя зависимости к конфигурации classpath. Таким же способом вы задаете, например, путь к классам компиляции Java. Вы можете использовать любой тип зависимости, описанный в Секции 25.4 Как объявить ваши зависимости, за исключением зависимостей проекта.

Пример 43.8. Сборочный скрипт с внешними зависимостями

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

> gradle -q encode
aGVsbG8gd29ybGQK
	  

В многопроектных сборках зависимости, объявленные методом проекта buildscript(), доступны в сборочных скриптах всех подпроектов.

Зависимости сборочного скрипта могут быть плагинами Gradle. Пожалуйста, обратитесь к Главе 27 Плагины Gradle, чтобы узнать больше о плагинах Gradle.

У каждого проекта есть задача buildEnvironment типа BuildEnvironmentReportTask, которую можно выполнить и получить отчет о разрешение зависимостей сборочного скрипта.

43.7. Необязательные зависимости Ant.

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

Пример 43.9. Необязательные зависимости Ant

build.gradle

configurations {
    ftpAntTask
}

dependencies {
    ftpAntTask("org.apache.ant:ant-commons-net:1.9.6") {
        module("commons-net:commons-net:1.4.1") {
            dependencies "oro:oro:2.0.8:jar"
        }
    }
}

task ftp {
    doLast {
        ant {
            taskdef(name: 'ftp',
                    classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
                    classpath: configurations.ftpAntTask.asPath)
            ftp(server: "ftp.apache.org", userid: "anonymous", password: "me@myorg.com") {
                fileset(dir: "htdocs/manual")
            }
        }
    }
}
	  

Также это хороший пример использования клиентских модулей. В этом случае, файл POM в Maven Central для задачи ant-commons-net task не предоставляет верной информации.

43.8. Заключение.

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