Про Gradle для любопытных.


Предыстория.

Вот раньше был Ant. Простой и понятный инструмент для сборки проектов. Открываешь xml-ку и видишь: здесь мы хотим скомпилировать файлы, здесь скопировать всё в папку dist, а здесь сделать jar-ик.

Потом придумали Maven. Это была небольшая революция. Искать и подключать популярные библиотеки стало намного проще. Все стали использовать приблизительно одинаковую структуру проектов (исходники хранились в одной папке, конфигурационные файлы – в другой, тесты – в третьей).

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

Разобраться, как работает Maven; понять, что означают все эти тэги – было весьма затруднительно. Всё взаимодействие ограничивалось, как правило, запуском команды mvn clean install. Для многих это действие означало что-то похожее на взмах волшебной палочки с параллельным произнесением заклинаний: “Expelliarmus” или “Avada Kedavra“.

Любая проблема, требующая каких-либо нестандартных исправлений в pom.xml, первым делом решалась поиском “нужного заклинания” в stackoverflow или Google. Главная причина недовольства, которое высказывали многие программисты, что не понятно как и в какой последовательности всё работает. Например, что нужно сделать, чтобы просто скопировать файлик и затем выложить его на ftp (или запустить обфускатор)?

Тем не менее, Maven постепенно занял свое место в умах людей. Кто-то из любопытствующих и упорных в итоге разобрался в его внутреннем устройстве. Кто-то просто свыкся. Но время идет, мода меняется. Находятся люди, для которых уже и Мaven – это неудобный и даже архаичный инструмент.

Время не щадит никого…

Что пишут учебники про Gradle.

Отчасти мода на Gradle продиктована Google-ом, т.к. Android SDK сейчас во всю использует именно его. Возможно Maven и дальше будет вытесняться Gradle, хотя бы в силу того, что последний синтаксически проще: Вместо того, чтобы писать в pom.xml:

 
  com.squareup.okhttp
  okhttp
  2.2.0


Мы пишем в файле build.gradle:

    compile 'com.squareup.okhttp:okhttp:2.2.0'

Но что такое Gradle на самом деле?
Почти каждому программисту, который только начинает разбираться с Gradle, говорят, что это всего-то “DSL на базе Groovy для сборки проектов”. Дальше приводятся примеры, как сделать свой проект на Java, какую команду запустить для компиляции, как вызвать ant-овые задачи (Да, Gradle фактически из коробки может работать с Ant, что радует).

Первым делом приводят пример файла gradle.build:

task hello {
  doLast {
      println  "hello world"
  }
}

Это пример, который приводится в каждом учебнике по Gradle.

Вроде как создается впечатление, что все понятно. На самом же деле, далеко не каждый Java-программист знаком с синтаксисом Groovy. И далеко не каждый представляет, что такое DSL, или Domain Specific Language, так как в обычной жизни не так уж и часто приходится с ним встречаться. Поэтому объяснение “DSL на Groovy для сборки проектов” создает массу новых вопросов. Очень хочется узнать– а какой именно DSL? А как он сделан? Неужели и правда там просто Groovy?

Поэтому лично мне стало любопытно, как этот Gradle устроен внутри.
Хотя, по правде говоря, если бы Google не внес изменения в свое Android SDK и не перешел бы на Gradle, я бы так и сидел на Ant и Maven.

Что скрывается внутри.

Итак, если грубо, то DSL на Groovy – это просто Groovy-скрипт с набором удобных и готовых функций для сборки проектов. Такой подход – не экзотика. Например, в Scala есть sbt – “свой DSL на Scala”, который используется для этой же цели.

Другими словами, gradle.build – это просто программа на Groovy. Со своей спецификой, с набором библиотек и функций из коробки, но все-таки – программа на Groovy.
Соответственно, если это просто Groovy-скрипт, то у вас так же заработает build.gradle, который содержит только:

println "hello world"

Более того, теоретически вы можете написать и более изощренный скрипт. Например, такой:

def count = 0;
new groovy.swing.SwingBuilder().frame(title:'Frame', size:[300,300], show: true) {
    borderLayout()
    textlabel = label(text:"Click the button!", constraints: java.awt.BorderLayout.NORTH)
    button(text:'Click Me',
         actionPerformed: {count++; textlabel.text = "Clicked ${count} time(s)."; println "clicked"},
         constraints:java.awt.BorderLayout.SOUTH)
}
Thread.sleep(10000);

– который выдаст такую картинку:
graddle_cmd

Конечно, это чисто теоретически. Практического смысла в такого рода скриптах сборки (которые показывают формочки с кнопками) я не вижу и не призываю писать аналогичные скрипты.
Это просто демонстрация того, что Gradle не такой уж страшный, и, по сути, его скрипты – это обычные программы.

С самим языком программирования Groovy я сталкиваюсь крайне редко и последний раз писал на нем очень давно.
Поэтому, возможно, для многих Groovy-программистов следующие вещи будут очевидными. Но (опять-таки), поскольку Gradle-ом пользуются не только гуру Groovy-разработки, думаю, следующие объяснения будет любопытно прочитать многим.

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

