Пользователь
0,0
рейтинг
18 ноября 2013 в 15:30

Разработка → Пишем свой Gradle плагин для AnnotatedSql

Вступление



Привет, коллеги. Давно я не писал ничего на Хабр. Вот, решил исправить это досадное недоразумение.

Не так давно я сменил место работы, и проект, над которым я теперь работаю, использует для сборки Gradle. Более того, проект достаточно развесистый и сложный, и Gradle скрипт в нем весьма непростой. Поэтому я решил, что надо подучить Gradle. Как один из шагов обучения я решил написать свой собственный плагин. Плагин посвящен замечательной библиотеке annotated-sql, созданной моим хорошим товарищем Геннадием hamsterksu. Я использую эту библиотеку в персональных проектах, поэтому мне нужен удобный способ прикреплять и конфигурировать ее к ним. Библиотека использует процессоры аннотаций, поэтому цель плагина — подружить эти процессоры и gradle сборку.

Задача


Перед тем, как приступить к плагину, давайте сначала определим, что мы хотим получить в итоге от плагина.
Библиотека состоит из двух частей — jar с аннотациями, которые мы используем в нашем коде для описания нашей схемы БД и jar с процессорами, который не линкуется в наш код, но используется на этапе компиляции. Наш плагин должен определить, где находятся эти jar's (в идеале, он должен их скачать, но это только после того, как у кого-то из нас дойдут руки их захостить на maven central). После этого он должен настроить процесс компиляции java, чтобы использовать процессоры.

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

Конфигурация


Поскольку мы не хотим линковать наши процессоры к коду, мы определим отдельную конфигурацию зависимостей, назовем ее asqlapt.
configurations {
    asqlapt
}

и сконфигурируем ее.
dependencies {
    asqlapt fileTree(dir: 'libs.apt', include: '*.jar')
}

здесь мы говорим, что зависимости нашего asqlapt находятся в папке libs.apt нашего проекта.

Модификация компиляции


Следующий шаг — вклиниться в компилятор и указать ему на наш процессор. Давайте рассмотрим код, который делает это:
android.applicationVariants.all { variant ->
	def aptOutput = file("$buildDir/source/apt_generated/$variant.dirName")
		
	variant.javaCompile.doFirst {
		aptOutput.mkdirs()
		variant.javaCompile.options.compilerArgs += [
			'-processorpath', configurations.asqlapt.getAsPath(), '-processor',
			'com.annotatedsql.processor.provider.ProviderProcessor,com.annotatedsql.processor.sql.SQLProcessor',
			'-s', aptOutput
		]
	}
}

Итак, для каждого варианта сборки андроид приложения мы выполняем следующие шаги:
  • Определяем папку, в которую поместятся наши сгенерированные классы.
  • Для каждого варианта сборки существует задача javaCompile. Мы можем вклиниться в исполнение этой задачи при помощи метода doFirst. Здесь мы добавляем аргументы компилятору, указываем на путь к процессорам и сами процессоры, а так же папку, куда поместить результат

Вот, в принципе, и все, что нам нужно. Но мы-то хотим оформить это как плагин, не так ли?

Создание плагина



Как написано в документации, наш плагин дожен находиться в папочке buildSrc в корневом проекте. Создадим эту папочку и build.gradle следующего содержания:

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}


теперь объявим наш плагин в файле src/main/resources/META-INF/gradle-plugins/annotatedsql.properties следующим образом:

implementation-class=com.evilduck.annotatedsql.AnnotatedSqlPlugin


С рутиной покончено, теперь к коду. Код плагинов пишется на Groovy.

Создадим наш класс:

public class AnnotatedSqlPlugin implements Plugin<Project>  {

    private Project project

    public void apply(Project project) {
        this.project = project
    }

}


вот так выглядит заготовка плагина. Начнем выполнять действия в методе apply.

Первое, что мы хотим — добавить конфигурации зависимостей:

def setupDefaultAptConfigs() {
        project.configurations.create('apt').with {
            visible = false
            transitive = true
            description = 'The apt libraries to be used for annotated sql.'
        }

        project.configurations.create('annotatedsql').with {
            extendsFrom project.configurations.compile
            visible = false
            transitive = true
            description = 'The compile time libraries to be used for annotated sql.'
        }

        project.dependencies {
            apt project.fileTree(dir: "$project.projectDir/libs-apt", include: '*.jar')
            annotatedsql project.files("$project.projectDir/libs/sqlannotation-annotations.jar")
        }
}


этот метод создает две конфигурации — apt и annotatedsql. Первая — для процессоров, вторая — для API. Далее мы инициализируем эти конфигурации значениями по-умолчанию.

Следующий шаг — настройка компилятора:

def modifyJavaCompilerArguments() {
        project.android.applicationVariants.all { variant ->
            def aptOutput = project.file("$project.buildDir/source/$extension.aptOutputDir/$variant.dirName")

            variant.javaCompile.doFirst {
                aptOutput.mkdirs()

                variant.javaCompile.options.compilerArgs += [
                        '-processorpath', project.configurations.apt.getAsPath(), '-processor',
                        'com.annotatedsql.processor.provider.ProviderProcessor,com.annotatedsql.processor.sql.SQLProcessor',
                        '-s', aptOutput
                ]
            }
        }
}


Тоже ничего нового. Но подождите, что такое extension. Gradle позволяет нам при создании плагинов создавать объекты — расширения. Это простые POGO объекты, хранящие конфигурацию плагина. Самое близкое нам, андроидщикам расширение — android. Да, это как раз тот android обьект, в котором вы конфигурируете свою сборку. Давайте посмотрим, как мы объявили наш extension:

