腦洞大開,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)
}
複製代碼

固然,咱們在這個過程當中還可使用各種編程技巧。

此時,咱們已經擁有了無限可能,根據項目的實際需求 ,自行拓展吧。

相關文章
相關標籤/搜索