Рубрики
Без рубрики

Простая модульность: поддержание чистоты ваших сценариев сборки Gradle и устранение дублирования в ваших многомодульных проектах.

Использование предварительно скомпилированных плагинов скриптов для упрощения логики сборки. С тегами gradle, android, java, kotlin.

Недавно я начал разрушать монолит, который унаследовал на своем нынешнем месте работы. Чтобы упростить задачу для себя и для остальной команды, которой придется ее поддерживать, я создал три плагина соглашения в buildSrc . Gradle вызывает эти предварительно скомпилированные плагины скриптов , и они могут быть написаны либо на Groovy, либо на Kotlin. Приведенные ниже примеры приведены в Kotlin. Давайте посмотрим!

buildSrc/build.gradle.kts

plugins {
  `kotlin-dsl`
}

repositories {
  jcenter()
  google()
}

dependencies  {
  implementation("com.android.tools.build:gradle:4.0.1")
  implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72")
}

Это дополнение к минимальному минимуму, который вам понадобится, если у вас есть плагины для конвенций, которые применяют любой из плагинов Android или Kotlin.

buildSrc/src/main/kotlin/android-library-convention.gradle.kts

plugins {
  id("com.android.library")
  id("kotlin-android")
}
android {
  compileSdkVersion(30)
  defaultConfig {
    minSdkVersion(21)
    targetSdkVersion(30)
    versionCode = 1
    versionName = "1"
  }
  compileOptions {
    targetCompatibility = JavaVersion.VERSION_1_8
    sourceCompatibility = JavaVersion.VERSION_1_8
  }
  kotlinOptions {
    jvmTarget = "1.8"
  }
  testOptions {
    unitTests.isReturnDefaultValues = true
    unitTests.isIncludeAndroidResources = true
  }
}
dependencies {
  implementation(platform(project(":platform")))
  implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}

Любой разработчик Android распознал бы этот скрипт, поскольку это именно то, что они использовали бы, если бы использовали Kotlin DSL и просто создали скрипт на месте, примерно в android-lib-module/build.gradle.kts . Теперь вы можете применить это следующим образом:

android-lib-модуль/build.gradle.kts

plugins {
  `android-library-convention`
}

И это все! Вы, конечно, можете настроить его так, как вам нравится, например, добавив зависимости, необходимые этому модулю (но не необходимые для всех модулей и, следовательно, не являющиеся частью плагина соглашения).

Внимательные читатели наверняка заметили в плагине что-то слегка необычное:

dependencies {
  implementation(platform(project(":platform")))
}

Это относится к проекту Java Platform и является текущей передовой практикой для управления и централизации версий зависимостей. Обсуждение плагина платформы Java выходит за рамки этой статьи, но я включаю его здесь в качестве примера того, как вы можете использовать этот подход к плагину соглашения для уменьшения шаблонности — без этого вам нужно было бы добавить implementation(platform(project(":platform"))) к каждая библиотека Android создает скрипт сборки, который легко забыть.

Это очень просто:

buildSrc/src/main/kotlin/java-library-convention.gradle.kts

plugins {
  `java-library`
}

java {
  sourceCompatibility = JavaVersion.VERSION_1_8
  targetCompatibility = JavaVersion.VERSION_1_8
}

dependencies {
  implementation(platform(project(":platform")))
}

И применяется, как и раньше: java-lib-module/build.gradle.kts

plugins {
  `java-library-convention`
}

Что мне особенно нравится в этом, так это то, что мне не нужно забывать добавлять Совместимость с источниками и targetCompatibility для каждого модуля библиотеки Java, который у меня есть.

Это тоже очень просто:

buildSrc/src/main/kotlin/kotlin-библиотека-convention.gradle.kts

plugins {
  id("org.jetbrains.kotlin.jvm")
}

tasks.withType().configureEach {
  kotlinOptions {
    jvmTarget = "1.8"
  }
}

dependencies {
  implementation(platform(project(":platform")))
  implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}

И применяется, как и раньше: kotlin-lib-module/build.gradle.kts

plugins {
  `kotlin-library-convention`
}

Честно говоря, я даже не знаю, сколько раз я создавал новый модуль Kotlin и забывал добавить stdlib. В IDE все выглядит нормально, но удачи в его компиляции!

Причина, по которой я решил показать три отдельных примера, заключается в том, что я придаю большое значение ценностям конкретности и минимализма. 1 Мы могли бы еще больше сократить шаблонность, если бы остановились после написания нашего android-library-convention плагин. В конце концов, он уже знает, как компилировать проекты Android, библиотеки Java и библиотеки Kotlin.

