文章目录
  1. 1. Jenkins配置以及构建通知邮件内容定制
    1. 1.1 Jenkins安装、配置与使用
    2. 1.2 构建结果邮件定制
      1. 1.2.1 Email Extension 配置
      2. 1.2.2 Android Lint
      3. 1.2.3 FindBugs
      4. 1.2.4 动态集成自定义的gradle任务方案
      5. 1.2.5 FireLine
      6. 1.2.6 Apk方法数、大小以及资源文件分析
        1. 1.2.6.1 Apk方法数、大小统计
        2. 1.2.6.2 资源文件大小统计
      7. 1.2.7 Git Changelog分析
  2. 2. Jenkins与Docker结合实现快速配置
    1. 2.1 Dockerfile文件配置
    2. 2.2 使用Dockerfile文件构建镜像
  3. 3. 参考资料

随着软件开发复杂度的不断提高,如何能在不断变化的需求中快速适应和保证软件的质量显得尤其的重要,持续集成正是针对这一类问题的一种软件开发实践。

本文中的Jenkins-ci容器化方案能够在项目构建过程中提取出更多自定义的需求信息并进一步将持续集成的环境参数配置标准化。
因此,在项目的构建过程中,我们对用户比较关心的信息(如代码质量、apk方法数和大小、资源文件变化、代码提交记录等)进行跟踪、收集、分析和统计,并将结果以图表的形式在通知邮件中展现给用户,让用户对项目的迭代情况有更多的了解。

1. Jenkins配置以及构建通知邮件内容定制

1.1 Jenkins安装、配置与使用

关于详细的Jenkins安装、配置与使用说明,可参考文章Jenkins工具(一)之 Jenkins集成android工程
Jenkins工具(二)之 Jenkins集成android工程,文中不再详细叙述。

其中,构建中使用到的插件几个主要插件:

1.2 构建结果邮件定制

完成Jenkins的安装以及环境参数配置后,新建一个项目用于构建,项目构建完成后需要将结果内容通过邮件的方式通知用户。将构建结果通知到用户的邮件内容的定制,则是本次实践的重点。

本次自动化构建将包括以下几个内容:FireLine(源码检查)、Android LintFindBugs(Class文件检查)、Apk包大小和方法数统计、资源文件统计,以及Git commit日志分析。

1.2.1 Email Extension 配置

构建结果邮件内容定制依赖于前面提到的Email Extension Plugin,该插件目前支持JellyGroovy两种语法编写模板,本文的模板内容定制将采用Groovy(个人认为Jelly标签不够灵活)。同时,创建的模板文件要放在Jenkins根目录下的email-templates文件夹下。模板文件配置如下:

1
${SCRIPT, template="ht-ci.template"}

另外,想要在模板中获取构建结果信息,可以通过获取Jenkins环境中的Action对象来实现。Action是插件用来在JobBuild页面增加功能的一种主要方式,是Jenkins最常用的一个扩展点。如org.jenkinsci.plugins.android_lint.LintResultAction、hudson.plugins.findbugs.FindBugsResultAction等,后文将多次用到。

1.2.2 Android Lint

Android Lint是一个静态代码分析工具,它能够对你的Android项目中潜在的bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查。支持自定义Lint规则,可参考文章浅谈Android自定义Lint规则的实现 (一)浅谈Android自定义Lint规则的实现 (二)

安装插件Android Lint Plugin后,进入项目的配置页,并添加构建后操作Publish Android Lint results,根据页面提示设置参数,若不设置参数,则使用默认参数。同时,在Invoke Gradle ScriptTask中添加对应的lint检查任务。

通过阅读 android-lint-plugin 的源码可知,只要获取org.jenkinsci.plugins.android_lint.LintResultAction对应的Action实例,就可以得到lint检查的数据。

最终,邮件中的显示结果如图:

点击链接地址,会跳转到Lint Issues的包含图文描述的界面,如图:

Lint Issues

