service:gradle
Различия
Показаны различия между двумя версиями страницы.
service:gradle [09.04.2024 07:28] – [Tasks / Жизненный цикл сборки] viacheslav | service:gradle [30.07.2024 19:21] (текущий) – внешнее изменение 127.0.0.1 | ||
---|---|---|---|
Строка 1: | Строка 1: | ||
+ | ====== Gradle ====== | ||
+ | https:// | ||
+ | Gradle Tutorial - Crash Course: https:// | ||
+ | Understanding Gradle (playlist): https:// | ||
+ | Gradle и система сборки Android: https:// | ||
+ | |||
+ | Система сборки ПО с автоматизацией. https:// | ||
+ | |||
+ | ====== Терминология, | ||
+ | > Project - то, что собственно собирается. Скрипт сборки - '' | ||
+ | >> Task - этап сборки, | ||
+ | >>> | ||
+ | >>>> | ||
+ | >>>>> | ||
+ | |||
+ | * '' | ||
+ | plugins { | ||
+ | id(" | ||
+ | id(" | ||
+ | id(" | ||
+ | } | ||
+ | </ | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | ... | ||
+ | rootProject.name = "My app" | ||
+ | include(": | ||
+ | include(": | ||
+ | </ | ||
+ | |||
+ | ===== Android Gradle plugin (AGP) ===== | ||
+ | |||
+ | <code java> | ||
+ | plugins { | ||
+ | id(" | ||
+ | } | ||
+ | |||
+ | android { | ||
+ | compileSdk = 33 | ||
+ | // Обязательный блок, указываются версии самого ПО и SDK | ||
+ | defaultConfig { | ||
+ | applicationId = " | ||
+ | minSdk = 24 | ||
+ | targetSdk = 33 | ||
+ | versionCode = 1 | ||
+ | versionName = " | ||
+ | } | ||
+ | signingConfigs { ... } | ||
+ | buildTypes { | ||
+ | release { ... } | ||
+ | debug { ... } | ||
+ | } | ||
+ | productFlavors { ... } | ||
+ | compileOptions { ... } | ||
+ | splits { ... } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== buildTypes ==== | ||
+ | Варианты сборки одного и того же пакета, | ||
+ | <code java> | ||
+ | buildTypes { | ||
+ | release { | ||
+ | isMinifyEnabled = true | ||
+ | proguardFiles( | ||
+ | getDefaultProguardFile(" | ||
+ | ) | ||
+ | } | ||
+ | debug { | ||
+ | isMinifyEnabled = true | ||
+ | signingConfig = signingConfigs.getByName(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== productFlavors ==== | ||
+ | Похоже на buildTypes в том плане, что делаются разные пакеты, | ||
+ | <code java> | ||
+ | flavorDimensions += " | ||
+ | productFlavors { | ||
+ | create(" | ||
+ | dimension = " | ||
+ | buildConfigField(" | ||
+ | } | ||
+ | create(" | ||
+ | dimension = " | ||
+ | buildConfigField(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== buildVariant ==== | ||
+ | Совокупность buildTypes и productFlavors. Т. е., в рамках buildVariant собираются PaidDebug, PaidRelease, | ||
+ | |||
+ | ====== Зависимости ====== | ||
+ | Зависимости - это | ||
+ | - Сторонний код (библиотека), | ||
+ | - Сторонний код (библиотека), | ||
+ | |||
+ | Gradle не просто подключает какую-то библиотеку, | ||
+ | |||
+ | Чтобы подключить библиотеку, | ||
+ | - Откуда её скачать <code java> | ||
+ | // попытки скачать с указанных ресурсов идут построчно, | ||
+ | repositories { | ||
+ | google() | ||
+ | mavenCentral() | ||
+ | mavelLocal() | ||
+ | maven(" | ||
+ | maven(" | ||
+ | credentials { | ||
+ | username = System.getenv(" | ||
+ | password = System.getenv(" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | - Как её скачать <code java> | ||
+ | dependencies { | ||
+ | // Указатель пакета - " | ||
+ | implementation(" | ||
+ | api(" | ||
+ | compileOnly(" | ||
+ | runtimeOnly(" | ||
+ | testImplementation(" | ||
+ | debugApi(" | ||
+ | implementation(project(": | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Различия при подключении библиотеки к самому проекту или к сборке проекта ===== | ||
+ | К проекту | ||
+ | <code java> | ||
+ | repositories { | ||
+ | google() | ||
+ | mavenCentral() | ||
+ | } | ||
+ | dependencies { | ||
+ | implementation(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | К сборке проекта: | ||
+ | <code java> | ||
+ | buildscript { | ||
+ | repositories { | ||
+ | google() | ||
+ | mavenCentral() | ||
+ | } | ||
+ | dependencies { | ||
+ | classpath(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Проблемы при подключении зависимостей ===== | ||
+ | Самые распространённые - это | ||
+ | - java.lang.NoSuchMethodError или java.langNoClassDefFoundError | ||
+ | - Duplicate class | ||
+ | |||
+ | У первого случая суть в том, что когда подключаются два одинаковых API разных версий, | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Костыль для обхода этой проблемы - форсирование использования более старого API. | ||
+ | <code java> | ||
+ | configurations.all { | ||
+ | resolutionStrategy { | ||
+ | force(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | Но тогда может возникнуть другая проблема, | ||
+ | |||
+ | Второй случай (Duplicate class) возникает, | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Костыль для обхода - исключить (exclude) модуль googleApi, который тянет за собой firebase. | ||
+ | <code java> | ||
+ | dependencies { | ||
+ | implementation(" | ||
+ | exclude(group = " | ||
+ | } | ||
+ | implementation(" | ||
+ | } | ||
+ | </ | ||
+ | Опять же, это чревато тем, что firebase может работать неправильно или не работать вообще, | ||
+ | |||
+ | Ещё один пример exclude - guava (улучшенная работа с коллекциями) тянет за собой довольно тяжёлый модуль findbugs, который в проде вряд ли пригодится. | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Тем не менее, необходимо хорошо понимать, | ||
+ | |||
+ | ====== Многомодульность ====== | ||
+ | Многомодульность - это разделение кода проекта на подпроекты (модули) и правильная организация связей между этими модулями. | ||
+ | |||
+ | Плюсы: | ||
+ | * Деление на независимые части делает код более понятным и стройным | ||
+ | * Меньше конфликтов при командной разработке - каждый разрабатывает свою часть приложения. Это не всегда так гладко работает - например, | ||
+ | * Возможность сборки разных целей (targets) приложений на единой кодовой базе. К примеру, | ||
+ | * В некоторых ситуациях, | ||
+ | |||
+ | Минусы | ||
+ | * Более сложная структура, | ||
+ | * Необходимость следить за связями между модулями для исключения конфликтов | ||
+ | * В некоторых ситуациях, | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ===== Настройка многомодульности ===== | ||
+ | После добавления каталогов (в них содержится код подпроектов/ | ||
+ | |||
+ | Добавить их в файл '' | ||
+ | <code java> | ||
+ | include(": | ||
+ | include(": | ||
+ | include(": | ||
+ | include(": | ||
+ | include(": | ||
+ | </ | ||
+ | |||
+ | В модулях feature1 и feature2 (внутри их каталогов) нужно подключить другие модули | ||
+ | <code java> | ||
+ | pluigns { | ||
+ | id(" | ||
+ | } | ||
+ | dependencies { | ||
+ | implementation(project(": | ||
+ | implementation(project(": | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | В app прописывается всё, это центральная точка. | ||
+ | <code java> | ||
+ | pluigns { | ||
+ | id(" | ||
+ | } | ||
+ | dependencies { | ||
+ | implementation(project(": | ||
+ | implementation(project(": | ||
+ | implementation(project(": | ||
+ | implementation(project(": | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Рекомендации ===== | ||
+ | |||
+ | Необходимо определить, | ||
+ | |||
+ | Нужно избегать прямой связи между feature-модулями. К примеру, | ||
+ | Если есть зависимость между feature1 и feature2, то параллельности их сборки добиться не получится - всё будет выполняться последовательно из-за зависимостей одного от другого.\\ | ||
+ | Лучше, чтобы от часто изменяемых модулей зависело как можно меньше других модулей. По той же причине, | ||
+ | |||
+ | ===== Побочные эффекты многомодульности ===== | ||
+ | |||
+ | ==== Проблема дублирования зависимостей ==== | ||
+ | |||
+ | К примеру, | ||
+ | <code java> | ||
+ | // app | ||
+ | dependencies { | ||
+ | implementation(" | ||
+ | implementation(" | ||
+ | } | ||
+ | // feature1 | ||
+ | dependencies { | ||
+ | implementation(" | ||
+ | implementation(" | ||
+ | } | ||
+ | // feature2 | ||
+ | dependencies { | ||
+ | implementation(" | ||
+ | implementation(" | ||
+ | } | ||
+ | //core | ||
+ | dependencies { | ||
+ | implementation(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | В этом случае, | ||
+ | |||
+ | Варианты решения этой проблемы: | ||
+ | |||
+ | * Шарить версии библиотек через ext или gradle.properties | ||
+ | * Version catalog (рекомендуется) | ||
+ | |||
+ | === Version catalog === | ||
+ | - Создать .toml с описанием зависимостей, | ||
+ | - Задекларировать Version catalog <file java settings.gradle.kts> | ||
+ | dependencyResolutionManagement { | ||
+ | versionCatalogs { | ||
+ | create(" | ||
+ | from(files(" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | - Использовать Version catalog <file yaml libs.toml> | ||
+ | [versions] | ||
+ | kotlin = " | ||
+ | appcompat = " | ||
+ | |||
+ | [libraries] | ||
+ | kotlin-android-ktx = { group = " | ||
+ | android-appcompat = { group = " | ||
+ | |||
+ | [bundles] | ||
+ | feature-deps = [" | ||
+ | </ | ||
+ | |||
+ | Теперь можно писать в '' | ||
+ | <code java> | ||
+ | // можно так, если в libs.toml нет секции [bundles] | ||
+ | dependencies { | ||
+ | implementation(libs.files.io) | ||
+ | implementation(libs.lifecycle.runtime.ktx) | ||
+ | } | ||
+ | // или, если секция [bundles] есть, сразу так | ||
+ | dependencies { | ||
+ | implementation(libs.bundles.feature) | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | **Та же схема работает и для плагинов** - их так же можно подцепить через файл .toml. | ||
+ | |||
+ | <file yaml libs.toml> | ||
+ | [versions] | ||
+ | kotlin = " | ||
+ | agp = " | ||
+ | |||
+ | [plugins] | ||
+ | kotlin-android = { id = " | ||
+ | android-library = { id = " | ||
+ | </ | ||
+ | |||
+ | <file java feature1/ | ||
+ | plugins { | ||
+ | alias(libs.plugins.kotlin.android) | ||
+ | alias(libs.plugins.android.library) | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Помимо удобства, | ||
+ | |||
+ | ==== Проблема дублирования кода конфигурации сборок ==== | ||
+ | т. е., '' | ||
+ | |||
+ | <file java build.gradle.kts> | ||
+ | plugins { | ||
+ | alias(libs.plugins.android.library) | ||
+ | alias(libs.plugins.kotlin.android) | ||
+ | } | ||
+ | |||
+ | android { | ||
+ | namespace = " | ||
+ | compileSdk = 33 | ||
+ | defaultConfig { | ||
+ | minsdk = 24 | ||
+ | } | ||
+ | } | ||
+ | |||
+ | dependencies { | ||
+ | implementation (libs.bundles.feature.deps) | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | решение - convention plugins | ||
+ | |||
+ | ==== Проблема увеличения времени конфигурации проекта ==== | ||
+ | Решение - '' | ||
+ | |||
+ | ===== Проблемы больших проектов ===== | ||
+ | |||
+ | ==== Конфигурация сборки и доп. логика сборки начинает смешиваться, | ||
+ | m( С 49-й минуты - идиотское программистское красноглазие - мегаудобство, | ||
+ | |||
+ | Например, | ||
+ | <file java app/ | ||
+ | import java.io.File | ||
+ | import java.net.URL | ||
+ | |||
+ | tasks.register(" | ||
+ | doLast { | ||
+ | in uploadApkToGoogle(file(File(rootDir, | ||
+ | } | ||
+ | } | ||
+ | tasks.findByName(" | ||
+ | </ | ||
+ | |||
+ | Для решения этой проблемы изобрели Convention plugins. | ||
+ | |||
+ | Создаётся каталог с любым именем (ну, т. е., ещё один плагин/ | ||
+ | Затем, в корневом '' | ||
+ | <code java> | ||
+ | pluginManagement { | ||
+ | includeBuild(" | ||
+ | } | ||
+ | </ | ||
+ | В '' | ||
+ | <file java build-logic/ | ||
+ | dependencyResolutionManagement { | ||
+ | versionCatalogs { | ||
+ | create(" | ||
+ | from(files(" | ||
+ | } | ||
+ | } | ||
+ | repositories { | ||
+ | google() | ||
+ | mavenCentral() | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | Там же '' | ||
+ | <file java build-logic/ | ||
+ | plugins { | ||
+ | // без этого не взлетит | ||
+ | " | ||
+ | } | ||
+ | |||
+ | dependencies { | ||
+ | implementation(libs.agp) | ||
+ | implementation(libs.cotlin.gradle.plugin) | ||
+ | // Костыль для работы version plugins внутри Convention plugins, иначе не работает (система ниппель!) | ||
+ | implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) | ||
+ | } | ||
+ | </ | ||
+ | После этого можно создавать сами файлы Convention-плагинов. Вид дерева каталогов: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Названия файлов важны, т. к. к ним потом нужно будет обращаться. | ||
+ | |||
+ | <file java android-feature-convention.gradle.kts> | ||
+ | plugins { | ||
+ | id(" | ||
+ | id(" | ||
+ | } | ||
+ | |||
+ | android { | ||
+ | compileSdk = 33 | ||
+ | | ||
+ | defaultConfig { | ||
+ | minSdk = 24 | ||
+ | } | ||
+ | } | ||
+ | dependencies { | ||
+ | implementation(libs.bundles.feature.deps) | ||
+ | } | ||
+ | </ | ||
+ | В файле выше не будет работать конструкция '' | ||
+ | Поэтому надо найти плагин, | ||
+ | |||
+ | В файл '' | ||
+ | <file java publish-google-convention.gradle.kts> | ||
+ | import java.io.File | ||
+ | import java.net.URL | ||
+ | |||
+ | tasks.register(" | ||
+ | doLast { | ||
+ | in uploadApkToGoogle(file(File(rootDir, | ||
+ | } | ||
+ | } | ||
+ | tasks.findByName(" | ||
+ | </ | ||
+ | |||
+ | Теперь '' | ||
+ | |||
+ | <file java feature1/ | ||
+ | plugins { | ||
+ | id(" | ||
+ | } | ||
+ | |||
+ | android { | ||
+ | namespace = " | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <file java app/ | ||
+ | plugins { | ||
+ | id(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== Gradle wrapper ===== | ||
+ | Рекомендуемый способ от разработчика Gradle для работы с ним. Структура файлов: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | Здесь команда '' | ||
+ | |||
+ | Основной параметр в файле '' | ||
+ | <file java gradle-wrapper.properties> | ||
+ | // Указывает, | ||
+ | distributionUrl=https\:// | ||
+ | </ | ||
+ | Если Гредл уже скачан, | ||
+ | |||
+ | Преимущества использования wrapper: | ||
+ | * Легко использовать Гредл без предварительной установки | ||
+ | * Стандартизация сборки. К примеру, | ||
+ | * Быстрое переключение версии Гредла для проекта. | ||
+ | |||
+ | ===== Gradle daemon ===== | ||
+ | Это фоновый JVM-процесс, | ||
+ | - Кэширует информацию о проектах между сборками | ||
+ | - Висит в фоне, поэтому при запуске сборки не тратится время на инициализацию JVM | ||
+ | - Следит за изменениями файлов проекта | ||
+ | |||
+ | Схема работы: | ||
+ | |||
+ | <code bash> | ||
+ | # Статус запущенных демонов | ||
+ | ./gradlew --status | ||
+ | # Запуск сборки с демоном из терминала | ||
+ | ./gradlew build --daemon | ||
+ | # Запуск сборки без демона | ||
+ | ./gradlew build --no-daemon | ||
+ | # Принудительно остановить все демоны (например, | ||
+ | ./gradlew --stop | ||
+ | </ | ||
+ | |||
+ | <file java gradle.properties> | ||
+ | // Настройка Гредла для запуска сборки с демоном (false - без демона) | ||
+ | org.gradle.daemon=true | ||
+ | </ | ||
+ | |||
+ | ===== Жизненный цикл сборки / Tasks ===== | ||
+ | * Инициализация | ||
+ | * Анализ '' | ||
+ | * Создание экземпляра класса Project для каждого проекта/ | ||
+ | * Конфигурация | ||
+ | * Анализ сценария сборки из '' | ||
+ | * Создание направленного ациклического графа задач (DAG) для каждого проекта. Задачи выполняются в порядке их зависимости друг от друга. | ||
+ | * Выполнение | ||
+ | * Планирование и выполнение всех задач в соответствии с построенным графом | ||
+ | |||
+ | Gradle task - кусок логики, |