Category Archives: Gradle
Сегодня Тимур мне рассказал, что для градла есть удобнейшая штука – враппер. Например, вы используете gradle с версией X (икс). И ваш скрипт сборки точно будет работать на этой версии. Будет ли он работать на других версиях – вопрос. Программист, скачавший ваш репозиторий с кодом и скриптами сборки, вынужден будет установить у себя такую же версию градла. Неудобно. Но градло спешит на помощь ! Можно добавить в build.gradle следующее:
task wrapper(type: Wrapper) { gradleVersion = '2.0' // Желаемая версия } |
и после этого выполнить команду gradle wrapper
, которая сгенерирует следующие файлы:
gradlew gradlew.bat gradle/wrapper/ gradle-wrapper.jar gradle-wrapper.properties |
Эти файлы нужно закоммитить (да-да, джарник тоже, но он крошечный). И теперь чтобы собрать проект, достаточно вместо gradle
использовать gradlew
:
gradlew build |
Враппер сам скачает нужную версию градла, поместит её в .gradle
-кеш и использует её для сборки. Таким образом, пользователю, скачавшему ваш репозиторий, вообще ничего не нужно делать, кроме запуска bat-файла (или shell-скрипта в не-Windows системах) ! Вот такой вот best practice. Разве не блистательно ?
Когда я сталкиваюсь с новым языком, мне очень важно знать, что означает та или иная запись, как её правильно разбирать мозгом. Без этого не достичь понимания языка, всё время придётся копаться и шаманить. В случае с градло ́м это недопустимо, так как тыкания и шаманство могут занимать очень уж много времени. И в конце концов всё же придется плюнуть и досконально разобраться с тем, как работают сценарии сборки. Что со мной и произошло. Целый день я провёл в документации, на сетевых местах типа переполнения стека и в открытых источниках программы градл. Вот краткий пересказ усвоенного.
Итак, основной вопрос градла звучит так: что делает этот кусок кода ? То, как отвечать на этот вопрос, мы и рассмотрим.
База
Для начала чуть-чуть перескажу официальную документацию по DSL. Каждый сценарий суть код конфигурации некоторого объекта. Глобально есть три вида сценариев: Build script, Init script и Settings script. И конфигурируют они соответственно объекты типа Project, Gradle и Settings.
То есть на входе обычного build.gradle мы имеем экземпляр класса Project. У него есть методы и свойства, которые мы можем вызывать. Например, так:
version = '1.0' // Эквивалентно setVersion('1.0) |
Или так:
repositories { mavenCentral() } |
Что происходит здесь ? Здесь вызывается метод Project.repositories(Closure configureClosure)
, в качестве Closure передаётся блок кода в фигурных скобках. Но этот блок вызывается уже не в контексте экземпляра Project, а в контексте экземпляра класса RepositoryHandler
(это описано в документации к методу). Таким образом, этот блок конфигурирует RepositoryHandler. А у него есть метод mavenCentral(Map<String, ?> args)
, который добавляет центральный мейвеновский репозиторий.
В этом кусочке заключена основная идея градла – вы через пробел пишете Closure, который конфигурирует что-то. Внутри этого Closure вам доступны все методы и свойства конфигурируемого объекта. А для того, чтобы знать, с объектом какого класса вы имеете дело в конкретном блоке, нужно пользоваться DSL Reference и JavaDocs.
Но кроме этого есть ещё некий дополнительный синтаксис, который определяется специально. Например здесь мы видим, что после task идет hello, хотя, судя по сигнатуре метода, здесь должна быть строка. К сожалению, в JavaDocs это не описано, и нам нужно смотреть DSL Reference.
Погружение
Разберём более сложный пример – сборку WAR с включённой фильтрацией XML-ресурсов. Если вы имели дело с ant или maven, то вы наверняка сталкивались с этой задачей. В градле она записывается так:
apply plugin: 'war' war { filesMatching('WEB-INF/*.xml', { FileCopyDetails fileDetails -> logger.error("File filtered: " + fileDetails.path) filter(ReplaceTokens, tokens: [ "host" : project.property("host"), "port" : project.property("port"), "username": project.property("username"), "password": project.property("password") ]) }) } |
Как это работает ? Смотрим документацию по плагину War. Она говорит о том, что при подключении плагина в project добавляется задача war типа War. К сожалению, не указано, что блок после war конфигурирует именно тип War
, но из контекста примера можно об этом догадаться. В документации к классу находим метод filesMatching(String pattern, Action<? super FileCopyDetails> action)
. Action, как и Closure, Groovy позволяет нам определять блоком. Блок конфигурирует объект класса FileCopyDetails
(обратите внимание на декларацию в начале блока – она позволяет нам оставить себе на будущее напоминание о том, с объектом какого типа мы работаем в этом блоке). Ну а у класса FileCopyDetails есть метод filter(Map<String,?> properties, Class<? extends FilterReader> filterType)
. На самом деле там три сигнатуры этого метода, и внимательный читатель заметит, что ни одна из сигнатур не подходит к тому, что мы написали. Здесь срабатывает хитрый алгоритм разрешения методов груви. Дело в том, что вторым параметром мы передали не мапу, а использовали синтаксис именованных параметров. А груви обрабатывает вызовы, в которых использованы именованные параметры, особым образом – выполняя объединение всех именованных параметров в одну мапу и пихая её в метод первым фактическим аргументом. А все остальные аргументы идут дальше (хотя записаны в вызове они могут быть первыми). Таким образом, вызов получается эквивалентен следующему:
filter([tokens: [ "host" : project.property("host"), "port" : project.property("port"), "username": project.property("username"), "password": project.property("password") ]], ReplaceTokens) |
Очень непривычно работать с такими вещами, но это груви, и придётся привыкать.
Откуда взялось свойство war ?
И снова внимательные телезрители в недоумении – откуда же взялось свойство war
? Ведь нельзя же просто так добавить свойство к классу Project! Ответ мы найдём в документации к DSL :
A project has 5 property ‘scopes’, which it searches for properties. You can access these properties by name in your build file, or by calling the project’s Project.property() method. The scopes are:
…
The extensions added to the project by the plugins. Each extension is available as a read-only property with the same name as the extension.
Так что плагин просто добавил себя в дополнительный синтаксис, обеспечиваемый встроенным поведением класса Project
. Чуть дальше мы видим
The extensions added to the project by the plugins. Each extension is available as a method which takes a closure or Action as a parameter.
Вот и ответ на наш вопрос. Мы можем писать war и потом блок для его конфигурации, что мы и сделали. Пример того, как плагин может себя зарегистрировать, можно найти в документации
Оффтоп: почему “градло”??
Локальный мемчик. Один из наших программистов невзлюбил мейвен и называл его “мавно”. Забавное название пошло в народ, и теперь мы не только обзываем мейвен, но и зовём ant антлом, а gradle – градлом.
Подводим итоги
Итак, тезисно:
– Каждый script block что-то конфигурирует
– Что именно – смотрим в DSL Reference и Java Docs
– Может встречаться дополнительный синтаксис (apply plugin:, task myTaskName), инфа по нему должна быть в DSL Reference
– Нужно что-то похакать / внедриться в процесс ? Смотрим документацию и джавадоки по плагину и таскам, шаманим, дергаем методы
– Знание Groovy желательно
– Документация, документация, документация, исходники, документация
1