Вероятно, самая большая проблема, связанная с упрощением до такой степени, заключается в негативном влиянии, которое это окажет на производительность сборки. Создание библиотек Android намного сложнее и медленнее, чем создание библиотек Java. Создание библиотек Kotlin также происходит медленнее, чем создание библиотек Java. И обработка аннотаций происходит намного медленнее, чем без обработки аннотаций! (Пожалуйста, не применяйте kotlin-kapt если только вы на самом деле не нуждаетесь в обработке аннотаций Kotlin.) Применяйте только то, что вам нужно, и не более: плагины не бесплатны.

С точки зрения архитектуры программного обеспечения также полезно быть конкретным. Применяя только плагин JVM, вы указываете, что он предназначен как библиотека JVM, и вы усложняете его (хорошая вещь!) для следующего разработчика, переходящего к импорту android.content. Контекст .

Зачем останавливаться на достигнутом? Я регулярно вижу, как люди спрашивают о следующем сценарии:

У меня гетерогенная сборка с модулями приложений Android, модулями библиотеки Android и модулями JVM (Java/Kotlin). Я хочу иметь возможность запускать что-то вроде ./gradlew test и запускать все важные тестовые задачи во всех модулях. Как я могу это сделать?

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

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

...

tasks.register("mainTest") {
  dependsOn("testDebugUnitTest") // for example
}

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

...

tasks.register("mainTest") {
  dependsOn("test")
}

(И то же самое для kotlin-library-convention .)

Теперь вы можете легко воспользоваться одним из удобств Gradle 2 и выполнить

./gradlew mainTest

Когда вы это сделаете, Gradle выполнит основной тест задача в каждом модуле, в котором она есть. Если в модуле его нет, все в порядке; ошибка будет только в том случае, если задача отсутствует в any module.

Стоит отметить, что следующее было бы не желательно:

./gradlew test testDebugUnitTest

Вы можете подумать, что это позволяет выполнить то же самое за счет всего лишь еще одного слова в командной строке, без необходимости добавлять задачу в каждый модуль. К сожалению, вы столкнетесь с тем фактом, что Android projects do имеют задачу с именем “test”! Вот часть результатов выполнения ./gradlew приложение: test -- dry-run для моего небольшого проекта (с двумя типами сборки и двумя вариантами продукта):

...
:app:preProductionReleaseBuild SKIPPED
:app:compileProductionReleaseAidl SKIPPED
:app:compileProductionReleaseRenderscript SKIPPED
:app:generateProductionReleaseBuildConfig SKIPPED
:app:generateProductionReleaseResValues SKIPPED
:app:generateProductionReleaseResources SKIPPED
:app:injectCrashlyticsMappingFileIdProductionRelease SKIPPED
:app:processProductionReleaseGoogleServices SKIPPED
:app:mergeProductionReleaseResources SKIPPED
:app:createProductionReleaseCompatibleScreenManifests SKIPPED
:app:extractDeepLinksProductionRelease SKIPPED
:app:processProductionReleaseManifest SKIPPED
:app:processProductionReleaseResources SKIPPED
:app:kaptGenerateStubsProductionReleaseKotlin SKIPPED
:app:kaptProductionReleaseKotlin SKIPPED
:app:compileProductionReleaseKotlin SKIPPED
:app:javaPreCompileProductionRelease SKIPPED
:app:compileProductionReleaseJavaWithJavac SKIPPED
:app:kaptGenerateStubsProductionReleaseUnitTestKotlin SKIPPED
:app:kaptProductionReleaseUnitTestKotlin SKIPPED
:app:compileProductionReleaseUnitTestKotlin SKIPPED
:app:preProductionReleaseUnitTestBuild SKIPPED
:app:javaPreCompileProductionReleaseUnitTest SKIPPED
:app:compileProductionReleaseUnitTestJavaWithJavac SKIPPED
:app:mergeProductionReleaseShaders SKIPPED
:app:compileProductionReleaseShaders SKIPPED
:app:generateProductionReleaseAssets SKIPPED
:app:mergeProductionReleaseAssets SKIPPED
:app:packageProductionReleaseUnitTestForUnitTest SKIPPED
:app:generateProductionReleaseUnitTestConfig SKIPPED
:app:processProductionReleaseJavaRes SKIPPED
:app:processProductionReleaseUnitTestJavaRes SKIPPED
:app:testProductionReleaseUnitTest SKIPPED
:app:test SKIPPED

Это даже не одна десятая его части. Если вы еще этого не знали, вы бы быстро узнали, что test – это, по сути, псевдоним для каждого варианта модульного теста.

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

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

1 Однако не в концевых сносках. Чем больше, тем веселее! вверх 2 Gradle отлично удобен. вверх

Оригинал: “https://dev.to/autonomousapps/easy-modularity-keeping-your-gradle-build-scripts-clean-and-eliminating-duplication-in-your-multi-module-projects-3pa”