脑洞大开,Gradle项目管理依赖的船新版本

Gradle依赖管理最佳实践

写在最前

笔者最近接受了 躺平不是等死),换了一份新工做。html

这部份内容,也是从前东家的 实际状况 出发的,惋惜没法亲手去推广落地了。java

在前段时间,我发布过一篇拙见:三思系列:组件化场景下module依赖优雅实践方案, 该文在组件化背景下,探索了一种方案,能够同时知足 减小编译数量以减小编译时间便捷的修改依赖树以灵活改动任意层级的Module内容具体内容能够阅读前文,再也不赘述android

除却Module依赖,还有 库包依赖 ,本文着重于探索 库包依赖项 的管理方式,并且是狭义上的 仓库下的库包编程

斗胆 称之为 最佳实践后端

问题背景和必要知识

首先肯定一件事情:markdown

implementation fileTree(dir: 'libs', include: ['*.jar'])
复制代码

此类方式,引入的库包不属于 仓库 范畴,仅讨论基于Maven仓库的范畴,赘述一句,仓库按照习惯又能够分为两种类型:网络

  • Local:特指Maven的MavenLocal仓库,或者Gradle的Cache,MavenLocal和Gradle的Cache本质是一致
  • Remote:经过Uri指定的特定位置的仓库,最为常见的是MavenCentral和JCenter仓库。固然,能够将本机的目录指定为 "远程仓库" 位置。

固然,这并不影响本文的讨论app


众所周知,使用Gradle肯定仓库的库包须要三个因素:框架

  • GroupId
  • ArtifactId
  • version ,'+'号通配符表达 最新 的含义

for example:运维

androidx.core:core-ktx:1.3.2
复制代码
  • GroupId 为 "androidx.core"
  • ArtifactId 为 "core-ktx"
  • version 为 "1.3.2"

问题背景

以Android为例,商业项目中,一个Project仅存在一个Module 的状况应该 很是少见 了, 每每一个Project下会存在多个Module,并且存在必定的依赖关系。

若是没有合适的管理手段,那么每一个Module均声明自身的依赖项,当发生版本变动时:

  • 修改过于零碎
  • 同一个依赖项在不一样Module下可能出现版本差别,这也是上一点所带来的后果

举个更典型的例子,以 后端项目为例微服务 的概念你们必定不陌生.

即便不曾深刻了解,也知道后端将整个服务体系进行了拆分,用多个子系统项目(微服务)共同 支撑完整的服务体系。 以此达到 下降复杂度根据业务特性使用不一样框架根据业务权重定制运维策略 等目的

而微服务之间经过RPC进行通讯,而此处势必牵涉一个最大的 痛点Service方法签名和DTO数据保持一致,不然会带来 方法不存在 或者 数据遗失、解析错误 等问题。

传统作法及其优劣

比较早期的作法,是在Gradle构建时的运行环境中,建立或者利用Project级别的集合对象,将依赖项信息所有写入其中,各个Module使用时,达成了统一。

你们对这种作法很熟悉,再也不用代码举例。 每每须要用到Extension扩展,为了方便描述,咱们将:存储依赖项信息的Project级别集合 称为 Ext.deps

优势

  • 统一管理入口。一次修改,全Project生效

缺点

  • 没法进行代码提示
  • 通常没法兼容于构建工具的 新版本提示
  • 仅针对单Project,没法应对多Project,后端的微服务每每是多Project

改良版

利用Gradle 能够apply 远程构建脚本 (xxx.gradle) 的特性,进行方案改进。

将 "构建 Ext.deps 信息" 的 脚本,存储于网络特定位置,以解决多Project难以管理的问题。

通常须要对脚本文件按照版本命名,并保有全部版本的脚本。

这样能够避免:项目回溯版本功能时,出现额外问题。

利用Gradle留的后门

Gradle编译项目是颇有意思的事情,咱们知道:在成功加载完Gradle项目后,会 编译Gradle脚本 并生成各种Gradle任务,实际状况会更加复杂,为了方便,咱们将之称为 Task编译

既然存在编译过程,Gradle团队索性留了一个后门:

若是根项目下存在"buildSrc", gradle 认为这是在Task编译过程当中须要编译的内容,这些内容可能包含了:

  • Gradle插件内容
  • 插件设置内容
  • 等等

而且其编译结果对于该项目下的Gradle内容透明

这并非一个新的特性,它至少已经有五年的历史了

