задачи
В следующем коде показаны три задачи Gradle, и мы объясним различия между этими тремя позже.
Задача mytask {println "Привет, мир!" } задание mytask {dolast {println "Привет, мир!" }} задача mytask << {println "Привет, мир!" }Моя цель - создать задачу, которая распечатывает "Привет, мир!" Когда он выполняется. Когда я впервые создал задачу, я догадался, что она должна быть написана так:
Задача mytask {println "Привет, мир!" }Теперь попробуйте выполнить эту MyTask, введите Gradle MyTask в командную строку и распечатайте его следующим образом:
пользователь $ gradle mytask Привет, мир! : MyTask в курсе
Эта задача выглядит так, как будто работает. Это печатает «Привет, мир!».
Тем не менее, это не то, что мы ожидали. Давайте посмотрим, почему. Введите задачи Gradle в командной строке, чтобы просмотреть все доступные задачи.
пользователь $ gradle Задачи привет, мир! : Задачи ------------------------------------------------------------ Все задачи, выполняемые из Root Project ---------------------------------------------------------------- Задачи настройки --------------------- Инициализирует новую сборку Градл. [инкубация] ..........
Подожди, почему "Привет, мир!" распечатано? Я просто хотел посмотреть, какие задачи доступны, и не выполнил никаких пользовательских задач!
Причина на самом деле очень проста. Gradle Task имеет две основные этапы в своем жизненном цикле: этап конфигурации и этап выполнения.
Может быть, мои слова не очень точны, но это действительно может помочь мне понять задачи.
Gradle должен настроить задачу перед его выполнением. Тогда вопрос в том, как я узнаю, какой код в моей задаче выполняется во время процесса конфигурации и какой код запускается при выполнении задачи? Ответ заключается в том, что код на верхнем уровне задачи - это код конфигурации, такой как:
Задача mytask {def name = "pavel" // <- Эта строка кода выполнит println "Привет, мир!" //// <- Эта строка кода также будет выполняться на этапе конфигурации}Вот почему, когда я выполняю задачи Gradle, я распечатываю "Привет, мир!" - Потому что код конфигурации выполняется. Но это не тот эффект, который я хочу, я хочу "Привет, мир!" Чтобы распечатать его только тогда, когда я явно называю MyTask. Для достижения этого эффекта самый простой способ - использовать метод задачи#dolast ().
Задача myTask {def Text = 'Привет, мир! // Настройка моей задачи Dolast {println Text // Это выполняется, когда моя задача называется}}Теперь "Привет, мир!" Будет распечатать только тогда, когда я выполняю Gradle MyTask. Круто, теперь я знаю, как настроить и заставить задачу поступить правильно. Есть еще один вопрос. В первоначальном примере означает << символ третьей задачи?
Задача mytask2 << {println "Привет, мир!" }Это на самом деле просто синтаксическая сахарная версия Dolast. Он имеет тот же эффект, что и следующий метод написания:
Задача mytask {dolast {println 'Привет, мир!' // это выполняется, когда моя задача называется}}Тем не менее, все коды таким образом выполняются, и нет конфигурационной части кода, поэтому они более подходят для тех простых задач, которые не требуют конфигурации. Как только ваша задача должна быть настроена, вам все равно нужно использовать версию Dolast.
грамматика
Сценарии Градл написаны на громком языке. Синтаксис Groovy немного похож на Java, я надеюсь, что вы сможете принять его.
Если вы уже знакомы с Groovy, вы можете пропустить эту часть.
В Groovy есть очень важная концепция, что вам нужно понять закрытие (закрытие)
Закрытие
Закрытие является ключом к нашему пониманию Градл. Закрытие - это отдельный блок кода, который может получать параметры, возвращаемые значения или назначаться переменным. Он похож на вызов, который можно понять в Java и Future, и это также похоже на указатель функции, который легко понять. Полем Полем
Ключ заключается в том, что этот код будет выполнен, когда вы его называете, а не когда он создается. См. Пример закрытия:
def myclosure = {println 'Hello World!' } // Выполните наш закрытие ()#output: hello world!Вот закрытие, которое получает параметры:
def myCloure = {String Str -> println str} // Выполните наш закрытый Closlosure ('Hello World!')#output: hello World!Если закрытие получает только один параметр, вы можете использовать его для ссылки на этот параметр:
def MyCloSure = {println it} // Выполните наш закрытие ('Hello World!')#output: hello World!Закрытие, которое получает несколько параметров:
def mycloure = {String Str, int num -> println "$ str: $ num"} // Выполнить наш реконструкция ('My String', 21) #Output: My String: 21Кроме того, тип параметра является необязательным, и приведенный выше пример можно сократить следующим образом:
def mycloure = {str, num -> println "$ str: $ num"} // Выполнить нашу закрытие ('My String', 21) #Output: my String: 21Что круто, так это то, что переменные из текущего контекста можно использовать при закрытии. По умолчанию текущий контекст - это класс, где было создано закрытие:
def myvar = 'Привет, мир!
Другая прохладная точка заключается в том, что контекст закрытия может быть изменен через закрытие#setDelegate (). Эта функция очень полезна:
def myCloSure = {println myvar} // Я ссылаюсь на myvar из MyClass classmyclass m = new myclass () myclosure.setdelegate (m) mycloster () класс myclass {def myvar = 'привет из MyClass!'}#Вывод: Привет из MyClass!Как видите, Myvar не существует при создании закрытия. В этом нет проблем, потому что когда мы выполняем закрытие, Myvar существует в контексте закрытия. В этом примере. Поскольку я изменил его контекст на M до выполнения закрытия, Myvar существует.
Передайте закрытие в качестве параметра
Преимущество закрытия состоит в том, что его можно передать в различные методы, которые могут помочь нам отделить логику выполнения. В предыдущем примере я показал, как передать закрытие к экземпляру класса. Ниже мы рассмотрим различные методы, которые получают закрытие в качестве параметров:
1. Получить только один параметр, а параметр - это метод закрытия: mymethod (myClosure)
2. Если метод получает только один параметр, можно пропустить скобки: MyMethod MyClosure
3. Вы можете использовать встроенное закрытие: mymethod {println 'Hello World'}
4. Метод получения двух параметров: mymethod (arg1, myclosure)
5. Аналогично 4, единственное закрытие встроено: mymethod (arg1, {println 'hello world'})
6. Если последний параметр - закрытие, его можно вывести из скобков: mymethod (arg1) {println 'hello world'}
Здесь я просто хочу напомнить вам, выглядит ли написание 3 и 6 знакомого?
Пример
Теперь, когда мы поняли основной синтаксис, как мы используем его в сценариях Gradle? Давайте посмотрим на следующий пример:
BuildScript {Repositories {jCenter ()} DevingDy {classPath 'com.android.tools.build:gradle:1.2.3'}} allProjects {Repositories {jCenter ()}} Как только вы узнаете синтаксис Groovy, легко ли понять приведенный выше пример?
Во -первых, это метод BuildScript, который получает закрытие:
def BuildScript (закрытие закрытия)
Следующим является метод AllProjects, который также получает параметр закрытия:
def AllProjects (закрытие закрытия)
Другие похожи. Полем Полем
Теперь это кажется гораздо проще, но я не понимаю ни одного, то есть где определяются эти методы? Ответ - проект
Проект
Это ключ к пониманию сценариев Градл.
Заявление блокирует на верхнем уровне сценария сборки сценария сборки, которые будут делегированы в экземпляры проекта, что показывает, что проект именно там, где я ищу.
Поиск метода BuildScript на странице документа проекта найдет сценарий BuildScript {} (блок скрипта). и т.д. Что, черт возьми, блок сценария? Согласно документации:
Блок сценария - это метод, который получает только закрытие в качестве параметра. Продолжайте читать документацию BuildScript. В документе говорится, что делегаты: Scripthandler из BuildScript. То есть мы передаем закрытие метода BuildScript, а окончательный контекст выполнения - Scripthandler. В приведенном выше примере наше закрытие передано в BuildScript вызывает методы (закрытие) и зависимости (закрытие). Поскольку закрытие было поручено Scripthandler, мы будем искать методы зависимостей в Scripthandler.
Были обнаружены пустоты (конфигурация закрытия). Согласно документации, зависимости используются для настройки зависимостей скрипта. И зависимости в конечном итоге были поручены зависимости.
Я видел, насколько широко используются градлы. Понимание поручения очень важно.
Блоки сценариев
По умолчанию многие блоки сценариев предопределены в проекте, но плагин Gradle позволяет нам определять новые блоки сценария сами!
Это означает, что если вы опубликуете {…} на верхнем уровне сценария сборки, но вы не можете найти этот блок сценария или метод в документации Градли, в большинстве случаев это некоторые блоки сценария, определенные в плагине.
Блок сценария Android
Давайте посмотрим на файл Android App/build.gradle по умолчанию:
Применить плагин: 'com.android.application''Android {compilesDkversion 22 BuildToolSversion "22.0.1" DefaultConfig {ApplicationId "com.trickyAndroid.testapp" minsdkversion 16 targetsdkversion 22 versioncode 1 versionname "1.0"} buld getDefaultProguardFile ('proguard android.txt'), 'proguard-dules.pro'}}}Заказ задачи
Я заметил, что большинство проблем, с которыми я столкнулся при использовании Gradle, связаны с порядком выполнения задачи. Очевидно, что если моя сборка будет работать лучше, если моя задача будет выполнена в нужное время. Давайте внимательнее рассмотрим, как изменить порядок выполнения задач.
Devingson
Я думаю, что самый прямой способ объяснить, как вы полагаетесь на другие задачи при выполнении вашей задачи, - это использовать метод Devingson.
Например, в следующем сценарии задача A уже существует. Мы хотим добавить задачу B, и ее выполнение должно быть после выполнения A:
Это очень простой сценарий, предполагая, что определение A и B заключается в следующем:
Задача a << {println 'hello от a a task b << {println' hello from b '} Просто позвоните B.Dependentson A, и это нормально.
Это означает, что до тех пор, пока я выполняю задачу B, задача A будет выполнять сначала.
Paveldudka $ Gradle B: Ахелло из A: Bhello от b
Кроме того, вы можете объявить его зависимости в области конфигурации задачи:
Задача a << {println 'hello from a a task b {devenson a dolast {println' hello from b '}}}}} Что если мы хотим вставить нашу задачу в существующую зависимость от задачи?
Процесс похож на то, что только сейчас. Предположим, что следующие зависимости задачи уже существуют:
Задача a << {println 'hello от a task b << {println' hello from b '} task c << {println' hello from c '} b.dependentson ac.dependentson bПрисоединяйтесь к нашей новой задаче
Задача B1 << {println 'hello from b1'} b1.dependson bc.dependson b1Выход:
Paveldudka $ Gradle C: Ahello из A: Bhello от B: B1Hello от B1: Chello из C
Обратите внимание, что Devintson добавляет задачи в зависимую коллекцию, поэтому не проблема полагаться на несколько задач.
Задача B1 << {println 'hello from b1'} b1.dependson bb1.dependson qВыход:
Paveldudka $ Gradle B1: Ахелло из A: Bhello от B: Qhello из Q: B1Hello от B1
Муструнафтер
Теперь предположим, что у меня есть еще одна задача, которая зависит от двух других задач. Здесь я использую реальный сценарий, в котором у меня есть две задачи, одна задача, протестированная в единице и одна проверенная задача пользовательского интерфейса. Существует также задача, которая запускает все тесты, которая зависит от двух предыдущих задач.
UNIT TARSE << {println 'Hello From Unit Tests'} Task ui << {println 'hello from tests'} tests tests << {println 'hello от всех тестов!'} tests.dependson unittests.dependson uiВыход:
Paveldudka $ gradle tests: uihello из тестов пользовательского интерфейса: Unithello из модульных тестов: TestShello из всех тестов!
Хотя Univerest и тест пользовательского интерфейса будет выполняться до задачи тестирования, порядок выполнения единицы и пользовательского интерфейса не может быть гарантирован. Несмотря на то, что сейчас он выполняется в порядке алфавита, это зависит от реализации Gradle, и вы не должны полагаться на этот заказ в своем коде.
Поскольку время тестирования пользовательского интерфейса намного длиннее, чем время для модульного теста, я хочу, чтобы модульный тест был выполнен в первую очередь. Одним из решений является заставить задачу пользовательского интерфейса зависеть от задачи устройства.
UNIT TASCE << {println 'Hello From Unit Tests'} Task UI << {println 'Hello From Tests'} tests tests << {println 'hello от всех тестов!'} tests.dependson unittests.dependson uiui.dependson // <- я добавил эту зависимость.Выход:
Paveldudka $ gradle tests: Unithello из модульных тестов: Uihello из тестов пользовательского интерфейса: TestShello из всех тестов!
Теперь модульный тест будет выполнен до теста пользовательского интерфейса.
Но здесь есть очень отвратительная проблема. Мой тест пользовательского интерфейса на самом деле не полагается на модульный тест. Я надеюсь, что смогу выполнить тест пользовательского интерфейса отдельно, но здесь каждый раз, когда я выполняю тест пользовательского интерфейса, я сначала выполню модульный тест.
Муструнафтер нужен здесь. Musstrunafter не добавляет зависимости, он просто сообщает Грэдл приоритет исполнения, если две задачи существуют одновременно. Например, мы можем указать здесь UI.Mustrunafter Unit. Таким образом, если задача и задача пользовательского интерфейса существуют одновременно, Gradle сначала выполнит модульный тест, и если будет выполнено только пользовательский интерфейс Gradle, задача устройства не будет выполнена.
Unit antage << {println 'Hello From Unit Tests'} Task UI << {println 'Hello From Tests'} tests tests << {println 'hello от всех тестов!Выход:
Paveldudka $ gradle tests: Unithello из модульных тестов: Uihello из тестов пользовательского интерфейса: TestShello из всех тестов!
Отношения зависимости следующие:
Musstrunafter в настоящее время является экспериментальной особенностью в Gradle 2.4.
Дополнительно
Теперь у нас есть две задачи, единица и пользовательский интерфейс, предполагая, что обе задачи выводят отчеты о тестировании, теперь я хочу объединить эти два отчета о испытаниях в одну:
task unit << {println 'Hello from unit tests'}task ui << {println 'Hello from UI tests'}task tests << {println 'Hello from all tests!'}task mergeReports << {println 'Merging test reports'}tests.dependsOn unittests.dependsOn uiui.mustRunAfter unitmergeReports.dependsOn testsТеперь, если я хочу получить отчеты о тестировании для пользовательского интерфейса и устройства, выполните задачу Mergereports.
Paveldudka $ Gradle MergeReports: Unithello из модульных тестов: Uihello из тестов пользовательского интерфейса: TestShello из всех тестов!
Эта задача работает, но она выглядит так глупо. MergeReports не чувствует себя особенно хорошим с точки зрения пользователя. Я хочу выполнить задачу Tests, чтобы получить отчет о тестировании, не узнавая о существовании MergeReports. Конечно, я могу перенести логику слияния в задачу тестов, но я не хочу делать задачу для тестов слишком раздутым, поэтому я буду продолжать помещать логику слияния в задачу MergeReports.
Finalizeby здесь, чтобы спасти сцену. Как следует из названия, Finalizeby - это задача, которая будет выполнена после выполнения задачи. Измените наш сценарий следующим образом:
UNIT UNIT << {println 'Hello From Unit Tests'} Task ui << {println 'hello from tests ui'} task -tests << {println 'hello из всех тестов!'} Задача MergeReports << {println 'reports tests'} tests.dependson unitests.dependson uiui.mustrunafter nepportssonssonssonssonssonssonssonssonssonsson. Tests.finalizedby MergeReportsТеперь выполните задачу тестов, и вы можете получить отчет о тестировании:
Paveldudka $ gradle tests: Unithello из модульных тестов: Uihello из тестов пользовательского интерфейса: TestShello из всех тестов!