Android 源码部署到服务器(Git+GitLab+Repo)
Android 源码部署到服务器端,一共分为七个部分。
- 软件环境
- GitLab 服务端部署
- Repo Manifest.xml 生成
- GitLab 建仓
- Git Push 源码到服务器
- Repo 同步源码
- 提交修改后的代码
一、软件环境
服务器端系统:Ubuntu 16.04
GitLab 软件:GitLab Community Edition
客户端系统:Ubuntu 14.04
Git 软件:Git 2.27.0
二、GitLab 服务端部署
- 必要组件安装
sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates
sudo apt-get install -y postfix
在安装 postfix 时候,需要使用左右键和回车键确认,并在下拉列表选择 Internet Site 并确认。
- 信任 GitLab 的 GPG 公钥
curl https://packages.gitlab.com/gpg.key 2> /dev/null | sudo apt-key add - &>/dev/null
- 配置镜像路径
如果没有安装 vim 先进行安装
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 文件不存在,自行创建即可,并赋予权限,可在 root 权限下进行。
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
- 执行配置
sudo gitlab-ctl reconfigure
- 启动 GitLab
sudo gitlab-ctl start
- 修改 external_url
sudo gedit /etc/gitlab/gitlab.rb
修改如下路径:
external_url 'http://gitlab.example.com'
具体要换成你服务端的 IP,比如,我这里使用的是 192.168.50.10
external_url 'http://192.168.50.10'
这里一定要重新配置,再次执行配置命令
sudo gitlab-ctl reconfigure
- 访问你的 GitLab 服务
打开同一网段的任何电脑上的浏览器,前提是电脑可以互相访问,如果使用了虚拟机,则要配置网络类型为桥接,并将虚拟机都配置在同一网段内,并且可以互相访问,可以 ping 一下,保证网络畅通。
浏览器地址栏输入:http://192.168.50.10(需要更换为你自己的 url)。
[图片上传失败...(image-54367b-1612399398269)]
接下来可以创建 root 用户,输入密码并确认。注册其他用户等等。
三、Repo Manifest.xml 生成
为什么需要自己去生成 Manifest.xml?如果你手上的代码已经不知道哪里同步来的,或者是方案厂商提供的,总之不是使用 Repo 管理代码,可能把修改的部分作为一个单独的 git 仓上库了。如此我们就需要根据这份现有的源码去生成 Manifest.xml。
前提是基于源码修改的仓都用 git 上库了,或者是增加了一些仓但是和 Manifest.xml 不同步,这都需要修改 Manifest.xml 保持源码仓和 Manifest.xml 对等。
因为源码中每个仓都有 .git 了,所以我们需要统计所有的 .git ,这是为了找出所以的 git 仓,然后写入 Manifest.xml,如此我们就可以使用 Repo 管理这些仓了。
- 找出所有 git 仓库
find myandroid/ -type d -name '.git' > git_pro.txt
打开 git_pro.txt 就会看到如下行
......
/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 "
" >>$1
while read line; do
echo " " >>$1
done
echo -e "\n " >>$1
运行脚本 gen_xml.sh 即可。
cat path.txt | ./gen_xml.sh default.xml
default.xml 内容如下,这就是我们要的清单文件(Manifest.xml)。
......
fetch 是 “…” 代表返回到上级目录,我这里将 manifest 仓库放在了 android6newc 组下面,详见下文。
revision 是 master 代表主干分支,这个要和 android6newc 组下面源码仓分支对应。
这二者配置不正确,repo 无法正常同步源码仓。
四、GitLab 建仓
源码中仓库太多了,不可能在浏览器内一个一个创建,所以需要借助 python-gitlab 库来自动完成。将 default.xml 放在和脚本同一目录,运行它。等待建仓完成,有点耗时,可能要半小时。如果没有安装 python-gitlab 可以借助 pip 进行安装。
脚本内写死了父组名(Android6NewC),这个组需要在 GitLab 网页中自行创建。同样 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"[^\"]+)\"\s+name=\"(?P[^\"]+)\"\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()
脚本基本工作原理较为简单,通过正则表达式解析 default.xml 中的 project 标签拿到 path,根据 path 创建 group (包括 subgroup),最后再创建 project。
脚本正确运行的话输出类似以下片段:
......
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
脚本运行完以后围观一下 GitLab 组页面,生成了我们需要的目录结构。
[图片上传失败...(image-717933-1612399398267)]
五、Git Push 源码到服务器
首先安装 Git,然后编写自动提交脚本将源码提交到仓库。
1. 安装 Git
使用以下命令在 Ubuntu 14.04 LTS 上安装 Git
apt-get install git
安装成功后键入命令可以查看 Git 版本,提示 git version 2.7.4,说明已经安装成功。
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"
生成提示如下,以下生成信息已经抹除了敏感信息,你的生成信息会变化。
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,确认即可。
Push 存在源码文件夹工程到仓库,下面是一个例子,脚本实际上就是包装了这些命令。
cd existing_folder
git init
git remote add origin [email protected]:android6newc/build.git
git add .
git commit -m "Initial commit"
git push -u origin master
当然由于以前存在使用 Git 同步过代码,因此需要先删除 .git,使用 rm 命令删除即可。
rm -rf .git
2. Push Manifest 清单文件到 GitLab 服务端
在 android6newc 组(Android6NewC)下(网页内操作)新建 manifest project。并将客户端的 default.xml push 过去。
cd existing_folder
git init
git remote add origin [email protected]:android6newc/manifest.git
git add .
git commit -m "Initial commit"
git push -u origin master
3. 自动 Push 代码到 GitLab 服务端
这需要开发一个脚本执行,这里还是用 python 实现,一定要注意切换到工作路径下,不然就踩坑了,当然脚本已经修复了这个 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"[^\"]+)\"\s+name=\"(?P[^\"]+)\"\s+/>",
re.DOTALL)
SOURCE_CODE_ROOT = "/home/captain/myandroid/"
REMOTE = "[email protected]: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 放在和脚本同一目录,运行它。运行结束会把所有的源码都同步到 GitLab。
六、Repo 同步源码
由于源码是部署在局域网,局域网不能访问互联网。因此还需要把 repo 部署到服务器上。
1. repo 部署到服务器
到能够访问互联网的电脑 clone 一份 repo。
git clone https://github.com/GerritCodeReview/git-repo.git
然后将它打包
tar cvf git-repo.tar git-repo/
拷贝 git-repo.tar 到内网电脑,并解压。
tar xvf git-repo.tar
删除 git-repo/ 下的 .git。
在 Android-Tools 组下(网页内操作)新建 git-repo project。并将 git-repo push 过去。
cd existing_folder
git init
git remote add origin [email protected]:android-tools/git-repo.git
git add .
git commit -m "add repo tool"
git push -u origin master
2. 客户端 repo 同步代码
在上一步中 git-repo 路径中找到 repo 文件。将其放到 ~/bin 下,赋予执行权限,并添加到环境变量 PATH。此文件也可以部署在 httpd 服务器上,安装 apache2 服务。
sudo apt-get install apache2
查看安装是否成功。
sudo systemctl status apache2
修改监听端口,我这里改为了 10086。我们知道 http 默认在 80 端口,但是这个端口已经被 GitLab 使用了,所以要腾出来“地盘”。
sudo vim /etc/apache2/ports.conf
将 repo 拷贝到 /var/www/html 目录下。
重启 apache2 服务。
sudo service apache2 restart
~/bin/ 目录下,下载 repo。
curl http://192.168.50.10:10086/repo > repo
修改权限和添加环境变量。
chmod a+x ~/bin/repo
export PATH=$PATH:~/bin
现在可以新建一个文件夹用来存放源码了。先 repo init,然后 repo sync。
repo init -u [email protected]:android6newc/manifest.git [email protected]:android-tools/git-repo.git --no-repo-verify
init 成功以后会在当前文件夹下创建 .repo 文件夹。现在可以运行 repo sync 开始同步代码。
repo sync
防止下载中跳出,我们使用以下脚本,先创建。
vim down.sh
写入下面内容:
#!/bin/sh
repo sync
while [ $? -ne 0 ]
do
repo sync
done
用这段脚本执行,替换 repo sync 这条命令。
chmod a+x down.sh
./down.sh
编译的时候 make 命令无法使用,是由于没有 Makefile 造成的,对比一下上库之前的代码,把源码目录下 Makefile 拷贝过来即可。此文件内容非常简单,就是把 build/core/main.mk 包含进来。
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
七、提交修改后的代码
给所有仓库创建一个本地分支,分支名 dev 可以任取。
repo start dev --all
提交代码到 remote。
git add .
git commit -m "xxx"
git push aosp dev
git push <远程主机名> <本地分支名>,这会在远程创建创建一个 dev 分支,请求合并。
aosp 是远程仓库名,不确定可以使用 git branch -a 查询。
repo upload . (注意最后有个点号)无法使用,因为我们没有配置 Gerrit 服务器。
另外同步代码可以使用 pull 命令。
git pull --rebase
高级用法
repo forall -c xxx 对所有仓库执行 -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
八、遇到的错误记录
1. default.xml fetch 配置错误
下面这张图是因为 default.xml fetch 配置错误造成的。
[图片上传失败...(image-b017f2-1612399398264)]
2. 空仓未提交内容
repo sync 期间报错 fatal: Couldn’t find remote ref refs/heads/master,是因为上库的仓是个空的,需要追加内容记录,已修正脚本。
[图片上传失败...(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. 编译缺少文件
这是因为 git push 源码仓的时候,某些文件加入到了 .gitignore,但忽略配置文件和实际编译需要的文件没有完全匹配,就会造成此问题。脚本中直接把此文件删除,已更正。
如果还是无法编译通过,一定是上库时,源码仓某些子文件夹下面还有 .gitignore,可以根据报错信息将相应文件上库。
比如下面缺了 netlink/version.h 头文件。
external/libnl/include/netlink/netlink.h:29:29: fatal error: netlink/version.h: No such file or directory
#include
参考资料:
- https://blog.csdn.net/xiezhi123456/article/details/80030593