Android 為企業提供一個新的市場,無論大企業,小企業都是處於同一個起跑線上。研究 Android 尤其是 Android 系統核心或者是驅動的開發,首先需要做的就是本地克隆建立一套 Android 版本庫管理機制。

Android 使用 Git 作為代碼管理工具,開發了 Gerrit 進行代碼審核以便更好的對代碼進行集中式管理,還開發了 Repo 命令行工具,對 Git 部分命令封裝,將 百多個 Git 庫有效的進行組織。要想克隆和管理這百多個 Git 庫,還真不是一件簡單的事情。

在研究 Repo 的過程中,發現很多文檔在 Google Group 上,非“翻牆”不可看。非法的事情咱不干,直接閱讀 repo 的代碼吧。

創建本地 Android 版本庫鏡像的思路

如果了解了 Repo 的實現,參考 《Using Repo and Git》 , 建立一個本地的 android 版本庫鏡像還是不難的:

  • 下載 repo bootstrap 腳本
    $ curl http://android.git.kernel.org/repo >~/bin/repo
    
    $ chmod a+x ~/bin/repo
    
    $ export PATH=$PATH:~/bin
  • 提供 –mirror 參數調用 repo init ,建立 git 版本庫克隆
    $ repo init -u git://android.git.kernel.org/platform/manifest.git --mirror
    • 使用 –morror 則下一步和源同步的時候,本地按照源的版本庫組織方式進行組織,否則會按照 manifest.xml 指定的方式重新組織並檢出到本地
  • 開始和源同步
    $ repo sync
  • 修改 manifest ,修改 git 庫地址,指向本地的 git 服務器
    • 修改 platform/manifest.git 庫中現有的 xml 文件,或者創建一個新的 xml 文件
    • 將 git 的地址改為本地地址,提交並 push
  • 本地 repo 鏡像建立完畢之后,就可以在執行 repo init 時,使用本地更改后的 manifest 庫,之后執行 repo sync 就是基於本地版本庫進行同步了。
  • 也可以改造 repo,使得不必為 repo 工具初始化,也在本地網絡完成操作…

Repo init 干了些什么?

實際上,得到客戶使用 repo 的信息后,首先下載 repo 執行腳本開始研究。

curl http://android.git.kernel.org/repo >~/bin/repo

難道只有 600 行的 python 代碼么?要是這樣應該很簡單的呀。可以看下來,卻發現遠非如此。

Shell script or python?

首先 repo 腳本使用了一個魔法:從腳本第一行的 shebang 來看應該是 shell 腳本,但是滿眼卻都是 python 語法,怎么回事?

 1 #!/bin/sh

 2

 3 ## repo default configuration

 4 ##

 5 REPO_URL='git://android.git.kernel.org/tools/repo.git'

 6 REPO_REV='stable'

 7

 8 # Copyright (C) 2008 Google Inc.

   ...



22 magic='--calling-python-from-/bin/sh--'

23 """exec" python -E "$0" "$@" """#$magic"

24 if __name__ == '__main__':

25   import sys

26   if sys.argv[-1] == '#%s' % magic:

27     del sys.argv[-1]

28 del magic

魔法就在第 23 行,巧妙的通過 python 三引號字串寫出了一個能被 python 和 shell script 都能理解的代碼,以此為界,代碼由 Shell 腳本進入了 Python 的世界。

Bootstrap 和真正的 repo

通過 curl 下載的的 repo 並非完整的 repo 腳本,只是一個 bootstrap。當 repo 執行時,會負責下載完整的 repo 代碼,並將控制權轉移給真正的 repo。

通過 main 函數,可以看到 repo 運行的開始,就試圖發現本地真正的完整的 repo 代碼,以便移交控制權:

544 def main(orig_args):

545   main, dir = _FindRepo()



586   try:

587     os.execv(main, me)

其中 545 行的 _FindRepo() 會在當前目錄開始向上遞歸查找 “.repo/repo/main.py”,如果找到則移交控制權(587行)。

Repo bootstrap 腳本調用 init 只完成第一階段的初始化

Repo 的 bootstrap 腳本只支持兩個命令 help 和 init,而 init 也只完成 repo 版本庫克隆(即安裝 repo 完整工具),之后就轉移控制權。

在 Repo bootstrap 執行 init 可以提供很多參數,但實際上第一階段初始化,只用到兩個參數(而且都有默認值)

  • 參數:–repo-url=URL
    repo 工具本身的 git 庫地址。缺省為:git://android.git.kernel.org/tools/repo.git
  • 參數:–repo-branch=REVISION
    使用 repo 的版本庫,即 repo git 庫的分支或者里程碑名稱。缺省為 stable

第二階段的 repo init

執行第二階段的 repo init,控制權已經移交給剛剛克隆出來的 repo git 庫的腳本。

Repo git 庫被克隆/檢出到執行 repo init 命令當前目錄下的 .repo/repo 子目錄中,主要的執行腳本為 .repo/repo/main.py。main.py 接着執行 repo init 命令。

Repo 的代碼組織的非常好,在 .repo/repo/subcmds/ 子目錄下,是各個 repo 命令的處理腳本。repo init 的第二階段腳本正是由 .repo/repo/subcmds/init.py 負責執行的。第二階段主要完成:

  • 克隆由 -u 參數提供的 manifest Git 庫,如克隆 android 庫時:
    $ repo init -u git://android.git.kernel.org/platform/manifest.git
  • 如果不提供 -b REVISION 或者 –manifest-branch=REVISION參數,則檢出 manifest Git 庫的 master 分支
  • 如果不提供 -m NAME.xml 或者 –manifest-name=NAME.xml 參數,則使用缺省值 default.xml
  • 如果提供 –mirror 參數,則后續同步操作會有相應的體現

Repo start 干了些什么?

Android 源碼網站在介紹 repo 的使用模型中,有一個圖片: http://source.android.com/images/git-repo-1.png , 介紹了 repo 的使用流程。其中 “repo start” 是緊接着 “repo sync” 后的第一個動作。那么這個動作是干什么的呢?

得益於 repo 對 git 操作的封裝,”repo start” 命令的處理代碼只有區區 68 行。

 37   def Execute(self, opt, args):  41     nb = args[0]  47     projects = []

 48     if not opt.all:

 49       projects = args[1:]  54     all = self.GetProjects(projects)



 57     for project in all:



 59       if not project.StartBranch(nb):

 60         err.append(project)

看到第 59 行了么,就是對 repo 同步下來的項目的多個 Git 版本庫,逐一執行 project.StartBranch 操作。 nb 是 repo start 的第一個參數,即分支名稱。

關於 StartBranch 的代碼,在 project.py 中:

 857   def StartBranch(self, name):

 858     """Create a new branch off the manifest's revision.

 859     """



 894     if GitCommand(self,

 895                   ['checkout', '-b', branch.name, revid],

 896                   capture_stdout = True,

 897                   capture_stderr = True).Wait() == 0:

 898       branch.Save()

 899       return True

原來如此, repo start <branch_name> 就是逐一為各個版本庫創建工作分支,以便在此分支下進行工作。

讀者可以按圖索驥,找到 repo 各個命令的實現,破解心中的疑惑。