Настройка Многомодульных Проектов



Фон Иногда, когда я откладываю дела на потом, я занимаюсь уборкой: убираю со стола, убираю вещи, прибираюсь в комнате.

По сути, я привожу обстановку в порядок – это заряжает меня энергией и настраивает на рабочий лад. У меня такая же ситуация с программированием, только я подчищаю проект: провожу рефакторинги, создаю различные инструменты и всячески стараюсь облегчить жизнь себе и коллегам.

Некоторое время назад мы в команде Android решили сделать один из наших проектов — Кошелек - многомодульный.

Это привело как к ряду преимуществ, так и к проблемам, одной из которых была необходимость перенастройки каждого модуля.

Конечно, мы можем просто скопировать конфигурацию из модуля в модуль, но если мы захотим что-то изменить, нам придется перебрать все модули.

Мне это не нравится, команде это не нравится, и вот шаги, которые мы предприняли, чтобы облегчить нашу жизнь и упростить обслуживание конфигураций.



Настройка многомодульных проектов



Первая итерация — удаление версий библиотеки

На самом деле, это уже было в проекте до меня, и вы, возможно, знакомы с этим подходом.

Я часто вижу, как разработчики используют его.

Подход заключается в перемещении версий библиотеки в отдельные глобальные свойства проекта, после чего они становятся доступными во всем проекте, что помогает их повторно использовать.

Обычно это делается в файле build.gradle на уровне проекта, но иногда эти переменные помещаются в отдельный файл .

gradle и включаются в основной файл build.gradle. Скорее всего, вы уже видели такой код в проекте.

В этом нет никакой магии, это всего лишь одно из расширений Gradle под названием Расширение ExtraPropertiesExtension .

Короче говоря, это просто карта , доступный по имени ext в объекте проект , а все остальное — работа как бы с объектом, блоками конфигурации и т. д. — это магия Gradle. Примеры:

.

gradle

.

gradle.kts

  
  
  
  
  
   

// creation ext { dagger = '2.25.3' fabric = '1.25.4' mindk = 17 } // usage println(dagger) println(fabric) println(mindk)



// creation val dagger by extra { "2.25.3" } val fabric by extra { "1.25.4" } val minSdk by extra { 17 } // usage val dagger: String by extra.properties val fabric: String by extra.properties val minSdk: Int by extra.properties

Что мне нравится в этом подходе, так это то, что он чрезвычайно прост и помогает сохранять версии вместе.

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

Кстати, аналогичного эффекта можно добиться, используя gradle.properties вместо ExtraPropertiesExtension только будь осторожен : ваши версии могут быть переопределены во время сборки с помощью флагов -P, и если вы обращаетесь к переменной просто по имени в groovy-скриптах, то gradle.properties также переопределит их.

Пример с gradle.properties и переопределить:

// grdle.properties overriden=2 // build.gradle ext.dagger = 1 ext.overriden = 1 // module/build.gradle println(rootProject.ext.dagger) // 1 println(dagger) // 1 println(rootProject.ext.overriden)// 1 println(overriden) // 2



Вторая итерация — project.subprojects

Мое любопытство вкупе с нежеланием копировать код и разбираться с настройкой каждого модуля привело меня к следующему шагу: я вспомнил, что в корне build.gradle есть блок, который генерируется по умолчанию — всепроекты .



allprojects { repositories { google() jcenter() } }

я пошел в документация и обнаружил, что в него можно передать блок кода, который будет настраивать этот проект и все подпроекты.

Но это было не совсем то, что мне нужно, поэтому я прокрутил дальше и нашел подпроекты — метод настройки всех вложенных проектов одновременно.

Мне пришлось добавить несколько проверок и вот что произошло .

Пример настройки модуля через project.subprojects

subprojects { project -> afterEvaluate { final boolean isAndroidProject = (project.pluginManager.hasPlugin('com.android.application') || project.pluginManager.hasPlugin('com.android.library')) if (isAndroidProject) { apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion vectorDrawables.useSupportLibrary = true } compileOptions { encoding 'UTF-8' sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } androidExtensions { experimental = true } } } dependencies { if (isAndroidProject) { // android dependencies here } // all subprojects dependencies here } project.tasks .

withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) .

all { kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() } } }

Теперь для любого модуля с подключенным плагином com.android.application или com.android.library мы можем настроить что угодно: плагины, конфигурации плагинов, зависимости.