То есть вместо:

println("hello")

можно просто написать:

println "hello"

Оба варианта будут работать одинаково.
Рассмотрим еще один пример.

task hello {
  doLast {
      println  “hello world”
  }
}

Мы видим, что doLast – это всего-навсего вызов doLast( // некоторая анонимная функция , точнее – кложура);

task hello {
  doLast ({ 
      println (“hello world”); 
   });
}

Мы вызываем doLast и передаем ей на вход кложур, в котором нужно выполнить println(“hello”);
В качестве иллюстрации вот приблизительная и грубая аналогия того, как это могло бы выглядеть на Java:

task.doLast(new Closure() { 
      public void call() {
             System.out.println(“hello world”);
      }
});

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

Место, куда складываются скомпилированные скрипты, может отличаться. У меня они оказались здесь:
“C:\Users\${user-dir}\.gradle\caches\${gradle-ver}\scripts”.

Итак, приближаемся к самому интересному.

Самое интересное.

Когда мы смотрим на начало скрипта (который приводится в каждом учебнике по Gradle):

task hello {
}

– мы видим и понимаем, что это не Groovy синтаксис!

Что такое hello? Если это строка, то тогда должны быть кавычки: task “hello”.
Если это имя переменной, то откуда она? Она нигде не объявлена. Предопределенной она тоже не может быть, так как название может быть каким угодно. Откуда компилятор может знать, как я назову задачу?
Почему после hello идут фигурные скобки?

В конце концов, что такое task? Если это функция, почему мы передаем непонятное hello? Мы знаем, что скобки можно опускать только от функции одной переменной, а здесь у нас два параметра.
На самом деле нет никакого смысла пытаться разгадать эту загадку, используя знание синтаксиса языка Groovy.

Дело в том, что создатели Gradle перед выполнением скрипта обрабатывают его «напильником». Для любопытных: напильник называется TaskDefinitionScriptTransformer и лежит здесь.

Вот часть его исходного кода:

  private void doVisitMethodCallExpression(MethodCallExpression call) {
           if (!isInstanceMethod(call, "task")) {
               return;
           }
 
           ArgumentListExpression args = (ArgumentListExpression) call.getArguments();
           if (args.getExpressions().size() == 0 || args.getExpressions().size() > 3) {
               return;
           }
 
           // Matches: task {1, 3}
 
           if (args.getExpressions().size() > 1) {
               if (args.getExpression(0) instanceof MapExpression && args.getExpression(1) instanceof VariableExpression) {
                   // Matches: task , , ?
                   // Map to: task(, '', ?)
                   transformVariableExpression(call, 1);
               } else if (args.getExpression(0) instanceof VariableExpression) {
                   // Matches: task , ?
                   transformVariableExpression(call, 0);
               }
               return;
           }
 
           // Matches: task  or task()
 
           Expression arg = args.getExpression(0);
           if (arg instanceof VariableExpression) {
               // Matches: task  or task()
               transformVariableExpression(call, 0);
           } else if (arg instanceof BinaryExpression) {
               // Matches: task   
               transformBinaryExpression(call, (BinaryExpression) arg);
           } else if (arg instanceof MethodCallExpression) {
               // Matches: task 
               maybeTransformNestedMethodCall((MethodCallExpression) arg, call);
           }
       }

Фактически вводится новое, чисто Gradle-овое (ключевое) слово task, которое трансформирует скрипт перед выполнением и делает его Groovy-совместимым.
Сам Task также представляет интересную и довольно простую конструкцию.

Например, при внимательно рассмотрении можно увидеть в нем метод leftShift, который делает то же самое, что и метод doLast. Таким способом заменяется действие оператора “<<" , что объясняет магию выполнения такой конструкции как:

task hello << {
   println(“hello”);
}

Этот пример также можно найти во многих учебниках по Gradle и на просторах интернета.

На этом все. Можно подводить итоги.

Итоги.

Когда мы видим конструкцию:

println 'hello world'

Это всего-навсего вызов:

println("hello world")

Когда мы видим:

doLast {
   // что-то делаем
}

Мы в doLast передаем кложур (анонимную функцию) в качестве аргумента:

doLast ({
   // что-то делаем
})

Когда мы видим:

task hello << {
}

- это магия. Это нужно просто запомнить.

Ну и самое главное. Когда вам кажется, что запуск Gradle-скрипта тормозит, не удивляйтесь. Знайте: в это время может происходить трансформация скрипта и дальнейшая его компиляция в байт-код (т.е. преобразование в class-файл), а этот процесс не всегда быстрый.

Любое использование либо копирование материалов или подборки материалов сайта, элементов дизайна и оформления допускается лишь с разрешения правообладателя и только со ссылкой на источник: programador.ru

Телеграм канал: @prgrmdr
Почта для связи: vit [at] programmisty.com