Jenkins代码审查功能的实现方案

前段时间试着在Jenkins上实现了代码审查功能,
本篇博客记录一下具体的实施方案,主要包括Jenkins、Gerrit Trigger、Git Hook等。


一、目的
为了减少不必要的编译错误,同时提高代码书写质量,可以在Jenkins上实现了代码审查的功能。
Jenkins具有该功能后,将自动对Gerrit上提交的代码进行编译及代码检查,并将检测的结果返回到Gerrit上。
通过这种方式,代码提交人员能够及时地根据反馈结果,对代码进行修改和完善。

二、Gerrit Trigger
为了实现代码审查功能,我们首先需要在Jenkins上下载并安装Gerrit Trigger插件,然后进行相应的配置。

2.1 Gerrit Trigger下载及安装

如上图所示,我们只需要点击Jenkins的Manage Jenkins选项,然后点击Manage Plugins选项,
在新加载的页面中,搜索并选择Gerrit Trigger插件,点击下载及安装即可(进行上述工作前必须具有管理员权限)。

Jenkins成功安装Gerrit Trigger插件后,在Manage Jenkins界面中就会出现如上图所示的Gerrit Trigger图标。

2.2 Gerrit Server配置
点击Gerrit Trigger图标后,会出现如下图所示的界面:
Jenkins代码审查功能的实现方案_第1张图片
点击Add New Server,就可以创建一个运行在Jenkins服务器上,专门用于监听Gerrit代码提交事件的Gerrit Server。

如上图所示,创建Gerrit Server时,我们只需要定义Server的名称,然后点击使用Default Configurations即可。
完成上述操作,点击OK后,将出现类似如下界面:

如图所示,该界面主要配置Gerrit代码服务器的信息,以便Gerrit Server能够与Gerrit代码服务器通信。

上图比较重要的部分已经用红线标出,其中:
Hostname和Frontend URL主要填写Grerrit代码服务器的地址;
SSH Keyfile是本地生成的SSH私钥地址,对应的公钥需要上传到Gerrit代码服务器,注意Username需要与SSH Keyfile一致。

配置完成后,可以点击Test Connection测试Gerrit Server与Gerrit代码服务器的连通性。

图中Gerrit Reporting Values主要设置代码审查完毕后,
Gerrit Server返回给Gerrit代码服务器的值,即审查通过后+1, 不通过-1。

当整个Gerrit Server配置完毕后,就可以点击Save按键保存。
此时,将会出现如下界面,其中图片上方就是Grerrit Server的默认配置:
Jenkins代码审查功能的实现方案_第2张图片
我们点击界面下方显示的Edit键时,可以重新对Gerrit Server进行配置;
点击Remove键时,可以移除此次创建的Gerrit Server;
如果检查Gerrit Server没有问题后,就可以点击Status下方的红色按键,正式启动Gerrit Server。

Gerrit Server启动后,对应状态将变为:
Jenkins代码审查功能的实现方案_第3张图片

2.3 Jenkins Item中配置Gerrit Trigger
Gerrit Server创建成功后,我们就可以在Jenkins中创建Item处理Gerrit Server发送的事件。
Jenkins中的Item才是执行代码审查工作的主体。

在Gerrit上创建Item的方式比较简单,主要是在Jenkins的主界面点击New Item,
然后选择创建Freestyle project即可。
我们用于审查代码的Item名称为Code_Verify。

Code_Verify的大部分配置与通常的线上编译Item类似,
我们仅记录主要的不同之处。

2.3.1 General部分
Jenkins代码审查功能的实现方案_第4张图片
Code_Verify仅用于验证提交的代码能否编译通过、代码质量是否符合规范,不需要负责发布正式版本,
因此出于节省内存空间的考虑,在General部分勾选了Discard old builds,丢弃历史编译的结果。
Jenkins代码审查功能的实现方案_第5张图片
此外,为了避免过多地占用Jenkins资源,影响正常项目的发布,我们将Code_Verfiy的编译功能限制在专门为测试提供服务的androidtest节点上。