不过需要注意的是,默认情况下,在Gradle插件com.android.applicationandroid对象中,lintOptionsabortOnError参数为true,在执行lint命令时,遇到错误即中止构建。因此,若希望lint执行出错后继续构建,则需要将该参数置为false,可以在build.gradle文件中动态修改:

1
2
3
4
5
6
7
...
afterEvaluate { project ->
if (project.properties.containsKey('android')) { //针对android应用和android库项目
project.android.lintOptions.abortOnError false //禁止遇到错误中止构建
}
}
...

1.2.3 FindBugs

Findbugs是一个静态分析工具,用来查找Java代码中的程序错误,将字节码与一组缺陷模式进行对比以发现可能的问题,并根据其可能产生的影响或严重程度,而对开发者的提示。并且,这组缺陷模式是可配置的,通过配置可以过滤掉一些我们不想或不需要检测的问题。

FindBugsAndroid Lint的集成方式基本一致。在安装插件FindBugs Plugin后,进入项目配置页,并添加构建后操作Publish FindBugs analysis results,根据页面提示设置参数,若不设置参数,则使用默认参数。同时,在Invoke Gradle ScriptTask中添加对应的findbugs检查任务。

1
2
3
4
5
6
7
8
//findbugs插件
apply plugin: 'findbugs'
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {//依赖Debug打包所产生的class文件
...
excludeFilter = file("${project.rootDir}/configs/scripts/findbugs-filter.xml") //配置过滤文件,减少不必要的检查
classes = files("${project.buildDir}/intermediates/classes/")//默认分析的class文件对象
...
}

结合findbugs-plugin的源码可知,只要获取hudson.plugins.findbugs.FindBugsResultAction对应的Action实例,就可以得到分析结果。最后,在邮件中的显示结果如图:

点击链接地址,同样跳转到FindBugs的图文界面。

1.2.4 动态集成自定义的gradle任务方案

在项目持续集成时,我们需要添加一些自定义的任务,但是又要尽量避免修改用户的项目代码。因此,本文采用了一个比较折中的方案:
Jenkins的根目录下建一个configs文件夹,放置一些项目构建过程使用的第三方库、脚本以及配置文件等;然后在项目构建之前,通过执行Job配置页的脚本将该文件夹及其内容复制到目标构建项目的工作空间中,同时修改项目的build.gradle文件;最后在项目构建过程中,调用这些第三方库、脚本以及配置文件,来完成自定义的额外的构建任务。

FindBugs任务的集成就应用了上述方案,后续介绍的FireLine集成以及Apk大小、方法数和资源文件统计的集成亦是如此。

例如,修改build.gradle文件,动态引入jenkins.gradle文件:

1
2
3
4
5
groovy
project(':app') { //在项目的主moudle(一般默认是app)中引用构建相关的gradle脚本
apply from: rootProject.getRootDir().getAbsolutePath() + '/configs/scripts/jenkins.gradle'
...
}

1.2.5 FireLine

FireLine 提供一种静态代码(指java源码,后来据说又支持class文件)扫描服务,基于PMD开源。它是360公司针对自己的产品定制的安全检查规则,使用这些规则对源代码进行扫描检测,找出代码潜在的安全风险。目前,对外也可以使用。因此,本文对该工具进行了集成。

FireLine 对外提供一个jar包,必须通过命令行的形式运行,因此需要添加一个fireLine的任务:

1
2
3
4
5
6
7
8
9
10
11
groovy
//扫描java源码,需指定扫描对象以及结果存储位置
task fireLine << {
def fireLineDir=env.JENKINS_HOME+"/jobs/"+env.JOB_NAME+"/builds/"+env.BUILD_NUMBER+"/"
exec {
workingDir './'
//命令行执行jar包
commandLine "java", "-jar", "${project.rootDir}/configs/jars/fireline.jar", "scanSrcDir="+env.WORKSPACE, "reportSaveDir="+fireLineDir,"reportFileName=fireLineResult","user=netease"
}
...
}

构建后,检查结果在邮件中显示如图:

点击链接地址,跳转到需要HTML Publisher Plugin支持的火线检查的详细界面:

注意,若FireLine报告不能正常显示,这是由于它使用了JavaScript,这里需要设置jenkins允许脚本执行(allow-scripts),需要在系统设置页的Jenkins Script Console选项中输入命令:

1
System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "")

并执行,从而修改Jenkins的默认配置参数,然后重新构建项目即可。

1.2.6 Apk方法数、大小以及资源文件分析

随着项目的不断迭代更新,android应用不得不面对64k方法数限制、Apk体积不断变大的问题。因此,本次实践中将对该类数据进行收集分析,更加直观的展现给关注该类信息的用户。

1.2.6.1 Apk方法数、大小统计

在Apk方法数统计集成中,采用了开源的Gradle插件项目-dexcount-gradle-plugin,该插件会根据配置为打包的每个Apk文件生成一份方法数统计的文件。

1
2
3
4
5
6
7
8
9
10
11
buildscript {
repositories {
mavenCentral() // or jcenter()
}
dependencies {
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.1'
}
}
// make sure this line comes *after* you apply the Android plugin
apply plugin: 'com.getkeepsafe.dexcount'

配置完成后,执行assemble任务时,会在app/build/outputs目录下生成apk方法数统计数据文件:

因此,在本次jenkins-ci实践中,我们需要做就是在构建项目中动态添加该插件,并将该插件生成的统计数据进行收集、转换。

和集成findbugs插件不同的是,这里还需要动态修改app项目的buildscript对象,并保证该插件是在com.android.application之后被应用。

1
2
3
4
5
//动态修改`app`项目的`buildscript`对象
project(':app') {
apply from: rootProject.getRootDir().getAbsolutePath() + '/configs/scripts/buildscript.gradle', to: buildscript
...
}

并通过调用gradle中的PluginManager对象的void withPlugin(String id, Action<? super AppliedPlugin> action)方法,来监听插件的添加事件。当com.android.application插件被添加时,就可以动态添加dexcount-gradle-plugin插件。

1
2
3
4
5
6
7
8
9
pluginManager.withPlugin("com.android.application", new Action<AppliedPlugin>() {
void execute(AppliedPlugin appliedPlugin) {
project(':app') { //主module名称
apply plugin: 'com.getkeepsafe.dexcount' //引入apk方法数统计插件
apply plugin: "com.netease.hearttouch.resourcesize" //引入资源文件统计插件
...
}
}
})

最后,为了在jenkins中使用该数据,创建一个apkMethodCounts任务将上述的所有的json格式文件进行统一解析转换,并将解析结果和生成的Apk文件–对应起来。构建结果如图:

1.2.6.2 资源文件大小统计

资源文件统计插件resource-size-plugindexcount-gradle-plugin的使用方法基本一致,该插件也是需要通过动态添加。

插件配置完成后,执行resourcesize任务,会在app/build/outputs目录下生成分析结果resoucesize.txt。其中,第一行表示所有资源文件的总大小,后面每行代表单个文件最大的文件名称以及文件大小:

最后,将数据进行简单处理,在jenkins的构建结果中显示如图:

1.2.7 Git Changelog分析

随着项目的迭代,代码的提交越来越频繁,代码的管理显得越来越重要。而commit message是开发者是对自己所提交代码的唯一说明,它能够直接反映开发者的意图,并方便快速查询和浏览,是必不可少的。在这些commit message中,比较重要的主要有feature(新功能)和bug fix(问题修复)两种类型,也是一个产品的关注点。

因此,为了在构建中提取该类信息,在参考网上的一些Git规范的基础上,整理出一份Git提交规范,规定了完整的git提交日志由信息头部、信息主体和信息尾部构成,其中信息头部需要包含类型、范围和主题三类信息。

1
2
3
4
5
<type>(<scope>):<subject>
空行
<body>
空行
<footer>

详细规范描述可参考规范文档。另外,Commitizen是一个撰写合格Commit message的工具,很好用,推荐~。使用该工提交代码之后,会自动生成上述格式规范的日志信息。