Все было бы замечательно, если бы не пара проблем: если мы захотим переопределить в модуле какие-то параметры, указанные в подпроектах, то мы не сможем этого сделать, так как настройка модуля происходит до использования подпроектов.

(Спасибо послеEvaluate ).

А если мы захотим не использовать эту автоматическую настройку в отдельных модулях, то в блоке подпроектов начнет появляться множество дополнительных проверок.

Поэтому я начал думать дальше.



Третья итерация — buildSrc и плагин

До этого момента я уже несколько раз слышал о buildSrc и видел примеры, в которых buildSrc использовался как альтернатива первому шагу в этой статье.

Еще я слышал о плагинах для Gradle, поэтому начал копать в этом направлении.

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

Немного разобравшись, я сделал плагин , в котором можно настроить всё необходимое с возможностью изменения при необходимости.

Код плагина

import org.gradle.api.JavaVersion import org.gradle.api.Plugin import org.gradle.api.Project class ModulePlugin implements Plugin<Project> { @Override void apply(Project target) { target.pluginManager.apply("com.android.library") target.pluginManager.apply("kotlin-android") target.pluginManager.apply("kotlin-android-extensions") target.pluginManager.apply("kotlin-kapt") target.android { compileSdkVersion Versions.sdk.compile defaultConfig { minSdkVersion Versions.sdk.min targetSdkVersion Versions.sdk.target javaCompileOptions { annotationProcessorOptions { arguments << ["dagger.gradle.incremental": "true"] } } } // resources prefix: modulename_ resourcePrefix "${target.name.replace("-", "_")}_" lintOptions { baseline "lint-baseline.xml" } compileOptions { encoding 'UTF-8' sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests { returnDefaultValues true includeAndroidResources true } } } target.repositories { google() mavenCentral() jcenter() // add other repositories here } target.dependencies { implementation Dependencies.dagger.dagger implementation Dependencies.dagger.android kapt Dependencies.dagger.compiler kapt Dependencies.dagger.androidProcessor testImplementation Dependencies.test.junit // add other dependencies here } } }

Теперь новая конфигурация проекта выглядит так применить плагин:⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠⁠’ru.yandex.money.module’ и это все.

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

больше не нужно думать о настройке.

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

Важный момент: если вы используете плагин Android gradle ниже 4.0, то некоторые вещи очень сложно сделать в скриптах Kotlin — по крайней мере, блок Android проще настроить в скриптах groovy. Есть проблема с тем, что некоторые типы при компиляции недоступны, а groovy типизируется динамически, и ему пофиг =)

Далее — автономный плагин или монорепо.

Конечно, третий шаг – это еще не все.

Нет предела совершенству, поэтому есть варианты, куда двигаться дальше.

Первый вариант - автономный плагин для Градла.

После третьего шага это уже не так сложно: нужно создать отдельный проект, перенести туда код и настроить публикацию.

Плюсы: плагин можно использовать между несколькими проектами, что упростит жизнь не одному проекту, а экосистеме.

Минусы: версионирование — при обновлении плагина вам придется обновлять и проверять его работоспособность сразу в нескольких проектах, а это может занять время.

Кстати, у моих коллег из backend-разработки есть отличное решение на эту тему, ключевое слово модернизатор — инструмент, который сам перебирает репозитории и обновляет зависимости.

Я не буду долго останавливаться на этом; лучше пусть они расскажут это сами.

Monorepo звучит грандиозно, но у меня нет опыта работы с ним, и у меня есть мысли только о том, что один проект, например buildSrc, можно использовать сразу в нескольких других проектах, и это могло бы помочь решить проблему с версионированием.

Если у вас вдруг появится опыт работы с монорепо, то поделитесь им в комментариях, чтобы я и другие читатели могли что-то о нем узнать.



Общий

В новом проекте делайте сразу третий шаг - buildSrc и плагин - всем будет проще, тем более что Я приложил код .

А второй шаг — project.subprojects — используйте для соединения общих модулей друг с другом.

Если у вас есть что добавить или возразить, пишите в комментариях или ищите меня в социальных сетях.

Теги: #Android #Разработка Android #Разработка мобильных приложений #Kotlin #разработка Android #gradle #Groovy #android dev #kotlin android #yumoney #yumani #yumani #yumani #buildSrc

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.