Gradle官方指导文档 ,官方文档对其使用方式作了概要的描述。

勘误

由于buildSrc机制已经不是一个新特性了,故而利用这个机制去 管理Gradle依赖信息 已是一个老话题了。

多是巧合,该作法出如今开发者视野中时,恰好是 gradle开始对 kotlin-dsl 进行支持,一样不是新特性,大约是三年前的Gradle-4.10

而开始流行的作法又刚好对新特性进行了尝鲜,而且在讲解视频中留下了一些坑,因而这一作法的着重点,便被吸引到了 如何正确使用kotlin管理Gradle项目的依赖项这一话题上。

这一作法和kotlin、kts脚本并没有实质关联

作法

在buildSrc目录下,按照标准sourceSet结构创建目录,并新增类文件例如:

buildSrc/src/main/java/Deps.java

public class Deps {
    public static String junit = "junit:junit:4.13.0";
}
复制代码

sync后,类会被编译,咱们能够在项目下的Gradle脚本中,只用使用,例如:

dependencies {
    //...
    
    testImplementation Deps.junit
// 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
复制代码

而且能够享有 代码提示跳转javaDoc弹窗 功能


而可查询到的常见作法,每每是使用kotlin类,那么就须要让buildSrc 在编译时支持kotlin ,那么天然须要 添加插件

在buildSrc下新建 build.gradle 并添加插件:

apply plugin: "kotlin"

buildscript {
    ext.kotlin_version = "1.4.21"
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
repositories {
    jcenter()
}
复制代码

便可,此时添加的kotlin类便可被编译。

buildSrc/src/main/java/KDeps.kt

object KDeps {
    @JvmStatic
    val ext_junit = "androidx.test.ext:junit:1.1.2"
}
复制代码

使用示例:

dependencies {
    testImplementation Deps.junit
// 'junit:junit:4.+'
    androidTestImplementation KDeps.ext_junit
    //'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
复制代码

一样能够享有 代码提示跳转javaDoc弹窗 功能

而网传的 kts脚本 以及添加 kotlin-dsl支持,其实在这个需求中,并没有真正的有效用途,只不过是应用了kts脚本后, 自己就须要编译kotlin内容,因此 默认使用了kotlin编译插件


言归正传,使用这种管理方式后,咱们解决了无代码提示的弊端,再次 利用机器解放生产力

可是,咱们没有解决服务端例子中的问题

将依赖信息打包发布

我想你已经深入意识到了buildSrc机制的本质是啥:

利用Gradle 编译 buildSrc内容,产物供 后续的 该项目的 Gradle编译过程 使用

那么你必定能够想到,buildSrc能够申明自身的依赖!

因而,咱们对经常使用库包进行分析后,选取对象并肯定版本后,便可编写一个Library,

  • 将库包信息写成常量
  • 对Library创建版本机制
  • 发布Library并在buildSrc中使用

这是最简单的作法,便可在多个Project下,以最小的人力成本管理依赖并知足 一致性需求

进阶

Library依赖 Gradle后,能够编写 Gradle-Task内容配置 的过程代码,封装 依赖添加依赖检查 等内容。

举个简单的例子:

object KDeps {
    // @JvmStatic
    const val ext_junit = "androidx.test.ext:junit:1.1.2"
}
复制代码
public class Deps {
    public static String junit = "junit:junit:4.13.0";

    public static void applyAll(Project project) {
        project.getDependencies().add(
                "testImplementation", junit
        );
        project.getDependencies().add(
                "androidTestImplementation",KDeps.ext_junit
        );
    }
}

复制代码

buildSrc/build.gradle

apply plugin: "kotlin"

buildscript {
    ext.kotlin_version = "1.4.21"
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// implementation 'com.android.tools.build:gradle:4.1.1'
        //gradle sdk
        gradleApi()
    }
}
repositories {
    jcenter()
}
复制代码

在app 的build.gradle中,能够这样使用:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    //略
}

dependencies {
    //略
    
    //修改成直接在 afterEvaluate 后调用函数设置
// testImplementation Deps.junit
// androidTestImplementation KDeps.ext_junit
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

afterEvaluate {
    Deps.applyAll(project)
}
复制代码

固然,咱们在这个过程当中还可使用各种编程技巧。

此时,咱们已经拥有了无限可能,根据项目的实际需求 ,自行拓展吧。

相关文章
相关标签/搜索