2.3.2 Source Code Management部分
与常规节点的代码管理策略不同,Code_Verify没有选择任何Jenkins支持代码管理工具,
例如Git、Gerrit Repo等,如下图所示。
Jenkins代码审查功能的实现方案_第6张图片
这里选择这种策略的原因是:
一般情况下,项目的代码基于Gerrit进行管理。
使用Jenkins内置的代码管理工具Gerrit Repo时,必须配置项目对应的分支信息。
即一旦使用Gerrit Repo,那么在Gerrit Trigger触发具体的编译前,分支信息已经被定义好并且无法修改。
这就要求一个项目对应一个代码检查节点。
但我们试图用一个节点来监控所有项目的提交,即所有的项目共用一个代码检查的Item。
因此,每次下载代码时使用的分支,必须能够根据实际的Gerrit提交动态地调整。

基于以上原因,最终我们放弃使用Jenkins自带的代码管理工具,
转而选择在编译前通过Shell命令动态下载所需分支的代码。

2.3.3 Build Triggers部分
Build Triggers部分的配置是触发代码审查的核心。
Jenkins代码审查功能的实现方案_第7张图片
如上图所示,我们首先选择触发事件的类型为Gerrit event。

然后,在新加载的界面内配置对应的Gerrit Trigger:
Jenkins代码审查功能的实现方案_第8张图片
如上图所示, 我们在Gerrit Trigger中选择Gerrit Server为前面配置的gerrit。
这样当gerrit监听到Gerrit代码服务器的通知后,就会触发Code_Verify进行代码检查。

Trigger on事件目前限定为PatchSet Created、Change Restored和Ref Updated。
其中,代码初次提交到Gerrit代码服务器时会触发PatchSet Created事件;
代码从abandon状态恢复时会触发Change Restored事件;
代码进行amend操作后会触发Ref Updated事件。

Jenkins代码审查功能的实现方案_第9张图片
如上图所示,目前在Dynamic Trigger Configuration中定义project为**, Branch为beta3.0。
于是, 用户向任何Project的beta3.0分支提交代码时,均会触发代码检查操作过程。

2.3.4 Build Environment部分
Jenkins代码审查功能的实现方案_第10张图片
最后,我们需要为Code_Verify添加SSH Agent(Jenkins需要提前安装SSH相关的插件)。
如上图所示,添加一个有效的key值即可。
需要注意的是:此处使用的key值必须与后文下载代码使用的账户一致。

至此,Code_Verify的基本配置就介绍完毕了。
接下来,Code_Verify只需要在检测到Gerrit代码提交时,
解析其中携带的信息,并下载对应分支的代码,然后进行编译及代码检查即可。

在介绍后续的内容前,我们先看一下正常情况下,我们下载代码所需要的命令:

// 此处使用的账户名与Code_Verify中添加的SSH key一致
repo init -u ssh://android@gerrit.xxxxx.org:29418/xxxx/test_Manifest -b master -m test_project.xml

repo sync

注意到repo init时,需要指明对应代码仓库的xml名称,如test_project.xml。
但是Gerrit提交代码时携带的信息中,并不会包含该xml名称。

为了解决这个问题,我们决定在每次提交代码时,
将代码对应代码仓库的xml信息写入commit-msg中。

不过,强制要求代码提交人员来手动写入xml信息,
显然增加了大家的工作量,同时容易引入不必要的书写错误。
为了避免这个问题,我们进一步引入了Git Hook机制。

三、Git Hook
简单来讲,Git Hook是内置在Git中的由特定动作触发的脚本,
可以在每次代码提交前、后等各个时机执行一些指定的操作。

举例来讲,当我们提交代码编辑对应的commit-msg时,仅会编辑commit-msg的正文部分。
但是如果我们通过git log观察实际生成的commit-msg时,会发现commit-msg中自动添加了其它的信息,如下图所示:
Jenkins代码审查功能的实现方案_第11张图片
其中的Author、Date、Change-Id字段就是Git Hook生成的。

Git为不同时机定义了多种Git Hook,
考虑到我们项目各个module的.git/hooks目录下均存在commit-msg Hook,
且该Hook均有指向根节点.repo/repo/hooks目录下的commit-msg,
因此我们决定统一修改.repo/repo/hooks中的commit-msg,
使得每次编辑commit-msg后,Hook能够像添加Change-Id一样,
自动添加对应project的xml信息。

commit-msg Hook中基本上混用了Shell及AWK的语法,
于是我们就以Shell脚本的风格实现了添加xml信息的函数,
如下所示:

