GrabDuck

Gradle и решение задач автоматизации

:

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

Лирика

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

Первое, что в нём понравилось, это наконец-то «прощай xml», о да, для меня это действительно как заново родиться, как же было невероятно приятно писать сборку на настоящем языке программирования, какая же это свобода и вдохновение.

В статье будут использоваться некоторые понятия из OSGI и eclipse rcp, я не буду заострять на них внимание, чтобы не отвлекаться от главной темы.

В приложении есть простенький проект с примерами скриптов и некоторыми исходниками используемыми в статье: тынц

Масштаб проблемы

Итак, что у нас было вначале: у нас имеется большая (по моим меркам) система запускаемая на OSGI платформе, проекты в системе это eclipse plug-in проекты, никакой интеграции с системой сборки нет, в качестве системы сборки используется ant, системы контроля зависимостей не было. Была задача сделать всё круто.

Итак я предложил Gradle мне дали добро, и началось.

Описание структуры проектов

Суть проблемы в том, что имеется директория с некой структурой в которой хранятся проекты.
Первое что нужно было сделать, это описать в скриптах сборки структуру директории и все имеющиеся проекты. Я это смог сделать с легкостью. В Gradle для этого используется понятие multi project. В корневой директории создаем файл settings.gradle и записываем пути к каждой папке где лежат проекты разделитель пути «:». Пример settings.gradle:
include "subproject-first"
include "subproject-first:child-first"
include "subproject-first:child-first:another-child:last-child"
include "subproject-first:child-first:another-child:another-last-child"

include "subproject-second"

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

gradle projects -q

Результат:

image

Настройка IDE

Следующим шагом была возможность импорта проектов в IDE. В Gradle есть плагины для idea и eclipse:
тынц
барабынц

У нас проекты очень жестко завязаны на eclipse из-за osgi, и куска eclipse-rcp который не позволял просто так взять target platform (это такая страшная штука из мира osgi, по сути это osgi framework и папка с кучей jar-ников) и использовать её в idea, но в общем это лирическое отступление.

Взяв eclipse плагин я смог настроить проекты именно так как мне надо было, а надо было мне сделать проекты как eclipse plug-in, прописать в настройках plugin dependencies, указать версию используемого компилятора. Пример настройки:

project.apply plugin: 'java'
project.apply plugin: 'eclipse'
project.apply plugin: 'idea'

project.eclipse {
    jdt { 
        sourceCompatibility = '1.6' 
    }
        
    project { 
        //применяем натуру и проект становится eclipse plug-in
        natures 'org.eclipse.pde.PluginNature' 
    }

    classpath {
        downloadSources = true
        downloadJavadoc = true
                
        //используем java se 1.6 jre
        containers.clear()
        containers.add("org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6")

        file {
            //ручная правка .classpath добавление plugin-dependencies зависимостей (то есть зависимости от целевой платформы — есть такое понятие в osgi и эклипсе)
            withXml {
                Node node = it.asNode()
                node.appendNode('classpathentry', [kind: 'con', path: 'org.eclipse.pde.core.requiredPlugins'])
            }
        }
    }
}

То есть, как видите настроек для eclipse много, а то что нельзя сделать через api eclipse плагина, можно сделать вручную исправив xml файл настроек.

Зависимости

Следующий шаг, dependency management: документация.

Я раньше думал, что нет ничего лучше dependency managment в maven, сейчас я думаю, что я ошибался. Gradle может использовать зависимости из maven, ivy, и простые папки и файлы, да и вообще при желании можно сделать, что-то очень нестандартное, например использовать ftp сервер в качестве repository. Для примера, как прописываются зависимости:

project(":myproject") {
    dependencies {
        //зависимость от другого проекта
        compile project(":other-my-project")

        //берем зависимости из локальной папки
        compile fileTree(dir: "libs", include: "*.jar")
        //берем зависимость из maven репозитория
        compile 'org.hibernate:hibernate-core:3.6.3.Final'
    }
}

