將AOSP加入Gerrit Server : 架設本地AOSP Gerrit Server完整指引 PART2

前言

本文在引導你如何將AOSP (Android Open Source Project)完整導入企業內部或個人Gerrit伺服器。AOSP包含近800個專案,要如何有效快速無誤的將這些專案導入Gerrit對很多IT人員來說若不知方法會是很頭痛的問題。

本文是系列文的第二篇,本系列在介紹如何建立本地自有 (local host) Gerrit Server並且將AOSP (Android Open Source Project)的原始碼完整導入此Gerrit Server。

系列分為三個部份,本文是第二部份,教導如何建立AOSP Git Mirror並且同步導入自建的Gerrit Server,如果你還不知如何架設一個Gerrit Server,可以回到這系列的

第一部份:架設Gerrit Server
第二部份:將AOSP完整導入Gerrit Server (本文)。
第三部份:如何從本地Gerrit Server建立一個AOSP Branch

進行此AOSP導入工作的主要目的在於本人專業工作上需要修改AOSP原始碼以符合客製化的需求,但AOSP本身龐大的原始碼及代碼庫(Repository)不適合再導入公司內部原有已存在的Gerrit Server。於是乎,將AOSP導入一個新建的Gerrit Server成為一個合理的選擇。另外更重要的原因是,自身內部專案的AOSP原始碼必須能夠提供以下功能:

  • 進行Code Review,確保穩定性及紀錄留存以保留技術知識。
  • 同步Merge Android官方原始碼,確保官方修正的問題可以同步合入自身專案。

在完成本文中所述的任務之後,你應該可以:

  • 建立基本Gerrit Server的能力
  • 抓取AOSP並導入Gerrit Server的能力
  • 同步AOSP並且合入官方修改的原始碼的能力
  • 建立自身AOSP Branch並且修改及進行Code Review

本文在Ubuntu 18.04進行,基本上我在Ubuntu 16.04也執行過同樣任務,所以差別不大,所以你的系統是在Ubuntu 16.04,應該完全可以適用。

所需軟體

  • Gerrit (version 2.16.4)
  • Git
  • Open JDK 8
  • Apache2 (非必要)
  • MySQL (非必要)
  • Gerrit Delete Project Plug-in (非必要,但很好用)

概念