#在原生的commit-msg hook中新增函数
add_ManifestXml(){
    # 前面这一部分主要是学习add_ChangeId的写法
    # 主要目的就是判断commit-message有问题时,不添加xml信息
    clean_message=`sed -e '
            /^diff --git .*/{
                s///
            q
            }
        /^Signed-off-by:/d
        /^#/d
    ' "$MSG" | git stripspace`
    if test -z "$clean_message"
    then
        return
    fi
    # Do not add xml to temp commits
    if echo "$clean_message" | head -1 | grep -q '^\(fixup\|squash\)!'
    then
        return
    fi

    # 以下部分就是功能的主体
    # commit-msg Hook运行在每个module的路径下
    # 我们首先获取.repo目录下manifest.xml链接的实际xml
    origin=$(ls -l ../.repo/manifest.xml)
    currentXml="Error.xml"
    if [ "$origin" != "" ]
    then
        tmp=${origin##*/}
    result=$(echo $tmp | grep ".xml")
    if [ "$result" != "" ]
    then
        currentXml=$tmp
        fi
    fi
    echo "currentXml: $currentXml"

    # 判断当前的commit-message中是否已经包含了有效的xml信息
    # 这主要处理git commit --amend的情况
    TAG="Manifest-Xml"
    if grep -i "^$TAG: $currentXml" "$MSG" >/dev/null
    then
        return
    fi

    # 如果当前的commit-message包含错误的xml信息,那么进行修正
    # 这里主要处理用户进行ln -sf相关的操作
    if grep -i "^Manifest-Xml:" "$MSG" >/dev/null
    then
        sed "s/^Manifest-Xml:.*/Manifest-Xml: $currentXml/g" $MSG > tmp.txt
        mv tmp.txt $MSG
    return
    fi

    # 如果当前的commit-message不包含xml信息,则直接添加
    # MSG就是最后生成的commit msg
    echo "Manifest-Xml: $currentXml" >> $MSG
}

#调用添加manifestXml函数
add_ManifestXml

#添加change-id的功能也在commit-msg中
add_ChangeId

修改commit-msg Hook后,当我们编辑commit-message后,xml信息就会自动添加上。
对应的效果类似于:
Jenkins代码审查功能的实现方案_第12张图片
从图中可以看出,自动添加了Manifest-Xml相关的信息。

四、代码下载及编译
当gerrit监听到代码提交时,会将该提交携带的信息一并传递Code_Verify。
Code_Verify解析出需要的信息,然后进行代码下载、编译及审查等工作。

代码下载和编译的工作都放在了Code_Verify的Build部分,主要依靠Shell脚本来完成工作。
这里我就不过多的用文字进行描述了,直接给出实现脚本和对应的注释:

#clean old data
rm -rf /data/jenkins/workspace/Code_Verify/*

#get xml info from commit msg
#GERRIT_CHANGE_COMMIT_MESSAGE为Gerrit内置的变量,表示Gerrit代码提交时对应的commit-msg
info=$(echo $(echo $GERRIT_CHANGE_COMMIT_MESSAGE | grep -Eo 'Manifest-Xml: .*.xml'))
xml=${info##* }

#download data
#这里根据commit-msg中携带的消息,下载对应xml的代码
repo init -u ssh://[email protected]:29418/xxxx/test_Manifest -b master -m $xml
repo sync

#这里GERRIT_PROJECT、GERRIT_CHANGE_NUMBER和GERRIT_PATCHSET_NUMBER均为Gerrit内置的变量
#指定了project、提交代码对应的的gerrit编号、patchSet应该是值同一个代码提交的次数(git commit --amend后,该值会递增)
#我们在这里策略是先下载全部代码,然后更新提交对应的代码(即完成覆盖操作)
repo download $GERRIT_PROJECT $GERRIT_CHANGE_NUMBER/$GERRIT_PATCHSET_NUMBER

#之后就可以执行project相关的具体编译了
.............

五、代码检查
代码编译完毕后,需要对代码规范性进行检查。
我们的项目是通过在shell里启动task对代码进行审查,
然后生成Lint.xml、CheckStyle.xml、FindBugs.xml及PMD.xml文件。

我们在Jenkins的Post-build Actions部分对这些xml进行打包处理,
然后根据检查的结果决定本次提交是否通过。
Jenkins代码审查功能的实现方案_第13张图片
Jenkins内置了对Lint.xml、CheckStyle.xml、FindBugs.xml及PMD.xml文件结果的检查项,
可以根据结果判定本次编译是否通过。
如上图所示,我们可以采用最为严格的策略,即检查任务错误,均判定本次提交不通过。

你可能感兴趣的:(工具)