Но это только начало. В Gradle есть понятие configurations — это что-то похожее на группы в которые добавляются зависимости. То есть в нашем примере compile это и есть configuration, которую создает java plugin.

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

Скачивание и распаковка архива:

//получаем путь к temp папке куда будем распаковывать архив
File sdkDir = getSdkDir(sdkVersion)

//создаем необходимые нам конфигурации
project.configurations {
    sdk
    libsFromSdk
}

//прописываем artifactory репозиторий откуда будем брать наш архив
project.repositories {
    maven { url 'http://localrepo:8081/artifactory/repo' } 
    mavenCentral()
    mavenLocal()
}

//прописываем зависимость
project.dependencies {
    sdk group: 'com.kontora', name: 'sdk', version: sdkVersion, ext: 'zip'
}

Configuration cfg = project.configurations.sdk
       
//берем zip файл из зависимости
cfg.files.each { File zipArchive ->
    //извлекаем архив в папку
    project.ant.unzip src: zipArchive, dest: sdkDir.absolutePath
}

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

Подключение зависимостей в проект из распакованного архива:

project.dependencies {
    libsFromSdk project.fileTree(dir: "$pathToSdk/libs", include: "*.jar")
		 …
    //копируем все зависимости из  libsFromSdk в compile конфигурацию
    project.compile  libsFromSdk
}

И итоге мы получим все наши зависимости для компиляции.

Я люблю тебя код, что само по себе и не ново

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

Трям.

После некоторого этапа исследований я написал свой первый плагин. Оказывается можно создать папку buildSrc в корневой папке системы, которая будет java, groovy, scala etc проектом по вашему выбору, и при старте системы сборки, в первую очередь будет скомпилирован проект buildSrc и классами из этого проекта можно будет пользоваться в скриптах сборки. По мне так это гениально.

В итоге, например сейчас у меня имена и пути к проектам прописаны только в одном месте в классе в buildSrc, а все остальные скрипты пользуются этими переменными в которых хранятся имена проектов (конечно разговор о преимуществах и недостатках этого подхода это отдельный разговор), при этом у меня есть авто дополнение кода, я просто пишу в любом месте сборки примерно так: MyProjects.subproject.child.absolutePath. Помимо этого большая часть логики сборки лежит в плагинах там же в buildSrc.
Ниже приведен пример плагина и его вызова в скрипте сборки:

Пример плагина:

package org.gradle.example

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

class MyPlugin implements Plugin<Project>{
	private static final String PLUGIN_EXTENSION = 'my'
	
	Project project;

	@Override
	public void apply(Project project) {
		MyPlugin plugin = project.extensions.create(PLUGIN_EXTENSION, MyPlugin.class)
        plugin.project = project;
	}
	
	public void applyCommonSettings() {
		project.apply plugin: 'java'
		project.apply plugin: 'eclipse'
		project.apply plugin: 'idea'
	}
}

Пример вызова плагина:

project.apply plugin: MyPlugin
my{
    applyCommonSettings()
}
А у вас есть точно такое же только с перламутровыми пуговицами?

И переходя к плагинам Gradle: у нас в системе есть разные проекты с разными структурами и настройками, Это java, flex, android проекты. И всё это хозяйство нужно настраивать по разному для экспорта в IDE. Например у нас бОльшая часть проектов — это Osgi проекты, другая часть — это простые проекты, у некоторых проектов имеется legacy структура (то есть только папка src) и т.д. И тут Gradle позволил легко справиться с этой задачей. Я написал несколько классов в которых указал какие у нас могут быть настройки, написал класс фабрики в котором описал все настройки каждого проекта, и когда происходит импорт проекта в ide, мы получаем эти настройки и применяем их. Примера кода я тут приводить не буду, потому что это просто groovy код с обычной логикой.

Также я смог написать свои собственные валидаторы проверяющие правильность структуры проектов.

Также планируется много всяких прикольных штук. К сожалению нет времени на то, чтобы рассказать про написание unit тестов для gradle плагинов, возможности логгирования и многое другое

Заключение

Я очень доволен гредлом, он оправдал себя на 101% желаю и вам познать радость работы с этой системой.