Monthly Archives: July 2014
Когда я сталкиваюсь с новым языком, мне очень важно знать, что означает та или иная запись, как её правильно разбирать мозгом. Без этого не достичь понимания языка, всё время придётся копаться и шаманить. В случае с градло ́м это недопустимо, так как тыкания и шаманство могут занимать очень уж много времени. И в конце концов всё же придется плюнуть и досконально разобраться с тем, как работают сценарии сборки. Что со мной и произошло. Целый день я провёл в документации, на сетевых местах типа переполнения стека и в открытых источниках программы градл. Вот краткий пересказ усвоенного.
Итак, основной вопрос градла звучит так: что делает этот кусок кода ? То, как отвечать на этот вопрос, мы и рассмотрим.
База
Для начала чуть-чуть перескажу официальную документацию по 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 желательно
– Документация, документация, документация, исходники, документация
Прочёл статью http://nolanlawson.com/2012/10/31/better-synonym-handling-in-solr/. И есть некоторые мысли по этому поводу.
Парень пишет что можно включать SynonymFilterFactory на query и index. В query не работают синонимы из нескольких слов и неправильно бустятся документы, содержащие редкие синонимы (которые по идее должны быть внизу). А в index – ну понятно, распухает индекс, плохо ищется всё и подсветка лагает. Дока по солру таки-рекомендует юзать путь через index. В общем ему пришлось патчить query-парсер чтоб заработали синонимы из нескольких слов.
Моё мнение – в index нельзя врубать синонимы, потому что это концептуально неверно, и порет содержимое документов. Думаю, это в общем довольно сильно портит релевантность, и странно, что этот момент он не описал. Синонимы должны отрабатывать на query, конечно. То, что не пашут синонимы из нескольких слов – это баг солра, должно настраиваться так, чтобы такие синонимы тоже работали. Но в принципе этой штукой можно пренебречь, так как синонимов из 2 слов не больно и много (мы ж не Яндекс делаем). Плюс, он почему-то в качестве синонимов приводит странный пример “собака-дворняга” ну это какбэ не больно и синонимы. Синонимы в солре в моем понимании это вещи, которые на 99% эквивалентны. Айфон и iPhone. И для них проблема буста отпадает. А вообще для решения проблемы с бустом редких синонимов можно применить приём – скопировать поле в поле_exact и добавить по нему дополнительный вес. То есть если запрос точно совпадает с содержимым документа, этот документ будет выше. Соответственно, все синонимичные доки будут ниже, что и требуется.
Так что всё норм. Надо только научить солр парсить синонимы-фразы из запроса. Но в нашем случае даже это не нужно. А вот что реально нужно – учёт словоформ (стемминга) – почему-то у него не отражено в статье. Но в принципе у меня стоит стеммер по словарю OpenOffice + стеммер лайтовый, и он умеет понимать, что “айфонов” == “айфон” == “iPhone”.
Несколько дней ковыряю Scala. Делюсь первыми соображениями по этому поводу.
Отмеченные удобства Scala:
- Лаконичный синтаксис: свойства, очень краткие определения бинов, вывод типов
- Кортежи – и опять же в замечательном лаконичном синтаксисе
- Анонимные функции, замыкания, вложенные функции
- Immutable коллекции из коробки
- Хороший плагин для IDEA (я использую IDEA 14 EAP)
Неудобства:
- Медленная компиляция (хотя у меня классики-то микромаленькие ещё)
- Хоть и хорошая, но всё-таки неидеальная поддержка в IDEA (подсказки включают в себя private методы/конструкторы, часто нет сообщения анализатора кода об ошибках, которые присутствуют, нельзя подсмотреть выведенный тип val/var. иногда кавардак в watches)
- Нет break/continue (есть кривая эмуляция через бросание исключения)
- Нет try-with-resources (есть кривая эмуляция, не включённая в стандартную библиотеку)
- Mutable HashMap и Immutable HashMap – одинаковые имена классов. Неудобно
- Нельзя сделать return в анонимной функции
- Не поддерживается старый добрый цикл for со счётчиком
- Нет операции инкремента и декремента
- Конструкции вида
while ((readed = inputStream.read(buffer)) != -1)
не поддерживаются
В целом довольно быстро втянулся, но пока с коллекциями мрак, всё время тыкаюсь. Некоторую боль доставило отсутствие привычных конструкций в циклах. Но несмотря на все проблемы, оптимизм не угас, что очень радует. Так что продолжаю тестить.
0