通过Commitizen工具(或者手动)提交之后,在GitLab上生成的Commit message如图:

从图中的示例可以看出,两个Commit message的类型是featurebug fix的,因此Jenkins构建的过程中需要进行处理并显示给用户。

另外,为了能够解析bug fix类型提交中的jira上的问题并生成跳转链接,需要在项目根目录下添加一个jenkins.xml文件,示例如下:

1
2
3
4
5
<jenkins>
<jira>
<url>http://jira.netease.com/projects/MINIFIVE/issues/MINIFIVE-#</url> <!--#作为占位符-->
</jira>
</jenkins>

综上,关于自定义Jenkins-ci构建通知邮件内容的部分已经介绍完了,下面我们要讲一下JenkinsDocker结合如何实现快速配置。

2. Jenkins与Docker结合实现快速配置

Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。基于Docker轻量级、可移植的特点,本次实践将Jenkins-ci的构建环境配置打包到Docker中,从而实现快速配置。

2.1 Dockerfile文件配置

创建一个自定义的docker镜像,可以通过Dockerfile描述文件来自动完成。Dockerfile文件包含了创建镜像所需要的全部指令,可以使用Docker build命令来创建镜像。

本文中使用的Dockerfile是基于jenkinsci开源的jenkins镜像,并根据需求进行了定制,部分内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM jenkins:latest //已配好java和git环境
...
# Same as "export TERM=dumb"; prevents error "Could not open terminal for stdout: $TERM not set"
ENV TERM dumb
#support 32 bit binary on a 64 bit system
RUN apt-get update && apt-get install -y lib32z1 lib32stdc++6 wget && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# 下载Android Sdk
...
RUN cd /opt && \
wget ${ANDROID_SDK_URL} && \
tar -xzf ${ANDROID_SDK_FILENAME} && \
rm ${ANDROID_SDK_FILENAME} &&\
echo y | android update sdk --no-ui --all --filter tools,platform-tools,extra-android-m2repository,android-23,build-tools-23.0.0,build-tools-23.0.1,build-tools-23.0.2,build-tools-23.0.3
...
# 拷贝JENKINS_HOME文件夹的内容至$JENKINS_HOME目录下,该文件夹与Dockerfile在同一目录,包含了jenkins ci的全部配置信
ADD JENKINS_HOME $JENKINS_HOME
EXPOSE 8080

在配置文件中,我们使用wget命令去官方下载最新的linuxandroid SDK包,并配置环境,然后使用android命令选择和自己项目匹配的SDK以及工具的版本(本次打包的镜像下载了android-23以及系列的编译工具,用户可根据需求自行修改)。

2.2 使用Dockerfile文件构建镜像

由于Docker是基于Linux,在Mac OS上运行起来需要一个虚拟的Linux环境,它还需要若干工具支持:

  • VirtualBox:虚拟机,用来运行Linux
  • docker-machine:用来管理虚拟机
  • docker:Docker本身
  • docker-compose(Mac OS only):用来管理多个docker容器
  • Kitematic:用来管理远程Docker Hub

上述工具安装完毕后(推荐使用Homebrew),就可以创建虚拟机,配置docker环境:

1
2
3
4
5
docker-machine create --driver virtualbox dev //1、创建虚拟机
docker-machine env dev //2、查看dev信息
eval "$(docker-machine env dev)" //3、添加到环境变量,关联当前shell

然后,进入到Dockerfile所在的目录,就可以执行build命令打包镜像:

1
2
3
4
5
6
docker build -t [docker-image-name\id] .
```
目前,该镜像已经上传到[网易蜂巢](https://c.163.com/),可通过一下命令获取:
```bash
docker pull hub.c.163.com/netease163/ht-jenkinsci:latest

更多内容可参考网易蜂巢使用指南
综上,本次Jenkins-ci容器化实践就讲完了~有问题或建议可以联系我,一起交流~

3. 参考资料