【转载】Jenkins代码审查功能的实现方案

原文地址: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图标后,会出现如下图所示的界面:

点击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的默认配置:

我们点击界面下方显示的Edit键时,可以重新对Gerrit Server进行配置;
点击Remove键时,可以移除此次创建的Gerrit Server;
如果检查Gerrit Server没有问题后,就可以点击Status下方的红色按键,正式启动Gerrit Server。

Gerrit Server启动后,对应状态将变为:

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部分

Code_Verify仅用于验证提交的代码能否编译通过、代码质量是否符合规范,不需要负责发布正式版本,
因此出于节省内存空间的考虑,在General部分勾选了Discard old builds,丢弃历史编译的结果。

此外,为了避免过多地占用Jenkins资源,影响正常项目的发布,我们将Code_Verfiy的编译功能限制在专门为测试提供服务的androidtest节点上。

2.3.2 Source Code Management部分
与常规节点的代码管理策略不同,Code_Verify没有选择任何Jenkins支持代码管理工具,
例如Git、Gerrit Repo等,如下图所示。

这里选择这种策略的原因是:
一般情况下,项目的代码基于Gerrit进行管理。
使用Jenkins内置的代码管理工具Gerrit Repo时,必须配置项目对应的分支信息。
即一旦使用Gerrit Repo,那么在Gerrit Trigger触发具体的编译前,分支信息已经被定义好并且无法修改。
这就要求一个项目对应一个代码检查节点。
但我们试图用一个节点来监控所有项目的提交,即所有的项目共用一个代码检查的Item。
因此,每次下载代码时使用的分支,必须能够根据实际的Gerrit提交动态地调整。

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

2.3.3 Build Triggers部分
Build Triggers部分的配置是触发代码审查的核心。

如上图所示,我们首先选择触发事件的类型为Gerrit event。

然后,在新加载的界面内配置对应的Gerrit Trigger:

如上图所示, 我们在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事件。


如上图所示,目前在Dynamic Trigger Configuration中定义project为**, Branch为beta3.0。
于是, 用户向任何Project的beta3.0分支提交代码时,均会触发代码检查操作过程。

2.3.4 Build Environment部分

最后,我们需要为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

  • 1
  • 2
  • 3
  • 4

注意到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中自动添加了其它的信息,如下图所示:

其中的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
<span class="hljs-comment"># 以下部分就是功能的主体</span>
<span class="hljs-comment"># commit-msg Hook运行在每个module的路径下</span>
<span class="hljs-comment"># 我们首先获取.repo目录下manifest.xml链接的实际xml</span>
origin=$(ls <span class="hljs-operator">-l</span> ../.repo/manifest.xml)
currentXml=<span class="hljs-string">"Error.xml"</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$origin</span>"</span> != <span class="hljs-string">""</span> ]
<span class="hljs-keyword">then</span>
    tmp=<span class="hljs-variable">${origin##*/}</span>
result=$(<span class="hljs-built_in">echo</span> <span class="hljs-variable">$tmp</span> | grep <span class="hljs-string">".xml"</span>)
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$result</span>"</span> != <span class="hljs-string">""</span> ]
<span class="hljs-keyword">then</span>
    currentXml=<span class="hljs-variable">$tmp</span>
    <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">fi</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"currentXml: <span class="hljs-variable">$currentXml</span>"</span>

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

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

<span class="hljs-comment"># 如果当前的commit-message不包含xml信息,则直接添加</span>
<span class="hljs-comment"># MSG就是最后生成的commit msg</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Manifest-Xml: <span class="hljs-variable">$currentXml</span>"</span> &gt;&gt; <span class="hljs-variable">$MSG</span>

}

#调用添加manifestXml函数
add_ManifestXml

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

修改commit-msg Hook后,当我们编辑commit-message后,xml信息就会自动添加上。
对应的效果类似于:

从图中可以看出,自动添加了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 ( < s p a n c l a s s = " h l j s − b u i l t i n " > e c h o < / s p a n > < s p a n c l a s s = " h l j s − v a r i a b l e " > (<span class="hljs-built_in">echo</span> <span class="hljs-variable"> (<spanclass="hljsbuiltin">echo</span><spanclass="hljsvariable">GERRIT_CHANGE_COMMIT_MESSAGE | grep -Eo 'Manifest-Xml: ..xml’))
xml=${info## }

#download data
#这里根据commit-msg中携带的消息,下载对应xml的代码
repo init -u ssh://android@gerrit.xxxxx.org: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 G E R R I T P R O J E C T < / s p a n > < s p a n c l a s s = " h l j s − v a r i a b l e " > GERRIT_PROJECT</span> <span class="hljs-variable"> GERRITPROJECT</span><spanclass="hljsvariable">GERRIT_CHANGE_NUMBER/$GERRIT_PATCHSET_NUMBER

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值