AOSP的相關資訊都在Android的官方網站 (https://source.android.com/),如果你還不曾完整Build過Android的Codebase (不論是AOSP,Qualcomm還是MTK),那麼你必須從:


這裏開始把Build Code的環境設定好。
AOSP是由一大堆Projects組成(本文選寫時,大約七八百個Project),利用repo這個命令管理。如果使用上一篇方法來的一個一個建立Projects,不僅曠日費時,也容易出錯。
但如果把repo命令,git mirror概念,gerrit命令的基本功能都弄清楚,則之後的導入同步及錯誤修正就不會很難。

什麼是Git Mirror?
用很簡單的直接的講法就是「完全複製一份出來」。以往利用git clone取得的Project,是針對單一branch取出原始碼。而git clone --mirror則是把所有的branches/tags都取出來。就是AOSP上所有的原始碼你可以一次性下載。那也就是我們的目的。

所以基本的做法很簡單,用以下步驟來完成轉移AOSP的工作。
  1. 取得一份完整的AOSP Git Repository (Git mirror)
  2. 把AOSP Git mirror中所有的Projects在Gerrit創建一次。
  3. 把所有的AOSP Git mirror中所有的branches/tags都push進Gerrit Server
  4. 留下AOSP Git Mirror做同步化,官方新的Patch可同步至本地Gerrit Server



概念很清楚簡單,但執行起來卻有很多細節處理。包含權限設定,錯誤處理,架構規劃,都可能會讓你卡住很久。所以我在做第一次轉移AOSP的動作時,也花了三天以上的時間。所以本文就是讓你能一次性完成,不必走一次一樣的冤枉路。

執行AOSP移轉匯入本地Gerrit Server 

  • 取得AOSP Git Repository (Mirror)

首先取得完整的AOSP Git Repository。取得的位址及方法在以下官網描述

https://source.android.com/setup/build/downloading

在官網中告訴你下載的方式為:repo init -u https://android.googlesource.com/platform/manifest
但這個方式是取得master branch,或是在其後加入-b <branch name>以取得其他Branch。
本文要完整取出所有的branch及tags,就必須加入--mirror參數。在本文範例中,我們先在另一台主機Gerrit Client建立一個目錄aosp_mirror,存放AOSP mirror
mkdir aosp_mirror
cd aosp_mirror
repo init -u https://android.googlesource.com/platform/manifest --mirror
repo sync

所以重點在--mirror這個參數,讓我們可以完整取得AOSP所有Repository。 注意,取得AOSP Repostory必須是在Gerrit Server有Administrator權限的人,在PART1中,此人為jeremychen。所以不一定要在Gerrit Server同一台執行,可以在遠端Client (以下稱之為Gerrit Client)取回AOSP Repository後,透過git push將原碼推至Gerrit Server。 最後用repo sync取得原始碼,這會花一段時間,我大概花了三個小時下載,因為大概有100 GBytes以上的資料量。
  • 在Gerrit Server建立所有Projects的前期工作
建立所有的Projects之前我們先建立一個Parent Repository--AOSP。(若你還沒有Gerrit server及Gerrit Web UI,請先至PART1建立)。
在Gerrit Web UI中,點選Browse->Repositories,點擊「CREATE NEW」

把一個專案設為一群專案的Parent,之後方便進行存取權設定
建立管理Repository用的Repository,以利之後的權限管理

輸入名稱為AOSP,在「Only server as parent for other repositories」選擇True。按下「CREATE」,完成建立。

AOSP只是個Parent Repository,主要是要把所有AOSP下的七百多個Projects在Gerrit上的權限在統一管理。之後加入的Projects將Parent設為AOSP,我們只要在AOSP下調整權限,就可以等同調整所有Projects的權限,同時也不影響其他已存在Gerrit Server的Projects的權限。

然後我們加入兩個Group (成員群組),android-admin及android-coder,剛剛加入的Parent Project “AOSP”是把相關的Projects做一個群組,而成員群組也是同樣的概念,把人加入某一個成員群組後,該成員的權限就能統一管控。

在Gerrit Web UI中,點選Browse->Groups,點擊「CREATE NEW」,輸入android-admin,按CREATE,建立群組。

Gerrit Web UI新增使用者群組android admin,以利使用者權限管理
新增Group,使用者群組

再重複以上動作一次,加入android-coder。因為創立者是預設member,所以點選android-admin/android-coder後,再點選左側的Memebers,就可以看到有哪些人在此群組。

android-admin顧名思義,就是管理AOSP Projects的人,包含建立/刪除/Merge/Review等管理工作,而android-coder就是把修改的Code推上Gerrit等待Review,請android-admin進行Review後Commit或Abandon。

所以接下來就是設定AOSP的權限:
1. 在Gerrit Web UI中,點選BROWSE->Repositories,選擇或搜尋AOSP。點選進入。
2. 在左側欄,點選Access。
3. 在中央主畫面中,點擊Edit。開始編輯權限
4. 此時會有第一個Preference出現,把ref/for/*改為ref/*
5. 下拉Add permission:,選擇Push,點選右邊的Add。此時有個Push權限加入。
6. 在Push下方的"add group"文字欄中,輸入android-admin,按Enter (或輸入至一半時,若跳出選單,選android-admin即可)。
7. 重覆步驟5和6,加入Forge Author Identity和Forge Committer Identity。
8. 結果如下:
把存取權設定給Gerrit使用者群組
在使用者群組中,新增存取權
每個權限的意義可以至官網查詢,這裏不再重述。以上的權限其實還不足,也不夠細緻。但主要是在匯入AOSP所有Projects時,不會有權限問題而推入失敗。

  • 在Gerrit Server匯入所有Projects

接下來是把七百多個Projects都建在Gerrit Server上。回到Gerrit Client主機上,進入剛剛建立的aosp_mirror目錄中。你可以先輸入:
repo forall -c 'echo $REPO_PROJECT'

來看所有的Projects,這個命令repo forall -c就是「在所有Projects執行命令」,而$REPO_PROJECT這變環境變數會隨著當前的有不同的值,其值就是Project Name。
所以以上的命令就是把所有的Project Name列出來,而沒有對Git/Gerrit做任何操作。

第一步,在Gerrit上新建所有Projects
理解了上述repo forall -c及$REPO_PROJECT變數後,以下的建立Projects的命令就很容易了解。

repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 jeremychen@10.50.100.56 gerrit create-project --owner android-admin $REPO_PROJECT;'
repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 jeremychen@10.50.100.56 gerrit set-project-parent --parent AOSP $REPO_PROJECT;'

第一個指令是建立所有Projects;第二個指令是把所有的Projects的Parent Project設為AOSP,所有的AOSP Projects的父專案就是AOSP,將來只要改變AOSP的權限就能設定所有Project存取權限。

repo forall -c是對每個Project做動作,echo是列出目前要重新建立在Gerrit Server的Project Name,如果發生了任何的Error Message,你也會比較容易知道是哪個Project出問題。 真正執行建立Project的命令是:ssh -p 29418 jeremychen@10.50.100.56 gerrit create-project --owner android-admin $REPO_PROJECT;
ssh -p 29418是用SSH的29418 Port通訊 (Gerrit Server預設Port),jeremychen是Adminstrator username, 10.50.100.56是Server IP。gerrit create-project是Gerrit命令,用來建立新的Project,--owner是Project管理者,所以是android-admin這個群組。最後repo forall替你把$REPO_PROJECT換成真正的Project Name。
以上的指令應該不會碰上什麼困難,就是把700多個Projects整個Looping一次,建立好,設定Parent Project。
此時你可以回到Gerrit Web UI,看一下所有Projects是否成功建立。

把AOSP所有專案項目匯入後,Gerrit AOSP專案項目列表
所有AOSP專案項目建立完成 (尚未有原始代碼)


可以看出已經有一大堆Projects列在Gerrit Web UI上了。


第二步,把所有的原始碼匯入剛剛建立的Projects:
在執行全部專案原始碼匯入之前,回到Gerrit Server主機,修改aosp_review_site/etc/gerrit.config並且重啟gerrit service。 修改如下:
[gerrit]
        basePath = /home/gerrit/aosp_git
        serverId = 03a2e828-86e2-47ac-abf2-facca5260695
        canonicalWebUrl = http://10.50.100.56:8080/
[database]
        type = mysql
        hostname = localhost
        database = reviewdb
        username = gerrit
[noteDb "changes"]
        disableReviewDb = true
        primaryStorage = note db
        read = true
        sequence = true
        write = true
[container]
        javaOptions = "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
        javaOptions = "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
        user = gerrit
        javaHome = /usr/lib/jvm/java-8-openjdk-amd64/jre
[index]
        type = LUCENE
[auth]
        type = OPENID
[receive]
        enableSignedPush = false
        maxBatchCommits = 1000000
        timeout = 120min
[sendemail]
        smtpServer = localhost
[sshd]
        listenAddress = *:29418
[httpd]
        listenUrl = http://*:8080/
[cache]
        directory = cache
[plugins]
        allowRemoteAdmin = true
加入第27,28行(Highlighten),maxBatchCommits=1000000是為了解決錯誤訊息為 (more than 10000 commits, and skip-validation not set),而timeout=120m是修正在滙入時,由於Project過於龐大,造成過時問題。 做一次匯入的時間很長,所以建議先改好gerrit.config,重啟gerrit server (gerrit.sh restart)之後再進入滙入工作。 如果不改gerrit.config,在執行滙入時,就會發生類似錯誤。
repo forall -c 'echo $REPO_PROJECT; git push ssh://jeremychen@10.50.100.56:29418/$REPO_PROJECT +refs/heads/*'
platform/art
Counting objects: 302384, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (107448/107448), done.
Writing objects: 100% (302384/302384), 199.43 MiB | 16.72 MiB/s, done.
Total 302384 (delta 193646), reused 299963 (delta 191268)
remote: Resolving deltas: 100% (193646/193646)
remote: Counting objects: 302384, done
remote: Processing changes: refs: 140, done    
To ssh://10.50.100.56:29418/platform/art
 * [new branch]            idea133 -> idea133
 * [new branch]            idea133-weekly-release -> idea133-weekly-release
 * [new branch]            kitkat-cts-dev -> kitkat-cts-dev
 * [new branch]            kitkat-cts-release -> kitkat-cts-release
 * [new branch]            kitkat-dev -> kitkat-dev
 * [new branch]            kitkat-mr1-release -> kitkat-mr1-release
 * [new branch]            kitkat-mr1.1-release -> kitkat-mr1.1-release
 * [new branch]            kitkat-mr2-release -> kitkat-mr2-release
 * [new branch]            kitkat-mr2.1-release -> kitkat-mr2.1-release
 * [new branch]            kitkat-mr2.2-release -> kitkat-mr2.2-release
 * [new branch]            kitkat-release -> kitkat-release
 * [new branch]            kitkat-wear -> kitkat-wear
 ! [remote rejected]       l-preview -> l-preview (more than 10000 commits, and skip-validation not set)
 ! [remote rejected]       lollipop-cts-release -> lollipop-cts-release (more than 10000 commits, and skip-validation not set)
 ! [remote rejected]       lollipop-dev -> lollipop-dev (more than 10000 commits, and skip-validation not set)
 ! [remote rejected]       lollipop-mr1-cts-release -> lollipop-mr1-cts-release (more than 10000 commits, and skip-validation not set)
可以看出錯誤訊息為 (more than 10000 commits, and skip-validation not set),而maxBatchCommits=1000000這個設定可以解決該問題。
接下來就真正進入Project滙入Gerrit Server的作業了。
對每一個AOSP Project執行git push的命令,將原始碼匯入:
repo forall -c 'echo $REPO_PROJECT; git push ssh://jeremychen@10.50.100.56:29418/$REPO_PROJECT +refs/heads/*'
repo forall -c 'echo $REPO_PROJECT; git push ssh://jeremychen@10.50.100.56:29418/$REPO_PROJECT +refs/heads/* +refs/tags/*'
此時視你內部網路及主機的效能而定,但仍需要約30分鐘至1小時以上的時間完成整個匯入的動作 (花了兩三小時才匯入也是正常)。

你可以看出我用了兩個很相似的命令去匯入原始碼,第一個是只推heads (branchs) 另一個加上tags。這看起來有點瞎,但在性能不太強大的Gerrit Server主機是必須的。否則會由於效能不足(RAM?)而發生internal server error!

下完命令後,大部份的Project都可以順利被匯入gerrit server,最可能出錯的Project是platform/manifest,如果你看到以下錯誤訊息(internal server error):

To ssh://jeremychen@10.50.100.56:29418/platform/manifest
 ! [remote rejected] adt_23.0.3 -> adt_23.0.3 (internal server error)
 ! [remote rejected] afw-test-harness-1.5 -> afw-test-harness-1.5 (internal server error)
 ! [remote rejected] afw-test-harness-2.1 -> afw-test-harness-2.1 (internal server error)
 ! [remote rejected] afw-test-harness-marshmallow-dev -> afw-test-harness-marshmallow-dev (internal server error)
 ! [remote rejected] afw-test-harness-nougat-dev -> afw-test-harness-nougat-dev (internal server error)
 ! [remote rejected] android-1.6_r1 -> android-1.6_r1 (internal server error)
 ! [remote rejected] android-1.6_r1.1 -> android-1.6_r1.1 (internal server error)
 ! [remote rejected] android-1.6_r1.2 -> android-1.6_r1.2 (internal server error)
 ! [remote rejected] android-1.6_r1.3 -> android-1.6_r1.3 (internal server error)
 ! [remote rejected] android-1.6_r1.4 -> android-1.6_r1.4 (internal server error)
 ! [remote rejected] android-1.6_r1.5 -> android-1.6_r1.5 (internal server error)

以經驗而言,Gerrit Server主機端發生問題,很多的狀況是RAM不足造成的。但也不要傻傻的就跑去擴充RAM(我的Gerrit Server有8G RAM,但在push manifest時,還是出現internal server error!)。仔細檢視platform/manifest這個Projects,很明顯它的refs非常多,所以如果這是個造成問題的變數,那我們可以各別ref推入的方式來解決這個問題。
首先回到Gerrit Client,進入aosp_mirror/platform/manifest.git
for n in $(git for-each-ref --format='%(refname)' refs/heads); do git push ssh://jeremychen@10.50.100.56:29418/platform/manifest $n; done
for n in $(git for-each-ref --format='%(refname)' refs/tags); do git push ssh://jeremychen@10.50.100.56:29418/platform/manifest $n; done

一樣分兩個命令,一個把所有Branch推入,另一個把所有Tags推入。這兩個命令就是先列出所有Branch/Tag後,逐個推入,這樣就不會有因RAM不足而造成的錯誤。缺點就是比較花時間。(但一定比去擴充RAM省時省錢,至於RAM有擴到多大才夠?我也沒試過!當然也有可能是Gerrit的編程時,沒考慮如此大量的refs時的狀況,所以在gerrit server發生問題時,我們從error log也看不出什麼問題,因為也沒看見任何Exception!!這只有真的去鑽Source Code才能得到答案了。)


  • 驗證,取回原始碼並且Build出Image在Emulator執行
目前我們取得的是AOSP Master branch,也就是最新的原始碼,通常都可以Build過。但如果你就這麼剛好在兩個Commit中間取出原始碼,Build Fail也不是不可能的事。
要驗證我們的Gerrit是不是沒有問題,第一步就是把AOSP整個取出,之後Build過一次:

repo init -u ssh://jeremychen@10.50.100.56:29418/platform/manifest
repo sync
source ./build/envsetup.sh
lunch aosp_x86_64-eng #官方Build出X86 Emulator的方法
make -j16 #開始執行Build

Build好之後,執行Android Emulator,在Emulator中,Settings->System->About emulated device,檢視Build Number,是你Build的日期及user name,也就確認此Emulator是由你自己Build出來的,也確認我們把AOSP滙入再取出後是完整的AOSP Codebase。
Emulator編譯執行成功後執行,進入系統>關於手機

  • 同步官方AOSP
完成AOSP Mirror轉移匯入本地Gerrit Server的工作後,不要就把Gerrit Client中的aosp_mirror幹掉!因為將來和官方AOSP的同步還要靠它!例如AOSP又出了一個Android Q/R...,或是用到不同平台裝置,例如IoT/Car,的Branch。這時如果你必須同步這些Code,就在aosp_mirror下,執行repo sync,把原始碼同步後,再匯入你的gerrit server。先回到Gerrit Client的aosp_mirror目錄,同步官方的AOSP方式:

repo sync -j8 #同步官方AOSP
repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 jeremychen@10.50.100.56 gerrit create-project --owner android-admin $REPO_PROJECT' #若有新的Project,就建立它,已經存在的Projects會回應Fatal error,這是正常的,無視
repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 jeremychen@10.50.100.56 gerrit set-project-parent --parent AOSP $REPO_PROJECT' #若有新的Project,將Parent設為AOSP
repo forall -c 'echo $REPO_PROJECT; git push ssh://jeremychen@10.50.100.56:29418/$REPO_PROJECT +refs/heads/* +refs/tags/*' #匯入所有新增的Code,完成同步


建立自有分枝Branch

一般而言,在AOSP上工作的研發人員不會在Master branch上修改Code。因為此時的Code未必穩定,而且可能Build不出來,也可能Build出來了,但常發生Crash。Master branch是最經常改Code的地方,如果在這個分枝工作,勢必常常面臨Merge Code的工作。

所以一般來說我們會在相對穩定的Branch/Tag上,建立自有的分枝後,在自有分枝上工作。這也是我們需要一個自有Gerrit Server並把AOSP完整移轉過來的最初原因。
如何建立分枝,可以看PART3,建立AOSP分枝並建立在Gerrit Server

雖然本文是要「完整的」移轉匯入AOSP,但事實上我們現在做完的移轉是不完整,而且很多Projects其實根本還沒匯入。為什麼?主要是因為AOSP Master branch並不是包含所有Projects,而是目前最新的Android版本中的Projects。例如,AOSP Master branch在本文發表時,是Android Q,而上個版本Android P中有個Project名稱為framework/data-binding,但在Android Q因架構改變,不再納入此Project。所以我們的Gerrit server沒有這個Project。那麼如果我要在Pixel 2上的Android P上修改Code,並且把這個修改納入Gerrit review,成為自己的ROM,及內部知識保留 (這也是本文一開始說的目的!)但我的Gerrit server沒有framework/data-binding這個Projects,怎麼辦?那這就是PART3要討論的部份了!







留言

  1. 在執行
    repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 jeremychen@10.50.100.56 gerrit create-project --owner android-admin $REPO_PROJECT;'
    這個指令的時候,會一直出現
    Permission denied (publickey).
    請問怎麼解決?

    回覆刪除
  2. Part 1 的"加入SSH Key" 部分看一下

    回覆刪除

張貼留言

這個網誌中的熱門文章

完整指引如何編譯AOSP (Build Android P),整合GMS及刷機 (Pixel 2)

架設Gerrit Server : 架設本地AOSP Gerrit Server完整指引 PART1