如何簡單快速搭建 Android 大倉
書接上文,上回提到 B 站Android團隊為了解決元件化後協作上的問題,已經採用了大倉(monorepo)
的方案來組織程式碼。
國內實踐大倉的團隊少之又少,更別提 Android 的大倉了,幾乎沒有來自其它團隊的可借鑑經驗。在這條路上,我們可以算作先行者。本文粗陋,文中所列思路不可能適用所有團隊,僅給同樣想實踐Android 大倉的人些許啟發。
一個標準的 Gradle 專案
首先回顧一下 Android 專案的組織方式。自從13年開始官方逐漸遷移到 Android Studio 做為 IDE 後,Android 專案的開發和編譯就綁在 Gradle 上了。
一個標準的 Gradle 專案
結構如下所示:
MyApp/ ├── build.gradle ├── settings.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradle.properties └── app ├── build.gradle └── src └── main ├── java ├── res └── AndroidManifest.xml
通常,會有多個Gradle Module存在:
MyApp/
├── build.gradle
├── settings.gradle
├── app
│ ├── build.gradle
│ └── src
├── lib1
│ ├── build.gradle
│ └── src
└── lib2
├── build.gradle
└── src
其中 settings.gradle
會註冊所有的 Module
include ':app', ':lib1', ':lib2'
多倉庫
隨業務的擴張,Module 數量會越來越多。遵循多數人實踐過的元件化的思路,按業務分倉庫存放便理所當然:
android group/
├── MyApp/
│ ├── build.gradle
│ └── settings.gradle
├── app1/
│ ├── build.gradle
│ └── settings.gradle
├── app2/
│ ├── build.gradle
│ └── settings.gradle
└── libs/
├── build.gradle
└── settings.gradle
每個倉庫都是一個標準 Gradle 專案
,通過 publishing
外掛將module 都上傳 aar(或者jar)到 maven私服(如nexus)上,再在 MyApp/build.gradle
中以 maven 元件的形式依賴它們,最終打包成apk:
repositories {
maven {
name = "myRepo"
url = "http://myrepo.example.com/android"
}
}
dependencies {
implementation 'com.example.android:app-a:1.0.0'
implementation 'com.example.android:app-b:1.0.0'
implementation 'com.example.android:lib-a:1.1.0'
}
[圖片上傳失敗...(image-7a44b2-1546505515555)]
此時的程式碼組織方式便是上文中所述的多倉庫形態(可能許多團隊正處於當前階段)。
多倉到大倉
那麼,如何既能快速搭建出適用於 Android 的大倉,又能不影響當前的團隊協作流程,還要儘量避免遷移帶來的開發效率降低?
經過一段時間的深入研究 Gradle API,終於找到一個簡單快速的可行方案——composite builds。
上面說過,其實每個倉庫都是按業務分離的標準的 Gradle 專案,那麼就可以通過Gradle 官方提供的一個直接引入其它專案的API——includeBuild——快速將所有倉庫組織起來。
在 MyApp/settings.gradle
中直接引入其它 Gradle 專案:
includeBuild '../app1'
includeBuild '../app2'
includeBuild '../libs'
另外比較令人驚喜的是,如Gradle 官方所說,includeBuild
會自動替換依賴com.example.android:app-a
為有對應宣告的module,如 project(:app1:app-a)
。這樣仍然可以用原有的依賴寫法,什麼都不用改,基本沒有遷移工作量。
具體示例可以見官方示例專案:https://github.com/gradle/gradle/tree/master/subprojects/docs/src/samples/compositeBuilds/hierarchical-multirepo
而且,使用 includeBuild
能保留當前的開發流程不變,每個模組仍釋出到 maven 上,不破壞既有協作流程。各個業務的開發,通過Android Studio 開啟自己專案的目錄即可,可以說幾乎沒有開發效率的影響。
解決了各個專案合併問題,另外一個就是要保證大倉的可維護性。
上文講過,依然沿用之前分層的方式,按約定的資料夾組織:
<root dir>
├── build.gradle
├── settings.gradle
├── app/
│ ├── app-a
│ │ ├── src
│ │ └── build.gradle
│ ├── app-b
│ │ ├── src
│ │ └── build.gradle
│ ├── build.gradle
│ └── settings.gradle
│
├── common/
│ ├── common-a
│ │ ├── src
│ │ └── build.gradle
│ ├── build.gradle
│ └── settings.gradle
│
├── framework/
│ ├── lib-a
│ │ ├── src
│ │ └── build.gradle
│ ├── build.gradle
│ └── settings.gradle
│
└── MyApp
├── src
└── build.gradle
各個業務方的程式碼只需要按層級對號入座即可。
對號入座的方式有很多種,最簡單的便是直接拷貝專案到對應目錄,而如果要保留原倉庫的提交記錄,則可以使用 git filter-branch 和 git update-index 等命令(略過不表╮( ̄▽ ̄)╭)。
收斂合併許可權
人總是會犯錯的,為了不讓近百名開發成員在一個倉庫裡打架,我們需要在 gitlab 許可權分配的基礎上,通過API實現自動化的方式來合併程式碼。
每個資料夾(或者模組)可以新增一個配置檔案,如OWNERS,業務團隊自己分配 owner和reviewer。
<root dir>
├── app
│ ├── OWNERS
│ └── app-a
│ └── OWNERS
├── common
│ ├── OWNERS
│ └── common-a
│ └── OWNERS
├── framework
│ ├── OWNERS
│ └── lib-a
│ └── OWNERS
└── MyApp
└── OWNERS
通過gitlab webhook及note api來實現一個自動化工具:
當開發人員提交一個新的 Merge Reqeust 時,工具找到有變更資料夾的對應 owner 及 reviewer,提醒他來review 和操作合併;當他在MR中提交merge指令時,由自動化工具執行合併操作。
綜述
- 通過 Gradle 的
includeBuild
將所有分開的倉庫合併到一起,並按一定的目錄層級組織各個子專案。 - 通過 gitlab 的 api 實現一個自動化工具來合併程式碼。
通過上述兩個步驟,便打造了一個大倉雛形。然而這只是大倉的第一步,未來還有很多工作呢…
關於大倉,本文只簡單描述了一下方案大致思路,行文粗糙,希望對想實踐大倉的你有所幫助,如有疑問歡迎評論留言。