- PVSM.RU - https://www.pvsm.ru -

Использование buildSrc для внедрения дополнительной логики в Gradle

Интересная заметка от Madis Pink в блоге ZeroTurnaround Rebel Labs [1]. Если кто-то вас разбудит посреди ночи и спросит: “какую фичу в Gradle должен знать каждый?” — с уверенностью отвечай, что это buildSrc. Это особый магический Gradle-проект внутри твоего репозитория, доступный всем файлам build.gradle в виде библиотеки .

Описанный далее подход позволяет писать код на удобном тебе JVM-языке, и результат использовать прямо в своих сборочных скриптах. Как бонус, можно покрыть юнит-тестами особо хитрые моменты в этих скриптах. Добро пожаловать под кат!

Привет, мир buildSrc!

Подключить эту фичу просто. Создаем директорию с именем buildSrc в корне проекта (в той же директории, где лежит settings.gradle):

mkdir buildSrc

К этому проекту одновременно применятся и плагин java, и плагин groovy. Поэтому, дальше мы просто делаем стандартную директорию src/main/java и начинаем добавлять туда код.

mkdir -p buildSrc/src/main/java

Теперь можно синхронизировать проект с IDE и создать новый Java-класс в свежеиспеченном проекте buildSrc:

package myapp.gradle;

public class Fun {
  public static void sayHello() {
    System.out.println("Hello from buildSrc!");
  }
}

Остался последний шаг: позвать метод Fun.sayHello из какого-нибудь файла build.gradle. Давайте сделаем простой таск hello в корневом проекте и посмотрим на выхлоп:

import myapp.gradle.Fun

task hello {
  doLast {
    Fun.sayHello()
  }
}

$ ./gradlew hello
> Task :hello
Hello from buildSrc!

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Вот и всё. Метод sayHello — это обычный Java-метод. Поскольку файл build.gradle работает как JVM-приложение, можно звать его из Groovy как любой другой Java-метод.

Пишем свои собственные таски

Переиспользование кода — это хорошо, но весь этот мусор из doLast {} при создании нового таска выглядит мерзко. С помощью buildSrc мы сможем выбросить мусор, создав вместо него специальный класс таска. Свой Gradle-таск — это просто Java-класс, который:

  • Наследуется от org.gradle.api.DefaultTask
  • Имеет публичный метод с аннотацией @TaskAction. Этот код исполняется сразу после запуска нашего таска.

Давайте отрефакторим метод Fun.sayHello в отдельный таск:

package myapp.gradle;

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;

public class HelloTask extends DefaultTask {
    @TaskAction
    public void run() {
        System.out.println("Hello from task " + getPath() + "!");
    }
}

Использовать этот таск из Gradle-скриптов проще некуда:

import myapp.gradle.HelloTask

task hello(type: HelloTask)

$ ./gradlew hello
> Task :hello
Hello from task :hello!

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Превращаем свои таски в полноценные плагины

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

package myapp.gradle;

import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class MyPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        project.getTasks().create("hello", HelloTask.class);
    }
}

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

apply plugin: myapp.gradle.MyPlugin

И да, оно работает!

$ ./gradlew hello
> Task :hello
Hello from task :hello!

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

Теперь у нас есть работающий вручную написанный плагин, который можно использовать в любом из подпроектов. Конечно, описанный выше пример с распечаткой текста на экране — невообразимо малая часть того, что можно устроить в своем проекте с помощью самописных плагинов. Если хочется разобраться в плагинах глубже, очень рекомендую вот эти доклады с Gradle Summit 2017:

Бонус для дочитавших до конца: управление зависимостями с помощью buildSrc

Днем автор этой заметки работает разработчиком в проекте JRebel for Android [4]. Это инструмент для разработчиков, который состоит из плагина для IDE, Gradle-плагина, приложения для Android и обычного приложения на Java SE. Чтобы собрать все эти артефакты, имеется огромный Gradle-проект, состоящий 90+ подпроектов. Все эти подпроекты имеют во многом похожие зависимости, например, commons-io и slf4j-api. Если тебе приходится собирать org.slf4j:slf4j-api:1.7.25 раз за разом, снова и снова, это быстро превращается в нудятину, по типа регулярного наведения порядка дома. То же самое про игру в угадывание правильной версии этих зависимостей.

Обычно, для управления ими предлагается использовать блок ext {} прямо в корне проекта. Пример есть вот в этом ответе на StackOverflow [5]. Обратная сторона такого подхода — в IDE отвалятся автодополнение и навигация по коду.

Мы пошли другим путём — определили зависимости как строковые константы в файле Deps.java внутри buildSrc. С этим подходом в IDE работает и автодополнение, и навигация! Давайте посмотрим как это выглядит на примере небольшого Android-проекта:

package myapp.gradle;

public class Deps {
    public static final String androidPlugin = "com.android.tools.build:gradle:3.0.0-beta6";

    public static final String kotlinVersion = "1.1.50";
    public static final String kotlinPlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:" + kotlinVersion;
    public static final String kotlinRuntime = "org.jetbrains.kotlin:kotlin-stdlib-jre7:" + kotlinVersion;

    public static final String appCompat = "com.android.support:appcompat-v7:26.1.0";
    public static final String constraintLayout = "com.android.support.constraint:constraint-layout:1.0.2";

    public static final String junit = "junit:junit:4.12";
}

Теперь можно ссылаться на них внутри любых файлов build.gradle вот так:

Использование buildSrc для внедрения дополнительной логики в Gradle - 1

Когда кладешь конфигурацию сборки в buildSrc, это ощущается… немного неправильным, что ли. Но преимущества такого подхода сильно перевешивают.

Будете ли вы пользоваться фокусом с buildSrc? Напишите в комментариях! Кроме того, автора можно достать через Twitter @madisp [6] и задать все наболевшие неудобные вопросы. Все примеры из этого поста доступны на GitHub [7].

Пост публикуется с разрешения компании ZeroTunrnaround и автора — Madis Pink. Сотрудники ZeroTurnaround часто присутствуют в программном коммитете [8] конференций JUG.ru Group, выступают как спикеры на конференциях [9] и часто приезжают просто как гости. Читателей Хабра мы тоже приглашаем поучаствовать в JPoint, который пройдет 6-7 апреля 2018 года [10].

Автор: olegchir

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/268853

Ссылки в тексте:

[1] ZeroTurnaround Rebel Labs: https://zeroturnaround.com/rebellabs/using-buildsrc-for-custom-logic-in-gradle-builds/

[2] Designing and writing Gradle plugins by Benjamin Muschko: https://www.youtube.com/watch?v=wLHqAC0Ag4M

[3] Extending the Gradle Android Plugin by Eyal Lezmy: https://www.youtube.com/watch?v=hxMhnOMbkbQ

[4] JRebel for Android: https://zeroturnaround.com/software/jrebel-for-android/

[5] вот в этом ответе на StackOverflow: https://stackoverflow.com/questions/9547170/in-gradle-how-do-i-declare-common-dependencies-in-a-single-place

[6] @madisp: https://twitter.com/madisp

[7] доступны на GitHub: https://github.com/madisp/gradle-buildsrc

[8] программном коммитете: http://2017.jpoint.ru/

[9] выступают как спикеры на конференциях: http://2015.jpoint.ru/talks/arkhipov/

[10] пройдет 6-7 апреля 2018 года: https://jpoint.ru/

[11] Источник: https://habrahabr.ru/post/342914/