- Gradle
- Задаём имя APK при компиляции
- Оптимизатор кода R8
- Сжимаем итоговый APK
- Класс BuildConfig
- Разделяем отладочную и финальную версию
- Прячем секретную информацию
- Автогенерация версии кода
- settings.gradle
- gradle.properties (Project Properties)
- Режим параллельного выполнения
- Gradle-демон
- Режим конфигурации при необходимости
- Меняем номер версии библиотек в одном месте
- Настройки в Android Studio
- Gradle Task
- Узнать debug.keystore: MD5 и SHA1
- Gradle Console
- Terminal
- Добавление зависимостей через интерфейс студии
- Дополнительное чтение
- Готовьсь, цельсь, пли! Как не обжечься при сборке Gradle-приложения, и настолько ли всё серьезно?
- Структура проекта
- Инициализация
- Конфигурация плагинов
- Подключение Gradle-проектов и композитная сборка
- Конфигурация
- Управление зависимостями
- Типы зависимостей
- Производительность конфигурации
- Как сделать задачу для этапа конфигурации?
- Немного о buildSrc
- Куда еще можно вынести общую логику билдскриптов?
- Сборка
- Коротко про Gradle Task
- Gradle Daemon
- Несколько советов по оптимизации скорости сборки
- Бонус для дочитавших до конца: Gradle-плагин
Gradle
Gradle — система автоматической сборки, построенная на принципах Apache Ant и Apache Maven. В Eclipse использовалась система Ant, но большинство разработчиков даже не замечало её работы. В основном возможности системы использовались в конторах для автоматизации различных задач. В Android Studio такой номер не пройдёт. Gradle сопровождает вас во время разработки постоянно. Поначалу, если вы перешли с Eclipse, Gradle сильно раздражает своими действиями. Но позже вы оцените её удобство и может даже полюбите её.
Gradle не является изобретением для Android Studio, система была разработана раньше и использовалась в приложениях для Java, Scala и других языках.
Система сборки Gradle очень мощная и сложная, чтобы о ней рассказать в двух словах. Есть целые книги о ней. Сами команды в Gradle представляют собой обычный текст с использованием синтаксиса Groove для конфигурации. Но нам не нужно знать всё. Познакомимся поближе с системой и научимся пользоваться ей.
Создайте новый проект или откройте любой существующий проект из Android Studio и посмотрите на структуру проекта.
В последних версиях студии файлы Gradle выделили в отдельную папку Gradle Script. Раскройте её. В основном вас должен интересовать файл build.gradle, который относится к модулю. Рядом с этим файлом в скобках будет написано Module: app. Двойным щелчком откройте его, вы увидите, что файл является текстовым.
Также есть файл build.gradle, который относится к проекту. Но с ним работают реже. Так находятся настройки для репозиториев и самого Gradle.
Вернёмся к файлу модуля, вы увидите много интересной информации. Например, вы там можете увидеть настройки, которые раньше вы могли видеть в манифесте — номера версий, номера SDK и так далее. Забегая вперёд, скажу, что здесь можно добавить всего одну волшебную строчку и нужная библиотека сама скачается из интернета и установится в проекте. Красота!
Однако вернёмся в корневую папку. Кроме файлов build.gradle мы можем заметить файлы gradle.properties, settings.gradle и другие. Трогать их не нужно.
В корневой папке также есть файлы gradlew и gradlew.bat для работы с Gradle Wrapper. В принципе вам не нужно знать о них ничего. Но для параноиков есть информация — если вы часто импортируете проекты из неизвестных источников, то они содержат файл gradle/wrapper/gradle-wrapper.properties. Откройте его текстовым редактором и посмотрите на адрес у distributionUrl. Путь должен вести на официальный сай //services.gradle.org или на внутренний корпоративный сервер. Другие адреса должны вызвать тревогу.
Вы могли заметить, что по сравнению с Eclipse изменилась структура файлов. В папке app находится папка src, а ней папка main, в которых папки java, res и файл манифеста. Новая структура лучше отвечает требованиям Gradle для управления файлами.
Вы, например, можете создавать альтернативные папки с ресурсами и с помощью build.gradle подключить их к проекту.
В этом примере мы указали, что существуют новая папка presentations в папке /src/main/ наряду с существующими папками java и res. Внутри созданной папки есть ещё две папки layout и animations, которые содержат файлы ресурсов.
Только помните, что вам нужно избегать конфликта имён при слиянии всех файлов при сборке.
Значение sourceSets указывает Gradle, какие папки следует использовать. Этим приёмом пользуются продвинутые программисты. Мы пока не будем использовать такую технику.
Другая полезная возможность — создавать разные версии приложений, например, демо-версию и платную версию. Немного об этом рассказано здесь.
Номер версии приложения и требования к версии Android прописаны в секции defaultConfig. Если у вас сохранились старые версии приложений, то в манифесте можете удалить данные записи. По-моему, там даже выводится соответствующая подсказка. Даже если вы эти данные в манифесте не удалите, то значения из gradle.build имеют больший приоритет и перепишут значения в манифесте при не совпадении.
Подключение библиотеки происходит одной строчкой. Например, нужно добавить библиотеку Picasso:
В Android Studio 3.0 используется новая версия Gradle, в которой compile считается устаревшей. Вместо него следует использовать новое слово implementation.
Есть похожая команда, которая подключает библиотеку, которая будет использоваться только для отладки приложения и в релизной версии она не нужна.
Далее включаете синхронизацию и через несколько секунд в папке появляется нужная библиотека, готовая к использованию. Сама библиотека скачивается с специального хранилища-репозитория JCenter. Данный репозиторий используется по умолчанию и прописан в buil.gradle проекта.
Можно указать другой репозиторий, например, Maven Central.
Для поиска через Maven-репозиторий используйте The Central Repository Search Engine.
Библиотеку можно подключать и старым способом, используя jar-файл, но такой способ уходит в прошлое.
Сам файл нужно скопировать в папку /libs.
При любом изменении файла недостаточно его сохранить. Нужно также произвести синхронизацию. Наверху обычно появляется жёлтая полоска с ссылкой Sync Now.
Задаём имя APK при компиляции
Можно задать собственное имя при компиляции проекта. Например, так.
Получим имя MyName-1.0.12-release.apk
Оптимизатор кода R8
Оптимизатор кода R8 имеет следующие возможности: урезание байт-кода, сжатие, обфускация, оптимизация, удаление «синтаксического сахара», преобразование в DEX. Оптимизатор может производить все операции за один шаг, что даёт сильное улучшение производительности. R8 был введён в Android Gradle plugin 3.3.0. Вам нужно только включить его.
R8 разработан для работы с существующими ProGuard-правилами, хотя возможны ситуации, когда нужно переработать правила.
Сжимаем итоговый APK
В Gradle 1.4 появилась возможность сжать итоговый файл, убрав неиспользуемые ресурсы, в том числе из библиотек, например, Google Play Services.
Во время сборки приложения вы можете увидеть строку:
Другой способ убрать неиспользуемые ресурсы конфигурации. Например, вам не нужные локализованные ресурсы для всех языков, которые входят в библиотеку Google Play Services или Android Support Library и др. Оставим только нужные языки. Возможно, вы также не хотите поддерживать mdpi или tvdpi-разрешения в своём приложении. Мы можем установить языки и разрешения, которые используются в приложении, остальные будут исключены, что позволит уменьшить вес приложения.
Можно перенести ключи из манифеста.
Чтобы их не светить, например, если выкладывать код на Гитхабе, то сделаем так.
И в манифесте переделаем код.
В большинстве случаев это сработает, но иногда ключ требуется при компиляции и указанный пример может не сработать. В таком случае применяют другой вариант через manifestPlaceholders.
В манифесте пишем.
Класс BuildConfig
В статье LogCat упоминался способ быстрого отключения журналирования.
Суть в следующем. Когда вы создаёте новые переменные в блоках defaultConfig или buildTypes (ветки debug и release), то Gradle создаёт специальный класс BuildConfig, и вы можете получить доступ к этим переменным.
Например, добавим переменную в defaultConfig
На языке Java это равносильно String YOUR_TOKEN = «ABRAKADABRA»;
Теперь мы можем обратиться к созданной строке.
С секцией buildType ситуация интереснее. У секции есть два блока debug и release. Можно создать переменные с разными значениями, которые будут использоваться в зависимости от ситуации. Например, у вас есть собственное API для сервера. Для тестирования вы используете один адрес, а для финальной версии — другой адрес. Тогда вы просто указываете разные адреса в разных ветках. Переменные могут быть не только строковыми.
Создаём код для перехода на веб-страницу.
Теперь вам не нужно переписывать каждый раз код. Загружаться будет страница по нужному адресу автоматически.
Разделяем отладочную и финальную версию
По такому же принципу можно организовать запуск новой версии программы, не затрагивая программу, которую вы установили с Google Play. Допустим вы на своём телефоне установили своё собственное приложение через Google Play. Теперь вам нужно написать новую версию и проверить на своём телефоне. Из-за конфликта имён новое тестируемое приложение перепишет финальную версию или вообще не установится. Поможет следующий трюк.
В специальных переменных applicationIdSuffix и versionNameSuffix мы задаём суффиксы, чтобы избежать конфликта. А в переменной resValue указываем название программы для отладочной и финальных версий, чтобы на устройстве можно было их найти. Не забудьте при этом удалить строковый ресурс app_name в res/values/strings.xml, иначе получите ошибку при компиляции. Теперь можете спокойно запускать приложение с новым кодом, не боясь повредить своё любимое приложение.
Прячем секретную информацию
Следующий совет больше подходит для компаний. Когда подписывается приложение, то нужно указывать пароль, хранилище и т.д. Чтобы их не светить в студии, можно прописать их в переменных и поместить в секцию signingConfigs. Сервер сам найдёт нужные ключи и воспользуется ими в своих сценариях.
Автогенерация версии кода
Нашёл совет, сам не применял. Не обязательно вручную менять версию приложения в атрибутах versionCode и versionName, можно сделать через переменные, а они сами подставятся в нужное место. На любителя.
settings.gradle
Файл settings.gradle обычно состоит из одной строчки.
Это означает, что у вас используется один проект для работы. Если вы будете подключать другие проекты, то здесь появятся новые строки.
gradle.properties (Project Properties)
Несколько советов по настройке файла gradle.properties.
Режим параллельного выполнения
В этом файле можно найти закомментированную строку # org.gradle.parallel=true. Если модули вашего проекта не используют друг друга как зависимости, создавая перекрёстные ссылки, можно включать режим параллельного выполнения, что ускорит скорость сборки до
Gradle-демон
Включение на компьютере демона Gradle даст значительный прирост в скорости сборки.
Режим конфигурации при необходимости
Если в проекте используется много модулей, то можно включить режим конфигурации при необходимости. Ускорение будет заметно при большом количестве используемых модулей:
Меняем номер версии библиотек в одном месте
Очень часто в проекте используются взаимосвязанные библиотеки с одинаковыми номерами.
Можно быстро поменять у всех номера через переменную. Для этого используется секция ext, в которой указывается переменная и номер версии. Затем в секции dependencies номер версии заменяется на имя переменной
Обратите внимание, что одинарные кавычки заменяются на двойные, а символ $ указывает на строковый тип.
Расширенная версия с разными переменными в другом виде.
Если в проекте используются несколько модулей с одинаковыми зависимостями, то эти записи можно перенести в корневой build.gradle, чтобы не менять номера версий в каждом модуле.
Настройки в Android Studio
Рассмотрим настройки, доступные в Android Studio. Закройте текущий проект, чтобы увидеть стартовое окно студии. В правой части нажмите на пункт Configure. В следующем окне выберите пункт Settings, чтобы оказаться в окне настроек студии. В левой части найдите пункт Build, Execution, Deployment, затем подпункт Build Tools, далее подпункт Gradle. По умолчанию, там всё чисто, только указан путь у Service directory path. Это были общие настройки.
Теперь рассмотрим настройки, относящиеся к проекту. Запустите любой проект в Android Studio. Выберите меню File | Settings. . Снова пройдитесь по пунктам Build, Execution, Deployment | Build Tools | Gradle. Вы увидите практически такое же окно с небольшими изменениями. Теперь поле Linked Gradle Projects не будет пустым, а также появятся дополнительные настройки. По умолчанию рекомендуют использовать Use default gradle wrapper.
Gradle Task
На правой стороне Android Studio имеется вертикальная вкладка Gradle, которую можно развернуть. Она содержит список задач (task), которая выполняет Gradle при работе с текущим проектом. Вы можете выделить любую из этих задач и запустить её двойным щелчком. Можно выделить несколько задач.
Узнать debug.keystore: MD5 и SHA1
Иногда требуется узнать значения debug.keystore: MD5 и SHA1. Обычно их получают через командную строку. Но это долго и неудобно, так как нужно помнить все аргументы. Есть способ проще. Открываем вкладку Gradle, нажимаем на кнопку со стрелками Refresh all Gradle Projects. Затем последовательно открываем элементы Tasks | android и запускаем команду signingReport. В нижнем окне Run увидите нужную информацию.
Gradle Console
Когда выполняется какая-то задача Gradle, то ход её выполнения можно увидеть в окне Gradle Console. Открыть её можно через вкладку Gradle Console в нижней правой части студии.
Terminal
Запускать задачи Gradle можно и в окне Terminal.
На панели инструментов имеется значок Sync Project with Gradle Files, которую следует использовать при редактировании файлов Gradle. Как правило, студия также выводит предупреждающее сообщение с ссылкой при изменении файла, которая делает ту же работу.
Добавление зависимостей через интерфейс студии
В статье описывался способ включения библиотеки в проект через редактирование файла build.gradle. Существует альтернативный вариант через настройки студии. Щёлкните правой кнопкой мыши на имени модуля (app) и выберите пункт Open Module Settings (быстрая клавиша F4). В правой части окна находятся вкладки, которые оказывают влияние на файл build.gradle. Например, вкладка Dependencies содержит подключаемые библиотеки.
Чтобы добавить новую зависимость, нажмите на значок с плюсом и выберите нужный вариант, например, Library dependency. Откроется список доступных библиотек из репозитория Maven.
Дополнительное чтение
В примере работы с PDF-файлами в папке assets использована операция запрета на сжатие файлов, которое происходит по умолчанию.
Задачи Gradle — теория для общего развития.
Источник
Готовьсь, цельсь, пли! Как не обжечься при сборке Gradle-приложения, и настолько ли всё серьезно?
Доброго дня, читатель! Меня зовут Стручков Михаил и я Android-разработчик в команде мобильного оператора Yota.
В последнее время особенности нашего приложения способствуют частой и кропотливой работе с Gradle. В своем опыте работы с ним я успел пройти через стадию поломанных сборок, отчаяния в попытках понять причину очередного фейла при билде, и неподдельной радости после успешной реализации собственных задумок.
Предлагаю упростить тернистый путь к пониманию сборки Gradle-приложений, разобрать основные этапы и их особенности, и попробовать совместно сократить трафик stackoverflow на тему Gradle. В качестве бонуса немного коснемся Gradle-плагинов и разберемся, как к ним подходить.
Очень сложно рассмотреть всю работу с Gradle в одной статье, поэтому планирую последовательно пополнять список:
Готовьсь, цельсь, пли! Как не обжечься при сборке Gradle-приложения, и настолько ли всё серьезно?
Для удобства навигации по статье, ниже представлено оглавление:
Gradle приобрел широкую популярность в качестве системы сборки не только JVM-приложений. Широта предоставляемого инструментария, возможности его адаптации под кастомные задачи, возможности для сборки крупных проектов, простота интеграции с различными CI-системами делают свое дело.
В этой статье поговорим про основные этапы сборки Gradle-приложения — инициализацию, конфигурацию и сборку, и про то, как нам с ними обращаться при работе. Для своих примеров я буду использовать Kotlin и конфигурацию на Kotlin DSL. Стильно, модно. Kotlin в сравнении c Groovy привносит много удобств. Ведь теперь мы можем легко выяснить — что и где, и какого оно типа, а Gradle API наконец начинает нам в этом помогать. С Groovy этого определенно не хватало. Кто еще не в курсе, в документации хорошо рассказано о том, как попробовать Kotlin DSL.
Структура проекта
Итак, главная сущность в мире Gradle – это Project. Project-ом выступает само наше приложение, которое мы только что создали. У project-а могут быть subproject-ы, у subproject-а могут быть свои subproject-ы, и таким образом получается древовидная структура. Хорошей практикой является подход, при котором степень вложенности не превышает двух. Такая структура подходит для большинства проектов. При этом subproject-ы можно просто называть Gradle-модулями.
Если вы пользуетесь IDE от JetBrains или Android Studio, по умолчанию они не дадут вам создать иерархию больше двух (для стандартных проектов), что уже наталкивает на мысль, что так делать не стоит.
На картинке снизу subproject-ы (Gradle-модули) — это app и lib. Корнем Gradle-структуры является сам проект (вот эта маленькая точка сверху):
Project узнаёт обо всех своих subproject из файла конфигурации settings.gradle (.kts). Написанный здесь скрипт будет выполняться на этапе инициализации проекта.
Инициализация
Итак, в settings.gradle (.kts) мы определяем содержимое нашего приложения, и учим Gradle к нему подступаться. В самом начале задаем имя нашего проекта:
Теперь мы можем легко его получить из любого subproject-а на последующих этапах.
Конфигурация плагинов
Далее мы можем определить блок pluginManagement , где есть возможность сконфигурировать плагины, которые используются в нашем приложении.
Внутри блока plugins есть возможность указать дефолтную версию для плагина, если не используется никакая другая. При этом эту версию можно брать извне, например, из gradle.properties:
Подключать дефолтные версии плагинов из внешних источников, на мой взгляд, уникальный случай, но, если это ваш случай — вы теперь знаете как это сделать. Если вы уже используете такую возможность в вашем проекте — напишите в комментариях, кейс интересный.
По умолчанию все наши плагины подключаются из gradlePluginPortal . Если же плагин необходимо достать из другого репозитория, в блоке repositories можно его определить. Например, мы хотим подключить Android Gradle Plugin. Он лежит в гугловском репозитории, что и объявляем явно:
Готово! Теперь можно добавить зависимость Android Gradle Plugin в classpath (о котором чуть позже) и затем успешно подключать Android-плагины в билдскриптах.
В блоке resolutionStrategy мы можем определить правила для подключения плагинов, используемых проекте. На коде ниже приведен пример того, как можно хитрым образом динамически подключить Android Gradle Plugin на тот случай, если в проекте он начнёт использоваться:
После такого финта ушами, AGP автоматически подключится в classpath по необходимости. Здесь в поле requested находится реквест плагина, который мы явно сформировали:
Чтобы узнать, что будет запрошено в конечном итоге, следует использовать поле target.
Важный момент: версию плагинов в явном виде стоит определять именно в рутовом build.gradle (.kts). Если сделать это как-то по-другому, чаще всего будет появляться следующее:
Полный зоопарк. Я не буду загружать подробностями (ибо у автора тоже с них взрывается мозг), но суть такова, что рутовый build.gradle (.kts) является для этого централизованным местом.
Подключение Gradle-проектов и композитная сборка
Далее с помощью include объявляем существующие в нашем проекте подпроекты (они же по-простому gradle-модули). При этом, если модуль лежит где-то в другом месте и вообще он гриб, можно явно указать, как и где его найти с помощью project:
И последнее, но не менее интересное – includeBuild. С помощью него можно определить проект, который мы бы хотели собрать, и определить интерфейс до сборки основного проекта. Таким образом, имеем возможность организовать композитную сборку:
C помощью лямбды в dependencySubstitution можно подменить зависимости, используемые в includeBuild-проекте, в том числе на те, что предоставляет наш проект, как на примере выше.
Где можно использовать композитную сборку?
Из своего опыта могу сказать, что композитная сборка может быть очень полезна, когда мы хотим отдебажить какой-нибудь Gradle-проект из другого репозитория. Например, это может быть наш собственный Gradle-плагин. Для этого просто выбираем Debug у Gradle-задачи из нашего плагина, который мы подключаем через композитную сборку:
После этого можно пользоваться всеми радостями отладки от IDE: ставить брейкпоинты, просматривать значения переменных и т.д. при выполнении кода Gradle-плагина.
P.S. Отладить таким образом можно не только Gradle-плагин, но и все содержимое build.gradle (kts), и в том числе на Groovy! Где ещё может пригодиться композитная сборка, поговорим немного дальше по ходу статьи.
Также settings.gradle (.kts) может с успехом отсутствовать. В этом случае Gradle поймёт, что других модулей проекте нет, и на этом успокоится.
Многие уже привыкли видеть settings.gradle (.kts) как скрипт, где только перечислены все известные в приложении Gradle-проекты. По опыту починки различных поломок с Gradle могу сказать, что сюда посмотрят в последнюю очередь. Если есть возможность не дописывать сложную логику сюда, лучше этого не делать. В большинстве случаев эту логику можно реализовать в build.gradle (.kts) рутового Gradle-проекта, что будет более очевидно для других разработчиков.
После выполнения инициализации Gradle создает объекты типа Project, с которыми мы продолжаем работу уже на этапе конфигурации.
Конфигурация
Также известна под именами “Sync Project With Gradle Files”, “Load Gradle Changes”, “Reload All Gradle Projects”. На данном этапе главными игроками выступают билдскрипты build.gradle (.kts).
Механизм достаточно простой — для построенного дерева Gradle-проектов на этапе инициализации последовательно вызывается соответствующий билдскрипт. Здесь выполняется следующее:
Резолвятся и загружаются зависимости для каждого из известного в settings.gradle (.kts) Gradle-проекта;
Строится граф выполнения Gradle-задач для этапа сборки;
Определяются переменные и конфигурируются данные для выполнения задач на этапе сборки.
О подкапотном пространстве 2 и 3 этапов предлагаю поговорить в статье про Gradle-задачи и в комментариях, а сейчас давайте немного сосредоточимся на том, как быть с зависимостями.
Управление зависимостями
Для подключения зависимостей в Gradle-проект существует два ключевых блока: repositories и dependencies .
В блоке repositories определяем список репозиториев, в которые нужно сходить для загрузки зависимостей. Gradle предоставляет возможности использовать внешние репозитории, внутренние репозитории, и локальные в виде путей к папкам. Возможности для конфигурации здесь достаточно большие.
При поиске зависимостей Gradle осуществляет поиск по репозиториям в том порядке, в котором мы эти репозитории у себя объявили. Из этого вытекает, что репозитории с максимальным числом зависимостей стоит выносить в самый верх, и в целом стараться держать как можно меньше репозиториев.
Сами зависимости мы определяем в блоке dependencies . Здесь правило похожее — чем меньше, тем лучше.
В блоке buildscript определяются зависимости, которые необходимо загрузить и положить в classpath для их доступности на этапе конфигурации:
Для подключения зависимостей на этапе сборки, блоки dependencies и repositories добавляются в корень билдскрипта build.gradle (.kts). В качестве зависимостей могут быть как другие gradle-проекты, так и внешние/локальные библиотеки:
Для подключения Gradle-проектов используется символ “:”. С его помощью мы определяем путь, по которому лежит подключаемый Gradle-проект относительно рутового Gradle-проекта.
Важно следить за тем, чтобы не организовать цикл между зависимостями. Если такое произойдет, в лучшем случае Gradle-сборка начнет падать с
, а в худшем — c NullPointerException, и проблему тогда станет искать намного сложнее.
В управлении зависимостями иногда бывают загвоздки. Например, при разрешении версий могут возникнуть конфликты, или же мы захотим явно определить нужную нам версию транзитивной зависимости ввиду проблем совместимости. Как решать подобные проблемы, хорошо описано в этой статье. А я не стану изобретать велосипед и доверю вас опытному автору.
Типы зависимостей
У Gradle бывают два типа зависимостей — runtime и compile-time. Хорошо объясняет положение вещей следующий рисунок:
При подключении зависимостей в compileClasspath, мы получим runtime exception при попытке достучаться до кода зависимости во время выполнения, поскольку зависимость не попала в приложение. Но в то же время код зависимости будет доступен на этапе сборки проекта. Подключая зависимости в runtimeClasspath, мы гарантируем их попадание в приложение, а значит, и безопасность выполнения кода в runtime. Здесь-то и приходит понимание, что implementation и api добавляют зависимости в оба classpath-а. При этом api также позволяет получить доступ к коду gradle-проекта в случае его транзитивного подключения.
В качестве apiElements и runtimeElements на рисунке обозначен код, который мы хотим отдать на использование в другие gradle-модули.
Производительность конфигурации
Gradle старается, насколько это возможно, оптимизировать процесс конфигурации. Наша задача этому не мешать, а именно, стараться не взаимодействовать с другими gradle-проектами на этапе конфигурации напрямую. Это приводит к связности проектов и замедляет скорость конфигурации.
Самый простой способ сделать проекты связными – определить блоки allprojects или subprojects . Чаще всего эти блоки используются чтобы добавить общее поведение, например, определить общую зависимость/репозиторий, определить Gradle-задачу, которую мы бы хотели выполнять для каждого проекта и.т.д.:
Такие блоки можно определить в root-проекте, поскольку он и так неявно связан со всеми подпроектами. Но стоит помнить, что выполнение подобного трюка в дочернем проекте оставит негативный след на скорости конфигурации. Если потребность всё же есть, лучше сделать зависимость между Gradle-задачами на этапе сборки.
Как сделать задачу для этапа конфигурации?
У Gradle есть на этот случай колбэки. Самый простой способ добавить колбэк для этапа конфигурации – определить блоки afterEvaluate или beforeEvaluate . Блок afterEvaluate можно использовать, например, в случае, когда мы хотим добавить задачу в граф выполнения и хотим, чтобы она выполнялась по определенному правилу. Например, после задачи myTask, как на примере ниже:
Почему не следует использовать dependsOn
Связывая задачи таким образом, мы должны быть уверены в том, что, как и когда выполняет Gradle-задача, от которой мы хотим зависеть. Нет никаких гарантий, что при её изменении наш код не сломается.
Чтобы избежать подобных проблем, задачи следует связывать с помощью их inputs и outputs. В этом случае в качестве бонуса мы получим еще и инкрементальность (aka up-to-date проверки), что значительно снизит время повторной сборки. Чтобы во всем разобраться, можно посмотреть доклад Степана Гончарова (таймкод) и пример из документации.
Немного о buildSrc
Еще одна вещь, о которой я не могу не упомянуть, это buildSrc. Модуль buildSrc собирается каждый раз перед конфигурацией нашего рутового проекта и поставляется на этап конфигурации в виде jar. Его довольно удобно использовать для объявления зависимостей, а также содержать общую логику для билдскриптов. Подробнее о том, как использовать buildSrc в Gradle-проекте хорошо описано в этой статье.
Но, к сожалению, не всё так гладко, как хотелось бы. У buildSrc есть одна достаточно весомая проблема — при любом его изменении мы теряем наш билд-кеш, и как следствие, заставляем проект пересобираться «на холодную». Если проект большой, это может быть особенно критично. О том, как решать проблему buildSrc, можно почитать в статье от ребят из Badoo. (Спойлер — решается миграцией на композитный билд).
P.S. Сейчас в нашем проекте несколько десятков модулей и сложно сказать, что проблема buildSrc ощутимо по нам ударила. Но я не исключаю, что в относительно близкое время композитный билд действительно станет нашим будущим.
Куда еще можно вынести общую логику билдскриптов?
Если вы используете Groovy, можно вынести логику в отдельный билдскрипт и подключать его в нужные места вот так:
При этом скрипт можно положить напрямую в папку с проектом. Поскольку Groovy интерпретируемый язык, то ничего заранее компилировать не придется.
Если вы пишете подключаемые билдскрипты на Kotlin DSL, то они обязательно должны лежать в buildSrc. Связано это с тем, что Kotlin-скрипт должен быть скомпилирован перед использованием. При этом для подключения нужно выполнить небольшой финт ушами и в build.gradle (.kts) модуля buildSrc добавить следующее:
Теперь можем добавить Kotlin-скрипт в buildSrc/src/main/kotlin (java):
И подключить его вот так:
Скрипт, подключаемый таким образом, компилируется всего один раз перед использованием, что не повлияет на скорость конфигурации, и даже ускорит её, если вынести достаточно жирный кусочек. Однако в buildSrc при этом будет сгенерировано приличное количество кода.
Для того, чтобы связать Kotlin и Groovy, в Kotlin-скрипты можно подключать стандартные *.gradle с помощью того же apply:
Сборка
Вот мы и плавно подобрались к тому, что чаще всего поджигает наш ноутбук рождает приложение на свет. На этом этапе Gradle начинает выполнять задачи, которые были добавлены в граф зависимости на этапе конфигурации в нужном порядке.
Коротко про Gradle Task
Gradle Task представляет собой единицу работы, которую выполняет сборка. К примеру, это может быть компиляция классов, создание JAR, создание документации Javadoc или публикация в репозиторий.
Простейшим вариантом написать задачу для этапа сборки является реализация лямбды в doFirst/doLast , как на примере ниже:
Задаче очень желательно добавлять description и group, чтобы без лишних телодвижений было понятно, чем она занимается. После добавления group задачу будет удобно искать и запускать:
Задачу можно выполнить также и через консоль с помощью команды:
Gradle Daemon
Непосредственным выполнением сборки занимается Gradle Daemon. Он включен по умолчанию для Gradle версии 3.0 и выше. Gradle Daemon является долгоживущим системным процессом, периодически осуществляющим сборку, когда мы этого хотим. Внутри него происходит много in-memory кеша, оптимизации работы с файловой системой и оптимизации кода выполнения сборки. Если коротко — всё идет на пользу. Пожалуй, исключение только одно — он довольно прожорлив, и Gradle любит держать несколько демонов на разные случаи жизни. Если система начинает ощутимо лагать, всегда можно всех за раз прибить командой
Несколько советов по оптимизации скорости сборки
Нетрудно понять, что сборка — это самый трудозатратный этап, занимающий львиную долю всего времени работы Gradle. Есть несколько базовых рекомендаций для того, как нам её ускорить, первая из которых — запастись терпением:
Параллельное выполнение тасок.
Всё просто — org.gradle.parallel=true в вашем gradle.properties, и Gradle будет стараться максимально распараллелить выполнения тасок на этапе сборки. Этот параметр по умолчанию включен при создании проекта.
Обновление Gradle.
Gradle важно периодически обновлять. Связано это с тем, что с каждой версией в Gradle привносят улучшения по скорости сборки, исправления багов и новые интересные плюшки. Для удобства обновления, был придуман Gradle Wrapper (или просто Wrapper). Это ни что иное, как обычная Gradle-задача. Теперь для обновления версии можно написать в рутовом build.gradle (.kts) следующее:
, а затем выполнить команду gradle wrapper . Или же выполнить
, где вместо gradle_version указываем желаемую версию Gradle. Тем самым Gradle сам загрузит нужную версию и положит её в папку с проектом. Она же будет использоваться в дальнейшем по умолчанию, а счастливые мы сможем запускать задачи с помощью скрипта gradlew, который появится в папке с нашим проектом.
Правильное использование api/implementation.
При изменении реализации зависимости Gradle пересобирает «на холодную» все зависящие от неё модули. Я уже упомянул про api и implementation в разговоре о способах подключения зависимостей, и теперь понятно, что при подключении зависимости через api, она также транзитивно попадает в classpath-ы модулей, в которые мы подключили наш модуль. Тем самым увеличивается количество модулей, которые gradle будет пересобирать при изменении зависимости. Если нет необходимости в транзитивном использовании кода из зависимости, для её подключения следует использовать implementation.
Инкрементальность и build-кеш.
Оптимизация Gradle позволяет нам пропускать выполнение задачи при определенных условиях. У Gradle существует 5 состояний задач — EXECUTED, UP-TO-DATE, FROM-CACHE, SKIPPED и NO-SOURCE. В разговоре про кеш нам интересны два из них — UP-TO-DATE и FROM-CACHE. Они сигнализируют о том, что результаты выполнения наших тасок были успешно подтянуты гредлом из результатов предыдущей сборки. Об остальных состояниях можно почитать в документации.
UP-TO-DATE. Возникает в случае, если разработчик задачи позаботился о ней и самостоятельно реализовал инкрементальность её выполнения (проверки на up-to-date), или же все зависимости этой задачи, если они есть, были успешно взяты из кеша, или признаны гредлом up-to-date.
FROM-CACHE. Это состояние возникает в случае, если Gradle смог восстановить outputs задачи из билд-кеша. При этом по умолчанию build-cache выключен. Build-cache довольно удобно использовать на CI, таким образом ускорив выполнение пайплайнов Gradle-сборки. Как организовать build-cache всегда можно узнать тут.
При использовании билд-кеша в локальных сборках, знаменитый Clean/Rebuild теряет свою магическую силу, поскольку параметры для задач всегда будут тянуться из Gradle-кеша. Однако для больших проектов отсутствие кеша может быть проблемой, поскольку время сборки «на холодную» способно занимать несколько десятков минут. Если это ваш случай, и вы сталкивались с проблемами инвалидации кеша, напишите в комментариях.
Бонус для дочитавших до конца: Gradle-плагин
Stefan Oehme — Composite Builds with Gradle. Доклад про суть композитной сборки в Gradle и способах ее применения (на английском).
Филипп Уваров — Gradle Plugin Development. Доклад с AppsConf про разработку Gradle-плагинов. Раскрывает область применения плагинов, способы их реализации и по каждому их преимущества/недостатки.
Источник