背景
有时候,当我拖延时间时,我会打扫房间:我打扫桌子,把东西拿出来,整理房间。实际上,我把环境整理得井井有条-它激发并为您的工作做好准备。使用编程,我的处境是一样的,只有我清理项目:我进行重构,制作各种工具,并尽我所能使自己和同事的生活更轻松。
前一段时间,我们在Android团队中决定将我们的项目之一-电子钱包-多模块化。这带来了许多优点和问题,其中之一就是需要从头开始配置每个模块。当然,您可以将配置从一个模块复制到另一个模块,但是如果要更改某些内容,则必须遍历所有模块。
我不喜欢这样,团队也不喜欢,这是我们为简化生活和使配置更易于维护而采取的步骤。
第一次迭代-提取库版本
实际上,这已经在我之前的项目中了,您可能知道这种方法。我经常看到开发人员使用它。
这种方法是有必要将库的版本移到项目的单独全局属性中,然后在整个项目中可用它们,这有助于重用它们。通常,这是在项目级别的build.gradle文件中完成的,但有时这些变量会被取出到单独的.gradle文件中,并包含在主build.gradle中。
您很可能已经在项目中看到了这样的代码。它没有魔力,它只是Gradle扩展之一,称为ExtraPropertiesExtension。简而言之,它只是Map <String,Object>,可通过ext在项目对象中使用,以及其他所有功能-就像与对象,配置块等一起使用-Gradle的魔力。例子:
.gradle | .gradle.kts |
---|---|
|
|
我喜欢这种方法的原因是它非常简单,可以帮助保持版本运行。但这有缺点:您需要确保开发人员使用该集合中的版本,并且这并不能大大简化新模块的创建,因为您仍然需要复制很多东西。
顺便说一句,使用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。
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插件的任何模块,我们可以配置任何东西:插件,插件配置,依赖项。
如果不是因为几个问题,一切都会好起来的:如果我们想覆盖模块子项目中指定的某些参数,那么我们将无法做到这一点,因为模块是在应用子项目之前配置的(感谢afterEvaluate)。而且,如果我们不想在各个模块中应用此自动配置,则许多其他检查将开始出现在子项目块中。所以我开始思考。
第三次迭代-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
}
}
}
现在,新项目的配置类似于apply插件:.ru.yandex.money.module,仅此而已。您可以在android或dependencies块中添加自己的内容,可以添加插件或对其进行自定义,但是主要的事情是新模块配置在一行中,并且其配置始终是相关的,产品开发人员不再需要考虑对其进行设置。
在缺点中,我要指出的是,此解决方案需要更多的时间和材料的研究,但是,以我的观点,这是值得的。如果您将来希望将插件作为一个单独的项目移出,则不建议在插件中的模块之间设置依赖项。
重要提示:如果您使用的是低于4.0的android gradle插件,那么在kotlin脚本中很难做一些事情-至少android块更容易在Groovy脚本中配置。问题在于某些类型在编译时不可用,并且groovy是动态类型的,这对他来说并不重要=)
下一步-独立插件或Monorepo
当然,第三步还不是全部。完美无止境,因此下一步有很多选择。
第一个选择是gradle的独立插件。第三步之后,它不再那么困难:您需要创建一个单独的项目,在此处传输代码并配置发布。
优点:插件可以在多个项目之间摸索,这不仅可以简化一个项目,而且可以简化生态系统中的生活。
缺点:版本控制-更新插件时,您将不得不一次在多个项目中更新并检查其功能,这可能会花费一些时间。顺便说一句,来自后端开发的同事在这个主题上有一个很好的解决方案,关键字是modernizer-这个工具本身会遍历存储库并更新依赖项。我不会在很长一段时间内对此进行详细介绍,让他们更好地告诉自己。
Monorepo-听起来很响,但是我没有经验,但是仅考虑一个项目,例如buildSrc,可以一次在其他多个项目中使用,这可以帮助解决版本控制问题。如果您突然有过Monorepo的经验,请在评论中分享它,以便我和其他读者可以从中学习到一些东西。
总
在一个新项目中,立即执行第三步-buildSrc和plugin-每个人都将更容易,尤其是因为我已经附加了代码。第二步(project.subprojects)用于将通用模块彼此连接。
如果您有任何要添加或反对的内容,请写评论或在社交网络上寻找我。