Вступление
Привет, коллеги. Давно я не писал ничего на Хабр. Вот, решил исправить это досадное недоразумение.
Не так давно я сменил место работы, и проект, над которым я теперь работаю, использует для сборки 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 { 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") compile project.configurations.annotatedsql } }
этот метод создает две конфигурации — 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 { 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") compile project.configurations.annotatedsql } } 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 в целом. Лично я, чем больше узнаю, тем сильнее и сильнее влюбляюсь в эту систему сборки.
На этом поспешу распрощаться. Всем спасибо за внимание!
ссылка на оригинал статьи http://habrahabr.ru/post/202480/
Добавить комментарий