Android 源碼部署到服務(wù)器(Git+GitLab+Repo)
Android 源碼部署到服務(wù)器端爆班,一共分為七個(gè)部分衷掷。
- 軟件環(huán)境
- GitLab 服務(wù)端部署
- Repo Manifest.xml 生成
- GitLab 建倉
- Git Push 源碼到服務(wù)器
- Repo 同步源碼
- 提交修改后的代碼
一、軟件環(huán)境
服務(wù)器端系統(tǒng):Ubuntu 16.04
GitLab 軟件:GitLab Community Edition
客戶端系統(tǒng):Ubuntu 14.04
Git 軟件:Git 2.27.0
二柿菩、GitLab 服務(wù)端部署
- 必要組件安裝
sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates
sudo apt-get install -y postfix
在安裝 postfix 時(shí)候戚嗅,需要使用左右鍵和回車鍵確認(rèn),并在下拉列表選擇 Internet Site 并確認(rèn)枢舶。
- 信任 GitLab 的 GPG 公鑰
curl https://packages.gitlab.com/gpg.key 2> /dev/null | sudo apt-key add - &>/dev/null
- 配置鏡像路徑
如果沒有安裝 vim 先進(jìn)行安裝
vim /etc/apt/sources.list.d/gitlab-ce.list
打開 gitlab-ce.list 后懦胞,寫入:
deb https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu xenial main
如果 gitlab-ce.list 文件不存在,自行創(chuàng)建即可凉泄,并賦予權(quán)限躏尉,可在 root 權(quán)限下進(jìn)行。
touch /etc/apt/sources.list.d/gitlab-ce.list
chmod 777 /etc/apt/sources.list.d/gitlab-ce.list
- 安裝 gitlab-ce
sudo apt-get update
sudo apt-get install gitlab-ce
- 執(zhí)行配置
sudo gitlab-ctl reconfigure
- 啟動(dòng) GitLab
sudo gitlab-ctl start
- 修改 external_url
sudo gedit /etc/gitlab/gitlab.rb
修改如下路徑:
external_url 'http://gitlab.example.com'
具體要換成你服務(wù)端的 IP后众,比如胀糜,我這里使用的是 192.168.50.10
external_url 'http://192.168.50.10'
這里一定要重新配置,再次執(zhí)行配置命令
sudo gitlab-ctl reconfigure
- 訪問你的 GitLab 服務(wù)
打開同一網(wǎng)段的任何電腦上的瀏覽器蒂誉,前提是電腦可以互相訪問教藻,如果使用了虛擬機(jī),則要配置網(wǎng)絡(luò)類型為橋接右锨,并將虛擬機(jī)都配置在同一網(wǎng)段內(nèi)怖竭,并且可以互相訪問,可以 ping 一下陡蝇,保證網(wǎng)絡(luò)暢通。
瀏覽器地址欄輸入:http://192.168.50.10(需要更換為你自己的 url)哮肚。
[圖片上傳失敗...(image-54367b-1612399398269)]
接下來可以創(chuàng)建 root 用戶登夫,輸入密碼并確認(rèn)。注冊其他用戶等等允趟。
三恼策、Repo Manifest.xml 生成
為什么需要自己去生成 Manifest.xml?如果你手上的代碼已經(jīng)不知道哪里同步來的潮剪,或者是方案廠商提供的涣楷,總之不是使用 Repo 管理代碼,可能把修改的部分作為一個(gè)單獨(dú)的 git 倉上庫了抗碰。如此我們就需要根據(jù)這份現(xiàn)有的源碼去生成 Manifest.xml狮斗。
前提是基于源碼修改的倉都用 git 上庫了,或者是增加了一些倉但是和 Manifest.xml 不同步弧蝇,這都需要修改 Manifest.xml 保持源碼倉和 Manifest.xml 對等碳褒。
因?yàn)樵创a中每個(gè)倉都有 .git 了景殷,所以我們需要統(tǒng)計(jì)所有的 .git 攀芯,這是為了找出所以的 git 倉,然后寫入 Manifest.xml,如此我們就可以使用 Repo 管理這些倉了伊约。
- 找出所有 git 倉庫
find myandroid/ -type d -name '.git' > git_pro.txt
打開 git_pro.txt 就會(huì)看到如下行
......
/home/snake/Share/art/.git
......
使用 bash 指令“掐頭去尾”(刪掉前綴路徑 /home/snake/Share/ 和后綴 .git)
cat git_pro.txt | cut -c 18- | sed 's/.....$//' > path.txt
得到如下路徑:
art
接著需要生成清單文件。
gen_xml.sh
#!/bin/bash
echo -e "
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<manifest>
<remote name=\"aosp\"
fetch=\"..\"/>
<default revision=\"master\"
remote=\"aosp\"
sync-j=\"4\" />" >>$1
while read line; do
echo " <project path=\"$line\" name=\"$line\" />" >>$1
done
echo -e "\n</manifest>" >>$1
運(yùn)行腳本 gen_xml.sh 即可闲询。
cat path.txt | ./gen_xml.sh default.xml
default.xml 內(nèi)容如下竭望,這就是我們要的清單文件(Manifest.xml)。
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote name="aosp"
fetch=".."/>
<default revision="master"
remote="aosp"
sync-j="4" />
<project path="art" name="art" />
<project path="abi/cpp" name="abi/cpp" />
......
</manifest>
fetch 是 “…” 代表返回到上級目錄是复,我這里將 manifest 倉庫放在了 android6newc 組下面删顶,詳見下文。
revision 是 master 代表主干分支佑笋,這個(gè)要和 android6newc 組下面源碼倉分支對應(yīng)翼闹。
這二者配置不正確,repo 無法正常同步源碼倉蒋纬。
四猎荠、GitLab 建倉
源碼中倉庫太多了,不可能在瀏覽器內(nèi)一個(gè)一個(gè)創(chuàng)建蜀备,所以需要借助 python-gitlab 庫來自動(dòng)完成关摇。將 default.xml 放在和腳本同一目錄,運(yùn)行它碾阁。等待建倉完成输虱,有點(diǎn)耗時(shí),可能要半小時(shí)脂凶。如果沒有安裝 python-gitlab 可以借助 pip 進(jìn)行安裝宪睹。
腳本內(nèi)寫死了父組名(Android6NewC),這個(gè)組需要在 GitLab 網(wǎng)頁中自行創(chuàng)建蚕钦。同樣 url 和 token 需要更換為你自己的亭病,生成 token 是在 GitLab Settings -> Access Tokens -> Add a personal access token。
AndroidSourceCodeGitlabManager.py
#!/usr/bin/python3
import gitlab
import os
import re
import time
MANIFEST_XML = "default.xml"
ROOT = os.getcwd()
ROOT_GROUP = "Android6NewC"
MANIFEST_XML_PATH_NAME_RE = re.compile(r"<project\s+path=\"(?P<path>[^\"]+)\"\s+name=\"(?P<name>[^\"]+)\"\s+/>",
re.DOTALL)
gl = gitlab.Gitlab('http://192.168.50.10/', private_token='xxxxxxx')
manifest_xml_project_paths = []
def parse_repo_manifest():
with open(os.path.join(ROOT, MANIFEST_XML), "rb") as strReader:
for line in strReader:
if line is not None and len(line) != 0:
this_temp_line = line.decode()
if line.find("path".encode(encoding="utf-8")):
s = MANIFEST_XML_PATH_NAME_RE.search(this_temp_line)
if s is not None:
manifest_xml_project_paths.append(s.group("path"))
print("manifest_xml_project_paths=" + str(manifest_xml_project_paths))
print("manifest_xml_project_paths len=" + str(len(manifest_xml_project_paths)))
def create_group_and_project():
all_groups = gl.groups.list(all=True)
print("all_groups=" + str(all_groups))
group_parent = None
for g in all_groups:
if g.name == ROOT_GROUP:
group_parent = g
break
print("group parent=" + str(group_parent))
for path in manifest_xml_project_paths:
print("path=" + path)
paths = path.split("/")
print("paths=" + str(paths))
last_path_index = len(paths) - 1
group = group_parent
for index in range(0, last_path_index):
p = paths[index]
print("p=" + p)
# is the group exist
print("parent group=" + group.name)
try:
all_groups = group.subgroups.list(all=True)
except AttributeError:
all_groups = []
print("AttributeError: clear all subgroups")
is_group_exist = False
for g in all_groups:
if g.name == p:
is_group_exist = True
group = g
print("group exist=" + g.name)
break
if is_group_exist:
continue
# create subgroup
data = {
"name": p,
"path": p,
"parent_id": group.id
}
try:
group = gl.groups.create(data)
print("group create success name=" + p)
time.sleep(1)
except gitlab.exceptions.GitlabCreateError as e:
if e.response_code == 400:
print("group:" + p + " has already been created")
query_groups = gl.groups.list(all=True)
print("query_groups:" + str(query_groups))
for query_group in query_groups:
if query_group.name == p and query_group.parent_id == group.id:
group = query_group
print("update exit group:" + group.name)
break
project = paths[last_path_index]
print("group project list group=" + group.name)
real_group = gl.groups.get(group.id, lazy=True)
all_projects = real_group.projects.list(all=True)
print("group all projects=" + str(all_projects))
is_project_exist = False
for p in all_projects:
if p.name == project:
is_project_exist = True
print("project exist=" + p.name)
break
if not is_project_exist:
print("create project=" + project)
gl.projects.create({'name': project, 'path': project, 'namespace_id': group.id})
print("project create success name=" + project)
time.sleep(1)
def test_create_project_with_dot_name():
# need use path field, if don't use path, GitLab url will replace "." to "_"
gl.projects.create({'name': "xxx.yy.xy", 'path': "xxx.yy.xy"})
if __name__ == '__main__':
parse_repo_manifest()
create_group_and_project()
腳本基本工作原理較為簡單嘶居,通過正則表達(dá)式解析 default.xml 中的 project 標(biāo)簽?zāi)玫?path罪帖,根據(jù) path 創(chuàng)建 group (包括 subgroup),最后再創(chuàng)建 project邮屁。
腳本正確運(yùn)行的話輸出類似以下片段:
......
path=external/mtd-utils
paths=['external', 'mtd-utils']
p=external
parent group=Android6NewC
group exist=external
group project list group=external
group all projects=[...]
create project=mtd-utils
project create success name=mtd-utils
......
Process finished with exit code 0
腳本運(yùn)行完以后圍觀一下 GitLab 組頁面整袁,生成了我們需要的目錄結(jié)構(gòu)。
[圖片上傳失敗...(image-717933-1612399398267)]
五佑吝、Git Push 源碼到服務(wù)器
首先安裝 Git坐昙,然后編寫自動(dòng)提交腳本將源碼提交到倉庫。
1. 安裝 Git
使用以下命令在 Ubuntu 14.04 LTS 上安裝 Git
apt-get install git
安裝成功后鍵入命令可以查看 Git 版本芋忿,提示 git version 2.7.4民珍,說明已經(jīng)安裝成功襟士。
git --version
Git 全局配置用戶名和郵箱
git config --global user.name "xxx"
git config --global user.email "xxx email address"
生成 SSH key
ssh-keygen -t ed25519 -C "xxx email address"
生成提示如下,以下生成信息已經(jīng)抹除了敏感信息嚷量,你的生成信息會(huì)變化陋桂。
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/captain/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/captain/.ssh/id_ed25519.
Your public key has been saved in /home/captain/.ssh/id_ed25519.pub.
The key fingerprint is:
xxxxxx xxx email address
The key's randomart image is:
+--[ED25519 256--+
| = |
| x * |
| . x x |
| xo |
| * o = S o |
| o = x o x |
| x o.4 . |
| x+ |
| xx |
+-----------------+
查看 SSH key
cat /home/captain/.ssh/id_ed25519.pub
顯示如下所示
ssh-ed25519 AAAAC3NzaC1lZXXXXXXXXXXXXXXsULA48NC2f+oOLf5EoFSpn4 xxx email address
GitLab 中新增 SSH key,將以上 key 粘貼到 Settings -> SSH Keys -> Add an SSH key蝶溶,確認(rèn)即可嗜历。
Push 存在源碼文件夾工程到倉庫,下面是一個(gè)例子抖所,腳本實(shí)際上就是包裝了這些命令梨州。
cd existing_folder
git init
git remote add origin git@192.168.50.10:android6newc/build.git
git add .
git commit -m "Initial commit"
git push -u origin master
當(dāng)然由于以前存在使用 Git 同步過代碼,因此需要先刪除 .git田轧,使用 rm 命令刪除即可暴匠。
rm -rf .git
2. Push Manifest 清單文件到 GitLab 服務(wù)端
在 android6newc 組(Android6NewC)下(網(wǎng)頁內(nèi)操作)新建 manifest project。并將客戶端的 default.xml push 過去傻粘。
cd existing_folder
git init
git remote add origin git@192.168.50.10:android6newc/manifest.git
git add .
git commit -m "Initial commit"
git push -u origin master
3. 自動(dòng) Push 代碼到 GitLab 服務(wù)端
這需要開發(fā)一個(gè)腳本執(zhí)行每窖,這里還是用 python 實(shí)現(xiàn),一定要注意切換到工作路徑下弦悉,不然就踩坑了窒典,當(dāng)然腳本已經(jīng)修復(fù)了這個(gè) bug。
PushAndroidSourceCode2GitLab.py
#!/usr/bin/python3
import os
import re
MANIFEST_XML = "default.xml"
ROOT = os.getcwd()
LOG_FILE_PATH = os.path.join(ROOT, "push.log")
MANIFEST_XML_PATH_NAME_RE = re.compile(r"<project\s+path=\"(?P<path>[^\"]+)\"\s+name=\"(?P<name>[^\"]+)\"\s+/>",
re.DOTALL)
SOURCE_CODE_ROOT = "/home/captain/myandroid/"
REMOTE = "git@192.168.50.10:android6newc/"
manifest_xml_project_paths = []
def parse_repo_manifest():
with open(os.path.join(ROOT, MANIFEST_XML), "rb") as strReader:
for line in strReader:
if line is not None and len(line) != 0:
this_temp_line = line.decode()
if line.find("path".encode(encoding="utf-8")):
s = MANIFEST_XML_PATH_NAME_RE.search(this_temp_line)
if s is not None:
manifest_xml_project_paths.append(s.group("path"))
print("manifest_xml_project_paths=" + str(manifest_xml_project_paths))
print("manifest_xml_project_paths len=" + str(len(manifest_xml_project_paths)))
def push_source_code_by_folder(str_writer):
for path in manifest_xml_project_paths:
print("path=" + path)
abs_path = SOURCE_CODE_ROOT + path
if os.path.exists(abs_path):
# change current work dir
os.chdir(abs_path + "/")
# 1\. delete .git & .gitignore folder
rm_git_cmd = "rm -rf .git"
rm_gitignore_cmd = "rm -rf .gitignore"
os.popen(rm_git_cmd)
os.popen(rm_gitignore_cmd)
# 2\. list dir
dir_data = os.listdir(os.getcwd())
cmd_list = []
print("changed cwd=" + os.getcwd())
if len(dir_data) == 0:
echo_cmd = "echo \"This is a empty repository.\" > ReadMe.md"
str_writer.write("empty repository:" + abs_path)
str_writer.write("\r\n")
cmd_list.append(echo_cmd)
git_init_cmd = "git init"
cmd_list.append(git_init_cmd)
git_remote_cmd = "git remote add origin " + REMOTE + path + ".git"
cmd_list.append(git_remote_cmd)
git_add_dot_cmd = "git add ."
cmd_list.append(git_add_dot_cmd)
git_commit_cmd = "git commit -m \"Initial commit\""
cmd_list.append(git_commit_cmd)
git_push_cmd = "git push -u origin master"
cmd_list.append(git_push_cmd)
for cmd in cmd_list:
print("begin exec cmd=" + cmd)
os.popen(cmd)
print("end exec cmd=" + cmd)
else:
print("abs_path=" + abs_path + " is not exist.")
str_writer.write("folder not exist:" + abs_path)
str_writer.write("\r\n")
def wrapper_push_source_code_write_log():
with open(LOG_FILE_PATH, 'wb+') as strWriter:
push_source_code_by_folder(strWriter)
strWriter.close()
def test_only_dot_git_folder():
subdir_and_file = os.listdir(os.getcwd())
print("subdir_and_file=" + str(subdir_and_file))
with open(LOG_FILE_PATH, 'wb+') as strWriter:
strWriter.write(str(subdir_and_file))
strWriter.write("\r\n")
strWriter.write(str(subdir_and_file))
strWriter.close()
if __name__ == '__main__':
parse_repo_manifest()
wrapper_push_source_code_write_log()
# test_only_dot_git_folder()
將 default.xml 放在和腳本同一目錄稽莉,運(yùn)行它瀑志。運(yùn)行結(jié)束會(huì)把所有的源碼都同步到 GitLab。
六污秆、Repo 同步源碼
由于源碼是部署在局域網(wǎng)劈猪,局域網(wǎng)不能訪問互聯(lián)網(wǎng)。因此還需要把 repo 部署到服務(wù)器上良拼。
1. repo 部署到服務(wù)器
到能夠訪問互聯(lián)網(wǎng)的電腦 clone 一份 repo战得。
git clone https://github.com/GerritCodeReview/git-repo.git
然后將它打包
tar cvf git-repo.tar git-repo/
拷貝 git-repo.tar 到內(nèi)網(wǎng)電腦,并解壓将饺。
tar xvf git-repo.tar
刪除 git-repo/ 下的 .git。
在 Android-Tools 組下(網(wǎng)頁內(nèi)操作)新建 git-repo project痛黎。并將 git-repo push 過去予弧。
cd existing_folder
git init
git remote add origin git@192.168.50.10:android-tools/git-repo.git
git add .
git commit -m "add repo tool"
git push -u origin master
2. 客戶端 repo 同步代碼
在上一步中 git-repo 路徑中找到 repo 文件。將其放到 ~/bin 下湖饱,賦予執(zhí)行權(quán)限掖蛤,并添加到環(huán)境變量 PATH。此文件也可以部署在 httpd 服務(wù)器上井厌,安裝 apache2 服務(wù)蚓庭。
sudo apt-get install apache2
查看安裝是否成功致讥。
sudo systemctl status apache2
修改監(jiān)聽端口,我這里改為了 10086器赞。我們知道 http 默認(rèn)在 80 端口垢袱,但是這個(gè)端口已經(jīng)被 GitLab 使用了,所以要騰出來“地盤”港柜。
sudo vim /etc/apache2/ports.conf
將 repo 拷貝到 /var/www/html 目錄下请契。
重啟 apache2 服務(wù)。
sudo service apache2 restart
~/bin/ 目錄下夏醉,下載 repo爽锥。
curl http://192.168.50.10:10086/repo > repo
修改權(quán)限和添加環(huán)境變量。
chmod a+x ~/bin/repo
export PATH=$PATH:~/bin
現(xiàn)在可以新建一個(gè)文件夾用來存放源碼了畔柔。先 repo init氯夷,然后 repo sync。
repo init -u git@192.168.50.10:android6newc/manifest.git --repo-url=git@192.168.50.10:android-tools/git-repo.git --no-repo-verify
init 成功以后會(huì)在當(dāng)前文件夾下創(chuàng)建 .repo 文件夾“胁粒現(xiàn)在可以運(yùn)行 repo sync 開始同步代碼腮考。
repo sync
防止下載中跳出,我們使用以下腳本奢啥,先創(chuàng)建秸仙。
vim down.sh
寫入下面內(nèi)容:
#!/bin/sh
repo sync
while [ $? -ne 0 ]
do
repo sync
done
用這段腳本執(zhí)行,替換 repo sync 這條命令桩盲。
chmod a+x down.sh
./down.sh
編譯的時(shí)候 make 命令無法使用寂纪,是由于沒有 Makefile 造成的,對比一下上庫之前的代碼赌结,把源碼目錄下 Makefile 拷貝過來即可捞蛋。此文件內(nèi)容非常簡單,就是把 build/core/main.mk 包含進(jìn)來柬姚。
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
七拟杉、提交修改后的代碼
給所有倉庫創(chuàng)建一個(gè)本地分支,分支名 dev 可以任取量承。
repo start dev --all
提交代碼到 remote搬设。
git add .
git commit -m "xxx"
git push aosp dev
git push <遠(yuǎn)程主機(jī)名> <本地分支名>,這會(huì)在遠(yuǎn)程創(chuàng)建創(chuàng)建一個(gè) dev 分支撕捍,請求合并拿穴。
aosp 是遠(yuǎn)程倉庫名,不確定可以使用 git branch -a 查詢忧风。
repo upload . (注意最后有個(gè)點(diǎn)號)無法使用默色,因?yàn)槲覀儧]有配置 Gerrit 服務(wù)器。
另外同步代碼可以使用 pull 命令狮腿。
git pull --rebase
高級用法
repo forall -c xxx 對所有倉庫執(zhí)行 -c 指定的命令
repo forall -c git add .
repo forall -c git commit -m "xxx"
repo forall -c git pull --rebase
repo forall -c git push aosp dev
八腿宰、遇到的錯(cuò)誤記錄
1. default.xml fetch 配置錯(cuò)誤
下面這張圖是因?yàn)?default.xml fetch 配置錯(cuò)誤造成的呕诉。
[圖片上傳失敗...(image-b017f2-1612399398264)]
2. 空倉未提交內(nèi)容
repo sync 期間報(bào)錯(cuò) fatal: Couldn’t find remote ref refs/heads/master,是因?yàn)樯蠋斓膫}是個(gè)空的吃度,需要追加內(nèi)容記錄甩挫,已修正腳本。
[圖片上傳失敗...(image-79515c-1612399398264)]
3. Git 版本低
git commit
git: ‘interpret-trailers’ is not a git command. See ‘git --help’.
cannot insert change-id line in .git/COMMIT_EDITMSG
[圖片上傳失敗...(image-c71a45-1612399398263)]
使用下面的命令更新
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
4. 編譯缺少文件
這是因?yàn)?git push 源碼倉的時(shí)候规肴,某些文件加入到了 .gitignore捶闸,但忽略配置文件和實(shí)際編譯需要的文件沒有完全匹配,就會(huì)造成此問題拖刃。腳本中直接把此文件刪除删壮,已更正。
如果還是無法編譯通過兑牡,一定是上庫時(shí)央碟,源碼倉某些子文件夾下面還有 .gitignore,可以根據(jù)報(bào)錯(cuò)信息將相應(yīng)文件上庫均函。
比如下面缺了 netlink/version.h 頭文件亿虽。
external/libnl/include/netlink/netlink.h:29:29: fatal error: netlink/version.h: No such file or directory
#include <netlink/version.h>
參考資料: