配置多模块项目

背景



有时候,当我拖延时间时,我会打扫房间:我打扫桌子,把东西拿出来,整理房间。实际上,我把环境整理得井井有条-它激发并为您的工作做好准备。使用编程,我的处境是一样的,只有我清理项目:我进行重构,制作各种工具,并尽我所能使自己和同事的生活更轻松。



前一段时间,我们在Android团队中决定将我们的项目之一-电子钱包-多模块化。这带来了许多优点和问题,其中之一就是需要从头开始配置每个模块。当然,您可以将配置从一个模块复制到另一个模块,但是如果要更改某些内容,则必须遍历所有模块。



我不喜欢这样,团队也不喜欢,这是我们为简化生活和使配置更易于维护而采取的步骤。







第一次迭代-提取库版本



实际上,这已经在我之前的项目中了,您可能知道这种方法。我经常看到开发人员使用它。



这种方法是有必要将库的版本移到项目的单独全局属性中,然后在整个项目中可用它们,这有助于重用它们。通常,这是在项目级别的build.gradle文件中完成的,但有时这些变量会被取出到单独的.gradle文件中,并包含在主build.gradle中。



您很可能已经在项目中看到了这样的代码。它没有魔力,它只是Gradle扩展之一,称为ExtraPropertiesExtension。简而言之,它只是Map <String,Object>,可通过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



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.applicationcom.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)用于将通用模块彼此连接。



如果您有任何要添加或反对的内容,请写评论或在社交网络上寻找我。



All Articles