精彩文章免费看

Android 源码部署到服务器(Git+GitLab+Repo)

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
    

    得到如下路径:

    接着需要生成清单文件。

    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
    echo -e "\n</manifest>" >>$1
    

    运行脚本 gen_xml.sh 即可。

    cat path.txt | ./gen_xml.sh default.xml
    

    default.xml 内容如下,这就是我们要的清单文件(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 代表主干分支,这个要和 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"<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)
                    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
                    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 git@192.168.50.10: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 git@192.168.50.10: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"<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 放在和脚本同一目录,运行它。运行结束会把所有的源码都同步到 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 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 下,赋予执行权限,并添加到环境变量 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 git@192.168.50.10:android6newc/manifest.git --repo-url=git@192.168.50.10:android-tools/git-repo.git --no-repo-verify
    

    init 成功以后会在当前文件夹下创建 .repo 文件夹。现在可以运行 repo sync 开始同步代码。

    repo sync
    

    防止下载中跳出,我们使用以下脚本,先创建。

    vim down.sh
    

    写入下面内容:

    #!/bin/sh
    repo sync
    while [ $? -ne 0 ]
    repo sync
    

    用这段脚本执行,替换 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 <netlink/version.h>
    

    参考资料:

  • https://blog.csdn.net/xiezhi123456/article/details/80030593
  •