class AnnotatedSqlExtension  {

    public String aptOutputDir

}


и в методе apply плагина:

extension = project.extensions.create("annotatedsql", AnnotatedSqlExtension)
extension.with {
            aptOutputDir = "aptGenerated"
}


инициализируем расширение и прикрепляем его к проекту.

Вот, собственно, и весь плагин.

Полный код
public class AnnotatedSqlPlugin implements Plugin<Project>  {

    private Project project

    private AnnotatedSqlExtension extension

    public void apply(Project project) {
        this.project = project

        project.apply plugin: 'android'

        extension = project.extensions.create("annotatedsql", AnnotatedSqlExtension)
        extension.with {
            aptOutputDir = "aptGenerated"
        }

        setupDefaultAptConfigs()
        modifyJavaCompilerArguments()
    }

    def setupDefaultAptConfigs() {
        project.configurations.create('apt').with {
            visible = false
            transitive = true
            description = 'The apt libraries to be used for annotated sql.'
        }

        project.configurations.create('annotatedsql').with {
            extendsFrom project.configurations.compile
            visible = false
            transitive = true
            description = 'The compile time libraries to be used for annotated sql.'
        }

        project.dependencies {
            apt project.fileTree(dir: "${project.projectDir}/libs-apt", include: '*.jar')
            annotatedsql project.files("${project.projectDir}/libs/sqlannotation-annotations.jar")
        }
    }

    def modifyJavaCompilerArguments() {
        project.android.applicationVariants.all { variant ->
            def aptOutput = project.file("$project.buildDir/source/$extension.aptOutputDir/$variant.dirName")

            variant.javaCompile.doFirst {
                aptOutput.mkdirs()

                variant.javaCompile.options.compilerArgs += [
                        '-processorpath', project.configurations.apt.getAsPath(), '-processor',
                        'com.annotatedsql.processor.provider.ProviderProcessor,com.annotatedsql.processor.sql.SQLProcessor',
                        '-s', aptOutput
                ]
            }
        }
    }

}



Теперь, давайте посмотрим, как мы его используем:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.6.+'
    }
}
apply plugin: 'android'
apply plugin: 'annotatedsql'

repositories {
    mavenCentral()
}

android {
    // наши конфигурации андроид проекта
}

/*
 * Если хотим, мы можем изменить aptOutputDir
 */
//annotatedsql {
//    aptOutputDir = "customAptOutputDir"
//}

/*
 * Если нужно, можем изменить локацию библиотек
 */
//dependencies {
//    apt project.fileTree(dir: "${project.projectDir}/libszzz-apt", include: '*.jar')
//    annotatedsql project.files("${project.projectDir}/libszzz/sqlannotation-annotations.jar")
//}


Как видно, если мы поместили библиотеки в нужное место, все, что нам нужно добавить в скрипт, это

apply plugin: 'annotatedsql'


Как я уже говорил, в идеале, если разместить jar'ы библиотеки в центральном репозитории, необходимость в ручном добавлении их в проект отпадет совсем. Gradle просто скачает их и положит сам в укромное место. К сожалению, пока репозиториев нет, и это не есть что-то, что я могу контролировать в плагине. Однако, если предположить, что библиотеки были загружены в репозиторий, всё, что нам нужно было бы сделать — это изменить локальные dependencies на удаленные. Что-то вроде:

project.dependencies {
            apt 'com.hamsterksu.asql:something-version'
            annotatedsql 'com.hamstersku.asql:something-else-version'
}


В этом случае, все, что нам нужно было бы сделать, это добавить плагин в проект (плагин тоже может быть в репозитории, аналогично android плагину) и применить его:

apply plugin: 'annotatedsql'


Завершение


Напоследок хочу сказать, что это вершина айсберга возможностей Gradle. Тем не менее, надеюсь, что кому-то это поможет начать разбираться в создании плагинов для Gradle, да и Gradle в целом. Лично я, чем больше узнаю, тем сильнее и сильнее влюбляюсь в эту систему сборки.
На этом поспешу распрощаться. Всем спасибо за внимание!
Александр @evilduck
карма
63,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (6)

  • –4
    Код плагинов пишется на Groovy.

    И тут-же сразу приводится код на Java. Генильно.
    А вообще плагины можно писать на чем угодно, что поддерживает линковку с Java — это, собственно, сама Java, Groovy и Scala.
    • +1
      Это код на Groovy. В Java этот код бы не скомпилировался. Не нужно быть гением, чтобы заметить это. Еще попытки подколоть?
      • 0
        Удалено
      • 0
        Еще бы ссылки на источники информации :)
        • 0
          В качестве источника инфо пользовался официальной документацией Gradle, в частности, по созданию плагинов, http://www.gradle.org/docs/current/userguide/custom_plugins.html, а также скачал и поизучал исходные коды. Они доступны на этом же сайте, и там есть много готовых плагинов. Я в частности смотрел на Findbugs плагин :)
  • 0
    Понимаю, что оживляю уже старый топик, но не могли бы вы добавить в текст, что вы опубликовали плагин в maven central и его можно подключить вот так в build.gradle:
    // ...
    apply plugin: 'annotatedsql'
    // ...
    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
    // тут может быть еще что-то
            classpath 'com.github.hamsterksu:android-annotatedsql-gradle-plugin:1.7.9'
        }
    }
    
    dependencies {
    // ...
    // подключаем, чтобы сама IDE адекватно показывала подстветку и узнавала классы
        compile 'com.github.hamsterksu:android-annotatedsql-api:1.7.8'
    // ...
    }
    
    

    Это может и очевидно, но я на это потратил несколько часов.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.